|
- /*
- ==============================================================================
-
- This file is part of the Water library.
- Copyright (c) 2016 ROLI Ltd.
- Copyright (C) 2017-2019 Filipe Coelho <falktx@falktx.com>
-
- Permission is granted to use this software under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license/
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
- TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
- OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
- USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
- OF THIS SOFTWARE.
-
- ==============================================================================
- */
-
- #ifndef WATER_XMLELEMENT_H_INCLUDED
- #define WATER_XMLELEMENT_H_INCLUDED
-
- #include "../containers/LinkedListPointer.h"
- #include "../memory/HeapBlock.h"
- #include "../text/Identifier.h"
-
- namespace water {
-
- //==============================================================================
- /** A handy macro to make it easy to iterate all the child elements in an XmlElement.
-
- The parentXmlElement should be a reference to the parent XML, and the childElementVariableName
- will be the name of a pointer to each child element.
-
- E.g. @code
- XmlElement* myParentXml = createSomeKindOfXmlDocument();
-
- forEachXmlChildElement (*myParentXml, child)
- {
- if (child->hasTagName ("FOO"))
- doSomethingWithXmlElement (child);
- }
-
- @endcode
-
- @see forEachXmlChildElementWithTagName
- */
- #define __forEachXmlChildElement(parentXmlElement, childElementVariableName) \
- \
- for (water::XmlElement* childElementVariableName = (parentXmlElement).getFirstChildElement(); \
- childElementVariableName != nullptr; \
- childElementVariableName = childElementVariableName->getNextElement())
-
- /** A macro that makes it easy to iterate all the child elements of an XmlElement
- which have a specified tag.
-
- This does the same job as the forEachXmlChildElement macro, but only for those
- elements that have a particular tag name.
-
- The parentXmlElement should be a reference to the parent XML, and the childElementVariableName
- will be the name of a pointer to each child element. The requiredTagName is the
- tag name to match.
-
- E.g. @code
- XmlElement* myParentXml = createSomeKindOfXmlDocument();
-
- forEachXmlChildElementWithTagName (*myParentXml, child, "MYTAG")
- {
- // the child object is now guaranteed to be a <MYTAG> element..
- doSomethingWithMYTAGElement (child);
- }
-
- @endcode
-
- @see forEachXmlChildElement
- */
- #define __forEachXmlChildElementWithTagName(parentXmlElement, childElementVariableName, requiredTagName) \
- \
- for (water::XmlElement* childElementVariableName = (parentXmlElement).getChildByName (requiredTagName); \
- childElementVariableName != nullptr; \
- childElementVariableName = childElementVariableName->getNextElementWithTagName (requiredTagName))
-
-
- //==============================================================================
- /** Used to build a tree of elements representing an XML document.
-
- An XML document can be parsed into a tree of XmlElements, each of which
- represents an XML tag structure, and which may itself contain other
- nested elements.
-
- An XmlElement can also be converted back into a text document, and has
- lots of useful methods for manipulating its attributes and sub-elements,
- so XmlElements can actually be used as a handy general-purpose data
- structure.
-
- Here's an example of parsing some elements: @code
- // check we're looking at the right kind of document..
- if (myElement->hasTagName ("ANIMALS"))
- {
- // now we'll iterate its sub-elements looking for 'giraffe' elements..
- forEachXmlChildElement (*myElement, e)
- {
- if (e->hasTagName ("GIRAFFE"))
- {
- // found a giraffe, so use some of its attributes..
-
- String giraffeName = e->getStringAttribute ("name");
- int giraffeAge = e->getIntAttribute ("age");
- bool isFriendly = e->getBoolAttribute ("friendly");
- }
- }
- }
- @endcode
-
- And here's an example of how to create an XML document from scratch: @code
- // create an outer node called "ANIMALS"
- XmlElement animalsList ("ANIMALS");
-
- for (int i = 0; i < numAnimals; ++i)
- {
- // create an inner element..
- XmlElement* giraffe = new XmlElement ("GIRAFFE");
-
- giraffe->setAttribute ("name", "nigel");
- giraffe->setAttribute ("age", 10);
- giraffe->setAttribute ("friendly", true);
-
- // ..and add our new element to the parent node
- animalsList.addChildElement (giraffe);
- }
-
- // now we can turn the whole thing into a text document..
- String myXmlDoc = animalsList.createDocument (String());
- @endcode
-
- @see XmlDocument
- */
- class XmlElement
- {
- public:
- //==============================================================================
- /** Creates an XmlElement with this tag name. */
- explicit XmlElement (const String& tagName);
-
- /** Creates an XmlElement with this tag name. */
- explicit XmlElement (const char* tagName);
-
- /** Creates an XmlElement with this tag name. */
- explicit XmlElement (const Identifier& tagName);
-
- /** Creates an XmlElement with this tag name. */
- explicit XmlElement (StringRef tagName);
-
- /** Creates an XmlElement with this tag name. */
- XmlElement (String::CharPointerType tagNameBegin, String::CharPointerType tagNameEnd);
-
- /** Creates a (deep) copy of another element. */
- XmlElement (const XmlElement&);
-
- /** Creates a (deep) copy of another element. */
- XmlElement& operator= (const XmlElement&);
-
- /** Deleting an XmlElement will also delete all of its child elements. */
- ~XmlElement() noexcept;
-
- //==============================================================================
- /** Compares two XmlElements to see if they contain the same text and attiributes.
-
- The elements are only considered equivalent if they contain the same attiributes
- with the same values, and have the same sub-nodes.
-
- @param other the other element to compare to
- @param ignoreOrderOfAttributes if true, this means that two elements with the
- same attributes in a different order will be
- considered the same; if false, the attributes must
- be in the same order as well
- */
- bool isEquivalentTo (const XmlElement* other,
- bool ignoreOrderOfAttributes) const noexcept;
-
- //==============================================================================
- /** Returns an XML text document that represents this element.
-
- The string returned can be parsed to recreate the same XmlElement that
- was used to create it.
-
- @param dtdToUse the DTD to add to the document
- @param allOnOneLine if true, this means that the document will not contain any
- linefeeds, so it'll be smaller but not very easy to read.
- @param includeXmlHeader whether to add the "<?xml version..etc" line at the start of the
- document
- @param encodingType the character encoding format string to put into the xml
- header
- @param lineWrapLength the line length that will be used before items get placed on
- a new line. This isn't an absolute maximum length, it just
- determines how lists of attributes get broken up
- @see writeToStream, writeToFile
- */
- String createDocument (StringRef dtdToUse,
- bool allOnOneLine = false,
- bool includeXmlHeader = true,
- StringRef encodingType = "UTF-8",
- int lineWrapLength = 60) const;
-
- /** Writes the document to a stream as UTF-8.
-
- @param output the stream to write to
- @param dtdToUse the DTD to add to the document
- @param allOnOneLine if true, this means that the document will not contain any
- linefeeds, so it'll be smaller but not very easy to read.
- @param includeXmlHeader whether to add the "<?xml version..etc" line at the start of the
- document
- @param encodingType the character encoding format string to put into the xml
- header
- @param lineWrapLength the line length that will be used before items get placed on
- a new line. This isn't an absolute maximum length, it just
- determines how lists of attributes get broken up
- @see writeToFile, createDocument
- */
- void writeToStream (OutputStream& output,
- StringRef dtdToUse,
- bool allOnOneLine = false,
- bool includeXmlHeader = true,
- StringRef encodingType = "UTF-8",
- int lineWrapLength = 60) const;
-
- #if 0
- /** Writes the element to a file as an XML document.
-
- To improve safety in case something goes wrong while writing the file, this
- will actually write the document to a new temporary file in the same
- directory as the destination file, and if this succeeds, it will rename this
- new file as the destination file (overwriting any existing file that was there).
-
- @param destinationFile the file to write to. If this already exists, it will be
- overwritten.
- @param dtdToUse the DTD to add to the document
- @param encodingType the character encoding format string to put into the xml
- header
- @param lineWrapLength the line length that will be used before items get placed on
- a new line. This isn't an absolute maximum length, it just
- determines how lists of attributes get broken up
- @returns true if the file is written successfully; false if something goes wrong
- in the process
- @see createDocument
- */
- bool writeToFile (const File& destinationFile,
- StringRef dtdToUse,
- StringRef encodingType = "UTF-8",
- int lineWrapLength = 60) const;
- #endif
-
- //==============================================================================
- /** Returns this element's tag type name.
- E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would return "MOOSE".
- @see hasTagName
- */
- const String& getTagName() const noexcept { return tagName; }
-
- /** Returns the namespace portion of the tag-name, or an empty string if none is specified. */
- String getNamespace() const;
-
- /** Returns the part of the tag-name that follows any namespace declaration. */
- String getTagNameWithoutNamespace() const;
-
- /** Tests whether this element has a particular tag name.
- @param possibleTagName the tag name you're comparing it with
- @see getTagName
- */
- bool hasTagName (StringRef possibleTagName) const noexcept;
-
- /** Tests whether this element has a particular tag name, ignoring any XML namespace prefix.
- So a test for e.g. "xyz" will return true for "xyz" and also "foo:xyz", "bar::xyz", etc.
- @see getTagName
- */
- bool hasTagNameIgnoringNamespace (StringRef possibleTagName) const;
-
- //==============================================================================
- /** Returns the number of XML attributes this element contains.
-
- E.g. for an element such as \<MOOSE legs="4" antlers="2">, this would
- return 2.
- */
- int getNumAttributes() const noexcept;
-
- /** Returns the name of one of the elements attributes.
-
- E.g. for an element such as \<MOOSE legs="4" antlers="2">, then
- getAttributeName(1) would return "antlers".
-
- @see getAttributeValue, getStringAttribute
- */
- const std::string& getAttributeName (int attributeIndex) const noexcept;
-
- /** Returns the value of one of the elements attributes.
-
- E.g. for an element such as \<MOOSE legs="4" antlers="2">, then
- getAttributeName(1) would return "2".
-
- @see getAttributeName, getStringAttribute
- */
- const String& getAttributeValue (int attributeIndex) const noexcept;
-
- //==============================================================================
- // Attribute-handling methods..
-
- /** Checks whether the element contains an attribute with a certain name. */
- bool hasAttribute (StringRef attributeName) const noexcept;
-
- /** Returns the value of a named attribute.
- @param attributeName the name of the attribute to look up
- */
- const String& getStringAttribute (StringRef attributeName) const noexcept;
-
- /** Returns the value of a named attribute.
- @param attributeName the name of the attribute to look up
- @param defaultReturnValue a value to return if the element doesn't have an attribute
- with this name
- */
- String getStringAttribute (StringRef attributeName, const String& defaultReturnValue) const;
-
- /** Compares the value of a named attribute with a value passed-in.
-
- @param attributeName the name of the attribute to look up
- @param stringToCompareAgainst the value to compare it with
- @param ignoreCase whether the comparison should be case-insensitive
- @returns true if the value of the attribute is the same as the string passed-in;
- false if it's different (or if no such attribute exists)
- */
- bool compareAttribute (StringRef attributeName,
- StringRef stringToCompareAgainst,
- bool ignoreCase = false) const noexcept;
-
- /** Returns the value of a named attribute as an integer.
-
- This will try to find the attribute and convert it to an integer (using
- the String::getIntValue() method).
-
- @param attributeName the name of the attribute to look up
- @param defaultReturnValue a value to return if the element doesn't have an attribute
- with this name
- @see setAttribute
- */
- int getIntAttribute (StringRef attributeName, int defaultReturnValue = 0) const;
-
- /** Returns the value of a named attribute as floating-point.
-
- This will try to find the attribute and convert it to a double (using
- the String::getDoubleValue() method).
-
- @param attributeName the name of the attribute to look up
- @param defaultReturnValue a value to return if the element doesn't have an attribute
- with this name
- @see setAttribute
- */
- double getDoubleAttribute (StringRef attributeName, double defaultReturnValue = 0.0) const;
-
- /** Returns the value of a named attribute as a boolean.
-
- This will try to find the attribute and interpret it as a boolean. To do this,
- it'll return true if the value is "1", "true", "y", etc, or false for other
- values.
-
- @param attributeName the name of the attribute to look up
- @param defaultReturnValue a value to return if the element doesn't have an attribute
- with this name
- */
- bool getBoolAttribute (StringRef attributeName, bool defaultReturnValue = false) const;
-
- /** Adds a named attribute to the element.
-
- If the element already contains an attribute with this name, it's value will
- be updated to the new value. If there's no such attribute yet, a new one will
- be added.
-
- Note that there are other setAttribute() methods that take integers,
- doubles, etc. to make it easy to store numbers.
-
- @param attributeName the name of the attribute to set
- @param newValue the value to set it to
- @see removeAttribute
- */
- void setAttribute (const Identifier& attributeName, const String& newValue);
-
- /** Adds a named attribute to the element, setting it to an integer value.
-
- If the element already contains an attribute with this name, it's value will
- be updated to the new value. If there's no such attribute yet, a new one will
- be added.
-
- Note that there are other setAttribute() methods that take integers,
- doubles, etc. to make it easy to store numbers.
-
- @param attributeName the name of the attribute to set
- @param newValue the value to set it to
- */
- void setAttribute (const Identifier& attributeName, int newValue);
-
- /** Adds a named attribute to the element, setting it to a floating-point value.
-
- If the element already contains an attribute with this name, it's value will
- be updated to the new value. If there's no such attribute yet, a new one will
- be added.
-
- Note that there are other setAttribute() methods that take integers,
- doubles, etc. to make it easy to store numbers.
-
- @param attributeName the name of the attribute to set
- @param newValue the value to set it to
- */
- void setAttribute (const Identifier& attributeName, double newValue);
-
- /** Removes a named attribute from the element.
-
- @param attributeName the name of the attribute to remove
- @see removeAllAttributes
- */
- void removeAttribute (const Identifier& attributeName) noexcept;
-
- /** Removes all attributes from this element. */
- void removeAllAttributes() noexcept;
-
- //==============================================================================
- // Child element methods..
-
- /** Returns the first of this element's sub-elements.
- see getNextElement() for an example of how to iterate the sub-elements.
- @see forEachXmlChildElement
- */
- XmlElement* getFirstChildElement() const noexcept { return firstChildElement; }
-
- /** Returns the next of this element's siblings.
-
- This can be used for iterating an element's sub-elements, e.g.
- @code
- XmlElement* child = myXmlDocument->getFirstChildElement();
-
- while (child != nullptr)
- {
- ...do stuff with this child..
-
- child = child->getNextElement();
- }
- @endcode
-
- Note that when iterating the child elements, some of them might be
- text elements as well as XML tags - use isTextElement() to work this
- out.
-
- Also, it's much easier and neater to use this method indirectly via the
- forEachXmlChildElement macro.
-
- @returns the sibling element that follows this one, or a nullptr if
- this is the last element in its parent
-
- @see getNextElement, isTextElement, forEachXmlChildElement
- */
- inline XmlElement* getNextElement() const noexcept { return nextListItem; }
-
- /** Returns the next of this element's siblings which has the specified tag
- name.
-
- This is like getNextElement(), but will scan through the list until it
- finds an element with the given tag name.
-
- @see getNextElement, forEachXmlChildElementWithTagName
- */
- XmlElement* getNextElementWithTagName (StringRef requiredTagName) const;
-
- /** Returns the number of sub-elements in this element.
- @see getChildElement
- */
- int getNumChildElements() const noexcept;
-
- /** Returns the sub-element at a certain index.
-
- It's not very efficient to iterate the sub-elements by index - see
- getNextElement() for an example of how best to iterate.
-
- @returns the n'th child of this element, or nullptr if the index is out-of-range
- @see getNextElement, isTextElement, getChildByName
- */
- XmlElement* getChildElement (int index) const noexcept;
-
- /** Returns the first sub-element with a given tag-name.
-
- @param tagNameToLookFor the tag name of the element you want to find
- @returns the first element with this tag name, or nullptr if none is found
- @see getNextElement, isTextElement, getChildElement, getChildByAttribute
- */
- XmlElement* getChildByName (StringRef tagNameToLookFor) const noexcept;
-
- /** Returns the first sub-element which has an attribute that matches the given value.
-
- @param attributeName the name of the attribute to check
- @param attributeValue the target value of the attribute
- @returns the first element with this attribute value, or nullptr if none is found
- @see getChildByName
- */
- XmlElement* getChildByAttribute (StringRef attributeName,
- StringRef attributeValue) const noexcept;
-
- //==============================================================================
- /** Appends an element to this element's list of children.
-
- Child elements are deleted automatically when their parent is deleted, so
- make sure the object that you pass in will not be deleted by anything else,
- and make sure it's not already the child of another element.
-
- Note that due to the XmlElement using a singly-linked-list, prependChildElement()
- is an O(1) operation, but addChildElement() is an O(N) operation - so if
- you're adding large number of elements, you may prefer to do so in reverse order!
-
- @see getFirstChildElement, getNextElement, getNumChildElements,
- getChildElement, removeChildElement
- */
- void addChildElement (XmlElement* newChildElement) noexcept;
-
- /** Inserts an element into this element's list of children.
-
- Child elements are deleted automatically when their parent is deleted, so
- make sure the object that you pass in will not be deleted by anything else,
- and make sure it's not already the child of another element.
-
- @param newChildElement the element to add
- @param indexToInsertAt the index at which to insert the new element - if this is
- below zero, it will be added to the end of the list
- @see addChildElement, insertChildElement
- */
- void insertChildElement (XmlElement* newChildElement,
- int indexToInsertAt) noexcept;
-
- /** Inserts an element at the beginning of this element's list of children.
-
- Child elements are deleted automatically when their parent is deleted, so
- make sure the object that you pass in will not be deleted by anything else,
- and make sure it's not already the child of another element.
-
- Note that due to the XmlElement using a singly-linked-list, prependChildElement()
- is an O(1) operation, but addChildElement() is an O(N) operation - so if
- you're adding large number of elements, you may prefer to do so in reverse order!
-
- @see addChildElement, insertChildElement
- */
- void prependChildElement (XmlElement* newChildElement) noexcept;
-
- /** Creates a new element with the given name and returns it, after adding it
- as a child element.
-
- This is a handy method that means that instead of writing this:
- @code
- XmlElement* newElement = new XmlElement ("foobar");
- myParentElement->addChildElement (newElement);
- @endcode
-
- ..you could just write this:
- @code
- XmlElement* newElement = myParentElement->createNewChildElement ("foobar");
- @endcode
- */
- XmlElement* createNewChildElement (StringRef tagName);
-
- /** Replaces one of this element's children with another node.
-
- If the current element passed-in isn't actually a child of this element,
- this will return false and the new one won't be added. Otherwise, the
- existing element will be deleted, replaced with the new one, and it
- will return true.
- */
- bool replaceChildElement (XmlElement* currentChildElement,
- XmlElement* newChildNode) noexcept;
-
- /** Removes a child element.
-
- @param childToRemove the child to look for and remove
- @param shouldDeleteTheChild if true, the child will be deleted, if false it'll
- just remove it
- */
- void removeChildElement (XmlElement* childToRemove,
- bool shouldDeleteTheChild) noexcept;
-
- /** Deletes all the child elements in the element.
- @see removeChildElement, deleteAllChildElementsWithTagName
- */
- void deleteAllChildElements() noexcept;
-
- /** Deletes all the child elements with a given tag name.
- @see removeChildElement
- */
- void deleteAllChildElementsWithTagName (StringRef tagName) noexcept;
-
- /** Returns true if the given element is a child of this one. */
- bool containsChildElement (const XmlElement* possibleChild) const noexcept;
-
- /** Recursively searches all sub-elements of this one, looking for an element
- which is the direct parent of the specified element.
-
- Because elements don't store a pointer to their parent, if you have one
- and need to find its parent, the only way to do so is to exhaustively
- search the whole tree for it.
-
- If the given child is found somewhere in this element's hierarchy, then
- this method will return its parent. If not, it will return nullptr.
- */
- XmlElement* findParentElementOf (const XmlElement* childToSearchFor) noexcept;
-
- //==============================================================================
- /** Returns true if this element is a section of text.
-
- Elements can either be an XML tag element or a secton of text, so this
- is used to find out what kind of element this one is.
-
- @see getAllText, addTextElement, deleteAllTextElements
- */
- bool isTextElement() const noexcept;
-
- /** Returns the text for a text element.
-
- Note that if you have an element like this:
-
- @code<xyz>hello</xyz>@endcode
-
- then calling getText on the "xyz" element won't return "hello", because that is
- actually stored in a special text sub-element inside the xyz element. To get the
- "hello" string, you could either call getText on the (unnamed) sub-element, or
- use getAllSubText() to do this automatically.
-
- Note that leading and trailing whitespace will be included in the string - to remove
- if, just call String::trim() on the result.
-
- @see isTextElement, getAllSubText, getChildElementAllSubText
- */
- const String& getText() const noexcept;
-
- /** Sets the text in a text element.
-
- Note that this is only a valid call if this element is a text element. If it's
- not, then no action will be performed. If you're trying to add text inside a normal
- element, you probably want to use addTextElement() instead.
- */
- void setText (const String& newText);
-
- /** Returns all the text from this element's child nodes.
-
- This iterates all the child elements and when it finds text elements,
- it concatenates their text into a big string which it returns.
-
- E.g. @code<xyz>hello <x>there</x> world</xyz>@endcode
- if you called getAllSubText on the "xyz" element, it'd return "hello there world".
-
- Note that leading and trailing whitespace will be included in the string - to remove
- if, just call String::trim() on the result.
-
- @see isTextElement, getChildElementAllSubText, getText, addTextElement
- */
- String getAllSubText() const;
-
- /** Returns all the sub-text of a named child element.
-
- If there is a child element with the given tag name, this will return
- all of its sub-text (by calling getAllSubText() on it). If there is
- no such child element, this will return the default string passed-in.
-
- @see getAllSubText
- */
- String getChildElementAllSubText (StringRef childTagName,
- const String& defaultReturnValue) const;
-
- /** Appends a section of text to this element.
- @see isTextElement, getText, getAllSubText
- */
- void addTextElement (const String& text);
-
- /** Removes all the text elements from this element.
- @see isTextElement, getText, getAllSubText, addTextElement
- */
- void deleteAllTextElements() noexcept;
-
- /** Creates a text element that can be added to a parent element. */
- static XmlElement* createTextElement (const String& text);
-
- /** Checks if a given string is a valid XML name */
- static bool isValidXmlName (StringRef possibleName) noexcept;
-
- //==============================================================================
- private:
- struct XmlAttributeNode
- {
- XmlAttributeNode (const XmlAttributeNode&) noexcept;
- XmlAttributeNode (const Identifier&, const String&) noexcept;
- XmlAttributeNode (String::CharPointerType, String::CharPointerType);
-
- LinkedListPointer<XmlAttributeNode> nextListItem;
- Identifier name;
- String value;
-
- private:
- XmlAttributeNode& operator= (const XmlAttributeNode&) WATER_DELETED_FUNCTION;
- };
-
- friend class XmlDocument;
- friend class LinkedListPointer<XmlAttributeNode>;
- friend class LinkedListPointer<XmlElement>;
- friend class LinkedListPointer<XmlElement>::Appender;
- friend class NamedValueSet;
-
- LinkedListPointer<XmlElement> nextListItem;
- LinkedListPointer<XmlElement> firstChildElement;
- LinkedListPointer<XmlAttributeNode> attributes;
- String tagName;
-
- XmlElement (int) noexcept;
- void copyChildrenAndAttributesFrom (const XmlElement&);
- void writeElementAsText (OutputStream&, int indentationLevel, int lineWrapLength) const;
- void getChildElementsAsArray (XmlElement**) const noexcept;
- void reorderChildElements (XmlElement**, int) noexcept;
- XmlAttributeNode* getAttribute (StringRef) const noexcept;
- };
-
- }
-
- #endif // WATER_XMLELEMENT_H_INCLUDED
|