Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
LibXML2.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <list>
7
8#include <libxml/xmlmemory.h>
9#include <libxml/xmlsave.h>
10
13#include "Stroika/Foundation/DataExchange/BadFormatException.h"
15#include "Stroika/Foundation/Execution/Throw.h"
17#include "Stroika/Foundation/Memory/Common.h"
20
21#include "LibXML2.h"
22
23using namespace Stroika::Foundation;
26using namespace Stroika::Foundation::DataExchange::XML;
27using namespace Stroika::Foundation::DataExchange::XML::DOM;
28using namespace Stroika::Foundation::DataExchange::XML::Schema;
29using namespace Stroika::Foundation::DataExchange::XML::Providers::LibXML2;
30using namespace Stroika::Foundation::Debug;
31using namespace Stroika::Foundation::Execution;
32using namespace Stroika::Foundation::Streams;
33
34using std::byte;
35
36// Comment this in to turn on aggressive noisy DbgTrace in this module
37//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
38
39static_assert (qStroika_HasComponent_libxml2, "Don't compile this file if qStroika_HasComponent_libxml2 not set");
40
41CompileTimeFlagChecker_SOURCE (Stroika::Foundation::DataExchange::XML, qStroika_HasComponent_libxml2, qStroika_HasComponent_libxml2);
42
43namespace {
44 // From https://www.w3.org/TR/xml-names/
45 // In a namespace declaration, the URI reference is the normalized value of the attribute, so replacement of XML
46 // character and entity references has already been done before any comparison.
47 //
48 // Not 100% sure, but I think that means decode %x stuff too (at least that fixes bug I'm encountering with ASTM-CCR files)
49 // --LGP 2024-01-31
50 constexpr auto kUseURIEncodingFlag_ = URI::StringPCTEncodedFlag::eDecoded;
51}
52
53namespace {
54 // I never found docs on how to do schema resolver. Closest I could find was:
55 // http://www.xmlsoft.org/examples/io1.c
56 // But this has several serious defects - like VERY minimal reentrancy support. But hopefully I can do enough magic with thread_local to worakround
57 // the lack --LGP 2024-03-03
58 //
59 struct RegisterResolver_ {
60 static inline thread_local RegisterResolver_* sCurrent_ = nullptr; // magic to workaround lack of 'context' / reentrancy support here in libxml2
61 const Resource::ResolverPtr fResolver_;
62
63 RegisterResolver_ (const Resource::ResolverPtr& resolver)
64 : fResolver_{resolver}
65 {
66 sCurrent_ = this;
67 if (resolver != nullptr) {
68 // @todo ALSO need GLOBAL flag - if this ever gets called, 2x at same time, from threads, xmlRegisterInputCallbacks not safe (though maybe OK if we are only ones ever using)?
69 if (xmlRegisterInputCallbacks (ResolverMatch_, ResolverOpen_, ResolverRead_, ResolverClose_) < 0) {
71 }
72 }
73 }
74
75 ~RegisterResolver_ ()
76 {
77 sCurrent_ = nullptr;
78 // don't bother unregistering (xmlCleanupInputCallbacks ) cuz more likely to cause re-entrancy problems than solve them...
79 }
80
81 /*
82 * @URI: an URI to test
83 * Returns 1 if yes and 0 if another Input module should be used
84 */
85 static int ResolverMatch_ (const char* URI)
86 {
87 if (sCurrent_) {
88 // No idea if that URI argument should be systemID, publicID, or namespace???
89 optional<Resource::Definition> r = sCurrent_->fResolver_.Lookup (Resource::Name{
90 .fNamespace = String::FromUTF8 (URI), .fPublicID = String::FromUTF8 (URI), .fSystemID = String::FromUTF8 (URI)});
91 if (not r) {
92 DbgTrace ("Note ResolveMatch {} failed to find an entry in resolver."_f, String::FromUTF8 (URI));
93 }
94 return r.has_value ();
95 }
96 return 0;
97 }
98
99 /**
100 * @URI: an URI to test
101 *
102 * Returns an Input context or NULL in case or error
103 */
104 static void* ResolverOpen_ (const char* URI)
105 {
106 if (sCurrent_) {
107 optional<Resource::Definition> r = sCurrent_->fResolver_.Lookup (Resource::Name{
108 .fNamespace = String::FromUTF8 (URI), .fPublicID = String::FromUTF8 (URI), .fSystemID = String::FromUTF8 (URI)});
109 // I think we must allocate saved/returned object on the heap, and return ptr to it, and hope CLOSE function gets called
110 // I THINK that's the 'context' passed to read... That is because the API used by libxml2 appears to just be a 'plain C pointer' we need to provide
111 if (r.has_value ()) {
112 return new InputStream::Ptr<byte> (r->fData.As<InputStream::Ptr<byte>> ());
113 }
114 }
115 return nullptr;
116 }
117
118 /**
119 * @context: the read context
120 *
121 * Close the sql: query handler
122 *
123 * Returns 0 or -1 in case of error
124 */
125 static int ResolverClose_ (void* context)
126 {
127 if (context == nullptr)
128 return -1;
129 AssertNotNull (sCurrent_); // my read of API is that this cannot happen (cuz opens will fail first and must have live resolver object)
130 delete reinterpret_cast<InputStream::Ptr<byte>*> (context);
131 return 0;
132 }
133
134 /**
135 * @context: the read context
136 * @buffer: where to store data
137 * @len: number of bytes to read
138 *
139 * Returns the number of bytes read or -1 in case of error
140 */
141 static int ResolverRead_ (void* context, char* buffer, int len)
142 {
143 AssertNotNull (sCurrent_);
144 auto inStream = reinterpret_cast<InputStream::Ptr<byte>*> (context);
145 span<byte> r = inStream->ReadBlocking (as_writable_bytes (span{buffer, static_cast<size_t> (len)}));
146 return static_cast<int> (r.size ());
147 }
148 };
149
150}
151
152namespace {
153 struct SchemaRep_ : ILibXML2SchemaRep {
154#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
155 static inline atomic<unsigned int> sLiveCnt{0};
156#endif
157 SchemaRep_ (const Streams::InputStream::Ptr<byte>& schemaData, const Resource::ResolverPtr& resolver)
158 : fResolver_{resolver}
159 , fSchemaData{schemaData.ReadAll ()}
160 {
161 RegisterResolver_ registerResolver{resolver};
162 xmlSchemaParserCtxt* schemaParseContext =
163 xmlSchemaNewMemParserCtxt (reinterpret_cast<const char*> (fSchemaData.data ()), static_cast<int> (fSchemaData.size ()));
164 fCompiledSchema = xmlSchemaParse (schemaParseContext);
165 xmlSchemaFreeParserCtxt (schemaParseContext);
166 Execution::ThrowIfNull (fCompiledSchema);
167#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
168 ++sLiveCnt;
169#endif
170 }
171 SchemaRep_ (const SchemaRep_&) = delete;
172 virtual ~SchemaRep_ ()
173 {
174 xmlSchemaFree (fCompiledSchema);
175#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
176 Assert (sLiveCnt > 0);
177 --sLiveCnt;
178#endif
179 }
180 Resource::ResolverPtr fResolver_{nullptr};
181 Memory::BLOB fSchemaData;
182 xmlSchema* fCompiledSchema{nullptr};
183
184 virtual const Providers::ISchemaProvider* GetProvider () const override
185 {
186 return &XML::Providers::LibXML2::kDefaultProvider;
187 }
188 virtual optional<URI> GetTargetNamespace () const override
189 {
190 Assert (fCompiledSchema != nullptr);
191 if (fCompiledSchema->targetNamespace != nullptr) {
192 return URI{libXMLString2String (fCompiledSchema->targetNamespace)};
193 }
194 return nullopt;
195 }
196 virtual Memory::BLOB GetData () override
197 {
198 return fSchemaData;
199 }
200 // not super useful, except if you want to clone
201 virtual Resource::ResolverPtr GetResolver () override
202 {
203 return fResolver_;
204 }
205 virtual xmlSchema* GetSchemaLibRep () override
206 {
207 AssertNotNull (fCompiledSchema);
208 return fCompiledSchema;
209 }
210 };
211}
212
213namespace {
214 struct SAXReader_ {
215 using Name = StructuredStreamEvents::Name;
216 xmlSAXHandler flibXMLSaxHndler_{};
218 SAXReader_ (StructuredStreamEvents::IConsumer& callback)
219 : fCallback_{callback}
220 {
221 flibXMLSaxHndler_.initialized = XML_SAX2_MAGIC;
222 flibXMLSaxHndler_.startDocument = [] (void* ctx) {
223 SAXReader_* thisReader = reinterpret_cast<SAXReader_*> (ctx);
224 Assert (thisReader->flibXMLSaxHndler_.initialized == XML_SAX2_MAGIC); // assure ctx ptr passed through properly
225 thisReader->fCallback_.StartDocument ();
226 };
227 flibXMLSaxHndler_.endDocument = [] (void* ctx) {
228 SAXReader_* thisReader = reinterpret_cast<SAXReader_*> (ctx);
229 Assert (thisReader->flibXMLSaxHndler_.initialized == XML_SAX2_MAGIC); // assure ctx ptr passed through properly
230 thisReader->fCallback_.EndDocument ();
231 };
232 flibXMLSaxHndler_.startElementNs = [] (void* ctx, const xmlChar* localname, [[maybe_unused]] const xmlChar* prefix, const xmlChar* URI,
233 [[maybe_unused]] int nb_namespaces, [[maybe_unused]] const xmlChar** namespaces,
234 int nb_attributes, [[maybe_unused]] int nb_defaulted, const xmlChar** attributes) {
235 SAXReader_* thisReader = reinterpret_cast<SAXReader_*> (ctx);
236 Assert (thisReader->flibXMLSaxHndler_.initialized == XML_SAX2_MAGIC); // assure ctx ptr passed through properly
238 if (attributes != nullptr) {
239 // Crazy way to decode attribute arguments - I would have never guessed --
240 // https://stackoverflow.com/questions/2075894/how-to-get-the-name-and-value-of-attributes-from-xml-when-using-libxml2-sax-pars
241 auto ai = attributes;
242 for (int i = 0; i < nb_attributes; ++i) {
243 // @todo fix must grab namespace, probably from namespaces, but will take some research to figure out how!!!
244 attrs.Add (Name{"", libXMLString2String (ai[0]), Name::eAttribute},
245 libXMLString2String (ai[3], static_cast<int> (ai[4] - ai[3])));
246 ai += 5;
247 }
248 }
249 if (URI == nullptr) {
250 thisReader->fCallback_.StartElement (Name{libXMLString2String (localname)}, attrs);
251 }
252 else {
253 thisReader->fCallback_.StartElement (Name{libXMLString2String (URI), libXMLString2String (localname)}, attrs);
254 }
255 };
256 flibXMLSaxHndler_.endElementNs = [] (void* ctx, const xmlChar* localname, [[maybe_unused]] const xmlChar* prefix, const xmlChar* URI) {
257 SAXReader_* thisReader = reinterpret_cast<SAXReader_*> (ctx);
258 if (URI == nullptr) {
259 thisReader->fCallback_.EndElement (Name{libXMLString2String (localname)});
260 }
261 else {
262 thisReader->fCallback_.EndElement (Name{libXMLString2String (URI), libXMLString2String (localname)});
263 }
264 };
265 ;
266 flibXMLSaxHndler_.characters = [] (void* ctx, const xmlChar* ch, int len) {
267 SAXReader_* thisReader = reinterpret_cast<SAXReader_*> (ctx);
268 // unclear how this handles 'continuations' and partial characters - may need a more sophisticated approach
269 thisReader->fCallback_.TextInsideElement (String::FromUTF8 (span{reinterpret_cast<const char*> (ch), static_cast<size_t> (len)}));
270 };
271 }
272 SAXReader_ (const SAXReader_&) = delete;
273 };
274}
275
276namespace {
277#if qStroika_Foundation_Debug_AssertionsChecked
278 bool ValidNewNodeName_ (const String& n)
279 {
280 if (n.empty ()) {
281 return false;
282 }
283 if (n.find (':') != wstring::npos) { // if triggered, you probably used XPath as arg for CreateElement call!!!
284 return false;
285 }
286 return true;
287 }
288#endif
289}
290
291namespace {
292 struct DocRep_;
293 DocRep_* GetWrapperDoc_ (xmlDoc* d);
294 DocRep_* GetWrapperDoc_ (xmlNode* n)
295 {
296 RequireNotNull (n);
297 RequireNotNull (n->doc);
298 return GetWrapperDoc_ (n->doc);
299 }
300 xmlNs* GetSharedReUsableXMLNSParentNamespace_ (xmlDoc* d);
301 xmlNs* GetSharedReUsableXMLNSParentNamespace_ (xmlNode* n)
302 {
303 RequireNotNull (n);
304 RequireNotNull (n->doc);
305 return GetSharedReUsableXMLNSParentNamespace_ (n->doc);
306 }
307}
308
309namespace {
310 Node::Ptr WrapLibXML2NodeInStroikaNode_ (xmlNode* n);
311}
312
313namespace {
314 struct NodeRep_ : ILibXML2NodeRep, Memory::UseBlockAllocationIfAppropriate<NodeRep_> {
315 public:
316 NodeRep_ (xmlNode* n)
317 : fNode_{n}
318 {
319 RequireNotNull (n);
320 }
321 virtual const Providers::IDOMProvider* GetProvider () const override
322 {
323 return &Providers::LibXML2::kDefaultProvider;
324 }
325 virtual bool Equals (const IRep* rhs) const override
326 {
327 RequireNotNull (fNode_);
328 RequireNotNull (rhs);
329 return fNode_ == dynamic_cast<const NodeRep_*> (rhs)->fNode_;
330 }
331 virtual Node::Type GetNodeType () const override
332 {
333 AssertNotNull (fNode_);
334 switch (fNode_->type) {
335 case XML_ELEMENT_NODE:
336 return Node::eElementNT;
337 case XML_ATTRIBUTE_NODE:
338 return Node::eAttributeNT;
339 case XML_TEXT_NODE:
340 return Node::eTextNT;
341 case XML_COMMENT_NODE:
342 return Node::eCommentNT;
343 default:
344 return Node::eOtherNT;
345 }
346 }
347 virtual NameWithNamespace GetName () const override
348 {
349 AssertNotNull (fNode_);
350 Require (GetNodeType () == Node::eElementNT or GetNodeType () == Node::eAttributeNT);
351 switch (fNode_->type) {
352 case XML_ATTRIBUTE_NODE:
353 case XML_ELEMENT_NODE: {
354 const xmlChar* ns = fNode_->ns == nullptr ? nullptr : fNode_->ns->href;
355 return NameWithNamespace{ns == nullptr ? optional<URI>{} : URI{libXMLString2String (ns)}, libXMLString2String (fNode_->name)};
356 }
357 default:
359 return NameWithNamespace{""};
360 }
361 }
362 virtual void SetName (const NameWithNamespace& name) override
363 {
364 AssertNotNull (fNode_);
365#if qStroika_Foundation_Debug_AssertionsChecked
366 Require (ValidNewNodeName_ (name.fName));
367#endif
368 if (name.fNamespace) {
369 AssertNotReached (); // pretty sure this is wrong...
370 // see genNS2Use_
371 // see SetAttribtues - simple now...
372 }
373 xmlNodeSetName (fNode_, BAD_CAST name.fName.AsUTF8 ().c_str ());
374 }
375 virtual String GetValue () const override
376 {
377 AssertNotNull (fNode_);
378 auto r = xmlNodeGetContent (fNode_);
379 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { xmlFree (r); });
380 return libXMLString2String (r);
381 }
382 virtual void SetValue (const String& v) override
383 {
384 AssertNotNull (fNode_);
385 // @todo could optimize and avoid xmlEncodeSpecialChars for most cases, by scanning for &<> etc... Maybe imporve logic in WriterUtils.h! - so can use that
386 bool mustEncode = true;
387 if (mustEncode) {
388 xmlChar* p = xmlEncodeSpecialChars (fNode_->doc, BAD_CAST v.AsUTF8 ().c_str ());
389 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { xmlFree (p); });
390 xmlNodeSetContent (fNode_, p);
391 }
392 else {
393 xmlNodeSetContent (fNode_, BAD_CAST v.AsUTF8 ().c_str ());
394 }
395 }
396 virtual void DeleteNode () override
397 {
398 RequireNotNull (fNode_);
399 xmlUnlinkNode (fNode_);
400 xmlFreeNode (fNode_);
401 fNode_ = nullptr;
402 }
403 virtual Node::Ptr GetParentNode () const override
404 {
405 RequireNotNull (fNode_);
406 if (fNode_->parent == nullptr) {
407 return Node::Ptr{nullptr};
408 }
409 return WrapLibXML2NodeInStroikaNode_ (fNode_->parent);
410 }
411 virtual void Write (const Streams::OutputStream::Ptr<byte>& to, const SerializationOptions& options) const override
412 {
413 xmlBufferPtr xmlBuf = xmlBufferCreate ();
414 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { xmlBufferFree (xmlBuf); });
415 if (int dumpRes = xmlNodeDump (xmlBuf, fNode_->doc, fNode_, 0, options.fPrettyPrint); dumpRes == -1) {
416 Execution::Throw (Execution::RuntimeErrorException{"failed dumping node to text"});
417 }
418 const xmlChar* t = xmlBufferContent (xmlBuf);
419 AssertNotNull (t);
420 to.Write (span{reinterpret_cast<const byte*> (t), static_cast<size_t> (::strlen (reinterpret_cast<const char*> (t)))});
421 }
422 virtual xmlNode* GetInternalTRep () override
423 {
424 return fNode_;
425 }
426 static xmlNsPtr genNS2Use_ (xmlNode* n, const URI& ns)
427 {
428 xmlNsPtr ns2Use = xmlSearchNsByHref (n->doc, n, BAD_CAST ns.As<String> (kUseURIEncodingFlag_).AsUTF8 ().c_str ());
429 string prefix2Try{"a"};
430 while (ns2Use == nullptr) {
431 // Need to hang the namespace declaration someplace? Could do it just on this element (xmlNewNs)
432 // Or on the root doc (xmlNewGlobalNs).
433 // For now - do on DOC, so we end up with a more terse overall document.
434 // Also - sadly - must cons up SOME prefix, which doesn't conflict. No good way I can see todo that, so do a bad way.
435 // OK - can do still manually using docs root elt - maybe - but do this way for now... cuz xmlNewGlobalNs deprecated
436 ns2Use = xmlNewNs (n, BAD_CAST ns.As<String> (kUseURIEncodingFlag_).AsUTF8 ().c_str (), BAD_CAST prefix2Try.c_str ());
437 if (ns2Use == nullptr) {
438 ++prefix2Try[0]; // if 'a' didn't work, try 'b' // @todo this could use better error handling, but pragmatically probably OK
439 }
440 }
441 return ns2Use;
442 }
443 // must carefully think out mem management here - cuz not ref counted - around as long as owning doc...
444 xmlNode* fNode_;
445 };
446}
447
448namespace {
449 DISABLE_COMPILER_MSC_WARNING_START (4250) // inherits via dominance warning
450 struct ElementRep_ : Element::IRep, Memory::InheritAndUseBlockAllocationIfAppropriate<ElementRep_, NodeRep_> {
452 ElementRep_ (xmlNode* n)
453 : inherited{n}
454 {
455 RequireNotNull (n);
456 Require (n->type == XML_ELEMENT_NODE);
457 }
458 virtual Node::Type GetNodeType () const override
459 {
460 AssertNotNull (fNode_);
461 Assert (fNode_->type == XML_ELEMENT_NODE);
462 return Node::eElementNT;
463 }
464 virtual optional<String> GetAttribute (const NameWithNamespace& attrName) const override
465 {
466 auto r = attrName.fNamespace ? xmlGetNsProp (fNode_, BAD_CAST attrName.fName.AsUTF8 ().c_str (),
467 BAD_CAST attrName.fNamespace->As<String> (kUseURIEncodingFlag_).AsUTF8 ().c_str ())
468 : xmlGetProp (fNode_, BAD_CAST attrName.fName.AsUTF8 ().c_str ());
469 if (r == nullptr) {
470 return nullopt;
471 }
472 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { xmlFree (r); });
473 return libXMLString2String (r);
474 }
475 virtual void SetAttribute (const NameWithNamespace& attrName, const optional<String>& v) override
476 {
477 RequireNotNull (fNode_);
478 Require (GetNodeType () == Node::eElementNT);
479 if (attrName == kXMLNS) {
480 /*
481 * Queer, but libxml2 appears to require this attribute have the given namespace (fine) - but not provide a usable xmlNs object.
482 * So we must create it on the document, and free it when we free the document.
483 *
484 * \see http://stroika-bugs.sophists.com/browse/STK-1001
485 */
486 xmlSetNsProp (fNode_, GetSharedReUsableXMLNSParentNamespace_ (fNode_), BAD_CAST attrName.fName.AsUTF8 ().c_str (),
487 v == nullopt ? nullptr : (BAD_CAST v->AsUTF8 ().c_str ()));
488 }
489 else if (attrName.fNamespace) {
490 // Lookup the argument ns and either add it to this node or use the existing one
491 xmlSetNsProp (fNode_, genNS2Use_ (fNode_, *attrName.fNamespace), BAD_CAST attrName.fName.AsUTF8 ().c_str (),
492 v == nullopt ? nullptr : (BAD_CAST v->AsUTF8 ().c_str ()));
493 }
494 else {
495 xmlSetProp (fNode_, BAD_CAST attrName.fName.AsUTF8 ().c_str (), v == nullopt ? nullptr : (BAD_CAST v->AsUTF8 ().c_str ()));
496 }
497 }
498 virtual Element::Ptr InsertElement (const NameWithNamespace& eltName, const Element::Ptr& afterNode) override
499 {
500#if qStroika_Foundation_Debug_AssertionsChecked
501 Require (ValidNewNodeName_ (eltName.fName));
502#endif
503 Require (afterNode == nullptr or this->GetChildren ().Contains (afterNode));
504 // when adding a child, if no NS specified, copy parents
505 xmlNs* useNS = eltName.fNamespace ? genNS2Use_ (fNode_, *eltName.fNamespace) : fNode_->ns;
506 xmlNode* newNode = xmlNewNode (useNS, BAD_CAST eltName.fName.AsUTF8 ().c_str ());
507 NodeRep_* afterNodeRep = afterNode == nullptr ? nullptr : dynamic_cast<NodeRep_*> (afterNode.GetRep ().get ());
508 if (afterNodeRep == nullptr) {
509 // unfortunately complicated - no prepend api (just append). Can say xmlAddPrevSibling for first child though which amounts
510 // to same thing (unless there is no first child)
511 if (fNode_->children == nullptr) {
512 xmlAddChild (fNode_->parent, newNode); // append=prepend
513 }
514 else {
515 xmlAddPrevSibling (fNode_->children, newNode);
516 }
517 }
518 else {
519 xmlAddNextSibling (afterNodeRep->fNode_, newNode);
520 }
521 return WrapLibXML2NodeInStroikaNode_ (newNode);
522 }
523 virtual Element::Ptr AppendElement (const NameWithNamespace& eltName) override
524 {
525#if qStroika_Foundation_Debug_AssertionsChecked
526 Require (ValidNewNodeName_ (eltName.fName));
527#endif
528 // when adding a child, if no NS specified, copy parents
529 xmlNs* useNS = eltName.fNamespace ? genNS2Use_ (fNode_, *eltName.fNamespace) : fNode_->ns;
530 xmlNode* newNode = xmlNewNode (useNS, BAD_CAST eltName.fName.AsUTF8 ().c_str ());
531 xmlAddChild (fNode_, newNode);
532 return WrapLibXML2NodeInStroikaNode_ (newNode);
533 }
534 virtual Iterable<Node::Ptr> GetChildren () const override
535 {
536 RequireNotNull (fNode_);
537 // No reference counting possible here because these notes - i THINK - are owned by document, and the linked list not
538 // reference counted (elts) - so just count on no changes during iteration
539 return Traversal::CreateGenerator<Node::Ptr> ([curChild = fNode_->children] () mutable -> optional<Node::Ptr> {
540 if (curChild == nullptr) {
541 return optional<Node::Ptr>{};
542 }
543 Node::Ptr r = WrapLibXML2NodeInStroikaNode_ (curChild);
544 curChild = curChild->next;
545 return r;
546 });
547 }
548 struct XPathLookupHelper_ {
549 xmlXPathContext* fCtx{nullptr};
550 xmlXPathObject* fResultNodeList{nullptr};
551 XPathLookupHelper_ () = delete;
552 XPathLookupHelper_ (const XPathLookupHelper_&) = delete;
553 XPathLookupHelper_ (xmlDoc* doc, xmlNode* contextNode, const XPath::Expression& e)
554 {
555 // based on code from http://www.xmlsoft.org/examples/xpath1.c
556 RequireNotNull (doc);
557 fCtx = xmlXPathNewContext (doc);
559 fCtx->error = [] ([[maybe_unused]] void* userData, [[maybe_unused]] const xmlError* error) {
560 // default function prints to console; we capture 'lastError' later so no need to do anything here.
561 };
562 try {
563 auto namespaceDefs = e.GetOptions ().fNamespaces;
564 if (namespaceDefs.GetDefaultNamespace ()) {
565 // According to https://gitlab.gnome.org/GNOME/libxml2/-/issues/585, this is XPath 2 feature NYI in libxml2
566 Execution::Throw (XPath::XPathExpressionNotSupported::kThe);
567 }
568 for (Common::KeyValuePair ni : namespaceDefs.GetPrefixedNamespaces ()) {
569 xmlXPathRegisterNs (fCtx, BAD_CAST ni.fKey.AsUTF8 ().c_str (),
570 BAD_CAST ni.fValue.As<String> (kUseURIEncodingFlag_).AsUTF8 ().c_str ());
571 }
572 fCtx->node = contextNode;
573 fResultNodeList = xmlXPathEvalExpression (BAD_CAST e.GetExpression ().AsUTF8 ().c_str (), fCtx);
574 if (fCtx->lastError.level != XML_ERR_NONE and fCtx->lastError.level != XML_ERR_WARNING) {
575 // lookup domain in xmlErrorDomain, and lastError.code in xmlParserErrors
576 Execution::ThrowIfNull (fResultNodeList, Execution::RuntimeErrorException{"Error parsing xpath {}: (domain {}, code {})"_f(
577 e, fCtx->lastError.domain, fCtx->lastError.code)});
578 }
579 Execution::ThrowIfNull (fResultNodeList);
580 }
581 catch (...) {
582 if (fResultNodeList != nullptr) {
583 xmlXPathFreeObject (fResultNodeList);
584 fResultNodeList = nullptr;
585 }
586 if (fCtx != nullptr) {
587 xmlXPathFreeContext (fCtx);
588 fCtx = nullptr;
589 }
591 }
592 }
593 ~XPathLookupHelper_ ()
594 {
595 AssertNotNull (fResultNodeList);
596 xmlXPathFreeObject (fResultNodeList);
597 AssertNotNull (fCtx);
598 xmlXPathFreeContext (fCtx);
599 }
600 static optional<XPath::Result> ToResult (xmlNode* n)
601 {
602 if (n == nullptr) [[unlikely]] {
603 return nullopt;
604 }
605 // for now only support elements/attributes...
606 switch (n->type) {
607 case XML_ATTRIBUTE_NODE:
608 case XML_ELEMENT_NODE: {
609 return WrapLibXML2NodeInStroikaNode_ (n);
610 }
611 }
612 return nullopt;
613 }
614 };
615 virtual optional<XPath::Result> LookupOne (const XPath::Expression& e) override
616 {
617 RequireNotNull (fNode_);
618 AssertNotNull (fNode_->doc);
619 XPathLookupHelper_ helper{fNode_->doc, fNode_, e};
620 xmlNodeSet* resultSet = helper.fResultNodeList->nodesetval;
621 size_t size = (resultSet) ? resultSet->nodeNr : 0;
622 if (size > 0) {
623 return XPathLookupHelper_::ToResult (resultSet->nodeTab[0]);
624 }
625 return nullopt;
626 }
627 virtual Traversal::Iterable<XPath::Result> Lookup (const XPath::Expression& e) override
628 {
629 // Could use generator, but little point since libxml2 has already done all the work inside
630 // xmlXPathEvalExpression, and we'd just save the wrapping in Stroika Node::Rep object/shared_ptr
631 // For now, KISS
632 // So essentially treat as if e.GetOptions().fSnapshot == true
633 // @todo - see if anything needed to support fOrdering???
634 RequireNotNull (fNode_);
635 AssertNotNull (fNode_->doc);
636 XPathLookupHelper_ helper{fNode_->doc, fNode_, e};
637 xmlNodeSet* resultSet = helper.fResultNodeList->nodesetval;
638 size_t size = (resultSet) ? resultSet->nodeNr : 0;
640 for (size_t i = 0; i < size; ++i) {
641 r += Memory::ValueOf (XPathLookupHelper_::ToResult (resultSet->nodeTab[i]));
642 }
643 return r;
644 }
645 };
646 DISABLE_COMPILER_MSC_WARNING_END (4250) // inherits via dominance warning
647}
648
649namespace {
650 Node::Ptr WrapLibXML2NodeInStroikaNode_ (xmlNode* n)
651 {
652 RequireNotNull (n);
653 if (n->type == XML_ELEMENT_NODE) [[likely]] {
654 return Node::Ptr{Memory::MakeSharedPtr<ElementRep_> (n)};
655 }
656 else {
657 return Node::Ptr{Memory::MakeSharedPtr<NodeRep_> (n)};
658 }
659 }
660}
661
662namespace {
663 struct MyLibXML2StructuredErrGrabber_ {
664 xmlParserCtxtPtr fCtx;
665 shared_ptr<Execution::RuntimeErrorException<>> fCapturedException;
666
667 MyLibXML2StructuredErrGrabber_ (xmlParserCtxtPtr ctx)
668 : fCtx{ctx}
669 {
670 xmlCtxtSetErrorHandler (ctx, xmlStructuredErrorFunc_, this);
671 }
672 ~MyLibXML2StructuredErrGrabber_ ()
673 {
674 xmlCtxtSetErrorHandler (fCtx, nullptr, nullptr);
675 }
676 MyLibXML2StructuredErrGrabber_& operator= (const MyLibXML2StructuredErrGrabber_&) = delete;
677
678 void ThrowIf ()
679 {
680 if (fCapturedException != nullptr) {
681 Execution::Throw (*fCapturedException);
682 }
683 }
684
685 private:
686 static void xmlStructuredErrorFunc_ (void* userData, const xmlError* error)
687 {
688 // nice to throw but this is 'C' land, so probably not safe
689 MyLibXML2StructuredErrGrabber_* useThis = reinterpret_cast<MyLibXML2StructuredErrGrabber_*> (userData);
690 // save first error
691 if (useThis->fCapturedException == nullptr) {
692 switch (error->level) {
693 case XML_ERR_NONE:
695 break;
696 case XML_ERR_WARNING:
697 DbgTrace ("libxml2 (xmlStructuredErrorFunc_): Ignore warnings for now: {}"_f, String::FromUTF8 (error->message));
698 break;
699 case XML_ERR_ERROR:
700 case XML_ERR_FATAL:
701 DbgTrace ("libxml2 (xmlStructuredErrorFunc_): Capturing Error {}"_f, String::FromUTF8 (error->message));
702 useThis->fCapturedException = make_shared<DataExchange::BadFormatException> (
703 "Failure Parsing XML: {}, line {}"_f(String::FromUTF8 (error->message), error->line),
704 static_cast<unsigned int> (error->line), nullopt, nullopt);
705 break;
706 }
707 }
708 };
709 };
710}
711
712namespace {
713 struct DocRep_ : ILibXML2DocRep {
714#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
715 static inline atomic<unsigned int> sLiveCnt{0};
716#endif
717 public:
718 DocRep_ (const Streams::InputStream::Ptr<byte>& in)
719 {
720 if (in == nullptr) {
721 fLibRep_ = xmlNewDoc (BAD_CAST "1.0");
722 fLibRep_->standalone = true;
723 }
724 else {
725 xmlParserCtxtPtr ctxt = xmlCreatePushParserCtxt (nullptr, nullptr, nullptr, 0, "in-stream.xml" /*filename*/);
727 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { xmlFreeParserCtxt (ctxt); });
728 MyLibXML2StructuredErrGrabber_ errCatcher{ctxt};
729 byte buf[1024]; // intentionally uninitialized
730 while (auto n = in.ReadBlocking (span{buf}).size ()) {
731 if (xmlParseChunk (ctxt, reinterpret_cast<char*> (buf), static_cast<int> (n), 0)) {
732 AssertNotNull (errCatcher.fCapturedException); // double check I understood API properly - and error handler getting called
733 }
734 errCatcher.ThrowIf ();
735 }
736 xmlParseChunk (ctxt, nullptr, 0, 1); // indicate the parsing is finished
737 errCatcher.ThrowIf ();
738 if (not ctxt->wellFormed) {
739 Execution::Throw (BadFormatException{"not well formed"sv}); // get good error message and throw that BadFormatException
740 }
741 fLibRep_ = ctxt->myDoc;
742 }
743 /*
744 * From https://opensource.apple.com/source/libxml2/libxml2-7/libxml2/doc/html/libxml-tree.html
745 *
746 * void * _private : For user data, libxml won't touch it (sometimes it says 'application data' - which is a bit less clear)
747 */
748 fLibRep_->_private = this;
749#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
750 ++sLiveCnt;
751#endif
752 }
753 DocRep_ (const DocRep_& from)
754 {
755 fLibRep_ = xmlCopyDoc (from.fLibRep_, 1); // unclear if this does the right thing with the xmlns???? if any pointers to it??? --LGP 2024-02-04
756 fLibRep_->_private = this;
757#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
758 ++sLiveCnt;
759#endif
760 }
761 DocRep_ (DocRep_&&) = delete;
762 DocRep_& operator= (DocRep_&) = delete;
763 ~DocRep_ ()
764 {
765 AssertNotNull (fLibRep_);
766 Assert (fLibRep_->_private == this);
767 xmlFreeDoc (fLibRep_);
768 for (auto i : fNSs2Free_) {
769 xmlFreeNs (i);
770 }
771#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
772 Assert (sLiveCnt > 0);
773 --sLiveCnt;
774#endif
775 }
776 virtual xmlDoc* GetLibXMLDocRep () override
777 {
778 return fLibRep_;
779 }
780 virtual const Providers::IDOMProvider* GetProvider () const override
781 {
782 return &Providers::LibXML2::kDefaultProvider;
783 }
784 virtual bool GetStandalone () const override
785 {
786 return !!fLibRep_->standalone;
787 }
788 virtual void SetStandalone (bool standalone) override
789 {
790 fLibRep_->standalone = standalone;
791 }
792 virtual Element::Ptr ReplaceRootElement (const NameWithNamespace& newEltName, bool childrenInheritNS) override
793 {
794 // This API is very confusing. I could find no examples online, or clear documentation on how to handle
795 // create a prefixed namespace and default namespace.
796 // I only came upon this series of hacks after looking carefully at the code and alot of experimenting...
797 // --LGP 2024-02-29
798 xmlNsPtr ns{nullptr};
799 if (newEltName.fNamespace) {
800 if (childrenInheritNS) {
801 ns = xmlNewNs (nullptr, BAD_CAST newEltName.fNamespace->As<String> (kUseURIEncodingFlag_).AsUTF8 ().c_str (), nullptr);
802 }
803 else {
804 // need some random prefix in this case...
805 ns = xmlNewNs (nullptr, BAD_CAST newEltName.fNamespace->As<String> (kUseURIEncodingFlag_).AsUTF8 ().c_str (), BAD_CAST "x");
806 }
807 }
808 xmlNodePtr n = xmlNewDocNode (fLibRep_, ns, BAD_CAST newEltName.fName.AsUTF8 ().c_str (), nullptr);
809 Assert (n->nsDef == nullptr);
810 if (childrenInheritNS and newEltName.fNamespace) {
811 n->nsDef = ns; // UGH
812 }
813 xmlDocSetRootElement (fLibRep_, n);
814 auto r = WrapLibXML2NodeInStroikaNode_ (n);
815 Ensure (r.GetName () == newEltName);
816 return r;
817 }
818 virtual void Write (const Streams::OutputStream::Ptr<byte>& to, const SerializationOptions& options) const override
819 {
820 TraceContextBumper ctx{"LibXML2::Doc::Write"};
821 AssertNotNull (fLibRep_);
822 xmlBufferPtr xmlBuf = xmlBufferCreate ();
823 [[maybe_unused]] auto&& cleanup1 = Execution::Finally ([&] () noexcept { xmlBufferFree (xmlBuf); });
824 constexpr char kTxtEncoding_[] = "UTF-8";
825 int useOptions = XML_SAVE_AS_XML;
826 if (options.fPrettyPrint) {
827 useOptions |= XML_SAVE_FORMAT | XML_SAVE_WSNONSIG;
828 }
829 // use xmlSaveToBuffer instead of xmlDocDumpFormatMemoryEnc () since that has options to control XML_SAVE_NO_EMPTY which changed in libxml2 2.13.1
830 xmlSaveCtxtPtr saveCtx = xmlSaveToBuffer (xmlBuf, kTxtEncoding_, useOptions);
831 [[maybe_unused]] auto&& cleanup2 = Execution::Finally ([&] () noexcept { xmlSaveClose (saveCtx); });
832 // could check for > 0 but bug in incomplete iml in current impl as libxml2 2.13.2
833 if (xmlSaveDoc (saveCtx, fLibRep_) >= 0 and xmlSaveFlush (saveCtx) >= 0) {
834 to.Write (span{reinterpret_cast<const byte*> (xmlBufferContent (xmlBuf)), static_cast<size_t> (xmlBufferLength (xmlBuf))});
835 }
836 else {
837 Execution::Throw (Execution::RuntimeErrorException{"failed dumping documented to text"});
838 return;
839 }
840 }
841 virtual Iterable<Node::Ptr> GetChildren () const override
842 {
843 AssertExternallySynchronizedMutex::ReadContext declareContext{fThisAssertExternallySynchronized_};
844 return Traversal::CreateGenerator<Node::Ptr> ([curChild = fLibRep_->children] () mutable -> optional<Node::Ptr> {
845 if (curChild == nullptr) {
846 return optional<Node::Ptr>{};
847 }
848 Node::Ptr r = WrapLibXML2NodeInStroikaNode_ (curChild);
849 curChild = curChild->next;
850 return r;
851 });
852 }
853 virtual void Validate (const Schema::Ptr& schema) const override
854 {
855 TraceContextBumper ctx{"LibXML2::Doc::Validate"};
856 RequireNotNull (schema);
857 Require (schema.GetRep ()->GetProvider () == &XML::Providers::LibXML2::kDefaultProvider);
858 xmlSchema* libxml2Schema = dynamic_pointer_cast<ILibXML2SchemaRep> (schema.GetRep ())->GetSchemaLibRep ();
859 RequireNotNull (libxml2Schema);
860 xmlSchemaValidCtxtPtr validateCtx = xmlSchemaNewValidCtxt (libxml2Schema);
861 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { xmlSchemaFreeValidCtxt (validateCtx); });
862 struct ValCB_ {
863 static void warnFun ([[maybe_unused]] void* ctx, [[maybe_unused]] const char* msg, ...)
864 {
865 // ignored for now
866 DbgTrace ("validate warn function ignored"_f);
867 }
868 static void errFun (void* ctx, const char* msg, ...)
869 {
870 // Keep first error - and guess NarrowSDK2Wide does right charset mapping
871 auto useCtx = reinterpret_cast<ValCB_*> (ctx);
872 if (useCtx->msg.empty ()) {
873 va_list argsList;
874 va_start (argsList, msg);
875 auto b = Characters::CString::FormatV (msg, argsList);
876 va_end (argsList);
877 useCtx->msg = String{"Failed schema validation: "_k + NarrowSDK2Wide (b, Characters::eIgnoreErrors)}.Trim ();
878 }
879 }
880 String msg;
881 };
882 ValCB_ validationCB;
883 xmlSchemaSetValidErrors (validateCtx, &ValCB_::errFun, &ValCB_::warnFun, &validationCB);
884 int r = xmlSchemaValidateDoc (validateCtx, fLibRep_);
885 if (r != 0) {
886 Assert (not validationCB.msg.empty ()); // guessing we only get validation error if error callback called?
887 optional<unsigned int> lineNumber;
888 optional<unsigned int> columnNumber;
889 optional<uint64_t> fileOffset;
890 if (const xmlError* err = xmlGetLastError ()) {
891 lineNumber = static_cast<unsigned int> (err->line);
892 if (static_cast<unsigned int> (err->int2) != 0) {
893 columnNumber = static_cast<unsigned int> (err->int2);
894 }
895 }
896 Execution::Throw (BadFormatException{validationCB.msg, lineNumber, columnNumber, fileOffset});
897 }
898 }
899 xmlDoc* fLibRep_{nullptr};
900 xmlNs* fXmlnsNamespace2Use{nullptr}; // some APIs require this existing, but not sure where to put it??? and dont want to create a bunch of them... --LGP 2024-02-04
901 list<xmlNsPtr> fNSs2Free_; // There probably is a better way with limxml2, but I cannot see how to avoid leaking these namespaces without this
902 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex fThisAssertExternallySynchronized_;
903 };
904 DocRep_* GetWrapperDoc_ (xmlDoc* d)
905 {
906 RequireNotNull (d);
907 DocRep_* wrapperDoc = reinterpret_cast<DocRep_*> (d->_private);
908 Assert (wrapperDoc->fLibRep_ == d); // else grave disorder
909 return wrapperDoc;
910 }
911 xmlNs* GetSharedReUsableXMLNSParentNamespace_ (xmlDoc* d)
912 {
913 auto wrapperDoc = GetWrapperDoc_ (d);
914 if (wrapperDoc->fXmlnsNamespace2Use == nullptr) {
915 // lazy create, since not always needed
916 wrapperDoc->fXmlnsNamespace2Use =
917 xmlNewNs (nullptr, BAD_CAST kXMLNS.fNamespace->As<String> (kUseURIEncodingFlag_).AsUTF8 ().c_str (), nullptr);
918 wrapperDoc->fNSs2Free_.push_front (wrapperDoc->fXmlnsNamespace2Use);
919 }
920 return wrapperDoc->fXmlnsNamespace2Use;
921 }
922}
923
924/*
925 ********************************************************************************
926 ******************* Provider::Xerces::libXMLString2String **********************
927 ********************************************************************************
928 */
929String Providers::LibXML2::libXMLString2String (const xmlChar* s, int len)
930{
931 return String{span{reinterpret_cast<const char*> (s), static_cast<size_t> (len)}};
932}
933String Providers::LibXML2::libXMLString2String (const xmlChar* t)
934{
935 return String::FromUTF8 (reinterpret_cast<const char*> (t));
936}
937
938/*
939 ********************************************************************************
940 ***************** Provider::Xerces::Providers::LibXML2::Provider ***************
941 ********************************************************************************
942 */
943Providers::LibXML2::Provider::Provider ()
944{
945 TraceContextBumper ctx{"LibXML2::Provider::CTOR"};
946#if qStroika_Foundation_Debug_AssertionsChecked
947 static unsigned int sNProvidersCreated_{0}; // don't create multiple of these - will lead to confusion
948 Assert (++sNProvidersCreated_ == 1);
949#endif
950 LIBXML_TEST_VERSION;
951}
952
953Providers::LibXML2::Provider::~Provider ()
954{
955 TraceContextBumper ctx{"LibXML2::Provider::DTOR"};
956#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
957 Require (SchemaRep_::sLiveCnt == 0); // Check for leaks but better/clearer than memory leaks check below
958 Require (DocRep_::sLiveCnt == 0); // ""
959#endif
960 xmlCleanupParser ();
961}
962
963shared_ptr<Schema::IRep> Providers::LibXML2::Provider::SchemaFactory (const InputStream::Ptr<byte>& schemaData, const Resource::ResolverPtr& resolver) const
964{
965 return Memory::MakeSharedPtr<SchemaRep_> (schemaData, resolver);
966}
967
968shared_ptr<DOM::Document::IRep> Providers::LibXML2::Provider::DocumentFactory (const Streams::InputStream::Ptr<byte>& in,
969 const Schema::Ptr& schemaToValidateAgainstWhileReading) const
970{
971 auto r = Memory::MakeSharedPtr<DocRep_> (in);
972 if (schemaToValidateAgainstWhileReading != nullptr) {
973 r->Validate (schemaToValidateAgainstWhileReading);
974 }
975 return r;
976}
977
978void Providers::LibXML2::Provider::SAXParse (const Streams::InputStream::Ptr<byte>& in, StructuredStreamEvents::IConsumer* callback,
979 const Schema::Ptr& schema) const
980{
982 optional<Streams::SeekOffsetType> seek2;
983 if (schema != nullptr and callback != nullptr) {
984 // @todo --- at least for now this is needed - cuz we read twice - maybe can fix...
985 useInput = Streams::ToSeekableInputStream::New (in);
986 seek2 = useInput.GetOffset ();
987 }
988 if (schema != nullptr) {
989 DocRep_ dr{useInput};
990 dr.Validate (schema);
991 }
992 if (callback != nullptr) {
993 SAXReader_ handler{*callback};
994 xmlParserCtxtPtr ctxt = xmlCreatePushParserCtxt (&handler.flibXMLSaxHndler_, &handler, nullptr, 0, nullptr);
996 [[maybe_unused]] auto&& cleanup = Execution::Finally ([&] () noexcept { xmlFreeParserCtxt (ctxt); });
997 MyLibXML2StructuredErrGrabber_ errCatcher{ctxt};
998 byte buf[1024];
999 if (seek2) {
1000 useInput.Seek (*seek2);
1001 }
1002 while (auto n = useInput.ReadBlocking (span{buf}).size ()) {
1003 if (xmlParseChunk (ctxt, reinterpret_cast<char*> (buf), static_cast<int> (n), 0)) {
1004 AssertNotNull (errCatcher.fCapturedException); // double check I understood API properly - and error handler getting called
1005 }
1006 errCatcher.ThrowIf ();
1007 }
1008 xmlParseChunk (ctxt, nullptr, 0, 1);
1009 errCatcher.ThrowIf ();
1010 }
1011}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireNotNull(p)
Definition Assertions.h:347
#define AssertNotReached()
Definition Assertions.h:355
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)
String libXMLString2String(const xmlChar *s, int len)
Definition LibXML2.cpp:929
#define DbgTrace
Definition Trace.h:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1592
nonvirtual size_t find(Character c, size_t startAt=0) const
Definition String.inl:1061
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:190
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
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:210
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
nonvirtual T As(optional< StringPCTEncodedFlag > pctEncoded={}) const
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual optional< ElementType > ReadBlocking() const
ReadBlocking () reads either a single element, or fills in argument intoBuffer - but never blocks (no...
nonvirtual SeekOffsetType Seek(SeekOffsetType offset) const
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
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
wstring NarrowSDK2Wide(span< const char > s)
Definition SDKString.inl:81
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
void ThrowIfNull(const Private_::ConstVoidStar &p, const HRESULT &hr)
Template specialization for ThrowIfNull (), for thing being thrown HRESULT - really throw HRESULTErro...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31