Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Xerces.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <fstream>
7
8#include <xercesc/validators/schema/identity/XPathException.hpp>
9
11#include "Stroika/Foundation/DataExchange/BadFormatException.h"
15#include "Stroika/Foundation/Execution/Exceptions.h"
16#include "Stroika/Foundation/Execution/Throw.h"
19#include "Stroika/Foundation/Memory/Common.h"
23
24#include "Xerces.h"
25
26using namespace Stroika::Foundation;
29using namespace Stroika::Foundation::DataExchange::XML;
30using namespace Stroika::Foundation::DataExchange::XML::DOM;
31using namespace Stroika::Foundation::DataExchange::XML::Schema;
32using namespace Stroika::Foundation::DataExchange::XML::Providers::Xerces;
33using namespace Stroika::Foundation::Debug;
34using namespace Stroika::Foundation::Execution;
35using namespace Stroika::Foundation::Streams;
36
37XERCES_CPP_NAMESPACE_USE;
38
39using std::byte;
40
41using Memory::MakeSharedPtr;
42
43// Comment this in to turn on aggressive noisy DbgTrace in this module
44//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
45
46static_assert (qStroika_HasComponent_xerces, "Don't compile this file if qStroika_HasComponent_xerces not set");
47
48CompileTimeFlagChecker_SOURCE (Stroika::Foundation::DataExchange::XML, qStroika_HasComponent_xerces, qStroika_HasComponent_xerces);
49
50namespace {
51 // From https://www.w3.org/TR/xml-names/
52 // In a namespace declaration, the URI reference is the normalized value of the attribute, so replacement of XML
53 // character and entity references has already been done before any comparison.
54 //
55 // Not 100% sure, but I think that means decode %x stuff too (at least that fixes bug I'm encountering with ASTM-CCR files)
56 // --LGP 2024-01-31
57 constexpr auto kUseURIEncodingFlag_ = URI::StringPCTEncodedFlag::eDecoded;
58}
59
60/*
61 */
62#define START_LIB_EXCEPTION_MAPPER_ try {
63#define END_LIB_EXCEPTION_MAPPER_ \
64 } \
65 catch (const xercesc::OutOfMemoryException&) \
66 { \
67 Execution::Throw (bad_alloc{}, "xerces OutOfMemoryException - throwing bad_alloc"); \
68 } \
69 catch (...) \
70 { \
71 Execution::ReThrow (); \
72 }
73
74#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
75/*
76 * A helpful class to isolate Xerces (etc) memory management calls. Could be the basis
77 * of future performance/memory optimizations, but for now, just a helpful debugging/tracking
78 * class.
79 */
80namespace {
81 struct MyXercesMemMgr_ : public MemoryManager {
82 public:
83 MyXercesMemMgr_ ()
84 : fAllocator{fBaseAllocator}
85 {
86 }
87 ~MyXercesMemMgr_ ()
88 {
89#if qStroika_Foundation_Debug_AssertionsChecked
90 auto snapshot = fAllocator.GetSnapshot ();
91 Assert (snapshot.fAllocations.empty ()); // else we have a memory leak which can be perused here in the debugger
92#endif
93 }
94
95 public:
98 mutex fLastSnapshot_CritSection;
99 Memory::LeakTrackingGeneralPurposeAllocator::Snapshot fLastSnapshot;
100
101 public:
102 void DUMPCurMemStats ()
103 {
104 TraceContextBumper ctx{"Xerces::MyXercesMemMgr_::DUMPCurMemStats"};
105 [[maybe_unused]] lock_guard critSec{fLastSnapshot_CritSection};
106 fAllocator.DUMPCurMemStats (fLastSnapshot);
107 // now copy current map to prev for next time this gets called
108 fLastSnapshot = fAllocator.GetSnapshot ();
109 }
110
111 public:
112 virtual MemoryManager* getExceptionMemoryManager () override
113 {
114 return this;
115 }
116 virtual void* allocate (XMLSize_t size) override
117 {
118 try {
119 return fAllocator.Allocate (size);
120 }
121 catch (...) {
122 // NB: use throw not Exception::Throw () since that requires its a subclass of exception (or SilentException)
123 throw (OutOfMemoryException{}); // quirk cuz this is the class Xerces expects and catches internally (why not bad_alloc?) - sigh...
124 }
125 }
126 virtual void deallocate (void* p) override
127 {
128 if (p != nullptr) {
129 return fAllocator.Deallocate (p);
130 }
131 }
132 };
133}
134#endif
135
136namespace {
137 struct MySchemaResolver_ : public XMLEntityResolver {
138 private:
139 Resource::ResolverPtr fResolver_;
140
141 public:
142 MySchemaResolver_ (const Resource::ResolverPtr& resolver)
143 : fResolver_{resolver}
144 {
145 }
146 virtual InputSource* resolveEntity (XMLResourceIdentifier* resourceIdentifier) override
147 {
148 // @todo consider exposting this API outside the module, and/or providing option to wget missing namespaces, or have option for where to fetch from?
149 TraceContextBumper ctx{"Xerces::{}::MySchemaResolver_::resolveEntity"};
150 RequireNotNull (resourceIdentifier);
151
152 if (fResolver_ != nullptr) {
153 // any of them may be null - a schema's xs:import has no public ID
154 auto optionalString = [] (const XMLCh* s) { return s == nullptr ? optional<String>{} : xercesString2String (s); };
155 optional<String> ns = optionalString (resourceIdentifier->getNameSpace ());
156 if (auto o = fResolver_.Lookup (Resource::Name{.fNamespace = ns ? optional<URI>{*ns} : nullopt,
157 .fPublicID = optionalString (resourceIdentifier->getPublicId ()),
158 .fSystemID = optionalString (resourceIdentifier->getSystemId ())})) {
159 return mkMemInputSrc_ (o->fData);
160 }
161 }
162 return nullptr;
163 }
164
165 private:
166 static InputSource* mkMemInputSrc_ (const Memory::BLOB& schemaData)
167 {
168 if (schemaData.empty ()) [[unlikely]] {
169 // not sure this is useful case? Should assert/throw?
170 return new MemBufInputSource{nullptr, 0, "", true};
171 }
172 else {
173 XMLByte* useBuf = new XMLByte[schemaData.GetSize ()];
174 memcpy (useBuf, schemaData.begin (), schemaData.GetSize ());
175 return new MemBufInputSource{useBuf, schemaData.GetSize (), "", true};
176 }
177 }
178 };
179}
180
181namespace {
182 void SetupCommonParserFeatures_ (SAX2XMLReader& reader)
183 {
184 reader.setFeature (XMLUni::fgSAX2CoreNameSpaces, true);
185 reader.setFeature (XMLUni::fgXercesDynamic, false);
186 reader.setFeature (XMLUni::fgSAX2CoreNameSpacePrefixes, false); // false: * *Do not report attributes used for Namespace declarations, and optionally do not report original prefixed names
187 }
188 void SetupCommonParserFeatures_ (SAX2XMLReader& reader, bool validatingWithSchema)
189 {
190 reader.setFeature (XMLUni::fgXercesSchema, validatingWithSchema);
191 reader.setFeature (XMLUni::fgSAX2CoreValidation, validatingWithSchema);
192
193 // The purpose of this maybe to find errors with the schema itself, in which case,
194 // we shouldn't bother (esp for release builds)
195 // (leave for now til we performance test)
196 // -- LGP 2007-06-21
197 reader.setFeature (XMLUni::fgXercesSchemaFullChecking, validatingWithSchema);
198 reader.setFeature (XMLUni::fgXercesUseCachedGrammarInParse, validatingWithSchema);
199 reader.setFeature (XMLUni::fgXercesIdentityConstraintChecking, validatingWithSchema);
200
201 // we only want to use loaded schemas - don't save any more into the grammar cache, since that
202 // is global/shared.
203 reader.setFeature (XMLUni::fgXercesCacheGrammarFromParse, false);
204 }
205}
206
207namespace {
208 struct Map2StroikaExceptionsErrorReporter_ : public XMLErrorReporter, public ErrorHandler {
209 public:
210 virtual void error ([[maybe_unused]] const unsigned int errCode, [[maybe_unused]] const XMLCh* const errDomain,
211 [[maybe_unused]] const ErrTypes type, const XMLCh* const errorText, [[maybe_unused]] const XMLCh* const systemId,
212 [[maybe_unused]] const XMLCh* const publicId, const XMLFileLoc lineNum, const XMLFileLoc colNum) override
213 {
214 Execution::Throw (BadFormatException{errorText, static_cast<unsigned int> (lineNum), static_cast<unsigned int> (colNum), 0});
215 }
216 virtual void resetErrors () override
217 {
218 }
219 virtual void warning ([[maybe_unused]] const SAXParseException& exc) override
220 {
221 // ignore
222 }
223 virtual void error (const SAXParseException& exc) override
224 {
225 Execution::Throw (BadFormatException{exc.getMessage (), static_cast<unsigned int> (exc.getLineNumber ()),
226 static_cast<unsigned int> (exc.getColumnNumber ()), 0});
227 }
228 virtual void fatalError (const SAXParseException& exc) override
229 {
230 Execution::Throw (BadFormatException{exc.getMessage (), static_cast<unsigned int> (exc.getLineNumber ()),
231 static_cast<unsigned int> (exc.getColumnNumber ()), 0});
232 }
233 };
234}
235
236namespace {
237 struct SchemaRep_ : IXercesSchemaRep {
238#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
239 static inline atomic<unsigned int> sLiveCnt{0};
240#endif
241 SchemaRep_ (const shared_ptr<const void>& library, const Streams::InputStream::Ptr<byte>& schemaData, const Resource::ResolverPtr& resolver)
242 : fLibrary_{library}
243 , fTargetNamespace{}
244 , fResolver{resolver}
245 , fSchemaData{schemaData.ReadAll ()}
246 {
247 AssertNotNull (XMLPlatformUtils::fgMemoryManager);
248 XMLGrammarPoolImpl* grammarPool = new (XMLPlatformUtils::fgMemoryManager) XMLGrammarPoolImpl{XMLPlatformUtils::fgMemoryManager};
249 try {
250 Require (not fSchemaData.empty ());
251 MemBufInputSource mis{reinterpret_cast<const XMLByte*> (fSchemaData.begin ()), fSchemaData.GetSize (), u""};
252
253 MySchemaResolver_ mySchemaResolver{resolver};
254 // Directly construct SAX2XMLReaderImpl so we can use XMLEntityResolver - which passes along namespace (regular
255 // EntityResolve just passes systemID
256 // shared_ptr<SAX2XMLReader> reader = shared_ptr<SAX2XMLReader> (XMLReaderFactory::createXMLReader (XMLPlatformUtils::fgMemoryManager, grammarPool));
257 //
258 shared_ptr<SAX2XMLReaderImpl> reader = shared_ptr<SAX2XMLReaderImpl> (
259 new (XMLPlatformUtils::fgMemoryManager) SAX2XMLReaderImpl{XMLPlatformUtils::fgMemoryManager, grammarPool});
260 reader->setXMLEntityResolver (&mySchemaResolver);
261
262 SetupCommonParserFeatures_ (*reader, true);
263
264 // Reset fgXercesCacheGrammarFromParse to TRUE so we actually load the XSD here
265 reader->setFeature (XMLUni::fgXercesCacheGrammarFromParse, true);
266 reader->setErrorHandler (&fErrorReporter_);
267 xercesc::Grammar* g = reader->loadGrammar (mis, Grammar::SchemaGrammarType, true);
268 AssertNotNull (g);
269 const XMLCh* ts = g->getTargetNamespace ();
270 if (ts and *ts) {
271 fTargetNamespace = URI{xercesString2String (ts)};
272 }
273 }
274 catch (...) {
275 delete grammarPool;
276 Execution::ReThrow ();
277 }
278 // locked, as Xerces requires of a pool parsers on different threads share (XMLGrammarPool::lockPool): each parser
279 // adds the namespace URIs it meets to the pool's string pool - unlocked, an unsynchronized hash table. Locked here,
280 // before any parser is given it, as each takes that string pool as it is constructed
281 grammarPool->lockPool ();
282 fCachedGrammarPool = grammarPool;
283#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
284 ++sLiveCnt;
285#endif
286 }
287 SchemaRep_ (const SchemaRep_&) = delete;
288 virtual ~SchemaRep_ ()
289 {
290 delete fCachedGrammarPool;
291#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
292 Assert (sLiveCnt > 0);
293 --sLiveCnt;
294#endif
295 }
296 shared_ptr<const void> fLibrary_; // first, so destroyed last: Xerces lasts while anything of it does
297 optional<URI> fTargetNamespace;
298 Resource::ResolverPtr fResolver;
299 Memory::BLOB fSchemaData;
300 xercesc::XMLGrammarPool* fCachedGrammarPool{nullptr};
301 Map2StroikaExceptionsErrorReporter_ fErrorReporter_;
302
303 virtual const Providers::ISchemaProvider* GetProvider () const override
304 {
305 return &XML::Providers::Xerces::kDefaultProvider;
306 }
307 virtual optional<URI> GetTargetNamespace () const override
308 {
309 return fTargetNamespace;
310 }
311 virtual Memory::BLOB GetData () override
312 {
313 return fSchemaData;
314 }
315 // not super useful, except if you want to clone
316 virtual Resource::ResolverPtr GetResolver () override
317 {
318 return fResolver;
319 }
320 virtual xercesc::XMLGrammarPool* GetCachedGrammarPool () override
321 {
322 return fCachedGrammarPool;
323 }
324 };
325}
326
327namespace {
328 class StdIStream_InputSource_ : public InputSource {
329 protected:
330 class StdIStream_InputStream : public XERCES_CPP_NAMESPACE_QUALIFIER BinInputStream {
331 public:
332 StdIStream_InputStream (InputStream::Ptr<byte> in)
333 : fSource{in}
334 {
335 }
336 ~StdIStream_InputStream () = default;
337
338 public:
339 virtual XMLFilePos curPos () const override
340 {
341 return fSource.GetOffset ();
342 }
343 virtual XMLSize_t readBytes (XMLByte* const toFill, const XMLSize_t maxToRead) override
344 {
345 return fSource.ReadBlocking (span{reinterpret_cast<byte*> (toFill), maxToRead}).size ();
346 }
347 virtual const XMLCh* getContentType () const override
348 {
349 return nullptr;
350 }
351
352 protected:
354 };
355
356 public:
357 StdIStream_InputSource_ (InputStream::Ptr<byte> in, const XMLCh* const bufId = nullptr)
358 : InputSource{bufId}
359 , fSource{in}
360 {
361 }
362 virtual BinInputStream* makeStream () const override
363 {
364 return new (getMemoryManager ()) StdIStream_InputStream{fSource};
365 }
366
367 protected:
369 };
370}
371
372namespace {
373 class SAX2PrintHandlers_ : public DefaultHandler {
374 private:
376
377 public:
378 SAX2PrintHandlers_ (StructuredStreamEvents::IConsumer* callback)
379 : fCallback_{callback}
380 {
381 }
382
383 public:
384 virtual void startDocument () override
385 {
386 if (fCallback_ != nullptr) {
387 fCallback_->StartDocument ();
388 }
389 }
390 virtual void endDocument () override
391 {
392 if (fCallback_ != nullptr) {
393 fCallback_->EndDocument ();
394 }
395 }
396 virtual void startElement (const XMLCh* const uri, const XMLCh* const localName, const XMLCh* const /*qname*/, const Attributes& attributes) override
397 {
398 Require (uri != nullptr);
399 Require (localName != nullptr);
400 if (fCallback_ != nullptr) {
401 using Name = StructuredStreamEvents::Name;
402 Mapping<Name, String> useAttrs;
403 size_t attributesLen = attributes.getLength ();
404 for (XMLSize_t i = 0; i < attributesLen; ++i) {
405 Name attrName{xercesString2String (attributes.getURI (i)), xercesString2String (attributes.getLocalName (i)), Name::eAttribute};
406 useAttrs.Add (attrName, xercesString2String (attributes.getValue (i)));
407 }
408 fCallback_->StartElement (Name{xercesString2String (uri), xercesString2String (localName)}, useAttrs);
409 }
410 }
411 virtual void endElement (const XMLCh* const uri, const XMLCh* const localName, [[maybe_unused]] const XMLCh* const qname) override
412 {
413 Require (uri != nullptr);
414 Require (localName != nullptr);
415 Require (qname != nullptr);
416 if (fCallback_ != nullptr) {
417 fCallback_->EndElement (StructuredStreamEvents::Name{xercesString2String (uri), xercesString2String (localName)});
418 }
419 }
420 virtual void characters (const XMLCh* const chars, const XMLSize_t length) override
421 {
422 Require (chars != nullptr);
423 Require (length != 0);
424 if (fCallback_ != nullptr) {
425 fCallback_->TextInsideElement (xercesString2String (chars, chars + length));
426 }
427 }
428 };
429}
430
431namespace {
432 /*
433 * Short lifetime. Don't save these iterator objects. Just use them to enumerate a collection and then let them
434 * go. They (could) become invalid after a call to update the database.
435 */
436 class SubNodeIterator_ {
437 public:
438 class Rep {
439 public:
440 Rep () = default;
441 virtual ~Rep () = default;
442
443 public:
444 virtual bool IsAtEnd () const = 0;
445 virtual void Next () = 0;
446 virtual Node::Ptr Current () const = 0;
447 virtual size_t GetLength () const = 0;
448 };
449 explicit SubNodeIterator_ (const shared_ptr<Rep>& from)
450 : fRep{from}
451 {
452 }
453
454 public:
455 nonvirtual bool NotDone () const
456 {
457 return not fRep->IsAtEnd ();
458 }
459 nonvirtual bool IsAtEnd () const
460 {
461 return fRep->IsAtEnd ();
462 }
463 nonvirtual void Next ()
464 {
465 fRep->Next ();
466 }
467 nonvirtual Node::Ptr Current () const
468 {
469 return fRep->Current ();
470 }
471 nonvirtual size_t GetLength () const
472 {
473 return fRep->GetLength ();
474 }
475 nonvirtual void operator++ ()
476 {
477 Next ();
478 }
479 nonvirtual void operator++ (int)
480 {
481 Next ();
482 }
483 nonvirtual Node::Ptr operator* () const
484 {
485 return Current ();
486 }
487
488 protected:
489 shared_ptr<Rep> fRep;
490 };
491}
492
493namespace {
494 template <class TYPE>
495 class AutoRelease_ {
496 public:
497 AutoRelease_ (TYPE* p)
498 : p_ (p)
499 {
500 }
501 AutoRelease_ (const AutoRelease_<TYPE>&) = delete;
502 AutoRelease_<TYPE>& operator= (const AutoRelease_<TYPE>&) = delete;
503 ~AutoRelease_ ()
504 {
505 if (p_ != 0)
506 p_->release ();
507 }
508
509 TYPE& operator* () const
510 {
511 return *p_;
512 }
513 TYPE* operator->() const
514 {
515 return p_;
516 }
517 operator TYPE*() const
518 {
519 return p_;
520 }
521 TYPE* get () const
522 {
523 return p_;
524 }
525 TYPE* adopt ()
526 {
527 TYPE* tmp = p_;
528 p_ = 0;
529 return tmp;
530 }
531 TYPE* swap (TYPE* p)
532 {
533 TYPE* tmp = p_;
534 p_ = p;
535 return tmp;
536 }
537 void set (TYPE* p)
538 {
539 if (p_ != 0)
540 p_->release ();
541 p_ = p;
542 }
543
544 private:
545 TYPE* p_;
546 };
547}
548
549namespace {
550 DOMImplementation& GetDOMIMPL_ ()
551 {
552 static constexpr XMLCh kDOMImplFeatureDeclaration_[] = u"Core";
553 // safe to save in a static var? -- LGP 2007-05-20
554 // from perusing implementation - this appears safe to cache and re-use in differnt threads
555 static DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation (kDOMImplFeatureDeclaration_);
556 AssertNotNull (impl);
557 return *impl;
558 }
559
560 constexpr bool qDumpXMLOnValidationError_ = qStroika_Foundation_Debug_AssertionsChecked;
561
562#if qStroika_Foundation_Debug_AssertionsChecked
563 bool ValidNewNodeName_ (const String& n)
564 {
565 if (n.empty ()) {
566 return false;
567 }
568 if (n.find (':') != wstring::npos) { // if triggered, you probably used XPath as arg for CreateElement call!!!
569 return false;
570 }
571 return true;
572 }
573#endif
574}
575
576namespace {
577 void DoWrite2Stream_ (xercesc::DOMNode* node2Write, const Streams::OutputStream::Ptr<byte>& to, const SerializationOptions& options)
578 {
579 AutoRelease_<DOMLSOutput> theOutputDesc = GetDOMIMPL_ ().createLSOutput ();
580 theOutputDesc->setEncoding (XMLUni::fgUTF8EncodingString);
581 AutoRelease_<DOMLSSerializer> writer = GetDOMIMPL_ ().createLSSerializer ();
582 DOMConfiguration* dc = writer->getDomConfig ();
583 dc->setParameter (XMLUni::fgDOMWRTFormatPrettyPrint, options.fPrettyPrint);
584 // no byte order mark: XML declares its own encoding, and one trips readers that do not expect it
585 dc->setParameter (XMLUni::fgDOMWRTBOM, false);
586 class myOutputter : public XMLFormatTarget {
587 public:
589 myOutputter (const Streams::OutputStream::Ptr<byte>& to)
590 : fOut{to}
591 {
592 }
593 virtual void writeChars (const XMLByte* const toWrite, const XMLSize_t count, [[maybe_unused]] XMLFormatter* const formatter) override
594 {
595 fOut.Write (span<const byte>{reinterpret_cast<const byte*> (toWrite), count});
596 }
597 virtual void flush () override
598 {
599 fOut.Flush ();
600 }
601 };
602 myOutputter dest{to};
603 theOutputDesc->setByteStream (&dest);
604 writer->write (node2Write, theOutputDesc);
605 }
606 // Currently unused but maybe needed again if we support 'moving' nodes from one doc to another
607 DOMNode* RecursivelySetNamespace_ (DOMNode* n, const XMLCh* namespaceURI)
608 {
609 RequireNotNull (n);
610 // namespaceURI CAN be nullptr
611 switch (n->getNodeType ()) {
612 case DOMNode::ELEMENT_NODE: {
613 xercesc::DOMDocument* doc = n->getOwnerDocument ();
614 AssertNotNull (doc);
615 n = doc->renameNode (n, namespaceURI, n->getNodeName ());
616 ThrowIfNull (n);
617 for (DOMNode* child = n->getFirstChild (); child != nullptr; child = child->getNextSibling ()) {
618 child = RecursivelySetNamespace_ (child, namespaceURI);
619 }
620 } break;
621 }
622 return n;
623 }
624 constexpr XMLCh* kXerces2XMLDBDocumentKey_ = nullptr; // just a unique key to lookup our doc object from the xerces doc object.
625 // Could use real str, then xerces does strcmp() - but this appears slightly faster
626 // so long as no conflict....
627 String GetTextForDOMNode_ (const DOMNode* node)
628 {
629 RequireNotNull (node);
630 if (node->getNodeType () == DOMNode::COMMENT_NODE) {
631 // The below hack doesn't seem to work for comment nodes - at least in one case - they had
632 // no children nodes so just returned empty string...
633 //
634 // The above comemnt about comment-nodes not working is old, and undated. Revisit this at some point. Though this backup mode isn't so bad.
635 goto BackupMode;
636 }
637
638 {
639 // try quick impl if all children are textnodes
640 //
641 // This trick is from:
642 // http://www.codesynthesis.com/~boris/blog/category/xerces-c/
643 // 2007-06-13, because default impl appears to allocate strings and never frees (til owning doc freed)
644 // for getTextContent calls...
645 //
646 using xercesc::DOMNode;
647 using xercesc::DOMText;
649 for (DOMNode* n = node->getFirstChild (); n != nullptr; n = n->getNextSibling ()) {
650 switch (n->getNodeType ()) {
651 case DOMNode::TEXT_NODE:
652 case DOMNode::CDATA_SECTION_NODE: {
653 DOMText* t (static_cast<DOMText*> (n));
654 // Note - we don't do the usual 'reserve' trick here because the string is generally made up of a single text node
655 r += t->getData ();
656 break;
657 }
658 case DOMNode::ELEMENT_NODE: {
659 goto BackupMode;
660 }
661 }
662 }
663 return r.str ();
664 }
665 BackupMode:
666 /*
667 * Note that this is SOMETHING OF A (temporary) memory leak. Xerces does free the memory when the document is freed.
668 */
669 DbgTrace ("WARNING: GetTextForDOMNode_::BackupMode used"_f);
670 return node->getTextContent ();
671 }
672
673 Node::Ptr WrapXercesNodeInStroikaNode_ (DOMNode* n);
674 Element::Ptr WrapXercesNodeInStroikaNode_ (DOMElement* n);
675#if qStroika_Foundation_Debug_AssertionsChecked
676 weak_ptr<Document::IRep> DocumentOf_ (DOMNode* n);
677#endif
678}
679
680namespace {
681 class SubNodeIteratorOver_SiblingList_Rep_ : public SubNodeIterator_::Rep,
682 Memory::UseBlockAllocationIfAppropriate<SubNodeIteratorOver_SiblingList_Rep_> {
683 public:
684 // Called iterates over CHILDREN of given parentNode
685 SubNodeIteratorOver_SiblingList_Rep_ (DOMNode* nodeParent)
686 : fParentNode{nodeParent}
687 , fCachedMainListLen{static_cast<size_t> (-1)}
688 {
689 RequireNotNull (nodeParent);
690 START_LIB_EXCEPTION_MAPPER_
691 {
692 fCurNode_ = nodeParent->getFirstChild ();
693 }
694 END_LIB_EXCEPTION_MAPPER_
695 }
696 virtual bool IsAtEnd () const override
697 {
698 return fCurNode_ == nullptr;
699 }
700 virtual void Next () override
701 {
702 Require (not IsAtEnd ());
703 AssertNotNull (fCurNode_);
704 START_LIB_EXCEPTION_MAPPER_
705 {
706 fCurNode_ = fCurNode_->getNextSibling ();
707 }
708 END_LIB_EXCEPTION_MAPPER_
709 }
710 virtual Node::Ptr Current () const override
711 {
712 return WrapXercesNodeInStroikaNode_ (fCurNode_);
713 }
714 virtual size_t GetLength () const override
715 {
716 if (fCachedMainListLen == static_cast<size_t> (-1)) {
717 size_t n = 0;
718 START_LIB_EXCEPTION_MAPPER_
719 {
720 for (DOMNode* i = fParentNode->getFirstChild (); i != nullptr; (i = i->getNextSibling ()), ++n)
721 ;
722 }
723 END_LIB_EXCEPTION_MAPPER_
724 fCachedMainListLen = n;
725 }
726 return fCachedMainListLen;
727 }
728
729 private:
730 DOMNode* fParentNode{nullptr};
731 DOMNode* fCurNode_{nullptr};
732 mutable size_t fCachedMainListLen{};
733 };
734}
735
736namespace {
737 struct NodeRep_ : IXercesNodeRep, Memory::UseBlockAllocationIfAppropriate<NodeRep_> {
738 NodeRep_ (DOMNode* n)
739 : fNode_{n}
741 , fOwningDoc_{DocumentOf_ (n)}
742#endif
743 {
744 RequireNotNull (n);
745 }
746#if qStroika_Foundation_Debug_AssertionsChecked
747 ~NodeRep_ ()
748 {
749 // a node is an internal pointer into its document (Node::Ptr): whoever holds it must hold the Document::Ptr longer -
750 // so any use of this node after its document went would have read freed memory. Here catches every such use, but only
751 // once it is too late: the same Require goes at any use worth the cost of checking too
752 Require (not fOwningDoc_.expired ());
753 }
754#endif
755 virtual const Providers::IDOMProvider* GetProvider () const override
756 {
757 return &Providers::Xerces::kDefaultProvider;
758 }
759 virtual bool Equals (const IRep* rhs) const override
760 {
761 RequireNotNull (fNode_);
762 RequireNotNull (rhs);
763 return fNode_ == dynamic_cast<const NodeRep_*> (rhs)->fNode_;
764 }
765 virtual Node::Type GetNodeType () const override
766 {
767 AssertNotNull (fNode_);
768 START_LIB_EXCEPTION_MAPPER_
769 {
770 switch (fNode_->getNodeType ()) {
771 case DOMNode::ELEMENT_NODE:
772 return Node::eElementNT;
773 case DOMNode::ATTRIBUTE_NODE:
774 return Node::eAttributeNT;
775 case DOMNode::TEXT_NODE:
776 return Node::eTextNT;
777 case DOMNode::COMMENT_NODE:
778 return Node::eCommentNT;
779 default:
780 return Node::eOtherNT;
781 }
782 }
783 END_LIB_EXCEPTION_MAPPER_
784 }
785 virtual NameWithNamespace GetName () const override
786 {
787 AssertNotNull (fNode_);
788 Require (GetNodeType () == Node::eElementNT or GetNodeType () == Node::eAttributeNT);
789 START_LIB_EXCEPTION_MAPPER_
790 {
791 AssertNotNull (fNode_->getNodeName ());
792 const XMLCh* n = fNode_->getNamespaceURI ();
793 /*
794 * getLocalName, NOT getNodeName: a NameWithNamespace carries the local name, and the prefix a
795 * document chose is no part of it (@see XML::NameWithNamespace). getNodeName is the QUALIFIED name,
796 * so this said "x:Why" where libxml2 said "Why" until Stroika v3.0d25. It is null only for a node
797 * made by a DOM Level 1 call, which has no namespace to speak of - nothing here makes one, and a
798 * parsed document always has it, but fall back rather than return nothing.
799 */
800 const XMLCh* localName = fNode_->getLocalName ();
801 return NameWithNamespace{n == nullptr ? optional<URI>{} : URI{xercesString2String (n)},
802 localName == nullptr ? fNode_->getNodeName () : localName};
803 }
804 END_LIB_EXCEPTION_MAPPER_
805 }
806 virtual void SetName (const NameWithNamespace& name) override
807 {
808 AssertNotNull (fNode_);
809#if qStroika_Foundation_Debug_AssertionsChecked
810 Require (ValidNewNodeName_ (name.fName));
811#endif
812 START_LIB_EXCEPTION_MAPPER_
813 {
814 xercesc::DOMDocument* doc = fNode_->getOwnerDocument ();
815 AssertNotNull (doc);
816 fNode_ = doc->renameNode (
817 fNode_, name.fNamespace == nullopt ? nullptr : name.fNamespace->As<String> (kUseURIEncodingFlag_).As<u16string> ().c_str (),
818 name.fName.As<u16string> ().c_str ());
819 AssertNotNull (fNode_);
820 }
821 END_LIB_EXCEPTION_MAPPER_
822 }
823 virtual String GetValue () const override
824 {
825 AssertNotNull (fNode_);
826 START_LIB_EXCEPTION_MAPPER_
827 {
828 return GetTextForDOMNode_ (fNode_);
829 }
830 END_LIB_EXCEPTION_MAPPER_
831 }
832 virtual void SetValue (const String& v) override
833 {
834 AssertNotNull (fNode_);
835 START_LIB_EXCEPTION_MAPPER_
836 {
837 fNode_->setTextContent (v.empty () ? nullptr : v.As<u16string> ().c_str ());
838 }
839 END_LIB_EXCEPTION_MAPPER_
840 }
841 virtual void DeleteNode () override
842 {
843 START_LIB_EXCEPTION_MAPPER_
844 {
845 DOMNode* selNode = fNode_;
846 ThrowIfNull (selNode);
847 DOMNode* parentNode = selNode->getParentNode ();
848 if (parentNode == nullptr) {
849 // This happens if the selected node is an attribute
850 if (fNode_ != nullptr) {
851 const XMLCh* ln = selNode->getNodeName ();
852 AssertNotNull (ln);
853 DOMElement* de = dynamic_cast<DOMElement*> (fNode_);
854 de->removeAttribute (ln);
855 }
856 }
857 else {
858 (void)parentNode->removeChild (selNode);
859 }
860 }
861 END_LIB_EXCEPTION_MAPPER_
862 }
863 virtual Node::Ptr GetParentNode () const override
864 {
865 AssertNotNull (fNode_);
866 START_LIB_EXCEPTION_MAPPER_
867 {
868 auto p = fNode_->getParentNode ();
869 return p == nullptr ? nullptr : WrapXercesNodeInStroikaNode_ (p);
870 }
871 END_LIB_EXCEPTION_MAPPER_
872 }
873 virtual void Write (const Streams::OutputStream::Ptr<byte>& to, const SerializationOptions& options) const override
874 {
875 START_LIB_EXCEPTION_MAPPER_
876 {
877 DoWrite2Stream_ (fNode_, to, options);
878 }
879 END_LIB_EXCEPTION_MAPPER_
880 }
881 virtual xercesc::DOMNode* GetInternalTRep () override
882 {
883 return fNode_;
884 }
885 // not ref counted: freed with its document, which whoever holds this must keep (Node::Ptr) - checked in the DTOR
886 DOMNode* fNode_;
887#if qStroika_Foundation_Debug_AssertionsChecked
888 weak_ptr<Document::IRep> fOwningDoc_;
889#endif
890 };
891}
892
893namespace {
894 DISABLE_COMPILER_MSC_WARNING_START (4250) // inherits via dominance warning
895 struct ElementRep_ : Element::IRep, Memory::InheritAndUseBlockAllocationIfAppropriate<ElementRep_, NodeRep_> {
897 ElementRep_ (DOMNode* n)
898 : inherited{n}
899 {
900 RequireNotNull (n);
901 Require (n->getNodeType () == DOMNode::ELEMENT_NODE);
902 }
903 virtual Node::Type GetNodeType () const override
904 {
905 AssertNotNull (fNode_);
906 Assert (fNode_->getNodeType () == DOMNode::ELEMENT_NODE);
907 return Node::eElementNT;
908 }
909 virtual optional<String> GetAttribute (const NameWithNamespace& attrName) const override
910 {
911 AssertNotNull (fNode_);
912 START_LIB_EXCEPTION_MAPPER_
913 {
914 if (fNode_->getNodeType () == DOMNode::ELEMENT_NODE) {
915 DOMElement* elt = Debug::UncheckedDynamicCast<DOMElement*> (fNode_);
916 const XMLCh* s = attrName.fNamespace
917 ? elt->getAttributeNS (attrName.fNamespace->As<String> (kUseURIEncodingFlag_).As<u16string> ().c_str (),
918 attrName.fName.As<u16string> ().c_str ())
919 : elt->getAttribute (attrName.fName.As<u16string> ().c_str ());
920 AssertNotNull (s);
921 if (*s != '\0') {
922 return s;
923 }
924 }
925 return nullopt;
926 }
927 END_LIB_EXCEPTION_MAPPER_
928 }
929 virtual void SetAttribute (const NameWithNamespace& attrName, const optional<String>& v) override
930 {
931 Require (GetNodeType () == Node::eElementNT);
932 AssertNotNull (fNode_);
933 START_LIB_EXCEPTION_MAPPER_
934 {
935 DOMElement* element = dynamic_cast<DOMElement*> (fNode_);
936 ThrowIfNull (element);
937 /*
938 * SINCE STROIKA V3.0d5 - if no namespace given on attrName, we use nullptr as the namespace (not do any inheritance)
939 * attributes usually have no namespace!!!!
940 */
941 if (v) {
942 /*
943 * For reasons that elude maybe (maybe because it was standard for XML early on)
944 * all my attributes are free of namespaces. So why use setAttributeNS? Because otherwise
945 * the XQilla code fails to match on the attribute names at all in its XPath stuff.
946 * Considered copying the namespace from the parent element (fNode_->getNamespaceURI()),
947 * but XQilla didnt like that either (maybe then I needed M: on xpath).
948 * A different subclass object of DOMAttrNode is created - one that doesnt have a getLocalName,
949 * or something like that. Anyhow - this appears to do the right thing for now...
950 * -- LGP 2007-06-13
951 *
952 * Not using XQilla anymore. And may have been another misunderstanding anyhow. So have attributes
953 * inherit the namespace of the element they are from, by default --LGP 2024-01-09
954 *
955 * MAYBE related to https://github.com/SophistSolutions/Stroika/issues/1131 (STK-999) - diff symptoms but similar workaround
956 */
957 if (attrName.fNamespace == kXMLNS.fNamespace) {
958 /*
959 * A namespace DECLARATION - the default one (kXMLNS, named xmlns) or a prefix's (named for the
960 * prefix). The one namespace no prefix may be bound to, so never the prefixing below - but
961 * setAttributeNS wants the QUALIFIED name, and a prefix is declared by the name xmlns:PREFIX.
962 */
963 u16string qName = attrName.fName.As<u16string> ();
964 if (attrName.fName != kXMLNS.fName) {
965 qName = u"xmlns:" + qName;
966 }
967 element->setAttributeNS (attrName.fNamespace->As<String> (kUseURIEncodingFlag_).As<u16string> ().c_str (),
968 qName.c_str (), v->As<u16string> ().c_str ());
969 }
970 else if (attrName.fNamespace) {
971 /*
972 * An attribute is in NO namespace unless it carries a prefix - a default namespace declaration
973 * applies to elements only (XML Namespaces, section 6.2). So find the prefix this element has for
974 * that namespace, or declare one: without it setAttributeNS writes the attribute unqualified, which
975 * says the opposite of what was asked for.
976 */
977 u16string ns16 = attrName.fNamespace->As<String> (kUseURIEncodingFlag_).As<u16string> ();
978 const XMLCh* had = element->lookupPrefix (ns16.c_str ());
979 u16string prefix;
980 if (had != nullptr and *had != u'\0') {
981 prefix = had;
982 }
983 else {
984 for (char16_t p = u'a'; prefix.empty (); ++p) {
985 u16string candidate{p};
986 if (element->lookupNamespaceURI (candidate.c_str ()) == nullptr) {
987 prefix = candidate;
988 }
989 }
990 static const u16string kXMLNSNamespace_{u"http://www.w3.org/2000/xmlns/"};
991 element->setAttributeNS (kXMLNSNamespace_.c_str (), (u16string{u"xmlns:"} + prefix).c_str (), ns16.c_str ());
992 }
993 element->setAttributeNS (ns16.c_str (), (prefix + u":" + attrName.fName.As<u16string> ()).c_str (),
994 v->As<u16string> ().c_str ());
995 }
996 else {
997 element->setAttributeNS (nullptr, attrName.fName.As<u16string> ().c_str (), v->As<u16string> ().c_str ());
998 }
999 }
1000 else {
1001 element->removeAttributeNS (
1002 attrName.fNamespace ? attrName.fNamespace->As<String> (kUseURIEncodingFlag_).As<u16string> ().c_str () : nullptr,
1003 attrName.fName.As<u16string> ().c_str ());
1004 }
1005 }
1006 END_LIB_EXCEPTION_MAPPER_
1007 }
1008 virtual Element::Ptr InsertElement (const NameWithNamespace& eltName, const Element::Ptr& afterNode) override
1009 {
1010#if qStroika_Foundation_Debug_AssertionsChecked
1011 Require (ValidNewNodeName_ (eltName.fName));
1012#endif
1013 START_LIB_EXCEPTION_MAPPER_
1014 {
1015 xercesc::DOMDocument* doc = fNode_->getOwnerDocument ();
1016 // unsure if we should use smartpointer here - thinkout xerces & smart ptrs & mem management
1017 // unclear if we should set namespace to existing node namespace or nullptr here?
1018 u16string ns16, qName;
1019 const XMLCh* useNS = ElementNS2Use_ (fNode_, eltName, &ns16, &qName);
1020 DOMNode* child = doc->createElementNS (useNS, qName.c_str ());
1021 DOMNode* refChildNode = nullptr;
1022 if (afterNode == nullptr) {
1023 // this means PREPEND.
1024 // If there is a first element, then insert before it. If no elements, then append is the same thing.
1025 refChildNode = fNode_->getFirstChild ();
1026 }
1027 else {
1028 refChildNode = dynamic_cast<NodeRep_&> (*afterNode.GetRep ()).GetInternalTRep ()->getNextSibling ();
1029 }
1030 DOMNode* childx = fNode_->insertBefore (child, refChildNode);
1031 ThrowIfNull (childx);
1032 return WrapXercesNodeInStroikaNode_ (childx);
1033 }
1034 END_LIB_EXCEPTION_MAPPER_
1035 }
1036 /*
1037 * The namespace a new element goes in: kNoNamespace none at all, kInheritNamespace (which is what a name built
1038 * without one has) its parent's, and otherwise the one it names. 'buf' holds the string the result points into.
1039 */
1040 static const XMLCh* ElementNS2Use_ (DOMNode* parent, const NameWithNamespace& eltName, u16string* buf, u16string* qName)
1041 {
1042 RequireNotNull (buf);
1043 RequireNotNull (qName);
1044 *qName = eltName.fName.As<u16string> ();
1045 if (eltName.fNamespace == kNoNamespace) {
1046 return nullptr;
1047 }
1048 if (not eltName.fNamespace) {
1049 /*
1050 * Inheriting the parent's namespace - so inherit how it is WRITTEN too: unprefixed, Xerces declares the
1051 * namespace afresh on the child (xmlns=) instead of using the prefix its parent already declared, which
1052 * is legal but says with a declaration what a prefix already said.
1053 */
1054 if (const XMLCh* parentPrefix = parent->getPrefix (); parentPrefix != nullptr and *parentPrefix != u'\0') {
1055 *qName = u16string{parentPrefix} + u":" + *qName;
1056 }
1057 return parent->getNamespaceURI ();
1058 }
1059 *buf = eltName.fNamespace->As<String> (kUseURIEncodingFlag_).As<u16string> ();
1060 // a prefix already declared for that namespace is the one to write it with, for the same reason
1061 if (const XMLCh* had = parent->lookupPrefix (buf->c_str ()); had != nullptr and *had != u'\0') {
1062 *qName = u16string{had} + u":" + *qName;
1063 }
1064 return buf->c_str ();
1065 }
1066 virtual Element::Ptr AppendElement (const NameWithNamespace& eltName) override
1067 {
1068#if qStroika_Foundation_Debug_AssertionsChecked
1069 Require (ValidNewNodeName_ (eltName.fName));
1070#endif
1071 START_LIB_EXCEPTION_MAPPER_
1072 {
1073 xercesc::DOMDocument* doc = fNode_->getOwnerDocument ();
1074 DOMNode* child{};
1075 u16string ns16, qName;
1076 const XMLCh* useNS = ElementNS2Use_ (fNode_, eltName, &ns16, &qName);
1077 child = doc->createElementNS (useNS, qName.c_str ());
1078 DOMNode* childx = fNode_->appendChild (child);
1079 ThrowIfNull (childx);
1080 return WrapXercesNodeInStroikaNode_ (childx);
1081 }
1082 END_LIB_EXCEPTION_MAPPER_
1083 }
1084 /*
1085 * A deep COPY into this document - importNode, never appendChild of the original, which Xerces refuses across
1086 * documents anyhow (WRONG_DOCUMENT_ERR).
1087 */
1088 DOMNode* CloneIntoThisDocument_ (const Node::Ptr& cloneFrom)
1089 {
1090 RequireNotNull (cloneFrom);
1091 NodeRep_* fromRep = dynamic_cast<NodeRep_*> (cloneFrom.GetRep ().get ());
1092 Require (fromRep != nullptr); // the caller's contract: both documents are the same provider
1093 DOMNode* copy = fNode_->getOwnerDocument ()->importNode (fromRep->GetInternalTRep (), true /*deep*/);
1094 ThrowIfNull (copy);
1095 return copy;
1096 }
1097 virtual Element::Ptr AppendImportedNode (const Node::Ptr& cloneFrom) override
1098 {
1099 START_LIB_EXCEPTION_MAPPER_
1100 {
1101 DOMNode* childx = fNode_->appendChild (CloneIntoThisDocument_ (cloneFrom));
1102 ThrowIfNull (childx);
1103 return WrapXercesNodeInStroikaNode_ (childx);
1104 }
1105 END_LIB_EXCEPTION_MAPPER_
1106 }
1107 virtual Element::Ptr InsertImportedNode (const Node::Ptr& cloneFrom, const Element::Ptr& afterNode) override
1108 {
1109 Require (afterNode == nullptr or this->GetChildren ().Contains (afterNode));
1110 START_LIB_EXCEPTION_MAPPER_
1111 {
1112 DOMNode* copy = CloneIntoThisDocument_ (cloneFrom);
1113 DOMNode* refChildNode = afterNode == nullptr
1114 ? fNode_->getFirstChild ()
1115 : dynamic_cast<NodeRep_&> (*afterNode.GetRep ()).GetInternalTRep ()->getNextSibling ();
1116 DOMNode* childx = fNode_->insertBefore (copy, refChildNode); // nullptr refChild means at the end
1117 ThrowIfNull (childx);
1118 return WrapXercesNodeInStroikaNode_ (childx);
1119 }
1120 END_LIB_EXCEPTION_MAPPER_
1121 }
1122 virtual Iterable<Node::Ptr> GetChildren () const override
1123 {
1124 AssertNotNull (fNode_);
1125 START_LIB_EXCEPTION_MAPPER_
1126 {
1127 return Traversal::CreateGenerator<Node::Ptr> (
1128 [sni = SubNodeIterator_{MakeSharedPtr<SubNodeIteratorOver_SiblingList_Rep_> (fNode_)}] () mutable -> optional<Node::Ptr> {
1129 if (sni.IsAtEnd ()) {
1130 return optional<Node::Ptr>{};
1131 }
1132 Node::Ptr r = *sni;
1133 ++sni;
1134 return r;
1135 });
1136 }
1137 END_LIB_EXCEPTION_MAPPER_
1138 }
1139 struct XPathQueryHelper_ {
1140 optional<AutoRelease_<DOMXPathNSResolver>> resolver;
1141 DOMXPathResult::ResultType rt{};
1142 optional<AutoRelease_<DOMXPathExpression>> expr;
1143 XPathQueryHelper_ (DOMNode* n, const XPath::Expression& e, bool firstOnly)
1144 {
1145 xercesc::DOMDocument* doc = n->getOwnerDocument ();
1146 resolver.emplace (doc->createNSResolver (nullptr));
1147 auto namespaceDefs = e.GetOptions ().fNamespaces;
1148 if (namespaceDefs.GetDefaultNamespace ()) {
1149 (*resolver)->addNamespaceBinding (
1150 u"", namespaceDefs.GetDefaultNamespace ()->As<String> (kUseURIEncodingFlag_).As<u16string> ().c_str ());
1151 }
1152 for (Common::KeyValuePair ni : namespaceDefs.GetPrefixedNamespaces ()) {
1153 (*resolver)->addNamespaceBinding (ni.fKey.As<u16string> ().c_str (),
1154 ni.fValue.As<String> (kUseURIEncodingFlag_).As<u16string> ().c_str ());
1155 }
1156 try {
1157 expr.emplace (doc->createExpression (e.GetExpression ().As<u16string> ().c_str (), *resolver));
1158 }
1159 catch (const xercesc::DOMXPathException&) {
1160 // MANY basic things are not supported in Xerces XPath - like a[1] - brackets not supported.
1161 Execution::Throw (XPath::XPathExpressionNotSupported::kThe);
1162 }
1163 switch (e.GetOptions ().fResultTypeIndex.value_or (DOMXPathResult::ANY_TYPE)) {
1164 case XPath::ResultTypeIndex_v<Node::Ptr>: {
1165 auto o = e.GetOptions ();
1166 if (firstOnly) {
1167 rt = e.GetOptions ().fOrdered ? DOMXPathResult::FIRST_ORDERED_NODE_TYPE : DOMXPathResult::ANY_UNORDERED_NODE_TYPE;
1168 }
1169 else if (o.fSnapshot) {
1170 rt = e.GetOptions ().fOrdered ? DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE : DOMXPathResult::UNORDERED_NODE_SNAPSHOT_TYPE;
1171 }
1172 else {
1173 // Would make sense given docs, but appears unsupported by Xerces... --LGP 2024-01-12
1174 // rt = e.GetOptions ().fOrdered ? DOMXPathResult::ORDERED_NODE_ITERATOR_TYPE : DOMXPathResult::UNORDERED_NODE_ITERATOR_TYPE;
1175 rt = e.GetOptions ().fOrdered ? DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE : DOMXPathResult::UNORDERED_NODE_SNAPSHOT_TYPE;
1176 }
1177 } break;
1178 default:
1180 }
1181 }
1182 static optional<XPath::Result> ToResult_ (const xercesc::DOMXPathResult* r)
1183 {
1184 RequireNotNull (r);
1185 switch (r->getResultType ()) {
1186 case DOMXPathResult::NUMBER_TYPE:
1187 return XPath::Result{r->getNumberValue ()};
1188 case DOMXPathResult::BOOLEAN_TYPE:
1189 return XPath::Result{r->getBooleanValue ()};
1190 case DOMXPathResult::STRING_TYPE:
1191 return XPath::Result{xercesString2String (r->getStringValue ())};
1192 case DOMXPathResult::ANY_UNORDERED_NODE_TYPE:
1193 case DOMXPathResult::FIRST_ORDERED_NODE_TYPE:
1194 case DOMXPathResult::UNORDERED_NODE_ITERATOR_TYPE:
1195 case DOMXPathResult::ORDERED_NODE_ITERATOR_TYPE:
1196 case DOMXPathResult::UNORDERED_NODE_SNAPSHOT_TYPE:
1197 case DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE: {
1198 auto n = r->getNodeValue ();
1199 return n == nullptr ? optional<XPath::Result>{} : XPath::Result{Node::Ptr{WrapXercesNodeInStroikaNode_ (n)}};
1200 }
1201 default:
1203 }
1204 return nullopt;
1205 }
1206 };
1207 virtual optional<XPath::Result> LookupOne (const XPath::Expression& e) override
1208 {
1209 if constexpr (false) {
1210 // quickie impl just to test Lookup Code
1211 for (auto i : Lookup (e)) {
1212 return i;
1213 }
1214 return nullopt;
1215 }
1216 START_LIB_EXCEPTION_MAPPER_
1217 {
1218 XPathQueryHelper_ xpHelp{fNode_, e, true};
1219 AutoRelease_<xercesc::DOMXPathResult> r = (*xpHelp.expr)->evaluate (fNode_, xpHelp.rt, nullptr);
1220 return XPathQueryHelper_::ToResult_ (r);
1221 }
1222 END_LIB_EXCEPTION_MAPPER_
1223 }
1224 virtual Traversal::Iterable<XPath::Result> Lookup (const XPath::Expression& e) override
1225 {
1226 if (e.GetOptions ().fSnapshot) {
1227 XPath::Expression::Options e2o = e.GetOptions ();
1228 e2o.fSnapshot = false;
1229 return Sequence<XPath::Result>{this->Lookup (XPath::Expression{e.GetExpression (), e2o})};
1230 }
1231 shared_ptr<XPathQueryHelper_> xpHelp = MakeSharedPtr<XPathQueryHelper_> (fNode_, e, false);
1232 shared_ptr<AutoRelease_<xercesc::DOMXPathResult>> r =
1233 MakeSharedPtr<AutoRelease_<xercesc::DOMXPathResult>> ((*xpHelp->expr)->evaluate (fNode_, xpHelp->rt, nullptr));
1234 Assert (not e.GetOptions ().fSnapshot);
1235 if (xpHelp->rt == DOMXPathResult::UNORDERED_NODE_ITERATOR_TYPE or xpHelp->rt == DOMXPathResult::ORDERED_NODE_ITERATOR_TYPE) [[unlikely]] {
1236 return Traversal::CreateGenerator<XPath::Result> ([xpHelp, r, firstTime = true] () mutable -> optional<XPath::Result> {
1237 if (firstTime) {
1238 firstTime = false;
1239 return XPathQueryHelper_::ToResult_ (*r);
1240 }
1241 if ((*r)->iterateNext () == false) {
1242 return nullopt;
1243 }
1244 return XPathQueryHelper_::ToResult_ (*r);
1245 });
1246 }
1247 if (xpHelp->rt == DOMXPathResult::UNORDERED_NODE_SNAPSHOT_TYPE or xpHelp->rt == DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE) [[likely]] {
1248 return Traversal::CreateGenerator<XPath::Result> ([xpHelp, r, snapIdx = 0] () mutable -> optional<XPath::Result> {
1249 if (not(*r)->snapshotItem (snapIdx)) {
1250 return nullopt;
1251 }
1252 ++snapIdx;
1253 return XPathQueryHelper_::ToResult_ (*r);
1254 });
1255 }
1258 }
1259 virtual Element::Ptr GetChildElementByID (const String& id) const override
1260 {
1261 AssertNotNull (fNode_);
1262 START_LIB_EXCEPTION_MAPPER_
1263 {
1264 for (DOMNode* i = fNode_->getFirstChild (); i != nullptr; i = i->getNextSibling ()) {
1265 if (i->getNodeType () == DOMNode::ELEMENT_NODE) {
1266 DOMElement* elt = Debug::UncheckedDynamicCast<DOMElement*> (i);
1267 const XMLCh* s = elt->getAttribute (u"id");
1268 AssertNotNull (s);
1269 if (CString::Equals (s, id.As<u16string> ().c_str ())) {
1270 return WrapXercesNodeInStroikaNode_ (elt);
1271 }
1272 }
1273 }
1274 return Element::Ptr{nullptr};
1275 }
1276 END_LIB_EXCEPTION_MAPPER_
1277 }
1278 };
1279 DISABLE_COMPILER_MSC_WARNING_END (4250) // inherits via dominance warning
1280}
1281
1282namespace {
1283 inline void MakeXMLDoc_ (shared_ptr<xercesc::DOMDocument>& newXMLDoc)
1284 {
1285 Require (newXMLDoc == nullptr);
1286 newXMLDoc = shared_ptr<xercesc::DOMDocument> (GetDOMIMPL_ ().createDocument (0, nullptr, 0));
1287 newXMLDoc->setXmlStandalone (true);
1288 }
1289}
1290
1291namespace {
1292 class MyMaybeSchemaDOMParser_ {
1293 public:
1294 Map2StroikaExceptionsErrorReporter_ myErrReporter;
1295 shared_ptr<XercesDOMParser> fParser;
1296 Schema::Ptr fSchema{nullptr};
1297
1298 MyMaybeSchemaDOMParser_ () = delete;
1299 MyMaybeSchemaDOMParser_ (const MyMaybeSchemaDOMParser_&) = delete;
1300 MyMaybeSchemaDOMParser_ (const Schema::Ptr& schema)
1301 : fSchema{schema}
1302 {
1303 // a schema another provider compiled would not validate at all here, so it is refused, as libxml2 refuses one
1304 Require (schema == nullptr or schema.GetRep ()->GetProvider () == &Providers::Xerces::kDefaultProvider);
1305 shared_ptr<IXercesSchemaRep> accessSchema = dynamic_pointer_cast<IXercesSchemaRep> (schema.GetRep ());
1306 if (accessSchema != nullptr) {
1307 fParser = MakeSharedPtr<XercesDOMParser> (nullptr, XMLPlatformUtils::fgMemoryManager, accessSchema->GetCachedGrammarPool ());
1308 fParser->cacheGrammarFromParse (false);
1309 fParser->useCachedGrammarInParse (true);
1310 fParser->setDoSchema (true);
1311 fParser->setValidationScheme (AbstractDOMParser::Val_Always);
1312 fParser->setValidationSchemaFullChecking (true);
1313 fParser->setIdentityConstraintChecking (true);
1314 }
1315 else {
1316 fParser = MakeSharedPtr<XercesDOMParser> ();
1317 }
1318 fParser->setDoNamespaces (true);
1319 fParser->setErrorHandler (&myErrReporter);
1320
1321 // @todo make load-external DTD OPTION specified in NEW for document!!! - parser! --LGP 2023-12-16
1322
1323 // LGP added 2009-09-07 - so must test carefully!
1324 {
1325 // I THINK this prevents loading URL-based DTDs - like the one refered to in http://demo.healthframeowrks.com/ when I load the xhmtl as xml
1326 // (it tkaes forever)
1327 fParser->setLoadExternalDTD (false);
1328 // I THINK this prevents loading URL-based schemas (not sure if/how that would have ever happened so I'm not sure that
1329 // this is for) - guessing a bit...
1330 // -- LGP 2009-09-04
1331 fParser->setLoadSchema (false);
1332 }
1333 }
1334 };
1335}
1336
1337namespace {
1338 Element::Ptr WrapXercesNodeInStroikaNode_ (DOMElement* n);
1339}
1340namespace {
1341 // enable_shared_from_this is only to give each node a weak_ptr to its document (DocumentOf_, from the DocRep_* kept in the
1342 // xerces document's user data - a weak_ptr can be had from a raw pointer no other way): its DTOR check, so nothing without
1343 // assertions
1344 struct DocRep_ : DataExchange::XML::DOM::Document::IRep
1345#if qStroika_Foundation_Debug_AssertionsChecked
1346 ,
1347 enable_shared_from_this<DocRep_>
1348#endif
1349 {
1350#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
1351 static inline atomic<unsigned int> sLiveCnt{0};
1352#endif
1353 //
1354 // If this function is passed a nullptr exceptionResult - it will throw on bad validation.
1355 // If it is passed a non-nullptr exceptionResult - then it will map BadFormatException to being ignored, but filling in this
1356 // parameter with the exception details. This is used to allow 'advisory' read xsd validation failure, without actually fully
1357 // failing the read (for http://bugzilla/show_bug.cgi?id=513).
1358 //
1359 DocRep_ (const shared_ptr<const void>& library, const Streams::InputStream::Ptr<byte>& in, const Schema::Ptr& schema)
1360 : fLibrary_{library}
1361 {
1362 [[maybe_unused]] int ignoreMe = 0; // workaround quirk in clang-format
1363 START_LIB_EXCEPTION_MAPPER_
1364 {
1365 MakeXMLDoc_ (fXMLDoc);
1366 fXMLDoc->setUserData (kXerces2XMLDBDocumentKey_, this, nullptr);
1367 if (in != nullptr) {
1368 MyMaybeSchemaDOMParser_ myDOMParser{schema};
1369 myDOMParser.fParser->parse (StdIStream_InputSource_{in, u"XMLDB"});
1370 fXMLDoc.reset ();
1371 fXMLDoc = shared_ptr<xercesc::DOMDocument>{myDOMParser.fParser->adoptDocument ()};
1372 fXMLDoc->setXmlStandalone (true);
1373 fXMLDoc->setUserData (kXerces2XMLDBDocumentKey_, this, nullptr);
1374 }
1375 }
1376 END_LIB_EXCEPTION_MAPPER_
1377#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
1378 ++sLiveCnt;
1379#endif
1380 }
1381 DocRep_ (const DocRep_& from)
1382 : fLibrary_{from.fLibrary_}
1383 {
1384 START_LIB_EXCEPTION_MAPPER_
1385 {
1386 fXMLDoc = shared_ptr<xercesc::DOMDocument> (dynamic_cast<xercesc::DOMDocument*> (from.fXMLDoc->cloneNode (true)));
1387 fXMLDoc->setXmlStandalone (true);
1388 fXMLDoc->setUserData (kXerces2XMLDBDocumentKey_, this, nullptr);
1389 }
1390 END_LIB_EXCEPTION_MAPPER_
1391 EnsureNotNull (fXMLDoc);
1392#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
1393 ++sLiveCnt;
1394#endif
1395 }
1396 virtual ~DocRep_ ()
1397 {
1398#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
1399 Assert (sLiveCnt > 0);
1400 --sLiveCnt;
1401#endif
1402 }
1403 virtual const Providers::IDOMProvider* GetProvider () const override
1404 {
1405 return &Providers::Xerces::kDefaultProvider;
1406 }
1407 virtual bool GetStandalone () const override
1408 {
1409 AssertNotNull (fXMLDoc);
1410 return fXMLDoc->getXmlStandalone ();
1411 }
1412 virtual void SetStandalone (bool standalone) override
1413 {
1414 AssertNotNull (fXMLDoc);
1415 fXMLDoc->setXmlStandalone (standalone);
1416 }
1417 virtual Iterable<Node::Ptr> GetChildren () const override
1418 {
1419 AssertExternallySynchronizedChecker::ReadContext declareContext{fThisAssertExternallySynchronized_};
1420 AssertNotNull (fXMLDoc);
1421 START_LIB_EXCEPTION_MAPPER_
1422 return Traversal::CreateGenerator<Node::Ptr> (
1423 [sni = SubNodeIterator_{MakeSharedPtr<SubNodeIteratorOver_SiblingList_Rep_> (fXMLDoc.get ())}] () mutable -> optional<Node::Ptr> {
1424 if (sni.IsAtEnd ()) {
1425 return optional<Node::Ptr>{};
1426 }
1427 Node::Ptr r = *sni;
1428 ++sni;
1429 return r;
1430 });
1431 END_LIB_EXCEPTION_MAPPER_
1432 }
1433 virtual Element::Ptr ReplaceRootElement (const NameWithNamespace& newEltName, const optional<String>& namespacePrefix) override
1434 {
1435 // a prefix is said to createElementNS as part of the QUALIFIED name, which also declares xmlns:PREFIX; without
1436 // one the namespace becomes the element's default declaration
1437 u16string qName = newEltName.fName.As<u16string> ();
1438 if (namespacePrefix) {
1439 qName = namespacePrefix->As<u16string> () + u":" + qName;
1440 }
1441 DOMElement* n = newEltName.fNamespace == nullopt
1442 ? fXMLDoc->createElement (qName.c_str ())
1443 : fXMLDoc->createElementNS (newEltName.fNamespace->As<String> (kUseURIEncodingFlag_).As<u16string> ().c_str (),
1444 qName.c_str ());
1445 AssertNotNull (n);
1446 DOMElement* oldRoot = fXMLDoc->getDocumentElement ();
1447 if (oldRoot == nullptr) {
1448 (void)fXMLDoc->insertBefore (n, nullptr);
1449 }
1450 else {
1451 (void)fXMLDoc->replaceChild (n, oldRoot);
1452 /*
1453 * I THOGUHT this was a memory leak, but that appears to have been wrong. First, the
1454 * DOMNode objects get associated with the document, and when the document is destroyed
1455 * this is cleaned up. Secondly, there are enough other memory leaks - its unclear if this
1456 * actually helped. Plus the memory management pattern used by Xerces - with its own sub-chunking etc,
1457 * makes it hard to tell.
1458 *
1459 * More importantly - this caused a regression in HealthFrame - which I didn't debug. The OHSD reports
1460 * like AAFP CCR report - will be rejected by our 'valid HTML' tester. Unclear if that's cuz we generate
1461 * different HTML, but more likely a bug with the load/checker code. Still - not worth worrying
1462 * about why at this stage (especially as we are about to upgrade our Xerces version - could get fixed
1463 * by that?).
1464 *
1465 * -- LGP 2009-05-15
1466 *
1467 * oldRoot->release ();
1468 */
1469 }
1470 Assert (fXMLDoc->getDocumentElement () == n);
1471 Element::Ptr r{WrapXercesNodeInStroikaNode_ (n)};
1472 if (newEltName.fNamespace) {
1473 // declare it: as the default (xmlns=), or for the prefix the element was just given its name with
1474 r.SetAttribute (namespacePrefix == nullopt ? kXMLNS : NameWithNamespace{kXMLNS.fNamespace, *namespacePrefix},
1475 newEltName.fNamespace->As<String> ());
1476 }
1477 return r;
1478 }
1479 virtual void Write (const Streams::OutputStream::Ptr<byte>& to, const SerializationOptions& options) const override
1480 {
1481 TraceContextBumper ctx{"Xerces::DocRep_::Write"};
1482 AssertExternallySynchronizedChecker::ReadContext declareContext{fThisAssertExternallySynchronized_};
1483 AssertNotNull (fXMLDoc);
1484 START_LIB_EXCEPTION_MAPPER_
1485 {
1486 DoWrite2Stream_ (fXMLDoc.get (), to, options);
1487 }
1488 END_LIB_EXCEPTION_MAPPER_
1489 }
1490#if qCompilerAndStdLib_arm_asan_FaultStackUseAfterScope_Buggy
1491 Stroika_Foundation_Debug_ATTRIBUTE_NO_SANITIZE_ADDRESS
1492#endif
1493 virtual void Validate (const Schema::Ptr& schema) const override
1494 {
1495 TraceContextBumper ctx{"Xerces::DocRep_::Validate"};
1496 AssertExternallySynchronizedChecker::ReadContext declareContext{fThisAssertExternallySynchronized_};
1497 RequireNotNull (schema);
1498 Require (schema.GetRep ()->GetProvider () == &Providers::Xerces::kDefaultProvider);
1499 START_LIB_EXCEPTION_MAPPER_
1500 {
1501 try {
1502 DbgTrace ("Validating against schema (target namespace '{}')"_f, schema.GetTargetNamespace ());
1503 // As this CAN be expensive - especially if we need to externalize the file, and re-parse it!!! - just shortcut by
1504 // checking the top-level DOM-node and assure that has the right namespace. At least quickie first check that works when
1505 // reading files (doesn't help in pre-save check, of course)
1506 DOMNode* docNode = fXMLDoc->getDocumentElement ();
1507 if (docNode == nullptr) [[unlikely]] {
1508 Execution::Throw (BadFormatException{"No document", 0, 0, 0});
1509 }
1510 optional<URI> docURI = docNode->getNamespaceURI () == nullptr ? optional<URI>{} : docNode->getNamespaceURI ();
1511 if (docURI != schema.GetTargetNamespace ()) {
1512 Execution::Throw (BadFormatException{
1513 Format ("Wrong document namespace (found '{}' and expected '{}')"_f, docURI, schema.GetTargetNamespace ()), 0, 0, 0});
1514 }
1515
1516 // EXTERNALIZE, AND THEN RE-PARSE USING CACHED SAX PARSER WTIH LOADED GRAMMAR
1517 {
1518 MemBufFormatTarget destination;
1519 {
1520 AutoRelease_<DOMLSOutput> theOutputDesc = GetDOMIMPL_ ().createLSOutput ();
1521 theOutputDesc->setEncoding (XMLUni::fgUTF8EncodingString);
1522 AutoRelease_<DOMLSSerializer> writer = GetDOMIMPL_ ().createLSSerializer ();
1523 theOutputDesc->setByteStream (&destination);
1524 theOutputDesc->setEncoding (XMLUni::fgUTF8EncodingString);
1525 Assert (fXMLDoc->getXmlStandalone ());
1526 writer->write (fXMLDoc.get (), theOutputDesc);
1527 }
1528 MemBufInputSource readReadSrc{destination.getRawBuffer (), destination.getLen (), u"tmp"};
1529 readReadSrc.setEncoding (XMLUni::fgUTF8EncodingString);
1530 shared_ptr<IXercesSchemaRep> accessSchema = dynamic_pointer_cast<IXercesSchemaRep> (schema.GetRep ());
1531 {
1532 AssertNotNull (accessSchema); // Required above: this provider's schema
1533 shared_ptr<SAX2XMLReader> parser = shared_ptr<SAX2XMLReader> (
1534 XMLReaderFactory::createXMLReader (XMLPlatformUtils::fgMemoryManager, accessSchema->GetCachedGrammarPool ()));
1535 SetupCommonParserFeatures_ (*parser, true);
1536 Map2StroikaExceptionsErrorReporter_ myErrorReporter;
1537 parser->setErrorHandler (&myErrorReporter);
1538 parser->parse (readReadSrc);
1539 }
1540 }
1541 }
1542 catch (...) {
1543 if constexpr (qDumpXMLOnValidationError_) {
1544#if !qCompilerAndStdLib_arm_asan_FaultStackUseAfterScope_Buggy
1545 // Generate temp file (each with differnet names), and write out the bad XML.
1546 // Then - re-validate (with line#s) - and print the results of the validation to ANOTHER
1547 // temporary file
1548 //
1549 filesystem::path tmpFileName = IO::FileSystem::AppTmpFileManager::sThe.GetTmpFile ("FAILED_VALIDATION_.xml");
1550 DbgTrace ("Error validating - so writing out temporary file = '{}'"_f, tmpFileName);
1551 Write (IO::FileSystem::FileOutputStream::New (tmpFileName), SerializationOptions{.fPrettyPrint = true, .fIndent = 4});
1552 try {
1553 ValidateFile (tmpFileName, schema);
1554 }
1555 catch (const BadFormatException& vf) {
1556 String tmpFileNameStr = String{tmpFileName};
1557 size_t idx = tmpFileNameStr.find (".xml");
1558 String newTmpFile = tmpFileNameStr.substr (0, idx) + "_MSG.txt";
1559 ofstream msgOut{newTmpFile.AsNarrowSDKString (eIgnoreErrors).c_str ()};
1560 msgOut << "Reason:" << vf.GetDetails () << endl;
1561 optional<unsigned int> lineNum;
1562 optional<unsigned int> colNumber;
1563 optional<uint64_t> fileOffset;
1564 vf.GetPositionInfo (&lineNum, &colNumber, &fileOffset);
1565 if (lineNum) {
1566 msgOut << "Line:" << *lineNum << endl;
1567 }
1568 if (colNumber) {
1569 msgOut << "Col: " << *colNumber << endl;
1570 }
1571 if (fileOffset) {
1572 msgOut << "FilePos: " << *fileOffset << endl;
1573 }
1574 }
1575 catch (...) {
1576 }
1577#endif
1578 }
1579 Execution::ReThrow ();
1580 }
1581 }
1582 END_LIB_EXCEPTION_MAPPER_
1583 }
1584 shared_ptr<const void> fLibrary_; // first, so destroyed last: Xerces lasts while anything of it does
1585 shared_ptr<xercesc::DOMDocument> fXMLDoc;
1587 };
1588}
1589
1590namespace {
1591#if qStroika_Foundation_Debug_AssertionsChecked
1592 // the document a node belongs to - empty if it has none, or none a shared_ptr owns yet (while one is being built)
1593 weak_ptr<Document::IRep> DocumentOf_ (DOMNode* n)
1594 {
1595 RequireNotNull (n);
1596 xercesc::DOMDocument* doc = n->getNodeType () == DOMNode::DOCUMENT_NODE ? dynamic_cast<xercesc::DOMDocument*> (n) : n->getOwnerDocument ();
1597 auto wrapper = doc == nullptr ? nullptr : static_cast<DocRep_*> (doc->getUserData (kXerces2XMLDBDocumentKey_));
1598 return wrapper == nullptr ? weak_ptr<Document::IRep>{} : wrapper->weak_from_this ();
1599 }
1600#endif
1601
1602 Node::Ptr WrapXercesNodeInStroikaNode_ (DOMNode* n)
1603 {
1604 RequireNotNull (n);
1605 if (n->getNodeType () == DOMNode::ELEMENT_NODE) {
1606 return Node::Ptr{MakeSharedPtr<ElementRep_> (n)};
1607 }
1608 else {
1609 return Node::Ptr{MakeSharedPtr<NodeRep_> (n)};
1610 }
1611 }
1612 Element::Ptr WrapXercesNodeInStroikaNode_ (DOMElement* n)
1613 {
1614 RequireNotNull (n);
1615 return Element::Ptr{MakeSharedPtr<ElementRep_> (n)};
1616 }
1617}
1618
1619/*
1620 ********************************************************************************
1621 ********************* Provider::Xerces::xercesString2String ********************
1622 ********************************************************************************
1623 */
1624String Providers::Xerces::xercesString2String (const XMLCh* s, const XMLCh* e)
1625{
1626 if constexpr (same_as<XMLCh, char16_t>) {
1627 return String{span{s, e}};
1628 }
1629 // nb: casts required cuz Xerces doesn't (currently) use wchar_t/char16_t/char32_t but something the sizeof char16_t
1630 // --LGP 2016-07-29
1631 if constexpr (sizeof (XMLCh) == sizeof (char16_t)) {
1632 return String{span{reinterpret_cast<const char16_t*> (s), reinterpret_cast<const char16_t*> (e)}};
1633 }
1634 else if constexpr (sizeof (XMLCh) == sizeof (char32_t)) {
1635 return String{span{reinterpret_cast<const char32_t*> (s), reinterpret_cast<const char32_t*> (e)}};
1636 }
1637 else {
1639 return String{};
1640 }
1641}
1642
1643String Providers::Xerces::xercesString2String (const XMLCh* t)
1644{
1645 if constexpr (same_as<XMLCh, char16_t>) {
1646 return String{t};
1647 }
1648 // nb: casts required cuz Xerces doesn't (currently) use wchar_t/char16_t/char32_t but something the sizeof char16_t
1649 // --LGP 2016-07-29
1650 if constexpr (sizeof (XMLCh) == sizeof (char16_t)) {
1651 return String{reinterpret_cast<const char16_t*> (t)};
1652 }
1653 else if constexpr (sizeof (XMLCh) == sizeof (char32_t)) {
1654 return String{reinterpret_cast<const char32_t*> (t)};
1655 }
1656 else {
1658 return String{};
1659 }
1660}
1661
1662/*
1663 ********************************************************************************
1664 ************************* XML::Providers::LibXML2::Provider ********************
1665 ********************************************************************************
1666 */
1667namespace {
1668 // Xerces itself: the provider, and each schema and document it makes, hold one of these (as a shared_ptr<const void>), so
1669 // XMLPlatformUtils::Terminate runs once the last of them is gone, in whatever order they go
1670 struct Library_ {
1671 Library_ ()
1672 {
1673#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
1674 XMLPlatformUtils::Initialize (XMLUni::fgXercescDefaultLocale, 0, 0, &fMemoryManager_);
1675#else
1676 XMLPlatformUtils::Initialize (XMLUni::fgXercescDefaultLocale, 0, 0);
1677#endif
1678 }
1679 Library_ (const Library_&) = delete;
1680 ~Library_ ()
1681 {
1682 TraceContextBumper ctx{"Xerces::Library_::DTOR"};
1683 XMLPlatformUtils::Terminate ();
1684#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
1685 Assert (SchemaRep_::sLiveCnt == 0); // each holds this, so none can be left
1686 Assert (DocRep_::sLiveCnt == 0); // ""
1687#endif
1688 }
1689#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
1690 MyXercesMemMgr_ fMemoryManager_; // its DTOR, after Terminate, checks for leaks
1691#endif
1692 };
1693}
1694
1695Providers::Xerces::Provider::Provider ()
1696 : fLibrary_{MakeSharedPtr<Library_> ()}
1697{
1698 TraceContextBumper ctx{"Xerces::Provider::CTOR"};
1699#if qStroika_Foundation_Debug_AssertionsChecked
1700 static unsigned int sNProvidersCreated_{0}; // don't create multiple of these - will lead to confusion
1701 Assert (++sNProvidersCreated_ == 1);
1702#endif
1703}
1704
1705shared_ptr<Schema::IRep> Providers::Xerces::Provider::SchemaFactory (const InputStream::Ptr<byte>& schemaData, const Resource::ResolverPtr& resolver) const
1706{
1707 return MakeSharedPtr<SchemaRep_> (fLibrary_, schemaData, resolver);
1708}
1709
1710shared_ptr<DOM::Document::IRep> Providers::Xerces::Provider::DocumentFactory (const Streams::InputStream::Ptr<byte>& in,
1711 const Schema::Ptr& schemaToValidateAgainstWhileReading) const
1712{
1713 return MakeSharedPtr<DocRep_> (fLibrary_, in, schemaToValidateAgainstWhileReading);
1714}
1715
1716void Providers::Xerces::Provider::SAXParse (const Streams::InputStream::Ptr<byte>& in, StructuredStreamEvents::IConsumer* callback,
1717 const Schema::Ptr& schema) const
1718{
1719 // a schema another provider compiled would not validate at all here, so it is refused, as libxml2 refuses one
1720 // (XML::SAXParse converts one first)
1721 Require (schema == nullptr or schema.GetRep ()->GetProvider () == &Providers::Xerces::kDefaultProvider);
1722 SAX2PrintHandlers_ handler{callback};
1723 shared_ptr<IXercesSchemaRep> accessSchema;
1724 if (schema != nullptr) {
1725 accessSchema = dynamic_pointer_cast<IXercesSchemaRep> (schema.GetRep ());
1726 }
1727 shared_ptr<SAX2XMLReader> parser{XMLReaderFactory::createXMLReader (
1728 XMLPlatformUtils::fgMemoryManager, accessSchema == nullptr ? nullptr : accessSchema->GetCachedGrammarPool ())};
1729 SetupCommonParserFeatures_ (*parser, accessSchema != nullptr);
1730 parser->setContentHandler (&handler);
1731 Map2StroikaExceptionsErrorReporter_ mErrorReproter_;
1732 parser->setErrorHandler (&mErrorReproter_);
1733 parser->parse (StdIStream_InputSource_{in});
1734}
#define AssertNotNull(p)
Definition Assertions.h:336
#define EnsureNotNull(p)
Definition Assertions.h:343
#define AssertNotImplemented()
Definition Assertions.h:404
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define RequireNotNull(p)
Definition Assertions.h:350
#define AssertNotReached()
Definition Assertions.h:358
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
conditional_t< qStroika_Foundation_Memory_PreferBlockAllocation and andTrueCheck, BlockAllocationUseHelper< T >, Common::Empty > UseBlockAllocationIfAppropriate
Use this to enable block allocation for a particular class. Beware of subclassing.
bool Equals(const T *lhs, const T *rhs)
strcmp or wsccmp() as appropriate == 0
#define CompileTimeFlagChecker_SOURCE(NS_PREFIX, NAME, VALUE)
#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE
[[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this on things where its not b...
Definition StdCompat.h:481
#define DbgTrace
Definition Trace.h:327
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual string AsNarrowSDKString() const
Definition String.inl:832
nonvirtual String substr(size_t from, size_t count=npos) const
Definition String.inl:1090
nonvirtual size_t find(Character c, size_t startAt=0) const
Definition String.inl:1065
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:186
A generalization of a vector: a container whose elements are keyed by the natural numbers.
virtual void StartElement(const Name &name, const Mapping< Name, String > &attributes)
Definition IConsumer.cpp:24
nonvirtual void SetAttribute(const NameWithNamespace &attrName, const optional< String > &v)
Definition DOM.inl:148
nonvirtual shared_ptr< IRep > GetRep() const
return the associated shared_ptr (cannot be nullptr)
Node::Ptr is a smart pointer to a Node::IRep.
Definition DOM.h:243
nonvirtual shared_ptr< IRep > GetRep() const
return the associated shared_ptr (cannot be nullptr)
Definition DOM.inl:99
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
shared_lock< const AssertExternallySynchronizedChecker > ReadContext
Instantiate AssertExternallySynchronizedChecker::ReadContext to designate an area of code where prote...
nonvirtual filesystem::path GetTmpFile(const String &fileBaseName)
nonvirtual bool empty() const
Definition BLOB.inl:251
nonvirtual const byte * begin() const
Definition BLOB.inl:258
nonvirtual size_t GetSize() const
Definition BLOB.inl:269
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual SeekOffsetType GetOffset() const
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.
nonvirtual void Write(span< ELEMENT_TYPE2, EXTENT_2 > elts) const
nonvirtual void Flush() const
forces any data contained in this stream to be written.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:234
void ThrowIfNull(const Private_::ConstVoidStar &p, const HRESULT &hr)
Template specialization for ThrowIfNull (), for thing being thrown HRESULT - really throw HRESULTErro...
An element's or attribute's name (local-name, not including a prefix), plus the namespace that name i...
Definition Namespace.h:63
String fName
The LOCAL name - "Why" for <x:Why>
Definition Namespace.h:67
optional< URI > fNamespace
The namespace fName is in, however the document said so - nullopt for a name in no namespace.
Definition Namespace.h:72