# HG changeset patch # User František Kučera # Date 1546809337 -3600 # Node ID f21d6ae71bf0913b7b2aff36917738ecdd60dfc3 # Parent 7d3081e51970369c3d12382770bb7d09e0ffb8f3 move common XML code to relpipe-lib-xmlwriter (a header-only library) diff -r 7d3081e51970 -r f21d6ae71bf0 include/relpipe/xmlwriter/XMLWriter.h --- a/include/relpipe/xmlwriter/XMLWriter.h Sat Jan 05 20:05:32 2019 +0100 +++ b/include/relpipe/xmlwriter/XMLWriter.h Sun Jan 06 22:15:37 2019 +0100 @@ -18,9 +18,11 @@ #pragma once #include +#include #include #include #include +#include #include "RelpipeXMLWriterException.h" @@ -29,41 +31,138 @@ namespace relpipe { namespace xmlwriter { +/** + * A simple library for writing XML streams. + * + * Does: + * - convert wstring values to UTF-8 + * - escape values of text nodes and attributes + * - indent elements + * - preserve whitespace of text nodes + * - in comments: replace - with – + * Does not: + * - understand XML namespaces: + * - handles xmlns:x="y" as ordinary attributes + * - thus can not check whether elements/attributes belong to an declared namespace + * - thus can generate invalid XML – namespaces and their prefixes must be checked on the caller side + */ class XMLWriter { private: - std::ostream &output; + const char* INDENT = "\t"; + + ostream& output; + wstring_convert> convertor; // XML output will be always in UTF-8 + vector treePosition; + + const string escapeXmlText(const wstring& value) { + // TODO: really bad performance → rewrite + // 72 % of whole relpipe-out-xml according to valgrind/callgrind + // Moved here from XmlHandler.h (relpipe-out-xml). + std::wstringstream result; + + for (auto & ch : value) { + switch (ch) { + case L'&': result << L"&"; + break; + case L'<': result << L"<"; + break; + case L'>': result << L">"; + break; + case L'\'': result << L"'"; // TODO: escape ' and " only in attributes + break; + case L'"': result << L"""; // TODO: escape ' and " only in attributes + break; + default: result << ch; + } + } + + return convertor.to_bytes(result.str()); + } + + const string escapeXmlAttribute(const wstring& value) { + // TODO: escaping of ' is not necessary. + return escapeXmlText(value); + } + + const string escapeComment(const wstring& value) { + // TODO: really bad performance → rewrite + // 72 % of whole relpipe-out-xml according to valgrind/callgrind + std::wstringstream result; + + for (auto & ch : value) { + switch (ch) { + case L'-': result << L"–"; + break; + default: result << ch; + } + } + + return convertor.to_bytes(result.str()); + } + + void checkName(const wstring& name) { + // FIXME: throw exception on invalid names + } + + void writeAttributes(const vector& attributes) { + if (attributes.size() % 2) throw RelpipeXMLWriterException(L"attributes vector must contain name/value pairs (even number of elements)"); + for (int i = 0; i < attributes.size(); i = i + 2) { + wstring name = attributes[i]; + wstring value = attributes[i + 1]; + checkName(name); + output << " " << convertor.to_bytes(name) << "=\"" << escapeXmlAttribute(value) << "\""; + } + } public: XMLWriter(std::ostream& output) : output(output) { + output << "" << endl; } virtual ~XMLWriter() { output.flush(); } - void writeStartElement(const wstring& name, const vector& attributes) { - throw RelpipeXMLWriterException(L"not yet implemented"); + void writeStartElement(const wstring& name, const vector& attributes = {}) { + // FIXME: indentation + checkName(name); + treePosition.push_back(name); + output << "<" << convertor.to_bytes(name); + writeAttributes(attributes); + output << ">"; } void writeEndElement() { - throw RelpipeXMLWriterException(L"not yet implemented"); + // FIXME: indentation + if (treePosition.empty()) throw RelpipeXMLWriterException(L"unable to close element – all elements are already closed"); + output << ""; + treePosition.pop_back(); } - void writeEmptyElement(const wstring& name, const vector& attributes) { - throw RelpipeXMLWriterException(L"not yet implemented"); + void writeEmptyElement(const wstring& name, const vector& attributes = {}) { + // FIXME: indentation + checkName(name); + output << "<" << convertor.to_bytes(name); + writeAttributes(attributes); + output << "/>"; + } void writeTextElement(const wstring& name, const vector& attributes, const wstring& text) { - throw RelpipeXMLWriterException(L"not yet implemented"); + writeStartElement(name, attributes); + writeCharacters(text); + writeEndElement(); } void writeCharacters(const wstring& text) { - throw RelpipeXMLWriterException(L"not yet implemented"); + output << escapeXmlText(text); } - void writeComment(const wstring& text) { - throw RelpipeXMLWriterException(L"not yet implemented"); + void writeComment(const wstring& text, bool addSpaces = true) { + output << addSpaces ? "" : "-->"; } };