4#include "Stroika/Foundation/StroikaPreComp.h"
8#include <libxml/xmlmemory.h>
9#include <libxml/xmlsave.h>
12#include "Stroika/Foundation/DataExchange/BadFormatException.h"
14#include "Stroika/Foundation/Execution/Throw.h"
16#include "Stroika/Foundation/Memory/Common.h"
25using namespace Stroika::Foundation::DataExchange::XML;
26using namespace Stroika::Foundation::DataExchange::XML::DOM;
27using namespace Stroika::Foundation::DataExchange::XML::Schema;
28using namespace Stroika::Foundation::DataExchange::XML::Providers::LibXML2;
29using namespace Stroika::Foundation::Debug;
31using namespace Stroika::Foundation::Streams;
35using Memory::MakeSharedPtr;
40static_assert (qStroika_HasComponent_libxml2,
"Don't compile this file if qStroika_HasComponent_libxml2 not set");
45static_assert (LIBXML_VERSION >= 21400,
"Stroika needs libxml2 2.14 or later - configure --libxml2 use (the default) builds one");
54 constexpr auto kUseURIEncodingFlag_ = URI::StringPCTEncodedFlag::eDecoded;
63 xmlParserErrors ResourceLoader_ (
void* resolver,
const char* url,
const char* publicID, [[maybe_unused]] xmlResourceType type,
64 xmlParserInputFlags flags, xmlParserInput** out)
noexcept
71 String u = String::FromUTF8 (url);
72 if (optional<Resource::Definition> r =
reinterpret_cast<const Resource::ResolverPtr*
> (resolver)->Lookup (
73 Resource::Name{.fNamespace = u, .fPublicID = publicID ==
nullptr ? u : String::FromUTF8 (publicID), .fSystemID = u})) {
74 *out = xmlNewInputFromMemory (url, r->fData.data (), r->fData.size (), flags);
75 return *out ==
nullptr ? XML_ERR_NO_MEMORY : XML_ERR_OK;
77 return xmlNewInputFromUrl (url, flags, out);
81 DbgTrace (
"resolving '{}' failed: {}"_f, String::FromUTF8 (url), current_exception ());
82 return XML_ERR_INTERNAL_ERROR;
97 struct SchemaErrors_ {
100 optional<unsigned int> fLine;
101 optional<unsigned int> fColumn;
103 static void Handler (
void* userData,
const xmlError* error)
noexcept
105 auto useThis =
reinterpret_cast<SchemaErrors_*
> (userData);
107 if (error->level == XML_ERR_WARNING) {
108 DbgTrace (
"libxml2 warning ignored: {}"_f, String::FromUTF8 (error->message).Trim ());
110 else if (useThis->fFirstError.empty ()) {
111 useThis->fFirstError = String::FromUTF8 (error->message).Trim ();
112 if (error->line > 0) {
113 useThis->fLine =
static_cast<unsigned int> (error->line);
115 if (error->int2 > 0) {
116 useThis->fColumn =
static_cast<unsigned int> (error->int2);
126 return BadFormatException{fFirstError.empty () ? fWhatFailed : fWhatFailed +
": "_k + fFirstError, fLine, fColumn, nullopt};
132 struct SchemaRep_ final : ILibXML2SchemaRep {
133#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
134 static inline atomic<unsigned int> sLiveCnt{0};
138 , fResolver_{resolver}
139 , fSchemaData{schemaData.ReadAll ()}
141 xmlSchemaParserCtxt* schemaParseContext =
142 xmlSchemaNewMemParserCtxt (
reinterpret_cast<const char*
> (fSchemaData.data ()),
static_cast<int> (fSchemaData.size ()));
143 Execution::ThrowIfNull (schemaParseContext);
144 if (fResolver_ !=
nullptr) {
145 xmlSchemaSetResourceLoader (schemaParseContext, &ResourceLoader_, &fResolver_);
151 SchemaErrors_ errors{.fWhatFailed =
"Failed to compile schema"_k};
152 xmlSchemaSetParserStructuredErrors (schemaParseContext, &SchemaErrors_::Handler, &errors);
153 fCompiledSchema = xmlSchemaParse (schemaParseContext);
154 xmlSchemaFreeParserCtxt (schemaParseContext);
155 if (fCompiledSchema ==
nullptr) [[unlikely]] {
156 Execution::Throw (errors.AsException ());
158#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
162 SchemaRep_ (
const SchemaRep_&) =
delete;
163 virtual ~SchemaRep_ ()
165 xmlSchemaFree (fCompiledSchema);
166#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
167 Assert (sLiveCnt > 0);
171 shared_ptr<const void> fLibrary_;
174 xmlSchema* fCompiledSchema{
nullptr};
176 virtual const Providers::ISchemaProvider* GetProvider ()
const override
178 return &XML::Providers::LibXML2::kDefaultProvider;
180 virtual optional<URI> GetTargetNamespace ()
const override
182 Assert (fCompiledSchema !=
nullptr);
183 if (fCompiledSchema->targetNamespace !=
nullptr) {
197 virtual xmlSchema* GetSchemaLibRep ()
override
200 return fCompiledSchema;
206 struct SAXReader_ final {
208 xmlSAXHandler flibXMLSaxHndler_{};
210 xmlParserCtxtPtr fCtxt_{
nullptr};
212 exception_ptr fCapturedException_;
221 template <
typename CALL_CONSUMER>
222 static void Invoke_ (
void* ctx, CALL_CONSUMER&& callConsumer)
noexcept
224 SAXReader_* thisReader =
reinterpret_cast<SAXReader_*
> (ctx);
225 Assert (thisReader->flibXMLSaxHndler_.initialized == XML_SAX2_MAGIC);
226 if (thisReader->fCapturedException_ ==
nullptr) [[likely]] {
228 callConsumer (thisReader->fCallback_);
231 thisReader->fCapturedException_ = current_exception ();
232 xmlStopParser (thisReader->fCtxt_);
236 void ThrowIfCapturedException ()
238 if (fCapturedException_ !=
nullptr) [[unlikely]] {
239 rethrow_exception (fCapturedException_);
244 : fCallback_{callback}
246 flibXMLSaxHndler_.initialized = XML_SAX2_MAGIC;
247 flibXMLSaxHndler_.startDocument = [] (
void* ctx) {
250 flibXMLSaxHndler_.endDocument = [] (
void* ctx) {
253 flibXMLSaxHndler_.startElementNs = [] (
void* ctx,
const xmlChar* localname, [[maybe_unused]]
const xmlChar* prefix,
const xmlChar*
URI,
254 [[maybe_unused]]
int nb_namespaces, [[maybe_unused]]
const xmlChar** namespaces,
255 int nb_attributes, [[maybe_unused]]
int nb_defaulted,
const xmlChar** attributes) {
258 if (attributes !=
nullptr) {
261 auto ai = attributes;
262 for (
int i = 0; i < nb_attributes; ++i) {
271 if (
URI ==
nullptr) {
279 flibXMLSaxHndler_.endElementNs = [] (
void* ctx,
const xmlChar* localname, [[maybe_unused]]
const xmlChar* prefix,
const xmlChar*
URI) {
281 if (
URI ==
nullptr) {
289 flibXMLSaxHndler_.characters = [] (
void* ctx,
const xmlChar* ch,
int len) {
292 consumer.
TextInsideElement (String::FromUTF8 (span{
reinterpret_cast<const char*
> (ch),
static_cast<size_t> (len)}));
296 SAXReader_ (
const SAXReader_&) =
delete;
301#if qStroika_Foundation_Debug_AssertionsChecked
302 bool ValidNewNodeName_ (
const String& n)
307 if (n.
find (
':') != wstring::npos) {
317 DocRep_* GetWrapperDoc_ (xmlDoc* d);
318#if qStroika_Foundation_Debug_AssertionsChecked
319 weak_ptr<Document::IRep> DocumentOf_ (xmlNode* n);
321 DocRep_* GetWrapperDoc_ (xmlNode* n)
325 return GetWrapperDoc_ (n->doc);
327 xmlNs* GetSharedReUsableXMLNSParentNamespace_ (xmlDoc* d);
328 xmlNs* GetSharedReUsableXMLNSParentNamespace_ (xmlNode* n)
332 return GetSharedReUsableXMLNSParentNamespace_ (n->doc);
337 Node::Ptr WrapLibXML2NodeInStroikaNode_ (xmlNode* n);
343 NodeRep_ (xmlNode* n)
346 , fOwningDoc_{DocumentOf_ (n)}
351#if qStroika_Foundation_Debug_AssertionsChecked
357 Require (not fOwningDoc_.expired ());
360 virtual const Providers::IDOMProvider* GetProvider ()
const override
362 return &Providers::LibXML2::kDefaultProvider;
364 virtual bool Equals (
const IRep* rhs)
const override
368 return fNode_ ==
dynamic_cast<const NodeRep_*
> (rhs)->fNode_;
370 virtual Node::Type GetNodeType ()
const override
373 switch (fNode_->type) {
374 case XML_ELEMENT_NODE:
375 return Node::eElementNT;
376 case XML_ATTRIBUTE_NODE:
377 return Node::eAttributeNT;
379 return Node::eTextNT;
380 case XML_COMMENT_NODE:
381 return Node::eCommentNT;
383 return Node::eOtherNT;
389 Require (GetNodeType () == Node::eElementNT or GetNodeType () == Node::eAttributeNT);
390 switch (fNode_->type) {
391 case XML_ATTRIBUTE_NODE:
392 case XML_ELEMENT_NODE: {
393 const xmlChar* ns = fNode_->ns ==
nullptr ? nullptr : fNode_->ns->href;
404#if qStroika_Foundation_Debug_AssertionsChecked
405 Require (ValidNewNodeName_ (name.
fName));
412 xmlNodeSetName (fNode_, BAD_CAST name.
fName.
AsUTF8 ().c_str ());
414 virtual String GetValue ()
const override
417 auto r = xmlNodeGetContent (fNode_);
418 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([&] ()
noexcept { xmlFree (r); });
421 virtual void SetValue (
const String& v)
override
425 bool mustEncode =
true;
427 xmlChar* p = xmlEncodeSpecialChars (fNode_->doc, BAD_CAST v.
AsUTF8 ().c_str ());
428 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([&] ()
noexcept { xmlFree (p); });
429 ::xmlNodeSetContent (fNode_, p);
432 ::xmlNodeSetContent (fNode_, BAD_CAST v.
AsUTF8 ().c_str ());
435 virtual void DeleteNode ()
override
438 ::xmlUnlinkNode (fNode_);
439 ::xmlFreeNode (fNode_);
442 virtual Node::Ptr GetParentNode ()
const override
445 if (fNode_->parent ==
nullptr) {
448 return WrapLibXML2NodeInStroikaNode_ (fNode_->parent);
452 xmlBufferPtr xmlBuf = xmlBufferCreate ();
453 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([&] ()
noexcept { ::xmlBufferFree (xmlBuf); });
454 if (
int dumpRes = ::xmlNodeDump (xmlBuf, fNode_->doc, fNode_, 0, options.
fPrettyPrint); dumpRes == -1) {
457 const xmlChar* t = xmlBufferContent (xmlBuf);
459 to.
Write (span{
reinterpret_cast<const byte*
> (t),
static_cast<size_t> (::strlen (
reinterpret_cast<const char*
> (t)))});
461 virtual xmlNode* GetInternalTRep ()
override
470 static xmlNsPtr genPrefixedNS2Use_ (xmlNode* n,
const URI& ns)
472 const string href = ns.
As<
String> (kUseURIEncodingFlag_).AsUTF8<string> ();
473 for (xmlNode* i = n; i !=
nullptr and i->type == XML_ELEMENT_NODE; i = i->parent) {
474 for (xmlNs* d = i->nsDef; d !=
nullptr; d = d->next) {
475 if (d->prefix !=
nullptr and ::xmlStrEqual (d->href, BAD_CAST href.c_str ())) {
480 string prefix2Try{
"a"};
481 xmlNsPtr ns2Use =
nullptr;
482 while (ns2Use ==
nullptr) {
483 ns2Use = ::xmlNewNs (n, BAD_CAST href.c_str (), BAD_CAST prefix2Try.c_str ());
484 if (ns2Use ==
nullptr) {
494 static xmlNsPtr elementNS2Use_ (xmlNode* parent,
const NameWithNamespace& eltName)
505 static void undeclareDefaultNSIfInScope_ (xmlNode* newNode)
508 if (newNode->ns ==
nullptr) {
509 xmlNs* inScope = ::xmlSearchNs (newNode->doc, newNode->parent,
nullptr);
510 if (inScope !=
nullptr and inScope->href !=
nullptr and *inScope->href !=
'\0') {
511 ::xmlSetNsProp (newNode, GetSharedReUsableXMLNSParentNamespace_ (newNode), BAD_CAST
"xmlns", BAD_CAST
"");
515 static xmlNsPtr genNS2Use_ (xmlNode* n,
const URI& ns)
517 xmlNsPtr ns2Use = xmlSearchNsByHref (n->doc, n, BAD_CAST ns.
As<
String> (kUseURIEncodingFlag_).
AsUTF8 ().c_str ());
518 string prefix2Try{
"a"};
519 while (ns2Use ==
nullptr) {
525 ns2Use = xmlNewNs (n, BAD_CAST ns.
As<
String> (kUseURIEncodingFlag_).
AsUTF8 ().c_str (), BAD_CAST prefix2Try.c_str ());
526 if (ns2Use ==
nullptr) {
534#if qStroika_Foundation_Debug_AssertionsChecked
535 weak_ptr<Document::IRep> fOwningDoc_;
542 struct ElementRep_ : Element::
IRep, Memory::InheritAndUseBlockAllocationIfAppropriate<ElementRep_, NodeRep_> {
544 ElementRep_ (xmlNode* n)
548 Require (n->type == XML_ELEMENT_NODE);
550 virtual Node::Type GetNodeType ()
const override
553 Assert (fNode_->type == XML_ELEMENT_NODE);
554 return Node::eElementNT;
556 virtual optional<String> GetAttribute (
const NameWithNamespace& attrName)
const override
560 : ::xmlGetProp (fNode_, BAD_CAST attrName.fName.AsUTF8 ().c_str ());
564 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([&] ()
noexcept { xmlFree (r); });
565 return ::libXMLString2String (r);
567 virtual void SetAttribute (
const NameWithNamespace& attrName,
const optional<String>& v)
override
570 Require (GetNodeType () == Node::eElementNT);
580 ::xmlNewNs (fNode_, BAD_CAST v->AsUTF8<
string> ().c_str (), BAD_CAST attrName.
fName.
AsUTF8<
string> ().c_str ());
593 ::xmlSetNsProp (fNode_, GetSharedReUsableXMLNSParentNamespace_ (fNode_), BAD_CAST attrName.
fName.
AsUTF8 ().c_str (),
594 v == nullopt ?
nullptr : (BAD_CAST v->AsUTF8 ().c_str ()));
596 else if (v == nullopt) {
603 if (xmlNs* ns = ::xmlSearchNsByHref (fNode_->doc, fNode_,
605 ::xmlUnsetNsProp (fNode_, ns, BAD_CAST attrName.
fName.
AsUTF8 ().c_str ());
609 ::xmlUnsetProp (fNode_, BAD_CAST attrName.
fName.
AsUTF8 ().c_str ());
614 ::xmlSetNsProp (fNode_, genPrefixedNS2Use_ (fNode_, *attrName.
fNamespace), BAD_CAST attrName.
fName.
AsUTF8 ().c_str (),
615 BAD_CAST v->AsUTF8 ().c_str ());
618 ::xmlSetProp (fNode_, BAD_CAST attrName.
fName.
AsUTF8 ().c_str (), BAD_CAST v->AsUTF8 ().c_str ());
623#if qStroika_Foundation_Debug_AssertionsChecked
624 Require (ValidNewNodeName_ (eltName.
fName));
626 Require (afterNode ==
nullptr or this->GetChildren ().Contains (afterNode));
628 xmlNs* useNS = elementNS2Use_ (fNode_, eltName);
629 xmlNode* newNode = ::xmlNewNode (useNS, BAD_CAST eltName.
fName.
AsUTF8 ().c_str ());
630 NodeRep_* afterNodeRep = afterNode ==
nullptr ? nullptr :
dynamic_cast<NodeRep_*
> (afterNode.
GetRep ().get ());
631 if (afterNodeRep ==
nullptr) {
634 if (fNode_->children ==
nullptr) {
635 ::xmlAddChild (fNode_, newNode);
638 ::xmlAddPrevSibling (fNode_->children, newNode);
642 xmlAddNextSibling (afterNodeRep->fNode_, newNode);
644 undeclareDefaultNSIfInScope_ (newNode);
645 return WrapLibXML2NodeInStroikaNode_ (newNode);
649#if qStroika_Foundation_Debug_AssertionsChecked
650 Require (ValidNewNodeName_ (eltName.
fName));
653 xmlNs* useNS = elementNS2Use_ (fNode_, eltName);
654 xmlNode* newNode = xmlNewNode (useNS, BAD_CAST eltName.
fName.
AsUTF8 ().c_str ());
655 ::xmlAddChild (fNode_, newNode);
656 undeclareDefaultNSIfInScope_ (newNode);
657 return WrapLibXML2NodeInStroikaNode_ (newNode);
664 xmlNode* CloneIntoThisDocument_ (
const Node::Ptr& cloneFrom)
667 const NodeRep_* fromRep =
dynamic_cast<const NodeRep_*
> (cloneFrom.
GetRep ().get ());
668 Require (fromRep !=
nullptr);
669 xmlNode* copy = ::xmlDocCopyNode (fromRep->fNode_, fNode_->doc, 1 );
670 Execution::ThrowIfNull (copy);
675 xmlNode* copy = CloneIntoThisDocument_ (cloneFrom);
676 ::xmlAddChild (fNode_, copy);
677 ::xmlReconciliateNs (fNode_->doc, copy);
678 return WrapLibXML2NodeInStroikaNode_ (copy);
682 Require (afterNode ==
nullptr or this->GetChildren ().Contains (afterNode));
683 xmlNode* copy = CloneIntoThisDocument_ (cloneFrom);
684 NodeRep_* afterNodeRep = afterNode ==
nullptr ? nullptr :
dynamic_cast<NodeRep_*
> (afterNode.
GetRep ().get ());
685 if (afterNodeRep ==
nullptr) {
686 if (fNode_->children ==
nullptr) {
687 ::xmlAddChild (fNode_, copy);
690 ::xmlAddPrevSibling (fNode_->children, copy);
694 ::xmlAddNextSibling (afterNodeRep->fNode_, copy);
696 ::xmlReconciliateNs (fNode_->doc, copy);
697 return WrapLibXML2NodeInStroikaNode_ (copy);
704 return Traversal::CreateGenerator<Node::Ptr> ([curChild = fNode_->children] () mutable -> optional<Node::Ptr> {
705 if (curChild == nullptr) {
706 return optional<Node::Ptr>{};
708 Node::Ptr r = WrapLibXML2NodeInStroikaNode_ (curChild);
709 curChild = curChild->next;
713 struct XPathLookupHelper_ final {
714 xmlXPathContext* fCtx{
nullptr};
715 xmlXPathObject* fResultNodeList{
nullptr};
716 XPathLookupHelper_ () =
delete;
717 XPathLookupHelper_ (
const XPathLookupHelper_&) =
delete;
718 XPathLookupHelper_ (xmlDoc* doc, xmlNode* contextNode,
const XPath::Expression& e)
722 fCtx = xmlXPathNewContext (doc);
723 Execution::ThrowIfNull (fCtx);
724 fCtx->error = [] ([[maybe_unused]]
void* userData, [[maybe_unused]]
const xmlError* error) {
728 auto namespaceDefs = e.
GetOptions ().fNamespaces;
729 if (namespaceDefs.GetDefaultNamespace ()) {
731 Execution::Throw (XPath::XPathExpressionNotSupported::kThe);
734 xmlXPathRegisterNs (fCtx, BAD_CAST ni.fKey.AsUTF8 ().c_str (),
735 BAD_CAST ni.fValue.As<
String> (kUseURIEncodingFlag_).
AsUTF8 ().c_str ());
737 fCtx->node = contextNode;
738 fResultNodeList = xmlXPathEvalExpression (BAD_CAST e.
GetExpression ().
AsUTF8 ().c_str (), fCtx);
739 if (fCtx->lastError.level != XML_ERR_NONE and fCtx->lastError.level != XML_ERR_WARNING) {
742 e, fCtx->lastError.domain, fCtx->lastError.code)});
744 Execution::ThrowIfNull (fResultNodeList);
747 if (fResultNodeList !=
nullptr) {
748 xmlXPathFreeObject (fResultNodeList);
749 fResultNodeList =
nullptr;
751 if (fCtx !=
nullptr) {
752 xmlXPathFreeContext (fCtx);
755 Execution::ReThrow ();
758 ~XPathLookupHelper_ ()
761 xmlXPathFreeObject (fResultNodeList);
763 xmlXPathFreeContext (fCtx);
765 static optional<XPath::Result> ToResult (xmlNode* n)
767 if (n ==
nullptr) [[unlikely]] {
772 case XML_ATTRIBUTE_NODE:
773 case XML_ELEMENT_NODE: {
774 return WrapLibXML2NodeInStroikaNode_ (n);
784 XPathLookupHelper_ helper{fNode_->doc, fNode_, e};
785 xmlNodeSet* resultSet = helper.fResultNodeList->nodesetval;
786 size_t size = (resultSet) ? resultSet->nodeNr : 0;
788 return XPathLookupHelper_::ToResult (resultSet->nodeTab[0]);
801 XPathLookupHelper_ helper{fNode_->doc, fNode_, e};
802 xmlNodeSet* resultSet = helper.fResultNodeList->nodesetval;
803 size_t size = (resultSet) ? resultSet->nodeNr : 0;
805 for (
size_t i = 0; i < size; ++i) {
806 r += Memory::ValueOf (XPathLookupHelper_::ToResult (resultSet->nodeTab[i]));
811 DISABLE_COMPILER_MSC_WARNING_END (4250)
815 Node::Ptr WrapLibXML2NodeInStroikaNode_ (xmlNode* n)
818 if (n->type == XML_ELEMENT_NODE) [[likely]] {
819 return Node::Ptr{MakeSharedPtr<ElementRep_> (n)};
822 return Node::Ptr{MakeSharedPtr<NodeRep_> (n)};
828 struct MyLibXML2StructuredErrGrabber_ final {
829 xmlParserCtxtPtr fCtx;
831 exception_ptr fCapturedException;
833 MyLibXML2StructuredErrGrabber_ (xmlParserCtxtPtr ctx)
836 xmlCtxtSetErrorHandler (ctx, xmlStructuredErrorFunc_,
this);
838 ~MyLibXML2StructuredErrGrabber_ ()
840 xmlCtxtSetErrorHandler (fCtx,
nullptr,
nullptr);
842 MyLibXML2StructuredErrGrabber_& operator= (
const MyLibXML2StructuredErrGrabber_&) =
delete;
846 if (fCapturedException !=
nullptr) {
847 Execution::ReThrow (fCapturedException);
852 static void xmlStructuredErrorFunc_ (
void* userData,
const xmlError* error)
855 MyLibXML2StructuredErrGrabber_* useThis =
reinterpret_cast<MyLibXML2StructuredErrGrabber_*
> (userData);
857 if (useThis->fCapturedException ==
nullptr) {
858 switch (error->level) {
862 case XML_ERR_WARNING:
863 DbgTrace (
"libxml2 (xmlStructuredErrorFunc_): Ignore warnings for now: {}"_f, String::FromUTF8 (error->message));
867 DbgTrace (
"libxml2 (xmlStructuredErrorFunc_): Capturing Error {}"_f, String::FromUTF8 (error->message));
871 "Failure Parsing XML: {}, line {}"_f(String::FromUTF8 (error->message), error->line),
872 static_cast<unsigned int> (error->line), nullopt, nullopt});
875 useThis->fCapturedException = current_exception ();
887 struct DocRep_ final : ILibXML2DocRep
888#if qStroika_Foundation_Debug_AssertionsChecked
890 enable_shared_from_this<DocRep_>
893#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
894 static inline atomic<unsigned int> sLiveCnt{0};
901 fLibRep_ = xmlNewDoc (BAD_CAST
"1.0");
902 fLibRep_->standalone =
true;
905 xmlParserCtxtPtr ctxt = xmlCreatePushParserCtxt (
nullptr,
nullptr,
nullptr, 0,
"in-stream.xml" );
906 Execution::ThrowIfNull (ctxt);
908 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([&] ()
noexcept {
909 if (fLibRep_ ==
nullptr and ctxt->myDoc !=
nullptr) {
910 xmlFreeDoc (ctxt->myDoc);
912 xmlFreeParserCtxt (ctxt);
914 MyLibXML2StructuredErrGrabber_ errCatcher{ctxt};
917 if (xmlParseChunk (ctxt,
reinterpret_cast<char*
> (buf),
static_cast<int> (n), 0)) {
920 errCatcher.ThrowIf ();
922 xmlParseChunk (ctxt,
nullptr, 0, 1);
923 errCatcher.ThrowIf ();
924 if (not ctxt->wellFormed) {
927 fLibRep_ = ctxt->myDoc;
934 fLibRep_->_private =
this;
935#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
939 DocRep_ (
const DocRep_& from)
940 : fLibrary_{from.fLibrary_}
942 fLibRep_ = xmlCopyDoc (from.fLibRep_, 1);
943 fLibRep_->_private =
this;
944#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
948 DocRep_ (DocRep_&&) =
delete;
949 DocRep_& operator= (DocRep_&) =
delete;
953 Assert (fLibRep_->_private ==
this);
954 xmlFreeDoc (fLibRep_);
955 for (
auto i : fNSs2Free_) {
958#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
959 Assert (sLiveCnt > 0);
963 virtual xmlDoc* GetLibXMLDocRep ()
override
967 virtual const Providers::IDOMProvider* GetProvider ()
const override
969 return &Providers::LibXML2::kDefaultProvider;
971 virtual bool GetStandalone ()
const override
973 return !!fLibRep_->standalone;
975 virtual void SetStandalone (
bool standalone)
override
977 fLibRep_->standalone = standalone;
985 xmlNsPtr ns{
nullptr};
987 const string href = newEltName.
fNamespace->As<
String> (kUseURIEncodingFlag_).AsUTF8<string> ();
989 ns = namespacePrefix == nullopt
990 ? xmlNewNs (
nullptr, BAD_CAST href.c_str (),
nullptr)
991 : xmlNewNs (nullptr, BAD_CAST href.c_str (), BAD_CAST namespacePrefix->AsUTF8<string> ().c_str ());
993 xmlNodePtr n = xmlNewDocNode (fLibRep_, ns, BAD_CAST newEltName.
fName.
AsUTF8 ().c_str (),
nullptr);
994 Assert (n->nsDef ==
nullptr);
999 if (xmlNodePtr oldRoot = xmlDocSetRootElement (fLibRep_, n)) {
1000 xmlFreeNode (oldRoot);
1002 auto r = WrapLibXML2NodeInStroikaNode_ (n);
1003 Ensure (r.GetName () == newEltName);
1010 xmlBufferPtr xmlBuf = xmlBufferCreate ();
1011 [[maybe_unused]]
auto&& cleanup1 = Execution::Finally ([&] ()
noexcept { xmlBufferFree (xmlBuf); });
1012 constexpr char kTxtEncoding_[] =
"UTF-8";
1013 int useOptions = XML_SAVE_AS_XML;
1015 useOptions |= XML_SAVE_FORMAT | XML_SAVE_WSNONSIG;
1018 xmlSaveCtxtPtr saveCtx = xmlSaveToBuffer (xmlBuf, kTxtEncoding_, useOptions);
1019 [[maybe_unused]]
auto&& cleanup2 = Execution::Finally ([&] ()
noexcept { xmlSaveClose (saveCtx); });
1020#if LIBXML_VERSION >= 21400
1022 (void)xmlSaveSetIndentString (saveCtx,
string (*options.
fIndent,
' ').c_str ());
1026 if (xmlSaveDoc (saveCtx, fLibRep_) >= 0 and xmlSaveFlush (saveCtx) >= 0) {
1027 to.
Write (span{
reinterpret_cast<const byte*
> (xmlBufferContent (xmlBuf)),
static_cast<size_t> (xmlBufferLength (xmlBuf))});
1037 return Traversal::CreateGenerator<Node::Ptr> ([curChild = fLibRep_->children] () mutable -> optional<Node::Ptr> {
1038 if (curChild == nullptr) {
1039 return optional<Node::Ptr>{};
1041 Node::Ptr r = WrapLibXML2NodeInStroikaNode_ (curChild);
1042 curChild = curChild->next;
1046 virtual void Validate (
const Schema::Ptr& schema)
const override
1050 Require (schema.GetRep ()->GetProvider () == &XML::Providers::LibXML2::kDefaultProvider);
1051 xmlSchema* libxml2Schema = dynamic_pointer_cast<ILibXML2SchemaRep> (schema.GetRep ())->GetSchemaLibRep ();
1053 xmlSchemaValidCtxtPtr validateCtx = xmlSchemaNewValidCtxt (libxml2Schema);
1054 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([&] ()
noexcept { xmlSchemaFreeValidCtxt (validateCtx); });
1055 SchemaErrors_ errors{.fWhatFailed =
"Failed schema validation"_k};
1056 xmlSchemaSetValidStructuredErrors (validateCtx, &SchemaErrors_::Handler, &errors);
1057 if (xmlSchemaValidateDoc (validateCtx, fLibRep_) != 0) {
1058 Assert (not errors.fFirstError.empty ());
1059 Execution::Throw (errors.AsException ());
1062 shared_ptr<const void> fLibrary_;
1063 xmlDoc* fLibRep_{
nullptr};
1064 xmlNs* fXmlnsNamespace2Use{
nullptr};
1065 list<xmlNsPtr> fNSs2Free_;
1068 DocRep_* GetWrapperDoc_ (xmlDoc* d)
1071 DocRep_* wrapperDoc =
reinterpret_cast<DocRep_*
> (d->_private);
1072 Assert (wrapperDoc->fLibRep_ == d);
1075#if qStroika_Foundation_Debug_AssertionsChecked
1077 weak_ptr<Document::IRep> DocumentOf_ (xmlNode* n)
1080 if (n->doc ==
nullptr or n->doc->_private ==
nullptr) {
1083 return GetWrapperDoc_ (n->doc)->weak_from_this ();
1086 xmlNs* GetSharedReUsableXMLNSParentNamespace_ (xmlDoc* d)
1088 auto wrapperDoc = GetWrapperDoc_ (d);
1089 if (wrapperDoc->fXmlnsNamespace2Use ==
nullptr) {
1091 wrapperDoc->fXmlnsNamespace2Use =
1092 xmlNewNs (
nullptr, BAD_CAST kXMLNS.
fNamespace->As<
String> (kUseURIEncodingFlag_).
AsUTF8 ().c_str (),
nullptr);
1093 wrapperDoc->fNSs2Free_.push_front (wrapperDoc->fXmlnsNamespace2Use);
1095 return wrapperDoc->fXmlnsNamespace2Use;
1104String Providers::LibXML2::libXMLString2String (
const xmlChar* s,
int len)
1106 return String::FromUTF8 (span{
reinterpret_cast<const char*
> (s),
static_cast<size_t> (len)});
1108String Providers::LibXML2::libXMLString2String (
const xmlChar* t)
1110 return String::FromUTF8 (
reinterpret_cast<const char*
> (t));
1124 LIBXML_TEST_VERSION;
1126 Library_ (
const Library_&) =
delete;
1130#if qStroika_Foundation_DataExchange_XML_DebugMemoryAllocations
1131 Assert (SchemaRep_::sLiveCnt == 0);
1132 Assert (DocRep_::sLiveCnt == 0);
1134 xmlCleanupParser ();
1139Providers::LibXML2::Provider::Provider ()
1143#if qStroika_Foundation_Debug_AssertionsChecked
1144 static unsigned int sNProvidersCreated_{0};
1145 Assert (++sNProvidersCreated_ == 1);
1151 return MakeSharedPtr<SchemaRep_> (fLibrary_, schemaData, resolver);
1155 const Schema::Ptr& schemaToValidateAgainstWhileReading)
const
1157 auto r = MakeSharedPtr<DocRep_> (fLibrary_, in);
1158 if (schemaToValidateAgainstWhileReading !=
nullptr) {
1159 r->Validate (schemaToValidateAgainstWhileReading);
1168 optional<Streams::SeekOffsetType> seek2;
1169 if (schema !=
nullptr and callback !=
nullptr) {
1171 useInput = Streams::ToSeekableInputStream::New (in);
1174 if (schema !=
nullptr) {
1175 DocRep_ dr{fLibrary_, useInput};
1176 dr.Validate (schema);
1178 if (callback !=
nullptr) {
1179 SAXReader_ handler{*callback};
1180 xmlParserCtxtPtr ctxt = xmlCreatePushParserCtxt (&handler.flibXMLSaxHndler_, &handler,
nullptr, 0,
nullptr);
1182 handler.fCtxt_ = ctxt;
1183 [[maybe_unused]]
auto&& cleanup =
Execution::Finally ([&] ()
noexcept { xmlFreeParserCtxt (ctxt); });
1184 MyLibXML2StructuredErrGrabber_ errCatcher{ctxt};
1187 useInput.
Seek (*seek2);
1189 while (
auto n = useInput.
ReadBlocking (span{buf}).size ()) {
1190 if (xmlParseChunk (ctxt,
reinterpret_cast<char*
> (buf),
static_cast<int> (n), 0)) {
1192 handler.ThrowIfCapturedException ();
1195 handler.ThrowIfCapturedException ();
1196 errCatcher.ThrowIf ();
1198 xmlParseChunk (ctxt,
nullptr, 0, 1);
1199 handler.ThrowIfCapturedException ();
1200 errCatcher.ThrowIf ();
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
#define RequireNotNull(p)
#define AssertNotReached()
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)
String libXMLString2String(const xmlChar *s, int len)
#define Stroika_ATTRIBUTE_INDETERMINATE
Stroika_ATTRIBUTE_INDETERMINATE is used where you would use a C++ attribute for a variable that is in...
#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...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual T AsUTF8() const
nonvirtual String ReplaceAll(const RegularExpression ®Ex, const String &with) const
nonvirtual size_t find(Character c, size_t startAt=0) const
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)
A generalization of a vector: a container whose elements are keyed by the natural numbers.
virtual void EndDocument()
virtual void StartDocument()
virtual void TextInsideElement(const String &text)
virtual void EndElement(const Name &name)
virtual void StartElement(const Name &name, const Mapping< Name, String > &attributes)
nonvirtual shared_ptr< IRep > GetRep() const
return the associated shared_ptr (cannot be nullptr)
Node::Ptr is a smart pointer to a Node::IRep.
nonvirtual shared_ptr< IRep > GetRep() const
return the associated shared_ptr (cannot be nullptr)
nonvirtual String GetExpression() const
nonvirtual Options GetOptions() const
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...
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
nonvirtual T As(optional< StringPCTEncodedFlag > pctEncoded={}) 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.
DISABLE_COMPILER_MSC_WARNING_START(4996)
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 >
optional< unsigned int > fIndent
An element's or attribute's name (local-name, not including a prefix), plus the namespace that name i...
String fName
The LOCAL name - "Why" for <x:Why>
optional< URI > fNamespace
The namespace fName is in, however the document said so - nullopt for a name in no namespace.