| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the qmake application of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "xmloutput.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| XmlOutput::XmlOutput(QTextStream &file, ConverstionType type) |
| : xmlFile(file), indent("\t"), currentLevel(0), currentState(Bare), format(NewLine), |
| conversion(type) |
| { |
| tagStack.clear(); |
| } |
| |
| XmlOutput::~XmlOutput() |
| { |
| closeAll(); |
| } |
| |
| // Settings ------------------------------------------------------------------ |
| void XmlOutput::setIndentString(const QString &indentString) |
| { |
| indent = indentString; |
| } |
| |
| QString XmlOutput::indentString() |
| { |
| return indent; |
| } |
| |
| void XmlOutput::setIndentLevel(int level) |
| { |
| currentLevel = level; |
| } |
| |
| int XmlOutput::indentLevel() |
| { |
| return currentLevel; |
| } |
| |
| void XmlOutput::setState(XMLState state) |
| { |
| currentState = state; |
| } |
| |
| void XmlOutput::setFormat(XMLFormat newFormat) |
| { |
| format = newFormat; |
| } |
| |
| XmlOutput::XMLState XmlOutput::state() |
| { |
| return currentState; |
| } |
| |
| void XmlOutput::updateIndent() |
| { |
| currentIndent.clear(); |
| for (int i = 0; i < currentLevel; ++i) |
| currentIndent.append(indent); |
| } |
| |
| void XmlOutput::increaseIndent() |
| { |
| ++currentLevel; |
| updateIndent(); |
| } |
| |
| void XmlOutput::decreaseIndent() |
| { |
| if (currentLevel) |
| --currentLevel; |
| updateIndent(); |
| if (!currentLevel) |
| currentState = Bare; |
| } |
| |
| QString XmlOutput::doConversion(const QString &text) |
| { |
| if (!text.count()) |
| return QString(); |
| else if (conversion == NoConversion) |
| return text; |
| |
| QString output; |
| if (conversion == XMLConversion) { |
| |
| // this is a way to escape characters that shouldn't be converted |
| for (int i=0; i<text.count(); ++i) { |
| if (text.at(i) == QLatin1Char('&')) { |
| if ( (i + 7) < text.count() && |
| text.at(i + 1) == QLatin1Char('#') && |
| text.at(i + 2) == QLatin1Char('x') && |
| text.at(i + 7) == QLatin1Char(';') ) { |
| output += text.at(i); |
| } else { |
| output += QLatin1String("&"); |
| } |
| } else { |
| QChar c = text.at(i); |
| if (c.unicode() < 0x20) { |
| output += QString("&#x%1;").arg(c.unicode(), 2, 16, QLatin1Char('0')); |
| } else { |
| output += text.at(i); |
| } |
| } |
| } |
| } else { |
| output = text; |
| } |
| |
| if (conversion == XMLConversion) { |
| output.replace('\"', """); |
| output.replace('\'', "'"); |
| } else if (conversion == EscapeConversion) { |
| output.replace('\"', "\\\""); |
| output.replace('\'', "\\\'"); |
| } |
| return output; |
| } |
| |
| // Stream functions ---------------------------------------------------------- |
| XmlOutput& XmlOutput::operator<<(const QString& o) |
| { |
| return operator<<(data(o)); |
| } |
| |
| XmlOutput& XmlOutput::operator<<(const xml_output& o) |
| { |
| switch(o.xo_type) { |
| case tNothing: |
| break; |
| case tRaw: |
| addRaw(o.xo_text); |
| break; |
| case tDeclaration: |
| addDeclaration(o.xo_text, o.xo_value); |
| break; |
| case tTag: |
| newTagOpen(o.xo_text); |
| break; |
| case tTagValue: |
| addRaw(QString("\n%1<%2>").arg(currentIndent).arg(o.xo_text)); |
| addRaw(QString("%1").arg(o.xo_value)); |
| addRaw(QString("</%1>").arg(o.xo_text)); |
| break; |
| case tValueTag: |
| addRaw(QString("%1").arg(doConversion(o.xo_text))); |
| setFormat(NoNewLine); |
| closeTag(); |
| setFormat(NewLine); |
| break; |
| case tImport: |
| addRaw(QString("\n%1<Import %2=\"%3\" />").arg(currentIndent).arg(o.xo_text).arg(o.xo_value)); |
| break; |
| case tCloseTag: |
| if (o.xo_value.count()) |
| closeAll(); |
| else if (o.xo_text.count()) |
| closeTo(o.xo_text); |
| else |
| closeTag(); |
| break; |
| case tAttribute: |
| addAttribute(o.xo_text, o.xo_value); |
| break; |
| case tAttributeTag: |
| addAttributeTag(o.xo_text, o.xo_value); |
| break; |
| case tData: |
| { |
| // Special case to be able to close tag in normal |
| // way ("</tag>", not "/>") without using addRaw().. |
| if (!o.xo_text.count()) { |
| closeOpen(); |
| break; |
| } |
| QString output = doConversion(o.xo_text); |
| output.replace('\n', "\n" + currentIndent); |
| addRaw(QString("\n%1%2").arg(currentIndent).arg(output)); |
| } |
| break; |
| case tComment: |
| { |
| QString output("<!--%1-->"); |
| addRaw(output.arg(o.xo_text)); |
| } |
| break; |
| case tCDATA: |
| { |
| QString output("<![CDATA[\n%1\n]]>"); |
| addRaw(output.arg(o.xo_text)); |
| } |
| break; |
| } |
| return *this; |
| } |
| |
| |
| // Output functions ---------------------------------------------------------- |
| void XmlOutput::newTag(const QString &tag) |
| { |
| Q_ASSERT_X(tag.count(), "XmlOutput", "Cannot open an empty tag"); |
| newTagOpen(tag); |
| closeOpen(); |
| } |
| |
| void XmlOutput::newTagOpen(const QString &tag) |
| { |
| Q_ASSERT_X(tag.count(), "XmlOutput", "Cannot open an empty tag"); |
| closeOpen(); |
| |
| if (format == NewLine) |
| xmlFile << endl << currentIndent; |
| xmlFile << '<' << doConversion(tag); |
| currentState = Attribute; |
| tagStack.append(tag); |
| increaseIndent(); // ---> indent |
| } |
| |
| void XmlOutput::closeOpen() |
| { |
| switch(currentState) { |
| case Bare: |
| case Tag: |
| return; |
| case Attribute: |
| break; |
| } |
| xmlFile << '>'; |
| currentState = Tag; |
| } |
| |
| void XmlOutput::closeTag() |
| { |
| switch(currentState) { |
| case Bare: |
| if (tagStack.count()) |
| //warn_msg(WarnLogic, "<Root>: Cannot close tag in Bare state, %d tags on stack", tagStack.count()); |
| qDebug("<Root>: Cannot close tag in Bare state, %d tags on stack", tagStack.count()); |
| else |
| //warn_msg(WarnLogic, "<Root>: Cannot close tag, no tags on stack"); |
| qDebug("<Root>: Cannot close tag, no tags on stack"); |
| return; |
| case Tag: |
| decreaseIndent(); // <--- Pre-decrease indent |
| if (format == NewLine) |
| xmlFile << endl << currentIndent; |
| xmlFile << "</" << doConversion(tagStack.last()) << '>'; |
| tagStack.pop_back(); |
| break; |
| case Attribute: |
| xmlFile << " />"; |
| tagStack.pop_back(); |
| currentState = Tag; |
| decreaseIndent(); // <--- Post-decrease indent |
| break; |
| } |
| } |
| |
| void XmlOutput::closeTo(const QString &tag) |
| { |
| bool cont = true; |
| if (!tagStack.contains(tag) && !tag.isNull()) { |
| //warn_msg(WarnLogic, "<%s>: Cannot close to tag <%s>, not on stack", tagStack.last().latin1(), tag.latin1()); |
| qDebug("<%s>: Cannot close to tag <%s>, not on stack", tagStack.last().toLatin1().constData(), tag.toLatin1().constData()); |
| return; |
| } |
| int left = tagStack.count(); |
| while (left-- && cont) { |
| cont = tagStack.last().compare(tag) != 0; |
| closeTag(); |
| } |
| } |
| |
| void XmlOutput::closeAll() |
| { |
| if (!tagStack.count()) |
| return; |
| closeTo(QString()); |
| } |
| |
| void XmlOutput::addDeclaration(const QString &version, const QString &encoding) |
| { |
| switch(currentState) { |
| case Bare: |
| break; |
| case Tag: |
| case Attribute: |
| //warn_msg(WarnLogic, "<%s>: Cannot add declaration when not in bare state", tagStack.last().toLatin1().constData()); |
| qDebug("<%s>: Cannot add declaration when not in bare state", tagStack.last().toLatin1().constData()); |
| return; |
| } |
| QString outData = QString("<?xml version=\"%1\" encoding=\"%2\"?>") |
| .arg(doConversion(version)) |
| .arg(doConversion(encoding)); |
| addRaw(outData); |
| } |
| |
| void XmlOutput::addRaw(const QString &rawText) |
| { |
| closeOpen(); |
| xmlFile << rawText; |
| } |
| |
| void XmlOutput::addAttribute(const QString &attribute, const QString &value) |
| { |
| switch(currentState) { |
| case Bare: |
| case Tag: |
| //warn_msg(WarnLogic, "<%s>: Cannot add attribute since tags not open", tagStack.last().toLatin1().constData()); |
| qDebug("<%s>: Cannot add attribute (%s) since tag's not open", |
| (tagStack.count() ? tagStack.last().toLatin1().constData() : "Root"), |
| attribute.toLatin1().constData()); |
| return; |
| case Attribute: |
| break; |
| } |
| if (format == NewLine) |
| xmlFile << endl; |
| xmlFile << currentIndent << doConversion(attribute) << "=\"" << doConversion(value) << "\""; |
| } |
| |
| void XmlOutput::addAttributeTag(const QString &attribute, const QString &value) |
| { |
| switch(currentState) { |
| case Bare: |
| case Tag: |
| //warn_msg(WarnLogic, "<%s>: Cannot add attribute since tags not open", tagStack.last().toLatin1().constData()); |
| qDebug("<%s>: Cannot add attribute (%s) since tag's not open", |
| (tagStack.count() ? tagStack.last().toLatin1().constData() : "Root"), |
| attribute.toLatin1().constData()); |
| return; |
| case Attribute: |
| break; |
| } |
| xmlFile << " " << doConversion(attribute) << "=\"" << doConversion(value) << "\""; |
| } |
| |
| QT_END_NAMESPACE |