mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
XmlElement: Add new API to allow iterating over attributes
This commit is contained in:
parent
a3f813d8a5
commit
58fabf3a8f
4 changed files with 171 additions and 58 deletions
|
|
@ -277,20 +277,20 @@ void NamedValueSet::setFromXmlAttributes (const XmlElement& xml)
|
|||
{
|
||||
values.clearQuick();
|
||||
|
||||
for (auto* att = xml.attributes.get(); att != nullptr; att = att->nextListItem)
|
||||
for (const auto& [name, value] : xml.getAttributeIterator())
|
||||
{
|
||||
if (att->name.toString().startsWith ("base64:"))
|
||||
if (name.toString().startsWith ("base64:"))
|
||||
{
|
||||
MemoryBlock mb;
|
||||
|
||||
if (mb.fromBase64Encoding (att->value))
|
||||
if (mb.fromBase64Encoding (value))
|
||||
{
|
||||
values.add ({ att->name.toString().substring (7), var (mb) });
|
||||
values.add ({ name.toString().substring (7), var (mb) });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
values.add ({ att->name, var (att->value) });
|
||||
values.add ({ name, var (value) });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ XmlElement* XmlDocument::readNextElement (const bool alsoParseSubElements)
|
|||
if (nextChar == '"' || nextChar == '\'')
|
||||
{
|
||||
auto* newAtt = new XmlElement::XmlAttributeNode (attNameStart, attNameEnd);
|
||||
readQuotedString (newAtt->value);
|
||||
readQuotedString (newAtt->attribute.value);
|
||||
attributeAppender.append (newAtt);
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@
|
|||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static bool isValidXmlNameStartCharacter (juce_wchar character) noexcept
|
||||
{
|
||||
return character == ':'
|
||||
|
|
@ -67,21 +66,19 @@ static bool isValidXmlNameBodyCharacter (juce_wchar character) noexcept
|
|||
}
|
||||
|
||||
XmlElement::XmlAttributeNode::XmlAttributeNode (const XmlAttributeNode& other) noexcept
|
||||
: name (other.name),
|
||||
value (other.value)
|
||||
: attribute (other.attribute)
|
||||
{
|
||||
}
|
||||
|
||||
XmlElement::XmlAttributeNode::XmlAttributeNode (const Identifier& n, const String& v) noexcept
|
||||
: name (n), value (v)
|
||||
: attribute { n, v }
|
||||
{
|
||||
jassert (isValidXmlName (name));
|
||||
jassert (isValidXmlName (attribute.name));
|
||||
}
|
||||
|
||||
XmlElement::XmlAttributeNode::XmlAttributeNode (String::CharPointerType nameStart, String::CharPointerType nameEnd)
|
||||
: name (nameStart, nameEnd)
|
||||
: XmlAttributeNode ({ nameStart, nameEnd }, {})
|
||||
{
|
||||
jassert (isValidXmlName (name));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -281,7 +278,7 @@ void XmlElement::writeElementAsText (OutputStream& outputStream,
|
|||
auto attIndent = (size_t) (indentationLevel + tagName.length() + 1);
|
||||
int lineLen = 0;
|
||||
|
||||
for (auto* att = attributes.get(); att != nullptr; att = att->nextListItem)
|
||||
for (const auto& [name, value] : getAttributeIterator())
|
||||
{
|
||||
if (lineLen > lineWrapLength && indentationLevel >= 0)
|
||||
{
|
||||
|
|
@ -292,9 +289,9 @@ void XmlElement::writeElementAsText (OutputStream& outputStream,
|
|||
|
||||
auto startPos = outputStream.getPosition();
|
||||
outputStream.writeByte (' ');
|
||||
outputStream << att->name;
|
||||
outputStream << name;
|
||||
outputStream.write ("=\"", 2);
|
||||
XmlOutputFunctions::escapeIllegalXmlChars (outputStream, att->value, true);
|
||||
XmlOutputFunctions::escapeIllegalXmlChars (outputStream, value, true);
|
||||
outputStream.writeByte ('"');
|
||||
lineLen += (int) (outputStream.getPosition() - startPos);
|
||||
}
|
||||
|
|
@ -536,7 +533,7 @@ static const String& getEmptyStringRef() noexcept
|
|||
const String& XmlElement::getAttributeName (const int index) const noexcept
|
||||
{
|
||||
if (auto* att = attributes[index].get())
|
||||
return att->name.toString();
|
||||
return att->attribute.name.toString();
|
||||
|
||||
return getEmptyStringRef();
|
||||
}
|
||||
|
|
@ -544,16 +541,16 @@ const String& XmlElement::getAttributeName (const int index) const noexcept
|
|||
const String& XmlElement::getAttributeValue (const int index) const noexcept
|
||||
{
|
||||
if (auto* att = attributes[index].get())
|
||||
return att->value;
|
||||
return att->attribute.value;
|
||||
|
||||
return getEmptyStringRef();
|
||||
}
|
||||
|
||||
XmlElement::XmlAttributeNode* XmlElement::getAttribute (StringRef attributeName) const noexcept
|
||||
const XmlAttribute* XmlElement::getAttribute (StringRef attributeName) const noexcept
|
||||
{
|
||||
for (auto* att = attributes.get(); att != nullptr; att = att->nextListItem)
|
||||
if (att->name == attributeName)
|
||||
return att;
|
||||
for (const auto& att : getAttributeIterator())
|
||||
if (att.name == attributeName)
|
||||
return &att;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -617,12 +614,16 @@ bool XmlElement::compareAttribute (StringRef attributeName,
|
|||
const bool ignoreCase) const noexcept
|
||||
{
|
||||
if (auto* att = getAttribute (attributeName))
|
||||
return ignoreCase ? att->value.equalsIgnoreCase (stringToCompareAgainst)
|
||||
: att->value == stringToCompareAgainst;
|
||||
return att->equals (attributeName, stringToCompareAgainst, ignoreCase);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XmlElement::compareAttribute (const XmlAttribute& other, const bool ignoreCase) const noexcept
|
||||
{
|
||||
return compareAttribute (other.name, other.value, ignoreCase);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void XmlElement::setAttribute (const Identifier& attributeName, const String& value)
|
||||
{
|
||||
|
|
@ -634,9 +635,9 @@ void XmlElement::setAttribute (const Identifier& attributeName, const String& va
|
|||
{
|
||||
for (auto* att = attributes.get(); ; att = att->nextListItem)
|
||||
{
|
||||
if (att->name == attributeName)
|
||||
if (att->attribute.name == attributeName)
|
||||
{
|
||||
att->value = value;
|
||||
att->attribute.value = value;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -663,7 +664,7 @@ void XmlElement::removeAttribute (const Identifier& attributeName) noexcept
|
|||
{
|
||||
for (auto* att = &attributes; att->get() != nullptr; att = &(att->get()->nextListItem))
|
||||
{
|
||||
if (att->get()->name == attributeName)
|
||||
if (att->get()->attribute.name == attributeName)
|
||||
{
|
||||
delete att->removeNext();
|
||||
break;
|
||||
|
|
@ -794,7 +795,7 @@ bool XmlElement::isEquivalentTo (const XmlElement* const other,
|
|||
|
||||
for (auto* att = attributes.get(); att != nullptr; att = att->nextListItem)
|
||||
{
|
||||
if (! other->compareAttribute (att->name, att->value))
|
||||
if (! other->compareAttribute (att->attribute))
|
||||
return false;
|
||||
|
||||
++totalAtts;
|
||||
|
|
@ -818,11 +819,8 @@ bool XmlElement::isEquivalentTo (const XmlElement* const other,
|
|||
return false;
|
||||
}
|
||||
|
||||
if (thisAtt->name != otherAtt->name
|
||||
|| thisAtt->value != otherAtt->value)
|
||||
{
|
||||
if (thisAtt->attribute != otherAtt->attribute)
|
||||
return false;
|
||||
}
|
||||
|
||||
thisAtt = thisAtt->nextListItem;
|
||||
otherAtt = otherAtt->nextListItem;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,61 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
/** A name-value pair representing an attribute of an XML tag.
|
||||
|
||||
@see XmlElement
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
struct XmlAttribute
|
||||
{
|
||||
/** The name of the attribute. */
|
||||
Identifier name;
|
||||
|
||||
/** The value of the attribute. */
|
||||
String value;
|
||||
|
||||
/** Returns true if the name and value of this attribute compare equal to the passed-in strings.
|
||||
|
||||
The 'ignoreCase' option only affects the value strings.
|
||||
*/
|
||||
bool equals (StringRef otherName, StringRef otherValue, bool ignoreCase) const
|
||||
{
|
||||
if (name != otherName)
|
||||
return false;
|
||||
|
||||
return ignoreCase ? value.equalsIgnoreCase (otherValue)
|
||||
: value == otherValue;
|
||||
}
|
||||
|
||||
/** Returns true if this attribute compares equal to the passed-in attribute.
|
||||
|
||||
The 'ignoreCase' option only affects the value strings.
|
||||
*/
|
||||
bool equals (const XmlAttribute& other, bool ignoreCase) const
|
||||
{
|
||||
return equals (other.name, other.value, ignoreCase);
|
||||
}
|
||||
|
||||
/** Returns true if both attributes are equal.
|
||||
|
||||
This comparison is case-sensitive.
|
||||
*/
|
||||
bool operator== (const XmlAttribute& other) const
|
||||
{
|
||||
return equals (other, false);
|
||||
}
|
||||
|
||||
/** Returns true if the attributes have different values.
|
||||
|
||||
This comparison is case-sensitive.
|
||||
*/
|
||||
bool operator!= (const XmlAttribute& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Used to build a tree of elements representing an XML document.
|
||||
|
||||
|
|
@ -263,6 +318,15 @@ public:
|
|||
StringRef stringToCompareAgainst,
|
||||
bool ignoreCase = false) const noexcept;
|
||||
|
||||
/** Compares the value of a named attribute with a value passed-in.
|
||||
|
||||
@param attribute the name-value pair to search for in the current element
|
||||
@param ignoreCase whether the value 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 (const XmlAttribute& attribute, 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
|
||||
|
|
@ -657,14 +721,30 @@ private:
|
|||
//==============================================================================
|
||||
struct GetNextElement
|
||||
{
|
||||
XmlElement* getNext (const XmlElement& e) const { return e.getNextElement(); }
|
||||
using Value = XmlElement*;
|
||||
using Element = XmlElement*;
|
||||
|
||||
Element getNext (Element e) const { return e->getNextElement(); }
|
||||
|
||||
static const Value& deref (const Element& e)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
};
|
||||
|
||||
struct GetNextElementWithTagName
|
||||
{
|
||||
using Value = XmlElement*;
|
||||
using Element = XmlElement*;
|
||||
|
||||
GetNextElementWithTagName() = default;
|
||||
explicit GetNextElementWithTagName (String n) : name (std::move (n)) {}
|
||||
XmlElement* getNext (const XmlElement& e) const { return e.getNextElementWithTagName (name); }
|
||||
Element getNext (Element e) const { return e->getNextElementWithTagName (name); }
|
||||
|
||||
static const Value& deref (const Element& e)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
|
||||
String name;
|
||||
};
|
||||
|
|
@ -675,15 +755,17 @@ private:
|
|||
{
|
||||
public:
|
||||
using difference_type = ptrdiff_t;
|
||||
using value_type = XmlElement*;
|
||||
using value_type = typename Traits::Value;
|
||||
using pointer = const value_type*;
|
||||
using reference = value_type;
|
||||
using reference = const value_type&;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
|
||||
using Element = typename Traits::Element;
|
||||
|
||||
Iterator() = default;
|
||||
|
||||
template <typename... Args>
|
||||
Iterator (XmlElement* e, Args&&... args)
|
||||
Iterator (Element e, Args&&... args)
|
||||
: Traits (std::forward<Args> (args)...), element (e) {}
|
||||
|
||||
Iterator begin() const { return *this; }
|
||||
|
|
@ -692,12 +774,12 @@ private:
|
|||
bool operator== (const Iterator& other) const { return element == other.element; }
|
||||
bool operator!= (const Iterator& other) const { return ! operator== (other); }
|
||||
|
||||
reference operator*() const { return element; }
|
||||
pointer operator->() const { return &element; }
|
||||
reference operator*() const { return Traits::deref (element); }
|
||||
pointer operator->() const { return std::addressof (Traits::deref (element)); }
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
element = Traits::getNext (*element);
|
||||
element = Traits::getNext (element);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
@ -709,9 +791,40 @@ private:
|
|||
}
|
||||
|
||||
private:
|
||||
value_type element = nullptr;
|
||||
Element element{};
|
||||
};
|
||||
|
||||
struct XmlAttributeNode
|
||||
{
|
||||
XmlAttributeNode (const XmlAttributeNode&) noexcept;
|
||||
XmlAttributeNode (const Identifier&, const String&) noexcept;
|
||||
XmlAttributeNode (String::CharPointerType, String::CharPointerType);
|
||||
|
||||
XmlAttributeNode& operator= (const XmlAttributeNode&) = delete;
|
||||
XmlAttributeNode& operator= (XmlAttributeNode&&) = delete;
|
||||
|
||||
LinkedListPointer<XmlAttributeNode> nextListItem;
|
||||
XmlAttribute attribute;
|
||||
};
|
||||
|
||||
struct AttributeIteratorTraits
|
||||
{
|
||||
using Value = XmlAttribute;
|
||||
using Element = const XmlAttributeNode*;
|
||||
|
||||
static Element getNext (Element node)
|
||||
{
|
||||
return node->nextListItem.get();
|
||||
}
|
||||
|
||||
static const Value& deref (const Element& node)
|
||||
{
|
||||
return node->attribute;
|
||||
}
|
||||
};
|
||||
|
||||
using AttributeIterator = Iterator<AttributeIteratorTraits>;
|
||||
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Allows iterating the children of an XmlElement using range-for syntax.
|
||||
|
|
@ -744,6 +857,23 @@ public:
|
|||
return Iterator<GetNextElementWithTagName> { getChildByName (name), name };
|
||||
}
|
||||
|
||||
/** Allows iterating all attributes of an XmlElement using range-for syntax.
|
||||
|
||||
@code
|
||||
void doSomethingWithXmlAttributes (const XmlElement& myParentXml)
|
||||
{
|
||||
for (const auto& attribute : myParentXml.getAttributeIterator())
|
||||
{
|
||||
// Name and value are available as attribute.name and attribute.value
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
AttributeIterator getAttributeIterator() const
|
||||
{
|
||||
return AttributeIterator { attributes.get() };
|
||||
}
|
||||
|
||||
#ifndef DOXYGEN
|
||||
[[deprecated]] void macroBasedForLoop() const noexcept {}
|
||||
|
||||
|
|
@ -771,25 +901,10 @@ public:
|
|||
|
||||
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&) = delete;
|
||||
};
|
||||
|
||||
friend class XmlDocument;
|
||||
friend class LinkedListPointer<XmlAttributeNode>;
|
||||
friend class LinkedListPointer<XmlElement>;
|
||||
friend class LinkedListPointer<XmlElement>::Appender;
|
||||
friend class NamedValueSet;
|
||||
|
||||
LinkedListPointer<XmlElement> nextListItem, firstChildElement;
|
||||
LinkedListPointer<XmlAttributeNode> attributes;
|
||||
|
|
@ -800,9 +915,9 @@ private:
|
|||
void writeElementAsText (OutputStream&, int, int, const char*) const;
|
||||
void getChildElementsAsArray (XmlElement**) const noexcept;
|
||||
void reorderChildElements (XmlElement**, int) noexcept;
|
||||
XmlAttributeNode* getAttribute (StringRef) const noexcept;
|
||||
const XmlAttribute* getAttribute (StringRef) const noexcept;
|
||||
|
||||
// Sigh.. L"" or _T ("") string literals are problematic in general, and really inappropriate
|
||||
// L"" or _T ("") string literals are problematic in general, and really inappropriate
|
||||
// for XML tags. Use a UTF-8 encoded literal instead, or if you're really determined to use
|
||||
// UTF-16, cast it to a String and use the other constructor.
|
||||
XmlElement (const wchar_t*) = delete;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue