/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the qmake application of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $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(); currentIndent.reserve(currentLevel); 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.size()) 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')) { output += QLatin1String(">"); } else { if (c.unicode() < 0x20) { output += QString("&#x%1;").arg(c.unicode(), 2, 16, QLatin1Char('0')); } else { output += c; } } } } else { output = text; } if (conversion == XMLConversion) { output.replace('\"', QLatin1String(""")); output.replace('\'', QLatin1String("'")); } else if (conversion == EscapeConversion) { output.replace('\"', QLatin1String("\\\"")); output.replace('\'', QLatin1String("\\\'")); } 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(doConversion(o.xo_value)); addRaw(QString("").arg(o.xo_text)); break; case tValueTag: addRaw(doConversion(o.xo_text)); setFormat(NoNewLine); closeTag(); setFormat(NewLine); break; case tImport: addRaw(QString("\n%1").arg(currentIndent).arg(o.xo_text).arg(o.xo_value)); break; case tCloseTag: if (o.xo_value.size()) closeAll(); else if (o.xo_text.size()) 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 ("", not "/>") without using addRaw().. if (!o.xo_text.size()) { 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(""); addRaw(output.arg(o.xo_text)); } break; case tCDATA: { QString output(""); addRaw(output.arg(o.xo_text)); } break; } return *this; } // Output functions ---------------------------------------------------------- void XmlOutput::newTag(const QString &tag) { Q_ASSERT_X(tag.size(), "XmlOutput", "Cannot open an empty tag"); newTagOpen(tag); closeOpen(); } void XmlOutput::newTagOpen(const QString &tag) { Q_ASSERT_X(tag.size(), "XmlOutput", "Cannot open an empty tag"); closeOpen(); if (format == NewLine) xmlFile << Qt::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, ": Cannot close tag in Bare state, %d tags on stack", tagStack.count()); qDebug(": Cannot close tag in Bare state, %d tags on stack", int(tagStack.count())); else //warn_msg(WarnLogic, ": Cannot close tag, no tags on stack"); qDebug(": Cannot close tag, no tags on stack"); return; case Tag: decreaseIndent(); // <--- Pre-decrease indent if (format == NewLine) xmlFile << Qt::endl << currentIndent; xmlFile << "'; 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("") .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 << Qt::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