#pragma once #include #include #include #include #include #include #include "libxml-guards.h" namespace NXml { class TNode; class TConstNodes; class TConstNode; using TXPathContext = xmlXPathContext; class TDocument { public: enum Source { File, String, RootName, }; public: /** * create TDocument * @param source: filename, XML string, or name for the root element (depends on @src) * @param src: source type: File | String | RootName * throws if file not found or cannot be parsed */ TDocument(const TString& source, Source type = File); public: TDocument(const TDocument& that) = delete; TDocument& operator=(const TDocument& that) = delete; TDocument(TDocument&& that); TDocument& operator=(TDocument&& that); /** * get root element */ TNode Root(); TConstNode Root() const; void Save(IOutputStream& stream, TZtStringBuf enc = "", bool shouldFormat = true) const { int bufferSize = 0; xmlChar* xmlBuff = nullptr; const char* encoding = enc.size() ? enc.data() : Doc->encoding ? nullptr : "UTF-8"; xmlDocDumpFormatMemoryEnc(Doc.Get(), &xmlBuff, &bufferSize, encoding, shouldFormat); TCharPtr xmlCharBuffPtr(xmlBuff); stream.Write(xmlBuff, bufferSize); } TString ToString(TZtStringBuf enc = "", bool shouldFormat = true) const { TStringStream s; Save(s, enc, shouldFormat); return s.Str(); } void Swap(TDocument& that) { std::swap(this->Doc, that.Doc); } xmlDocPtr GetImpl() { return Doc.Get(); } private: void ParseFile(const TString& file); void ParseString(TZtStringBuf xml); TDocument(TDocHolder doc) : Doc(std::move(doc)) { } TDocHolder Doc; }; struct TNamespaceForXPath { TString Prefix; TString Url; }; typedef TVector TNamespacesForXPath; class TConstNodes { private: struct TConstNodesRef { explicit TConstNodesRef(TConstNodes& n) : r_(n) { } TConstNodes& r_; }; public: TConstNodes(const TConstNodes& nodes); TConstNodes& operator=(const TConstNodes& nodes); TConstNodes(TConstNodesRef ref); TConstNodes& operator=(TConstNodesRef ref); operator TConstNodesRef(); /** * get node by id * @param number: node id */ TConstNode operator[](size_t number) const; /** * get number of nodes */ size_t Size() const { return SizeValue; } size_t size() const { return SizeValue; } struct TNodeIter { const TConstNodes& Nodes; size_t Index; TConstNode operator*() const; bool operator==(const TNodeIter& other) const { return Index == other.Index; } bool operator!=(const TNodeIter& other) const { return !(*this == other); } TNodeIter operator++() { Index++; return *this; } }; TNodeIter begin() const { return TNodeIter{*this, 0}; } TNodeIter end() const { return TNodeIter{*this, size()}; } private: friend class TDocument; friend class TConstNode; friend class TNode; TConstNodes(xmlDoc* doc, TXPathObjectPtr obj); size_t SizeValue; xmlDoc* Doc; TXPathObjectPtr Obj; }; class TNode { public: friend class TDocument; friend class TConstNode; friend class TTextReader; /** * check if node is null */ bool IsNull() const; /** * check if node is element node */ bool IsElementNode() const; /** * Create xpath context to be used later for fast xpath evaluation. * @param nss: explicitly specify XML namespaces to use and their prefixes * * For better performance, when you need to evaluate several xpath expressions, * it makes sense to create a context, load namespace prefixes once * and use the context several times in Node(), Nodes(), XPath() function calls for several nodes. * The context may be used with any node of the current document, but * cannot be shared between different XML documents. */ TXPathContextPtr CreateXPathContext(const TNamespacesForXPath& nss = TNamespacesForXPath()) const; /** * get all element nodes matching given xpath expression * @param xpath: xpath expression * @param quiet: don't throw exception if zero nodes found * @param ns: explicitly specify XML namespaces to use and their prefixes * * For historical reasons, this only works for *element* nodes. * Use the XPath function if you need other kinds of nodes. */ TConstNodes Nodes(TZtStringBuf xpath, bool quiet = false, const TNamespacesForXPath& ns = TNamespacesForXPath()) const; /** * get all element nodes matching given xpath expression * @param xpath: xpath expression * @param quiet: don't throw exception if zero nodes found * @param ctxt: reusable xpath context * * For historical reasons, this only works for *element* nodes. * Use the XPath function if you need other kinds of nodes. */ TConstNodes Nodes(TZtStringBuf xpath, bool quiet, TXPathContext& ctxt) const; /** * get all nodes matching given xpath expression * @param xpath: xpath expression * @param quiet: don't throw exception if zero nodes found * @param ns: explicitly specify XML namespaces to use and their prefixes */ TConstNodes XPath(TZtStringBuf xpath, bool quiet = false, const TNamespacesForXPath& ns = TNamespacesForXPath()) const; /** * get all nodes matching given xpath expression * @param xpath: xpath expression * @param quiet: don't throw exception if zero nodes found * @param ctxt: reusable xpath context */ TConstNodes XPath(TZtStringBuf xpath, bool quiet, TXPathContext& ctxt) const; /** * get the first element node matching given xpath expression * @param xpath: path to node (from current node) * @param quiet: don't throw exception if node not found, * return null node (@see IsNull()) * @param ns: explicitly specify XML namespaces to use and their prefixes * * For historical reasons, this only works for *element* nodes. * Use the XPath function if you need other kinds of nodes. */ /// @todo: quiet should be default, empty nodeset is not an error TNode Node(TZtStringBuf xpath, bool quiet = false, const TNamespacesForXPath& ns = TNamespacesForXPath()); TConstNode Node(TZtStringBuf xpath, bool quiet = false, const TNamespacesForXPath& ns = TNamespacesForXPath()) const; /** * get the first element node matching given xpath expression * @param xpath: path to node (from current node) * @param quiet: don't throw exception if node not found, * return null node (@see IsNull()) * @param ctxt: reusable xpath context * * For historical reasons, this only works for *element* nodes. * Use the XPath function if you need other kinds of nodes. */ TNode Node(TZtStringBuf xpath, bool quiet, TXPathContext& ctxt); TConstNode Node(TZtStringBuf xpath, bool quiet, TXPathContext& ctxt) const; /** * get node first child * @param name: child name * @note if name is empty, returns the first child node of type "element" * @note returns null node if no child found */ TNode FirstChild(TZtStringBuf name); TConstNode FirstChild(TZtStringBuf name) const; TNode FirstChild(); TConstNode FirstChild() const; /** * get parent node * throws exception if has no parent */ TNode Parent(); TConstNode Parent() const; /** * get node neighbour * @param name: neighbour name * @note if name is empty, returns the next sibling node of type "element" * @node returns null node if no neighbour found */ TNode NextSibling(TZtStringBuf name); TConstNode NextSibling(TZtStringBuf name) const; TNode NextSibling(); TConstNode NextSibling() const; /** * create child node * @param name: child name * returns new empty node */ TNode AddChild(TZtStringBuf name); /** * create child node with given value * @param name: child name * @param value: node value */ template typename std::enable_if, TNode>::type AddChild(TZtStringBuf name, const T& value); TNode AddChild(TZtStringBuf name, TZtStringBuf value); /** * add child node, making recursive copy of original * @param node: node to copy from * returns added node */ TNode AddChild(const TConstNode& node); /** * create text child node * @param name: child name * @param value: node value */ template typename std::enable_if, TNode>::type AddText(const T& value); TNode AddText(TStringBuf value); /** * get node attribute * @param name: attribute name * throws exception if attribute not found */ template T Attr(TZtStringBuf name) const; /** * get node attribute * @param name: attribute name * returns default value if attribute not found */ template T Attr(TZtStringBuf name, const T& defvalue) const; /** * get node attribute * @param name: attribute name * @param value: return-value * throws exception if attribute not found */ template void Attr(TZtStringBuf name, T& value) const; /** * get node attribute * @param name: attribute name * @param defvalue: default value * @param value: return-value * returns default value if attribute not found, attr value otherwise */ template void Attr(TZtStringBuf name, T& value, const T& defvalue) const; /** * get node value (text) * @throws exception if node is blank */ template T Value() const; /** * get node value * @param defvalue: default value * returns default value if node is blank */ template T Value(const T& defvalue) const; /** * set node value * @param value: new text value */ template typename std::enable_if, void>::type SetValue(const T& value); void SetValue(TStringBuf value); /** * set/reset node attribute value, * if attribute does not exist, it'll be created * @param name: attribute name * @param value: attribute value */ template typename std::enable_if, void>::type SetAttr(TZtStringBuf name, const T& value); void SetAttr(TZtStringBuf name, TZtStringBuf value); void SetAttr(TZtStringBuf name); /** * delete node attribute * @param name: attribute name */ void DelAttr(TZtStringBuf name); /** * set node application data * @param priv: new application data pointer */ void SetPrivate(void* priv); /** * @return application data pointer, passed by SetPrivate */ void* GetPrivate() const; /** * get node name */ TString Name() const; /** * get node xpath */ TString Path() const; /** * get node xml representation */ TString ToString(TZtStringBuf enc = "") const { TStringStream s; Save(s, enc); return s.Str(); } void Save(IOutputStream& stream, TZtStringBuf enc = "", bool shouldFormat = false) const; void SaveAsHtml(IOutputStream& stream, TZtStringBuf enc = "", bool shouldFormat = false) const; /** * get pointer to internal node */ xmlNode* GetPtr(); const xmlNode* GetPtr() const; /** * check if node is text-only node */ bool IsText() const; /** * unlink node from parent and free */ void Remove(); /** * constructs null node */ TNode() : NodePointer(nullptr) , DocPointer(nullptr) { } private: friend class TConstNodes; TNode(xmlDoc* doc, xmlNode* node) : NodePointer(node) , DocPointer(doc) { } TNode Find(xmlNode* start, TZtStringBuf name); template void AttrInternal(TCharPtr& value, T& res, TStringBuf errContext) const; void SaveInternal(IOutputStream& stream, TZtStringBuf enc, int options) const; xmlNode* NodePointer; xmlDoc* DocPointer; }; class TConstNode { public: friend class TDocument; friend class TConstNodes; friend class TNode; /** * check if node is null */ bool IsNull() const { return ActualNode.IsNull(); } bool IsElementNode() const { return ActualNode.IsElementNode(); } TConstNode Parent() const { return ActualNode.Parent(); } /** * Create xpath context to be used later for fast xpath evaluation. * @param nss: explicitly specify XML namespaces to use and their prefixes */ TXPathContextPtr CreateXPathContext(const TNamespacesForXPath& nss = TNamespacesForXPath()) const { return ActualNode.CreateXPathContext(nss); } /** * get all element nodes matching given xpath expression * @param xpath: xpath expression * @param quiet: don't throw exception if zero nodes found * @param ns: explicitly specify XML namespaces to use and their prefixes * * For historical reasons, this only works for *element* nodes. * Use the XPath function if you need other kinds of nodes. */ TConstNodes Nodes(TZtStringBuf xpath, bool quiet = false, const TNamespacesForXPath& ns = TNamespacesForXPath()) const { return ActualNode.Nodes(xpath, quiet, ns); } /** * get all element nodes matching given xpath expression * @param xpath: xpath expression * @param quiet: don't throw exception if zero nodes found * @param ctxt: reusable xpath context * * For historical reasons, this only works for *element* nodes. * Use the XPath function if you need other kinds of nodes. */ TConstNodes Nodes(TZtStringBuf xpath, bool quiet, TXPathContext& ctxt) const { return ActualNode.Nodes(xpath, quiet, ctxt); } /** * get all nodes matching given xpath expression * @param xpath: xpath expression * @param quiet: don't throw exception if zero nodes found * @param ns: explicitly specify XML namespaces to use and their prefixes */ TConstNodes XPath(TZtStringBuf xpath, bool quiet = false, const TNamespacesForXPath& ns = TNamespacesForXPath()) const { return ActualNode.XPath(xpath, quiet, ns); } /** * get all nodes matching given xpath expression * @param xpath: xpath expression * @param quiet: don't throw exception if zero nodes found * @param ctxt: reusable xpath context */ TConstNodes XPath(TZtStringBuf xpath, bool quiet, TXPathContext& ctxt) const { return ActualNode.XPath(xpath, quiet, ctxt); } /** * get the first element node matching given xpath expression * @param xpath: path to node (from current node) * @param quiet: don't throw exception if node not found, * return null node (@see IsNull()) * @param ns: explicitly specify XML namespaces to use and their prefixes * * For historical reasons, this only works for *element* nodes. * Use the XPath function if you need other kinds of nodes. */ TConstNode Node(TZtStringBuf xpath, bool quiet = false, const TNamespacesForXPath& ns = TNamespacesForXPath()) const { return ActualNode.Node(xpath, quiet, ns); } /** * get the first element node matching given xpath expression * @param xpath: path to node (from current node) * @param quiet: don't throw exception if node not found, * return null node (@see IsNull()) * @param ctxt: reusable xpath context * * For historical reasons, this only works for *element* nodes. * Use the XPath function if you need other kinds of nodes. */ TConstNode Node(TZtStringBuf xpath, bool quiet, TXPathContext& ctxt) const { return ActualNode.Node(xpath, quiet, ctxt); } TConstNode FirstChild(TZtStringBuf name) const { return ActualNode.FirstChild(name); } TConstNode FirstChild() const { return ActualNode.FirstChild(); } /** * get node neighbour * @param name: neighbour name * throws exception if no neighbour found */ TConstNode NextSibling(TZtStringBuf name) const { return ActualNode.NextSibling(name); } TConstNode NextSibling() const { return ActualNode.NextSibling(); } /** * get node attribute * @param name: attribute name * throws exception if attribute not found */ template T Attr(TZtStringBuf name) const { return ActualNode.Attr(name); } /** * get node attribute * @param name: attribute name * returns default value if attribute not found */ template T Attr(TZtStringBuf name, const T& defvalue) const { return ActualNode.Attr(name, defvalue); } /** * get node attribute * @param name: attribute name * @param value: return-value * throws exception if attribute not found */ template void Attr(TZtStringBuf name, T& value) const { return ActualNode.Attr(name, value); } /** * get node attribute * @param name: attribute name * @param defvalue: default value * @param value: return-value * returns default value if attribute not found, attr value otherwise */ template void Attr(TZtStringBuf name, T& value, const T& defvalue) const { return ActualNode.Attr(name, value, defvalue); } /** * get node value (text) * @throws exception if node is blank */ template T Value() const { return ActualNode.Value(); } /** * get node value * @param defvalue: default value * returns default value if node is blank */ template T Value(const T& defvalue) const { return ActualNode.Value(defvalue); } /** * get node name */ TString Name() const { return ActualNode.Name(); } /** * @return application data pointer, passed by SetPrivate */ void* GetPrivate() const { return ActualNode.GetPrivate(); } /** * get pointer to internal node */ const xmlNode* GetPtr() const { return ActualNode.GetPtr(); } /** * check if node is text-only node */ bool IsText() const { return ActualNode.IsText(); } /** * get node xpath */ TString Path() const { return ActualNode.Path(); } /** * get node xml representation */ TString ToString(TZtStringBuf enc = "") const { return ActualNode.ToString(enc); } TConstNode() = default; TConstNode(TNode node) : ActualNode(node) { } TNode ConstCast() const { return ActualNode; } private: TNode ActualNode; }; }