/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #include "itkDOMNode.h" #include namespace itk { DOMNode::DOMNode() = default; void DOMNode::SetParent(DOMNode * node) { this->m_Parent = node; } DOMNode * DOMNode::GetParent() { return this->m_Parent; } const DOMNode * DOMNode::GetParent() const { return this->m_Parent; } /** Retrieve an attribute by key (return an empty string if not found). */ std::string DOMNode::GetAttribute(const std::string & key) const { // "id" is a special attribute if (StringTools::MatchWith(key, "id")) { return this->GetID(); } auto i = this->m_Attributes.find(key); if (i == this->m_Attributes.end()) { return ""; } else { return i->second; } } /** Check whether has an attribute. */ bool DOMNode::HasAttribute(const std::string & key) const { auto i = this->m_Attributes.find(key); return (i != this->m_Attributes.end()); } /** Add or replace an attribute. */ void DOMNode::SetAttribute(const std::string & key, const std::string & value) { // "id" is a special attribute if (StringTools::MatchWith(key, "id")) { this->SetID(value); return; } auto i = this->m_Attributes.find(key); if (i == this->m_Attributes.end()) { // add a new attribute this->m_Attributes[key] = value; // keep track of the adding order i = this->m_Attributes.find(key); this->m_OrderedAttributes.push_back(&(*i)); } else { // replace an existing attribute i->second = value; } } /** Remove an attribute by key (throw exception if not found). */ void DOMNode::RemoveAttribute(const std::string & key) { auto i = this->m_Attributes.find(key); if (i == this->m_Attributes.end()) { itkExceptionMacro("attribute does not exist"); } // remove it from the ordered container first for (auto j = this->m_OrderedAttributes.begin(); j != this->m_OrderedAttributes.end(); ++j) { if ((*j)->first == key) { this->m_OrderedAttributes.erase(j); break; } } // then remove it from the map container this->m_Attributes.erase(i); } /** * Return all attributes, in the order of being parsed or added (default), * or alphabetic order of the attribute keys (keepOriginalOrder = false). */ void DOMNode::GetAllAttributes(AttributesListType & output, bool keepOriginalOrder) const { if (keepOriginalOrder) { for (auto orderedAttribute : this->m_OrderedAttributes) { output.push_back(*orderedAttribute); } } else { for (const auto & attribute : this->m_Attributes) { output.push_back(attribute); } } } /** Remove all attributes. */ void DOMNode::RemoveAllAttributes() { this->m_Attributes.clear(); this->m_OrderedAttributes.clear(); } /** Get number of children. */ DOMNode::SizeType DOMNode::GetNumberOfChildren() const { return this->m_Children.size(); } /** Return all children. */ void DOMNode::GetAllChildren(ChildrenListType & output) { for (IdentifierType i = 0; i < static_cast(this->GetNumberOfChildren()); ++i) { DOMNode * node = this->GetChild(i); output.push_back(node); } } /** Return all children for read-only access. */ void DOMNode::GetAllChildren(ConstChildrenListType & output) const { for (IdentifierType i = 0; i < static_cast(this->GetNumberOfChildren()); ++i) { const DOMNode * node = this->GetChild(i); output.push_back(node); } } /** Return all children of a same tag name. */ void DOMNode::GetChildren(const std::string & tag, ChildrenListType & output) { for (IdentifierType i = 0; i < static_cast(this->GetNumberOfChildren()); ++i) { DOMNode * node = this->GetChild(i); if (tag == node->GetName()) { output.push_back(node); } } } /** Return all children of a same tag name for read-only access. */ void DOMNode::GetChildren(const std::string & tag, ConstChildrenListType & output) const { for (IdentifierType i = 0; i < static_cast(this->GetNumberOfChildren()); ++i) { const DOMNode * node = this->GetChild(i); if (tag == node->GetName()) { output.push_back(node); } } } /** Remove all children. */ void DOMNode::RemoveAllChildren() { this->m_Children.clear(); } /** Add a child in front of another child (throw exception if not able to add). */ void DOMNode::AddChild(DOMNode * node, IdentifierType i) { if (this->m_Children.empty()) { this->AddChildAtEnd(node); return; } if (node == nullptr || this->ShareRoot(node)) { itkExceptionMacro("not able to add child"); } if (i < 0 || i >= static_cast(this->m_Children.size())) { itkExceptionMacro("not able to add child"); } node->m_Parent = this; this->m_Children.insert(this->m_Children.begin() + i, Pointer(node)); } /** Add a child in front of the children list (throw exception if not able to add). */ void DOMNode::AddChildAtBegin(DOMNode * node) { if (node == nullptr || this->ShareRoot(node)) { itkExceptionMacro("not able to add child"); } node->m_Parent = this; this->m_Children.insert(this->m_Children.begin(), Pointer(node)); } /** Add a child at the end of the children list (throw exception if not able to add). */ void DOMNode::AddChildAtEnd(DOMNode * node) { if (node == nullptr || this->ShareRoot(node)) { itkExceptionMacro("not able to add child"); } node->m_Parent = this; this->m_Children.push_back(Pointer(node)); } /** Replace a child (throw exception if not able to replace). */ void DOMNode::SetChild(DOMNode * node, IdentifierType i) { if (node == nullptr || this->ShareRoot(node)) { itkExceptionMacro("not able to add child"); } if (i < 0 || i >= static_cast(this->m_Children.size())) { itkExceptionMacro("not able to add child"); } node->m_Parent = this; this->m_Children[i] = node; } /** Remove a child by index (throw exception if out of range). */ void DOMNode::RemoveChild(IdentifierType i) { if (i < 0 || i >= static_cast(m_Children.size())) { itkExceptionMacro("not able to remove child"); } this->m_Children[i]->m_Parent = nullptr; this->m_Children.erase(this->m_Children.begin() + i); } /** Remove all attributes and children. */ void DOMNode::RemoveAllAttributesAndChildren() { this->RemoveAllAttributes(); this->RemoveAllChildren(); } /** Retrieve a child by index (return nullptr if out of range). */ DOMNode * DOMNode::GetChild(IdentifierType i) { if (i < 0 || i >= static_cast(this->m_Children.size())) { return nullptr; } else { return (DOMNode *)this->m_Children[i]; } } /** Retrieve a child by index (return nullptr if out of range). */ const DOMNode * DOMNode::GetChild(IdentifierType i) const { // just use the non-const version and return it as a const, // this is safe as the non-const version actually doesn't modify the internal state. return const_cast(this)->GetChild(i); } /** Retrieve a child by tag name and an index (multiple children can have a same tag name, return nullptr if no such * child). */ DOMNode * DOMNode::GetChild(const std::string & tag, IdentifierType i) { IdentifierType k = 0; for (IdentifierType j = 0; j < static_cast(this->GetNumberOfChildren()); ++j) { DOMNode * node = this->GetChild(j); if (tag == node->GetName()) { if (k++ == i) { return node; } } } return nullptr; } /** Retrieve a child by tag name and an index (multiple children can have a same tag name, return nullptr if no such * child). */ const DOMNode * DOMNode::GetChild(const std::string & tag, IdentifierType i) const { // just use the non-const version and return it as a const, // this is safe as the non-const version actually doesn't modify the internal state. return const_cast(this)->GetChild(tag, i); } /** Retrieve a child by its unique "id" attribute value (return nullptr if not found). */ DOMNode * DOMNode::GetChildByID(const std::string & value) { for (IdentifierType j = 0; j < static_cast(this->GetNumberOfChildren()); ++j) { DOMNode * node = this->GetChild(j); if (value == node->GetID()) { return node; } } return nullptr; } /** Retrieve a child by its unique "id" attribute value (return nullptr if not found). */ const DOMNode * DOMNode::GetChildByID(const std::string & value) const { // just use the non-const version and return it as a const, // this is safe as the non-const version actually doesn't modify the internal state. return const_cast(this)->GetChildByID(value); } /** Retrieve an older or younger sibling by distance (return nullptr if no such sibling). */ DOMNode * DOMNode::GetSibling(OffsetType i) { DOMNode * parent = this->GetParent(); if (parent == nullptr) { return nullptr; } IdentifierType j; for (j = 0; j < static_cast(parent->GetNumberOfChildren()); ++j) { if (parent->GetChild(j) == this) { break; } } j += i; if (j < 0 || j >= static_cast(parent->GetNumberOfChildren())) { return nullptr; } else { return parent->GetChild(j); } } /** Retrieve an older or younger sibling by distance (return nullptr if no such sibling). */ const DOMNode * DOMNode::GetSibling(OffsetType i) const { // just use the non-const version and return it as a const, // this is safe as the non-const version actually doesn't modify the internal state. return const_cast(this)->GetSibling(i); } /** Return the root node. */ DOMNode * DOMNode::GetRoot() { DOMNode * node = this->GetParent(); if (node == nullptr) { return this; } else { return node->GetRoot(); } } /** Return the root node. */ const DOMNode * DOMNode::GetRoot() const { // just use the non-const version and return it as a const, // this is safe as the non-const version actually doesn't modify the internal state. return const_cast(this)->GetRoot(); } /** Test whether the input node and this node share the same root. */ bool DOMNode::ShareRoot(const DOMNode * node) const { return (node != nullptr && node->GetRoot() == this->GetRoot()); } /** * The following function finds a child or sibling or relative using a query string or path. * A path or QueryString consists of multiple following items that are separated by '/': * -[n] : an older sibling by distance 1 (when omitted) or n; * +[n] : a younger sibling by distance 1 (when omitted) or n; * n : a child at index n; * [:n] : a child at index 0 (when omitted) or n after filtering children with a tag name; * ![:n] : a child at index 0 (when omitted) or n within all text children; * : : a child by id; * . : current node; * .. : parent node; * / : absolute path (denote apath), search from the root. * * The method returns nullptr if queried node does not exist. */ DOMNode * DOMNode::Find(const std::string & path) { std::string s; std::string rpath; { size_t pos = path.find_first_of('/'); if (pos == std::string::npos) { s = path; rpath = ""; } else { s = path.substr(0, pos); rpath = path.substr(pos + 1); } } DOMNode * node = nullptr; // / if (s.empty()) { node = this->GetRoot(); } // if it is an unsigned integer number else if (s[0] >= '0' && s[0] <= '9') { std::istringstream iss(s); unsigned int i = 0; iss >> i; if (!iss.fail()) { node = this->GetChild(IdentifierType(i)); } } // +[n] else if (s[0] == '+') { if (s.size() > 1) { s = s.substr(1); std::istringstream iss(s); unsigned int i = 0; iss >> i; if (!iss.fail()) { node = this->GetSibling(OffsetType(i)); } } else { node = this->GetSibling(1); } } // -[n] else if (s[0] == '-') { if (s.size() > 1) { s = s.substr(1); std::istringstream iss(s); unsigned int i = 0; iss >> i; if (!iss.fail()) { node = this->GetSibling(-OffsetType(i)); } } else { node = this->GetSibling(-1); } } // : else if (s[0] == ':') { s = s.substr(1); node = this->GetChildByID(s); } // .. else if (s == "..") { node = this->GetParent(); } // . else if (s == ".") { node = this; } // [:n] else { size_t pos = s.find_first_of(':'); if (pos != std::string::npos) { std::string s2 = s.substr(pos + 1); s = s.substr(0, pos); std::istringstream iss(s2); IdentifierType i = 0; iss >> i; if (!iss.fail()) { node = this->GetChild(s, i); } } else { node = this->GetChild(s); } } if (rpath.empty() || node == nullptr) { return node; } else { return node->Find(rpath); } } /** * The following function finds a child or sibling or relative using a query string or path. * The method returns nullptr if queried node does not exist. */ const DOMNode * DOMNode::Find(const std::string & path) const { // just use the non-const version and return it as a const, // this is safe as the non-const version actually doesn't modify the internal state. return const_cast(this)->Find(path); } /** Return the path of this node within its root, in the form of a query string that uses only indices. */ std::string DOMNode::GetPath() const { std::string path = ""; const DOMNode * parent = this->GetParent(); if (parent == nullptr) { return path; } for (IdentifierType i = 0; i < static_cast(parent->GetNumberOfChildren()); ++i) { if (parent->GetChild(i) == this) { // set the global path path = parent->GetPath(); path.append(1, '/'); // add the local path std::string s; StringTools::FromData(s, i); path.append(s); // early exit as we've already found the path break; } } return path; } /** Get a child and cast it to a text node (return nullptr if out of range or not a text node). */ DOMTextNode * DOMNode::GetTextChild(IdentifierType i) { DOMNode * node = this->GetChild(i); return dynamic_cast(node); } /** Get a child and cast it to a text node (return nullptr if out of range or not a text node). */ const DOMTextNode * DOMNode::GetTextChild(IdentifierType i) const { const DOMNode * node = this->GetChild(i); return dynamic_cast(node); } /** Generate a text node from a string and add/insert it as a child (see AddChild(node,i)). */ void DOMNode::AddTextChild(const std::string & text, IdentifierType i) { auto tnode = DOMTextNode::New(); tnode->SetText(text); this->AddChild((DOMTextNode *)tnode, i); } /** Generate a text node from a string and add/insert in front of all children. */ void DOMNode::AddTextChildAtBegin(const std::string & text) { auto tnode = DOMTextNode::New(); tnode->SetText(text); this->AddChildAtBegin((DOMTextNode *)tnode); } /** Generate a text node from a string and add/insert at the end of all children. */ void DOMNode::AddTextChildAtEnd(const std::string & text) { auto tnode = DOMTextNode::New(); tnode->SetText(text); this->AddChildAtEnd((DOMTextNode *)tnode); } /** Generate a text node from a string and replace a child with it (see SetChild(node,i). */ void DOMNode::SetTextChild(const std::string & text, IdentifierType i) { auto tnode = DOMTextNode::New(); tnode->SetText(text); this->SetChild((DOMTextNode *)tnode, i); } } // namespace itk