Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
DOM.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_DataExchange_XML_DOM_h_
5#define _Stroika_Foundation_DataExchange_XML_DOM_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <variant>
10
12#include "Stroika/Foundation/DataExchange/BadFormatException.h"
14#include "Stroika/Foundation/DataExchange/XML/Namespace.h"
17#include "Stroika/Foundation/Execution/Exceptions.h"
20
21namespace Stroika::Foundation::DataExchange::XML::Providers {
22 struct IDOMProvider;
23};
24
25namespace Stroika::Foundation::DataExchange::XML::DOM {
26
27 using Traversal::Iterable;
28
29 /*
30 * Special Note about namespaces:
31 *
32 * o Default namespace does is inherited by enclosed elements, but **not** by enclosed attributes.
33 *
34 * The reason this basic fact is worthy of note is:
35 * o IMHO, it is not intuitive
36 * o https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ms754539(v=vs.85) says
37 * "All elements and attributes in the document that do not have a prefix will then belong to the default namespace"
38 * THIS IS WRONG - and the top google search hit when searching about default xml namespaces.
39 *
40 * The reason I believe my counter-claim above is:
41 * o https://www.w3.org/TR/xml-names/#defaulting
42 * "The namespace name for an unprefixed attribute name always has no value" (after saying the reverse for element names)
43 */
44
45 /**
46 * How Document::Ptr::Write writes a document.
47 */
49 /**
50 * false (the default) ADDS no whitespace: what is written is the document's text as it is - its whitespace-only
51 * text included - and nothing more. So a document built with this API is written with no whitespace between its
52 * elements, and one read in is written with exactly the whitespace text it was read with (text, not bytes: XML
53 * has every reader turn a line end into a line feed). Outside the root element a provider may still write line
54 * breaks (libxml2 does, after the XML declaration and at the end): no reader sees them as content.
55 *
56 * true adds whitespace to show the structure, so a reader of the result sees whitespace text the document did not
57 * have - and where differs by provider: as of libxml2 2.15 and Xerces 3.3, libxml2 indents only elements that hold
58 * no text, Xerces mixed content too, changing whitespace that is part of the text. For people, not for comparing.
59 *
60 * Neither promises the BYTES: a byte order mark (Xerces writes one), the XML declaration, quotes, empty elements and
61 * namespace declarations may all be spelled differently by each provider. So a test may compare the text read back, or
62 * look for pieces of it (with false, `</a><b` is safe) - never Write's whole output, which differs by provider, and has
63 * changed between libxml2 versions.
64 */
65 bool fPrettyPrint{false};
66
67 /**
68 * Spaces per level of indentation, when fPrettyPrint. Without it, both providers indent by 2. Xerces ignores it;
69 * libxml2 (2.14 and later) mostly uses it - within its limits, and otherwise keeping to 2.
70 */
71 optional<unsigned int> fIndent{};
72 };
73
74 namespace Node {
75 struct IRep;
76 class Ptr;
77 }
78
79 namespace Element {
80 class Ptr;
81 }
82
83 namespace XPath {
84
85 /**
86 * Notes about XPath support:
87 *
88 * Tutorial/Syntax:
89 * https://www.w3schools.com/xml/xpath_syntax.asp
90 *
91 * Xerces Limitations (as of Xerces 3.2 - 2024-01)
92 * o No [] support (so p/n[@f='3'] not supported, for example)
93 */
94
95 /**
96 * For example, Xerces 3.2 doesn't support [] in expressions
97 */
102 inline const XPathExpressionNotSupported XPathExpressionNotSupported::kThe;
103
104 /**
105 * \note the default value is an empty (nullptr) Node::Ptr
106 */
107 using Result = variant<Node::Ptr, bool, int, double, String>;
108
109 /**
110 * Some APIs (like Expression) need to know the index - not just the type itself, and this maps.
111 */
112 template <typename T>
113 constexpr uint8_t ResultTypeIndex_v = static_cast<uint8_t> (Common::VariantIndex<Result, T>);
114
115 /**
116 * Expression is provider independent, but since it is implemented with a shared_ptr and immutable,
117 * the providers can maintain a cache of mappings of expressions to internal compiled version of expression.
118 *
119 * So - it behaves rather obviously - except that if you copy an expression, you KNOW the underlying data wont
120 * change so can cache derived values (like a compiled expression).
121 *
122 * \note As of 2024-01-15, LibXML2 XPath doesn't support default namespace for XPath, so use explicit namespace prefixes.
123 */
125 public:
126 /**
127 */
128 struct Options {
129 /**
130 * prefixes available to use in expression
131 */
132 NamespaceDefinitions fNamespaces;
133
134 /**
135 * index into 'Result' variant expected. REJECT other values - required by Xerces XPATH API, defaults to Node::Ptr
136 */
137 optional<uint8_t> fResultTypeIndex{ResultTypeIndex_v<Node::Ptr>};
138
139 /**
140 * Caller cares (or does not) about order of elements returned (not sure what ordering is defined - depth first or breadth first?)
141 */
142 bool fOrdered{false};
143
144 /**
145 * Snapshot less efficient, but safer in light of modifications to the node tree (but still not clearly defined what modifications allowed and what not?).
146 */
147 bool fSnapshot{false};
148
149 /**
150 */
151 nonvirtual String ToString () const;
152 };
153 static inline const Options kDefaultOptions{
154 .fNamespaces = {}, .fResultTypeIndex = ResultTypeIndex_v<Node::Ptr>, .fOrdered = false, .fSnapshot = false};
155
156 public:
157 Expression (String&& e, const Options& o);
158 template <Characters::IConvertibleToString ST>
159 Expression (ST&& e, const Options& o = kDefaultOptions);
160
161 public:
162 struct IRep {
163 virtual ~IRep () = default;
164 virtual String GetExpression () const = 0;
165 virtual Options GetOptions () const = 0;
166 };
167
168 public:
169 /**
170 * The text of the expression
171 */
172 nonvirtual String GetExpression () const;
173
174 public:
175 /**
176 * Get options (like prefix to namespace map, etc)
177 */
178 nonvirtual Options GetOptions () const;
179
180 public:
181 /**
182 */
183 nonvirtual String ToString () const;
184
185 public:
186 nonvirtual shared_ptr<const IRep> GetRep () const;
187
188 private:
189 shared_ptr<const IRep> fRep_;
190 };
191
192 }
193
194 /**
195 * NB: A Node can be EITHER an ELEMENT or an ATTRIBUTE (mostly). Nodes are 'internal' to a Document, and are always somehow contained in some document).
196 * Nodes have an abstract IRep, the the Ptr object is just a 'smart pointer' to that IRep.
197 *
198 * Note - the IRep can be implemented by a variety of backend libraries (dubbed 'Providers').
199 *
200 * Principally, users will interact with a Document::Ptr (initialized from Document::New()), and only rarely deal with Nodes.
201 * But Nodes can be accessed, created, update etc, once you start with a Document::Ptr.
202 */
203 namespace Node {
204
205 /**
206 * Prior to Stroika v3.0d5, this was Node::NodeType
207 */
208 enum Type {
209 eAttributeNT,
210 eElementNT,
211 eTextNT,
212 eCommentNT,
213 eOtherNT
214 };
215
216 /**
217 * \brief Node::Ptr is a smart pointer to a Node::IRep
218 *
219 * \note Before Stroika v3.0d5 this was simply called "Node", and now Node::Ptr
220 *
221 * Nodes are not created directly, but either via Node::Ptr methods, or Document::Ptr methods (because nodes are always associated with some document).
222 *
223 * Node::Ptr maybe nullptr (default constructed, assigned nullptr, or rendered null by a call to DeleteNode). Calling other methods (like GetName () etc)
224 * while nullptr is a requires failure.
225 *
226 * A Node::Ptr (so an Element::Ptr) is an internal pointer into a well-known document - logically, if not physically: it does
227 * not own its node, nor keep its document alive. So whoever holds a node must hold its Document::Ptr longer; and must know that
228 * whoever else can reach the document can reach the node, change it, or delete it (Delete () through any Ptr to it leaves every
229 * other Ptr to it dangling). Watch for a temporary document:
230 * \code
231 * Element::Ptr root = Document::New (in).GetRootElement (); // WRONG: the Document::Ptr is gone at the ;
232 * for (auto e : Document::New (in).GetRootElement ().GetChildElements ()) {} // WRONG before C++23: gone before the body runs
233 * Document::Ptr doc = Document::New (in); // RIGHT: doc outlives its nodes
234 * \endcode
235 * Where assertions are checked, a node whose document went first fails a Require AS IT GOES - which catches every such use,
236 * a node being usable after its document only by outliving it, but says so late: a use in between reads freed memory first,
237 * as before Stroika v3.0d25, just no longer silently (and a node leaked, never destroyed, is never checked at all).
238 *
239 * \note Older XMLDB InsertNode/AppendNode APIs - we had some APIs which more generally operated on Nodes adding them and probably allowed moving them.
240 * I couldn't think of any cases where I needed that, and it made it harder to port to other libraries, so I removed those APIs (til I see there utility again).
241 * And then - need to better document just what they do/are for (so can do portable).
242 */
243 class Ptr {
244 public:
245 /**
246 */
247 Ptr () = default;
248 Ptr (nullptr_t);
249 Ptr (const shared_ptr<IRep>& from);
250
251 public:
252 /**
253 * \note a bit queer, but very helpful - two Node::Ptr's are equal not by comparing their pointers (shared_ptr rep) but
254 * if they refer to the same underlying (in providers representation) object. Meaning if they are created by two
255 * different APIs (say an interator and some other api) - you can check to see if they refer to the same underlying node.
256 *
257 * \pre the two Ptrs are either nullptr or come from the same XML Provider object.
258 */
259 nonvirtual bool operator== (const Ptr& rhs) const;
260 nonvirtual bool operator== (nullptr_t) const;
261
262 public:
263 /*
264 * Like shared_ptr::operator bool - return true if non-null ptr
265 */
266 explicit operator bool () const;
267
268 public:
269 /**
270 */
271 nonvirtual Type GetNodeType () const;
272
273 public:
274 /**
275 * \pre GetNodeType == eAttributeNT or eElementNT
276 */
277 nonvirtual NameWithNamespace GetName () const;
278
279 public:
280 /**
281 * \pre GetNodeType == eAttributeNT or eElementNT
282 */
283 nonvirtual void SetName (const NameWithNamespace& name);
284
285 public:
286 /**
287 * This is the value between the brackets <a>text</a>. Note that <a></a> is the same as <a/> - the empty string.
288 *
289 * \note before Stroika v3.0d5, the value API was VariantValue but that was always meaningless, and just treated as a String.
290 *
291 * \note the 'String' value maybe implemented in XML in a variety of ways (entities, CDATA, etc).
292 *
293 * The /0 overload looks at 'this node'. The overload taking an XPath::Expression looks at the first selected node (using this node as context)
294 * Either way, the result is a node, and its text is returned. For XPath case, if no node is returned, the return value is nullopt.
295 *
296 * \pre GetNodeType == eAttributeNT or eElementNT
297 */
298 nonvirtual String GetValue () const;
299
300 public:
301 /**
302 * \pre GetNodeType == eAttributeNT or eElementNT
303 */
304 nonvirtual void SetValue (const String& v);
305
306 public:
307 /**
308 * \pre *this != nullptr
309 * \post *this == nullptr
310 */
311 nonvirtual void Delete ();
312
313 public:
314 /**
315 * Can return nullptr
316 */
317 nonvirtual Ptr GetParentNode () const;
318
319 public:
320 /**
321 * \brief return the associated shared_ptr (cannot be nullptr)
322 *
323 * \post result != nullptr
324 */
325 nonvirtual shared_ptr<IRep> GetRep () const;
326
327 public:
328 /**
329 * \brief return the associated shared_ptr (can be nullptr)
330 */
331 nonvirtual shared_ptr<IRep> PeekRep () const;
332
333 public:
334 /**
335 */
336 nonvirtual Characters::String ToString () const;
337
338 private:
339 shared_ptr<IRep> fRep_;
340 };
341
342 /**
343 * DOM Nodes are typically 'elements', but can also be 'text' nodes, or comments, or attributes, etc...
344 */
345 struct IRep {
346 IRep () = default;
347 virtual ~IRep () = default;
348
349 virtual const Providers::IDOMProvider* GetProvider () const = 0;
350 virtual bool Equals (const IRep* rhs) const = 0;
351 virtual Type GetNodeType () const = 0;
352 // GetName () only allowed on element/attribute
353 virtual NameWithNamespace GetName () const = 0;
354 // SetName () only allowed on element/attribute
355 virtual void SetName (const NameWithNamespace& name) = 0;
356 // GetValue () only allowed on element/attribute
357 virtual String GetValue () const = 0;
358 // SetValue () only allowed on element/attribute
359 virtual void SetValue (const String& v) = 0;
360 virtual void DeleteNode () = 0;
361 virtual Ptr GetParentNode () const = 0;
362 virtual void Write (const Streams::OutputStream::Ptr<byte>& to, const SerializationOptions& options) const = 0;
363 };
364 }
365
366 namespace Element {
367
368 using namespace Node;
369
370 struct IRep;
371
372 /**
373 * Simple wrapper on Node::Ptr API, but with the Ptr objects refer to Elements.
374 *
375 * If you assign a non-Element to an Element::Ptr, it automatically, silently becomes nullptr (like dynamic_cast).
376 *
377 * \note Giving a namespace to an element is done by giving an xmlns=XXX attribute, so the namespace
378 * is inherited by all children
379 *
380 * TODO:
381 * \todo Maybe add GetNamespaceDefinitions () to return a NamespaceDefinitionsList object with the prefixes
382 * default, and namespace URIs for this particular element. But so far no need.
383 */
384 class Ptr : public Node::Ptr {
385 public:
386 /**
387 * for XPath::Result overload, require XPath produced right type (not string etc) - but allow for nullptr
388 */
389 Ptr () = default;
390 Ptr (nullptr_t);
391 Ptr (const Node::Ptr& p);
392 Ptr (const XPath::Result& p);
393 Ptr (const shared_ptr<IRep>& p);
394
395 public:
396 /**
397 * returns string value of attribute, and nullopt if doesn't exist
398 *
399 * \pre *this != nullptr
400 */
401 nonvirtual optional<String> GetAttribute (const NameWithNamespace& attrName) const;
402
403 public:
404 /**
405 * return true iff attribute exists on this node
406 * return true iff attribute exists on this node and equals (case sensitive) value
407 *
408 * \pre *this != nullptr
409 */
410 nonvirtual bool HasAttribute (const NameWithNamespace& attrName) const;
411 nonvirtual bool HasAttribute (const NameWithNamespace& attrName, const String& value) const;
412
413 public:
414 /**
415 * Note - setting the attribute to nullopt is equivalent to deleting the attribute
416 *
417 * The 'VariantValue' overload maps null to missing attribute, and any other value to a String (v.As<String>()).
418 *
419 * \pre *this != nullptr
420 */
421 nonvirtual void SetAttribute (const NameWithNamespace& attrName, const optional<String>& v);
422 template <same_as<VariantValue> VV>
423 nonvirtual void SetAttribute (const NameWithNamespace& attrName, const VV& v);
424
425 public:
426 /**
427 * \pre *this != nullptr
428 */
429 nonvirtual optional<String> GetID () const;
430
431 public:
432 /**
433 * Gets the XML xmlns= attribute associated with THIS element. This is the default namespace used for this element
434 * and all children. If its missing (even if a parent element has a default namespace) - this will return nullopt.
435 */
436 nonvirtual optional<URI> GetDefaultNamespace () const;
437
438 public:
439 /**
440 * \brief Sets the xmlns attribute of this element
441 *
442 * \see GetDefaultNamespace
443 */
444 nonvirtual void SetDefaultNamespace (const optional<URI> defaultNS = nullopt);
445
446 public:
447 /**
448 * This is the value between the brackets <a>text</a>. Note that <a></a> is the same as <a/> - the empty string.
449 *
450 * \note before Stroika v3.0d5, the value API was VariantValue but that was always meaningless, and just treated as a String.
451 *
452 * \note the 'String' value maybe implemented in XML in a variety of ways (entities, CDATA, etc).
453 *
454 * The /0 overload looks at 'this node'. The overload taking an XPath::Expression looks at the first selected node (using this node as context)
455 * Either way, the resulting eAttributeNT or eElementNT nodes value.
456 *
457 * \pre *this != nullptr
458 */
460 nonvirtual optional<String> GetValue (const XPath::Expression& e) const;
461
462 public:
463 /**
464 * \pre e is an expression resulting in DOM nodes (could be attributes), and then get its text, and add that to an Iterable. Ignores nodes in result set other than attribute/element (like comment nodes)
465 *
466 * \note COULD have usefully made this return a Set<> since that's the most common case, but iterable is about as good, and you might
467 * plausibly want to track dup results, or other???
468 *
469 * \pre *this != nullptr
470 */
471 nonvirtual Traversal::Iterable<String> GetValues (const XPath::Expression& e) const;
472
473 public:
474 /**
475 * For now, SetValue(e,v) - must finds an Element (or Attribute) node at 'e' - otherwise throws runtime exception,
476 * cuz no way to know in general where to add what element (could do in some specific cases maybe - like simple QName or @QName expression).
477 *
478 * The overloads with VariantValue are just a short-hand for v.As<String> () on the variant value (with no checking).
479 *
480 * \pre *this != nullptr
481 */
483 nonvirtual void SetValue (const XPath::Expression& e, const String& v);
484 template <same_as<VariantValue> VV>
485 nonvirtual void SetValue (const VV& v);
486 template <same_as<VariantValue> VV>
487 nonvirtual void SetValue (const XPath::Expression& e, const VV& v);
488
489 public:
490 /**
491 * \brief Insert Element (after argument node) inside of this 'Element'
492 *
493 * if afterNode is nullptr - then this is PREPEND, else require afterNode is a member of 'Node::Ptr::GetChildren()' - need not be an element
494 *
495 * \pre *this != nullptr
496 */
497 nonvirtual Ptr Insert (const NameWithNamespace& eltName, const Node::Ptr& afterNode);
498
499 public:
500 /**
501 * \brief Append a deep CLONE of 'cloneFrom' - an element of any document - to this element's children
502 *
503 * Import, as the DOM has always called it, is a COPY: 'cloneFrom' and the document it belongs to are left
504 * exactly as they were, and what lands here is a new subtree of this document, carrying cloneFrom's own
505 * children, attributes and namespace declarations. It is how a document of someone else's - a SOAP fault's
506 * detail, say - is carried into one being built, rather than escaped into text.
507 *
508 * The two-argument overload places it after 'afterNode', or first where that is nullptr - the rule Insert
509 * uses. Either way the new node is in the tree before it is returned: there is no detached-node state.
510 *
511 * \par Example Usage
512 * \code
513 * Document::Ptr little = Document::New ("<a f=\"x\">b</a>"sv);
514 * Document::Ptr big = Document::New ("<bigDoc/>"sv);
515 * big.GetRootElement ().ImportNode (little.GetRootElement ());
516 * // big is now <bigDoc><a f="x">b</a></bigDoc>
517 * // little is still <a f="x">b</a> - ImportNode copied it, and left it alone
518 * \endcode
519 *
520 * \pre Both documents must be the SAME XML provide
521 *
522 * \pre *this != nullptr
523 * \pre cloneFrom != nullptr
524 * \pre cloneFrom is an element of this same provider
525 */
526 nonvirtual Ptr ImportNode (const Node::Ptr& cloneFrom);
527 nonvirtual Ptr ImportNode (const Node::Ptr& cloneFrom, const Node::Ptr& afterNode); ///< \brief ... placed after afterNode, or first if nullptr
528
529 public:
530 /**
531 * value overload just creates the node and calls SetValue() before returning it (so simple shorthand).
532 *
533 * value=VariantValue overload is just a short-hand for value.As<String>() - so will fail if value cannot be converted to a String
534 *
535 * \pre *this != nullptr
536 */
537 nonvirtual Ptr Append (const NameWithNamespace& eltName);
538 nonvirtual Ptr Append (const NameWithNamespace& eltName, const String& value);
539 template <same_as<VariantValue> VV>
540 nonvirtual Ptr Append (const NameWithNamespace& eltName, const VV& value);
541
542 public:
543 /**
544 * \brief Trivial wrapper on AppendElement, but if v is missing then this is a no-op.
545 *
546 * Trivial, but I've found helpful in making certain uses more terse.
547 *
548 * VariantValue overload maps nullptr/nullopt=v to 'missing' case above, but otherwise calls v.As<String>() which may fail depending on the type of v.
549 *
550 * \pre *this != nullptr
551 */
552 nonvirtual Ptr AppendIf (const NameWithNamespace& eltName, const optional<String>& v);
553 template <same_as<VariantValue> VV>
554 nonvirtual Ptr AppendIf (const NameWithNamespace& eltName, const VV& v);
555
556 public:
557 [[deprecated]] Ptr AppendIfNotEmpty (const NameWithNamespace& eltName, const optional<String>& v)
558 {
559 return AppendIf (eltName, v == nullopt or v->empty () ? nullopt : v);
560 }
561
562 public:
563 /**
564 * creates a new (empty) node with same argument name (and namespace, and in the same position
565 *
566 * This can be used to delete all the children/content under a given node and is equivalent, except that it returns
567 * a new NodePtr, and invalidates this.
568 *
569 * Note - this CAN be used to replace the document root (same as Document::Ptr{}.ReplaceRootElement()).
570 *
571 * \pre *this != nullptr
572 */
573 nonvirtual Ptr Replace (const NameWithNamespace& newEltName);
574
575 public:
576 /**
577 * Can return nullptr
578 *
579 * \pre *this != nullptr
580 */
581 nonvirtual Ptr GetParent () const;
582
583 public:
584 /**
585 * note only returns sub-elements, so use Node::Ptr (inherited) GetChildren to get them all
586 *
587 * \warning Any modification of the DOM may invalidate live iterators or iterables, so re-fetch after each change
588 *
589 * \pre *this != nullptr
590 */
591 nonvirtual Iterable<Node::Ptr> GetChildNodes () const;
592
593 public:
594 /**
595 * note only returns sub-elements, so use Node::Ptr (inherited) GetChildren to get them all
596 *
597 * \warning Any modification of the DOM may invalidate live iterators or iterables, so re-fetch after each change
598 *
599 * \pre *this != nullptr
600 */
601 nonvirtual Iterable<Ptr> GetChildElements () const;
602
603 public:
604 /**
605 * can return a NULL Node Ptr if not found. Only examines this node's (direct) children elements (not attributes)
606 *
607 * \pre *this != nullptr
608 */
609 nonvirtual Ptr GetChild (const NameWithNamespace& eltName) const;
610
611 public:
612 /**
613 * can return a NULL Node Ptr if not found. Only examines this node's (direct) children
614 *
615 * \pre *this != nullptr
616 */
617 nonvirtual Ptr GetChildByID (const String& id) const;
618
619 public:
620 /**
621 * \note same as Lookup(), but returns 0 or 1 result, instead of iterable of all of them (so ignores e.GetOptions.fSnapshot).
622 * Often more performant and easier to use (if you know zero or one matching element).
623 *
624 * \pre *this != nullptr
625 */
627
628 public:
629 /**
630 * \note same as Lookup(), but returns 0 or 1 result, instead of iterable of all of them (so ignores e.GetOptions.fSnapshot).
631 * Often more performant and easier to use (if you know zero or one matching element).
632 *
633 * \pre *this != nullptr
634 */
635 nonvirtual Node::Ptr LookupOneNode (const XPath::Expression& e) const;
636
637 public:
638 /**
639 * PROBABLY quite unsafe to modify DOM while holding this result etc... Needs work on whehn/what is allowed.
640 * NOTE MORE SAFE IF expression uses 'snapshot'
641 *
642 * \pre *this != nullptr
643 */
645
646 public:
647 /**
648 * PROBABLY quite unsafe to modify DOM while holding this result etc... Needs work on whehn/what is allowed.
649 * NOTE MORE SAFE IF expression uses 'snapshot'
650 *
651 * \note Same as Lookup - but filters out all non-element nodes
652 *
653 * \pre *this != nullptr
654 */
656
657 public:
658 /**
659 * \brief return the associated shared_ptr (cannot be nullptr)
660 *
661 * \post result != nullptr
662 */
663 nonvirtual shared_ptr<IRep> GetRep () const;
664
665 public:
666 /**
667 * \brief return the associated shared_ptr (can be nullptr)
668 */
669 nonvirtual shared_ptr<IRep> PeekRep () const;
670 };
671
672 /**
673 * Elements are special nodes, that may contain sub-nodes.
674 */
675 struct IRep : virtual Node::IRep {
676 virtual optional<String> GetAttribute (const NameWithNamespace& attrName) const = 0;
677 virtual void SetAttribute (const NameWithNamespace& attrName, const optional<String>& v) = 0;
678 /**
679 * if afterNode is nullptr - then this is PREPEND, else require afterNode is a member of 'GetChildren()'
680 */
681 virtual Ptr InsertElement (const NameWithNamespace& eltName, const Ptr& afterNode) = 0;
682 virtual Ptr AppendElement (const NameWithNamespace& eltName) = 0;
683 /**
684 * A deep CLONE of cloneFrom - of any document of this provider - as a child of this element: at the end, or
685 * after afterNode (first, where that is nullptr), as AppendElement and InsertElement have it.
686 * \pre cloneFrom != nullptr, and is an element of this same provider
687 */
688 virtual Ptr AppendImportedNode (const Node::Ptr& cloneFrom) = 0;
689 virtual Ptr InsertImportedNode (const Node::Ptr& cloneFrom, const Ptr& afterNode) = 0;
690 virtual Iterable<Node::Ptr> GetChildren () const = 0;
691 // Redundant API, but provided since commonly used and can be optimized
692 virtual Ptr GetChildElementByID (const String& id) const;
693 // LookupOne returns zero or one results (nullopt or XPath::Result)
694 virtual optional<XPath::Result> LookupOne (const XPath::Expression& e) = 0;
695 // Lookup returns full iterator of all results (whether live or snapshot depends on params in e)
696 virtual Traversal::Iterable<XPath::Result> Lookup (const XPath::Expression& e) = 0;
697 };
698
699 }
700
701 namespace Document {
702
703 struct IRep;
704
705 /**
706 * \brief Document::Ptr is create with Document::New, and is a smart pointer to a DOM document object.
707 *
708 * \note nullptr is allowed for Ptr
709 *
710 * \note the document object does NOT have an intrinsically associated schema object. We considered/started with such a design.
711 * but then the trouble was, what if you wanted two (say a short and long format - schema). Or what if sometimes you wanted to read
712 * with no schema (validation) and sometimes with validation? It just added alot of ambiguity (in how to interpret optional/nullptr as meaning
713 * default or none).
714 *
715 * These matters could have been resolved, but in the end it seems maximally flexible, and super clear, to just not associate the schema
716 * with the document, and just specify in as an argument to read (New) or Write (serialize) etc.
717 *
718 *
719 * todo add better nullptr_t interop - static bool convert etc...
720 */
721 class Ptr {
722 public:
723 /**
724 */
725 Ptr () = default;
726 Ptr (nullptr_t);
727 Ptr (const shared_ptr<IRep>& rep);
728 Ptr (const Ptr& from) = default;
729
730 public:
731 Ptr& operator= (const Ptr& rhs) = default;
732
733 public:
734 ~Ptr () = default;
735
736 public:
737 /**
738 */
739 bool operator== (const Ptr&) const = default;
740 bool operator== (nullptr_t) const;
741
742 public:
743 nonvirtual bool GetStandalone () const;
744
745 public:
746 nonvirtual void SetStandalone (bool standalone);
747
748 public:
749 /**
750 * \brief The document as XML: to a byte stream as UTF-8, with no byte order mark - or as a String
751 */
752 nonvirtual void Write (const Streams::OutputStream::Ptr<byte>& to, const SerializationOptions& options = {}) const;
753 nonvirtual String Write (const SerializationOptions& options = {}) const; ///< \brief The document as XML
754
755 public:
756 /**
757 * throws BadFormatException exception on failure
758 *
759 * \pre schema was compiled by this document's provider - one another compiled is converted first
760 * (schema.As<Schema::Ptr> (provider))
761 */
762 nonvirtual void Validate (const Schema::Ptr& schema) const;
763
764 public:
765 /**
766 * Return (and iterable) of nodes, which represent the top level children of the document. Typically there will be one interesting - the
767 * root element, so calling GetRootElement() will typically be easier/more useful (so as to not see/get confused with things like comments, or processing directives).
768 */
769 nonvirtual Iterable<Node::Ptr> GetChildren () const;
770
771 public:
772 /**
773 * \brief always returns Node of eElement or return nullptr if none
774 */
775 nonvirtual Element::Ptr GetRootElement () const;
776
777 public:
778 /**
779 * \brief Make this the document's root element - and say HOW its namespace is declared on it, which is the one
780 * thing only the root can be told
781 *
782 * nullopt, the default, declares it as the DEFAULT namespace: xmlns="NS". A prefix declares xmlns:PREFIX="NS"
783 * and writes the element with it, which is how a document whose elements should read s:Envelope, s:Body is
784 * started - a prefix cannot be asked for anywhere else, an element made later taking whatever its parent has
785 * or whatever a provider invents.
786 *
787 * \code
788 * Document::Ptr doc = Document::New (p);
789 * doc.ReplaceRootElement (NameWithNamespace{kSOAPNamespace_, "Envelope"sv}, "s"sv); // <s:Envelope xmlns:s="...">
790 * doc.GetRootElement ().Append ("Body"sv); // <s:Body>, by inheritance
791 * \endcode
792 *
793 * \note Either way a child made with a bare name is in the same namespace as its parent - what this chooses is
794 * how that namespace is DECLARED, and so how those children are written. Which also decides whether a
795 * child of no namespace (kNoNamespace) must carry xmlns="" to say so: it must, where a default namespace
796 * is in scope, and need not where the declaration is prefixed.
797 *
798 * If there exists a root element, this is the same as GetRootElement ().Replace (arg), except for the namespace part.
799 */
800 nonvirtual Element::Ptr ReplaceRootElement (const NameWithNamespace& newEltName, const optional<String>& namespacePrefix = nullopt) const;
801
802 public:
803 /**
804 * \brief shorthand for GetRootElement ().LookupOneElement (e) - so requires root element exists
805 */
806 nonvirtual Element::Ptr LookupOneElement (const XPath::Expression& e) const;
807
808 public:
809 /**
810 * \brief shorthand for GetRootElement ().Lookup (e) - so requires root element exists
811 */
813
814 public:
815 /**
816 * \brief shorthand for GetRootElement ().LookupElements (e) - so requires root element exists
817 */
819
820 public:
821 /**
822 */
823 nonvirtual Characters::String ToString () const;
824
825 public:
826 /**
827 * @@todo - what happens if we rename this operator-> - IO think chaining works nicely then. maybe use that trick throughout Stroika if it works here.
828 */
829 nonvirtual shared_ptr<IRep> GetRep () const;
830
831 private:
832 shared_ptr<IRep> fRep_;
833 };
834
835 /**
836 * Create a Document object and return a smart pointer (Ptr) to it.
837 *
838 * Use the optionally provided stream to deserialize the document from (or create an empty one with a single root documentElement given by name/ns args).
839 *
840 * \note String in overload is trivial wrapper on Streams::TextToByteReader{}, in case you want adjust parameters.
841 * \note Document::Ptr overload is a way to 'clone' a Document (but even possibly across provider implementations)
842 * \note 'in' stream may be null to create a document with no root element (why is this useful?).
843 *
844 * @todo add overload taking String 'in' and parse using Streams::TextToByteReader
845 * @todo consider adding 'Resolver' argument - so missing #includes get loaded.
846 *
847 * If not otherwise specified (e..g if not read from stream where specified otherwise), XML documents default to standalone (=yes)
848 *
849 * The overloads taking a provider first use exactly that one; the rest use the one o names (by default, Stroika's).
850 *
851 * \pre schemaToValidateAgainstWhileReading, if given, was compiled by the provider that reads - one another
852 * compiled is converted first (schemaToValidateAgainstWhileReading.As<Schema::Ptr> (provider)), as XML::SAXParse
853 * does itself
854 */
855 Ptr New (const Providers::IDOMProvider& p);
856 Ptr New (const Providers::IDOMProvider& p, const NameWithNamespace& documentElementName); ///< \brief Create a Document object
857 Ptr New (const Providers::IDOMProvider& p, const Streams::InputStream::Ptr<byte>& in,
858 const Schema::Ptr& schemaToValidateAgainstWhileReading = nullptr); ///< \brief Create a Document object
859 Ptr New (const Providers::IDOMProvider& p, const String& in,
860 const Schema::Ptr& schemaToValidateAgainstWhileReading = nullptr); ///< \brief Create a Document object
861 Ptr New (const Providers::IDOMProvider& p, const Ptr& clone); ///< \brief Create a Document object
862#if qStroika_Foundation_DataExchange_XML_SupportDOM
863 Ptr New (const Options& o = {}); ///< \brief Create a Document object - with the provider o names
864 Ptr New (const NameWithNamespace& documentElementName, const Options& o = {}); ///< \brief Create a Document object - with the provider o names
865 Ptr New (const Streams::InputStream::Ptr<byte>& in, const Schema::Ptr& schemaToValidateAgainstWhileReading = nullptr,
866 const Options& o = {}); ///< \brief Create a Document object - with the provider o names
867 Ptr New (const String& in, const Schema::Ptr& schemaToValidateAgainstWhileReading = nullptr, const Options& o = {}); ///< \brief Create a Document object - with the provider o names
868 Ptr New (const Ptr& clone, const Options& o = {}); ///< \brief Create a Document object - with the provider o names
869#endif
870
871 /**
872 */
873 struct IRep {
874 virtual ~IRep () = default;
875 virtual const Providers::IDOMProvider* GetProvider () const = 0;
876 virtual bool GetStandalone () const = 0;
877 virtual void SetStandalone (bool standalone) = 0;
878 virtual Element::Ptr ReplaceRootElement (const NameWithNamespace& newEltName, const optional<String>& namespacePrefix) = 0;
879 virtual void Write (const Streams::OutputStream::Ptr<byte>& to, const SerializationOptions& options) const = 0;
880 virtual Iterable<Node::Ptr> GetChildren () const = 0;
881 virtual void Validate (const Schema::Ptr& schema) const = 0;
882 };
883
884 }
885
886}
887
888/*
889 ********************************************************************************
890 ***************************** Implementation Details ***************************
891 ********************************************************************************
892 */
893
894#include "DOM.inl"
895
896#endif /*_Stroika_Foundation_DataExchange_XML_DOM_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
Document::Ptr is create with Document::New, and is a smart pointer to a DOM document object.
Definition DOM.h:721
nonvirtual void Validate(const Schema::Ptr &schema) const
Definition DOM.cpp:151
nonvirtual void Write(const Streams::OutputStream::Ptr< byte > &to, const SerializationOptions &options={}) const
The document as XML: to a byte stream as UTF-8, with no byte order mark - or as a String.
Definition DOM.inl:380
nonvirtual Iterable< Node::Ptr > GetChildren() const
Definition DOM.inl:391
nonvirtual Traversal::Iterable< Element::Ptr > LookupElements(const XPath::Expression &e) const
shorthand for GetRootElement ().LookupElements (e) - so requires root element exists
Definition DOM.inl:420
nonvirtual shared_ptr< IRep > GetRep() const
Definition DOM.inl:363
nonvirtual Element::Ptr GetRootElement() const
always returns Node of eElement or return nullptr if none
Definition DOM.inl:395
nonvirtual Element::Ptr LookupOneElement(const XPath::Expression &e) const
shorthand for GetRootElement ().LookupOneElement (e) - so requires root element exists
Definition DOM.inl:410
nonvirtual Traversal::Iterable< XPath::Result > Lookup(const XPath::Expression &e) const
shorthand for GetRootElement ().Lookup (e) - so requires root element exists
Definition DOM.inl:415
nonvirtual Element::Ptr ReplaceRootElement(const NameWithNamespace &newEltName, const optional< String > &namespacePrefix=nullopt) const
Make this the document's root element - and say HOW its namespace is declared on it,...
Definition DOM.inl:405
nonvirtual Node::Ptr LookupOneNode(const XPath::Expression &e) const
Definition DOM.inl:320
nonvirtual shared_ptr< IRep > PeekRep() const
return the associated shared_ptr (can be nullptr)
nonvirtual optional< String > GetID() const
nonvirtual Ptr ImportNode(const Node::Ptr &cloneFrom, const Node::Ptr &afterNode)
... placed after afterNode, or first if nullptr
nonvirtual optional< URI > GetDefaultNamespace() const
Definition DOM.inl:162
nonvirtual Traversal::Iterable< Element::Ptr > LookupElements(const XPath::Expression &e) const
nonvirtual Ptr Append(const NameWithNamespace &eltName)
Definition DOM.inl:225
nonvirtual Element::Ptr LookupOneElement(const XPath::Expression &e) const
nonvirtual Traversal::Iterable< XPath::Result > Lookup(const XPath::Expression &e) const
nonvirtual Ptr AppendIf(const NameWithNamespace &eltName, const optional< String > &v)
Trivial wrapper on AppendElement, but if v is missing then this is a no-op.
Definition DOM.inl:250
nonvirtual void SetDefaultNamespace(const optional< URI > defaultNS=nullopt)
Sets the xmlns attribute of this element.
Definition DOM.inl:169
nonvirtual void SetAttribute(const NameWithNamespace &attrName, const optional< String > &v)
Definition DOM.inl:148
nonvirtual optional< String > GetAttribute(const NameWithNamespace &attrName) const
Definition DOM.inl:133
nonvirtual Ptr Insert(const NameWithNamespace &eltName, const Node::Ptr &afterNode)
Insert Element (after argument node) inside of this 'Element'.
Definition DOM.inl:221
nonvirtual shared_ptr< IRep > GetRep() const
return the associated shared_ptr (cannot be nullptr)
nonvirtual Traversal::Iterable< String > GetValues(const XPath::Expression &e) const
Definition DOM.inl:184
nonvirtual Ptr ImportNode(const Node::Ptr &cloneFrom)
Append a deep CLONE of 'cloneFrom' - an element of any document - to this element's children.
nonvirtual bool HasAttribute(const NameWithNamespace &attrName) const
Definition DOM.inl:137
Node::Ptr is a smart pointer to a Node::IRep.
Definition DOM.h:243
nonvirtual void SetValue(const String &v)
Definition DOM.inl:83
nonvirtual NameWithNamespace GetName() const
Definition DOM.inl:68
nonvirtual void SetName(const NameWithNamespace &name)
Definition DOM.inl:73
nonvirtual bool operator==(const Ptr &rhs) const
Definition DOM.inl:45
nonvirtual shared_ptr< IRep > PeekRep() const
return the associated shared_ptr (can be nullptr)
Definition DOM.inl:104
nonvirtual shared_ptr< IRep > GetRep() const
return the associated shared_ptr (cannot be nullptr)
Definition DOM.inl:99
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:219
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:234
virtual Ptr AppendImportedNode(const Node::Ptr &cloneFrom)=0
virtual Ptr InsertElement(const NameWithNamespace &eltName, const Ptr &afterNode)=0
An element's or attribute's name (local-name, not including a prefix), plus the namespace that name i...
Definition Namespace.h:63
used to specify default namespace, and any n: prefixes applicable to elements.
Definition Namespace.h:27
How XML is read and written - for now, with which provider - taken last by each XML function (DOM::Do...