1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 #include <sngxml/dom/Document.hpp>
  7 #include <sngxml/dom/Element.hpp>
  8 #include <sngxml/dom/Exception.hpp>
  9 #include <soulng/util/Unicode.hpp>
 10 #include <soulng/util/Error.hpp>
 11 
 12 namespace sngxml { namespace dom {
 13 
 14 using namespace soulng::unicode;
 15 
 16 Document::Document() : ParentNode(NodeType::documentNodeU"document")documentElement(nullptr)indexValid(false)xmlStandalone(false)
 17 {
 18 }
 19 
 20 void Document::Write(CodeFormatter& formatter)
 21 {
 22     if (!xmlVersion.empty() && !xmlEncoding.empty())
 23     {
 24         formatter.WriteLine("<?xml version=\"" + ToUtf8(xmlVersion) + "\" encoding=\"" + ToUtf8(xmlEncoding) + "\"?>");
 25     }
 26     ParentNode::Write(formatter);
 27 }
 28 
 29 std::std::unique_ptr<Node>Document::CloneNode(booldeep)
 30 {
 31     std::unique_ptr<Node> clonedDocument = std::unique_ptr<Node>(new Document());
 32     if (deep)
 33     {
 34         ParentNode* parentNode = static_cast<ParentNode*>(clonedDocument.get());
 35         CloneChildrenTo(parentNode);
 36     }
 37     return clonedDocument;
 38 }
 39 
 40 Node* Document::InsertBefore(std::std::unique_ptr<Node>&&newChildNode*refChild)
 41 {
 42     CheckValidInsert(newChild.get()refChild);
 43     if (newChild->GetNodeType() == NodeType::elementNode)
 44     {
 45         Assert(documentElement == nullptr"document element is not null");
 46         documentElement = static_cast<Element*>(newChild.get());
 47     }
 48     return ParentNode::InsertBefore(std::move(newChild)refChild);
 49 }
 50 
 51 std::std::unique_ptr<Node>Document::ReplaceChild(std::std::unique_ptr<Node>&&newChildNode*oldChild)
 52 {
 53     if (!oldChild)
 54     {
 55         throw DomException("could not replace node: given old child is null");
 56     }
 57     if (oldChild->Parent() != this)
 58     {
 59         throw DomException("could not replace node: given old child is not child of this node");
 60     }
 61     CheckValidInsert(newChild.get()nullptr);
 62     if (newChild->GetNodeType() == NodeType::elementNode)
 63     {
 64         std::unique_ptr<Node> removed = RemoveChild(oldChild);
 65         AppendChild(std::move(newChild));
 66         return removed;
 67     }
 68     else
 69     {
 70         return ParentNode::ReplaceChild(std::move(newChild)oldChild);
 71     }
 72 }
 73 
 74 std::std::unique_ptr<Node>Document::RemoveChild(Node*oldChild)
 75 {
 76     if (!oldChild)
 77     {
 78         throw DomException("could not remove node: given old child is null");
 79     }
 80     if (oldChild->Parent() != this)
 81     {
 82         throw DomException("could not remove node: given old child is not child of this node");
 83     }
 84     if (oldChild->GetNodeType() == NodeType::elementNode)
 85     {
 86         documentElement = nullptr;
 87     }
 88     return ParentNode::RemoveChild(oldChild);
 89 }
 90 
 91 Node* Document::AppendChild(std::std::unique_ptr<Node>&&newChild)
 92 {
 93     CheckValidInsert(newChild.get()nullptr);
 94     if (newChild->GetNodeType() == NodeType::elementNode)
 95     {
 96         Assert(documentElement == nullptr"document element is not null");
 97         documentElement = static_cast<Element*>(newChild.get());
 98     }
 99     return ParentNode::AppendChild(std::move(newChild));
100 }
101 
102 void Document::InternalInvalidateIndex()
103 {
104     indexValid = false;
105 }
106 
107 void Document::Accept(Visitor& visitor)
108 {
109     visitor.BeginVisit(this);
110     ParentNode::Accept(visitor);
111     visitor.EndVisit(this);
112 }
113 
114 class BuildIndexVisitor public Visitor
115 {
116 public:
117     BuildIndexVisitor(std::std::unordered_map<std::u32stringElement*>&elementsByIdMap_);
118     void BeginVisit(Element* element) override;
119 private:
120     std::std::unordered_map<std::u32stringElement*>&elementsByIdMap;
121 };
122 
123 BuildIndexVisitor::BuildIndexVisitor(std::std::unordered_map<std::u32stringElement*>&elementsByIdMap_):elementsByIdMap(elementsByIdMap_)
124 {
125 }
126 
127 void BuildIndexVisitor::BeginVisit(Element* element)
128 {
129     const std::u32string& id = element->GetAttribute(U"id");
130     if (!id.empty())
131     {
132         elementsByIdMap[id] = element;
133     }
134 }
135 
136 Element* Document::GetElementById(const std::u32string& elementId)
137 {
138     if (!indexValid)
139     {
140         elementsByIdMap.clear();
141         BuildIndexVisitor visitor(elementsByIdMap);
142         Accept(visitor);
143         indexValid = true;
144     }
145     std::unordered_map<std::u32stringElement*>::const_iterator it = elementsByIdMap.find(elementId);
146     if (it != elementsByIdMap.cend())
147     {
148         Element* element = it->second;
149         return element;
150     }
151     return nullptr;
152 }
153 
154 void Document::CheckValidInsert(Node* nodeNode* refNode)
155 {
156     if (node->GetNodeType() == NodeType::elementNode)
157     {
158         if (refNode != nullptr || documentElement != nullptr)
159         {
160             throw DomException("attempt to insert a second element to a document");
161         }
162     }
163 }
164 
165 } } // namespace sngxml::dom