1 // =================================
  2 // Copyright (c) 2024 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 
  9 namespace System.Xml
 10 {
 11     public class DocumentHandler : XmlContentHandler
 12     {
 13         public DocumentHandler() : textFileIndex(-1)
 14         {
 15         }
 16         public Document* GetDocument()
 17         {
 18             return document.Release();
 19         }
 20         [nodiscard]
 21         public override Result<bool> StartDocument(const System.Lex.Span& spanint fileIndex)
 22         {
 23             document.Reset(new Document(spanfileIndex));
 24             currentParentNode = document.Get();
 25             return Result<bool>(true);
 26         }
 27         [nodiscard]
 28         public override Result<bool> EndDocument()
 29         {
 30             return Result<bool>(true);
 31         }
 32         [nodiscard]
 33         public override Result<bool> Version(const ustring& xmlVersion)
 34         {
 35             auto result = ToUtf8(xmlVersion);
 36             if (result.Error())
 37             {
 38                 return Result<bool>(ErrorId(result.GetErrorId()));
 39             }
 40             document->SetXmlVersion(result.Value());
 41             return Result<bool>(true);
 42         }
 43         [nodiscard]
 44         public override Result<bool> Standalone(bool standalone)
 45         {
 46             document->SetXmlStandalone(standalone);
 47             return Result<bool>(true);
 48         }
 49         [nodiscard]
 50         public override Result<bool> Encoding(const ustring& encoding)
 51         {
 52             auto result = ToUtf8(encoding);
 53             if (result.Error())
 54             {
 55                 return Result<bool>(ErrorId(result.GetErrorId()));
 56             }
 57             document->SetXmlEncoding(result.Value());
 58             return Result<bool>(true);
 59         }
 60         [nodiscard]
 61         public override Result<bool> HandleText(const System.Lex.Span& spanint fileIndexconst ustring& text)
 62         {
 63             if (!textSpan.IsValid())
 64             {
 65                 textSpan = span;
 66             }
 67             else
 68             {
 69                 textSpan.Union(span);
 70             }
 71             textFileIndex = fileIndex;
 72             auto result = ToUtf8(text);
 73             if (result.Error())
 74             {
 75                 return Result<bool>(ErrorId(result.GetErrorId()));
 76             }
 77             textContent.Append(result.Value());
 78             return Result<bool>(true);
 79         }
 80         [nodiscard]
 81         public override Result<bool> HandleComment(const System.Lex.Span& spanint fileIndexconst ustring& comment)
 82         {
 83             auto result = AddTextContent();
 84             if (result.Error())
 85             {
 86                 return Result<bool>(ErrorId(result.GetErrorId()));
 87             }
 88             auto commentResult = ToUtf8(comment);
 89             if (commentResult.Error())
 90             {
 91                 return Result<bool>(ErrorId(commentResult.GetErrorId()));
 92             }
 93             currentParentNode->AppendChild(new Comment(spanfileIndexcommentResult.Value()));
 94             return Result<bool>(true);
 95         }
 96         [nodiscard]
 97         public override Result<bool> HandlePI(const System.Lex.Span& spanint fileIndexconst ustring& targetconst ustring& data)
 98         {
 99             auto result = AddTextContent();
100             if (result.Error())
101             {
102                 return Result<bool>(ErrorId(result.GetErrorId()));
103             }
104             auto targetResult = ToUtf8(target);
105             if (targetResult.Error())
106             {
107                 return Result<bool>(ErrorId(targetResult.GetErrorId()));
108             }
109             auto dataResult = ToUtf8(data);
110             if (dataResult.Error())
111             {
112                 return Result<bool>(ErrorId(dataResult.GetErrorId()));
113             }
114             currentParentNode->AppendChild(new ProcessingInstruction(spanfileIndextargetResult.Value()dataResult.Value()));
115             return Result<bool>(true);
116         }
117         [nodiscard]
118         public override Result<bool> HandleCDataSection(const System.Lex.Span& spanint fileIndexconst ustring& cdata)
119         {
120             auto result = AddTextContent();
121             if (result.Error())
122             {
123                 return Result<bool>(ErrorId(result.GetErrorId()));
124             }
125             auto cdataResult = ToUtf8(cdata);
126             if (cdataResult.Error())
127             {
128                 return Result<bool>(ErrorId(cdataResult.GetErrorId()));
129             }
130             currentParentNode->AppendChild(new CDataSection(spanfileIndexcdataResult.Value()));
131             return Result<bool>(true);
132         }
133         [nodiscard]
134         public override Result<bool> StartElement(const System.Lex.Span& spanint fileIndex
135             const ustring& namespaceUriconst ustring& localNameconst ustring& qualifiedNameconst Attributes& attributes)
136         {
137             auto result = AddTextContent(true);
138             if (result.Error())
139             {
140                 return Result<bool>(ErrorId(result.GetErrorId()));
141             }
142             parentNodeStack.Push(currentParentNode);
143             elementStack.Push(Rvalue(currentElement));
144             auto qualifiedNameResult = ToUtf8(qualifiedName);
145             if (qualifiedNameResult.Error())
146             {
147                 return Result<bool>(ErrorId(qualifiedNameResult.GetErrorId()));
148             }
149             currentElement.Reset(new Element(spanfileIndexqualifiedNameResult.Value()));
150             currentParentNode = currentElement.Get();
151             for (const auto& attribute : attributes)
152             {
153                 auto qualifiedNameResult = ToUtf8(attribute.QualifiedName());
154                 if (qualifiedNameResult.Error())
155                 {
156                     return Result<bool>(ErrorId(qualifiedNameResult.GetErrorId()));
157                 }
158                 auto valueResult = ToUtf8(attribute.Value());
159                 if (valueResult.Error())
160                 {
161                     return Result<bool>(ErrorId(valueResult.GetErrorId()));
162                 }
163                 currentElement->SetAttribute(attribute.Span()fileIndexqualifiedNameResult.Value()valueResult.Value());
164             }
165             currentElement->SetOwnerDocument(document.Get());
166             if (!namespaceUri.IsEmpty())
167             {
168                 auto namespaceUriResult = ToUtf8(namespaceUri);
169                 if (namespaceUriResult.Error())
170                 {
171                     return Result<bool>(ErrorId(namespaceUriResult.GetErrorId()));
172                 }
173                 currentElement->SetNamespaceUri(namespaceUriResult.Value());
174             }
175             return Result<bool>(true);
176         }
177         [nodiscard]
178         public override Result<bool> EndElement(const ustring& namespaceUriconst ustring& localNameconst ustring& qualifiedName)
179         {
180             auto result = AddTextContent();
181             if (result.Error())
182             {
183                 return Result<bool>(ErrorId(result.GetErrorId()));
184             }
185             if (parentNodeStack.IsEmpty())
186             {
187                 string errorMessage = "parent node stack is empty";
188                 int errorId = AllocateError(errorMessage);
189                 return Result<bool>(ErrorId(errorId));
190             }
191             currentParentNode = parentNodeStack.Pop();
192             currentParentNode->AppendChild(currentElement.Release());
193             if (elementStack.IsEmpty())
194             {
195                 string errorMessage = "element stack is empty";
196                 int errorId = AllocateError(errorMessage);
197                 return Result<bool>(ErrorId(errorId));
198             }
199             currentElement = elementStack.Pop();
200             return Result<bool>(true);
201         }
202         [nodiscard]
203         public override Result<bool> SkippedEntity(const ustring& entityName)
204         {
205             auto result = AddTextContent();
206             if (result.Error())
207             {
208                 return Result<bool>(ErrorId(result.GetErrorId()));
209             }
210             auto entityNameResult = ToUtf8(entityName);
211             if (entityNameResult.Error())
212             {
213                 return Result<bool>(ErrorId(entityNameResult.GetErrorId()));
214             }
215             currentParentNode->AppendChild(MakeEntityReference(entityNameResult.Value()));
216             return Result<bool>(true);
217         }
218         [nodiscard]
219         private Result<bool> AddTextContent()
220         {
221             return AddTextContent(false);
222         }
223         [nodiscard]
224         private Result<bool> AddTextContent(bool addSpace)
225         {
226             if (!currentElement.IsNull())
227             {
228                 auto utf32Result = ToUtf32(textContent);
229                 if (utf32Result.Error())
230                 {
231                     return Result<bool>(ErrorId(utf32Result.GetErrorId()));
232                 }
233                 auto trimResult = TrimAll(utf32Result.Value());
234                 if (trimResult.Error())
235                 {
236                     return Result<bool>(ErrorId(trimResult.GetErrorId()));
237                 }
238                 auto utf8Result = ToUtf8(trimResult.Value());
239                 if (utf8Result.Error())
240                 {
241                     return Result<bool>(ErrorId(utf8Result.GetErrorId()));
242                 }
243                 textContent = utf8Result.Value();
244                 if (!textContent.IsEmpty())
245                 {
246                     if (addSpace)
247                     {
248                         textContent.Append(' ');
249                     }
250                     currentElement->AppendChild(new Text(textSpantextFileIndextextContent));
251                     textSpan = System.Lex.Span();
252                 }
253             }
254             textContent.Clear();
255             return Result<bool>(true);
256         }
257         private UniquePtr<Document> document;
258         private ParentNode* currentParentNode;
259         private Stack<ParentNode*> parentNodeStack;
260         private UniquePtr<Element> currentElement;
261         private Stack<UniquePtr<Element>> elementStack;
262         private System.Lex.Span textSpan;
263         private int textFileIndex;
264         private string textContent;
265     }
266 }