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