1
0
Fork 0
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:
reuk 2025-06-16 14:46:19 +01:00
parent a3f813d8a5
commit 58fabf3a8f
No known key found for this signature in database
4 changed files with 171 additions and 58 deletions

View file

@ -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) });
}
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;