Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Variant/XML/Writer.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
8#include "Stroika/Foundation/Containers/Mapping.h"
9#include "Stroika/Foundation/Containers/Sequence.h"
10#include "Stroika/Foundation/Containers/Set.h"
11#include "Stroika/Foundation/DataExchange/BadFormatException.h"
12#include "Stroika/Foundation/DataExchange/XML/WriterUtils.h"
13#include "Stroika/Foundation/Execution/Throw.h"
15#include "Stroika/Foundation/Streams/TextToBinary.h"
16
17#include "Writer.h"
18
19using std::byte;
20
21using namespace Stroika::Foundation;
25using namespace Stroika::Foundation::DataExchange::XML;
26using namespace Stroika::Foundation::DataExchange::Variant::XML;
27using namespace Stroika::Foundation::Streams;
28
29using Memory::MakeSharedPtr;
30
31namespace {
32 // a name comes from data - a member's key - as often as from a Binding, and one that is not an XML name would be written
33 // as malformed XML, which nothing could read back
34 void ThrowUnlessNCName_ (const String& name)
35 {
36 if (not IsNCName (name)) {
37 Execution::Throw (BadFormatException{"'{}' is not an XML name, so cannot be written as one"_f(name)});
38 }
39 }
40
41 /*
42 * A VariantValue as XML (Variant::XML::Writer's documentation has the shape): an object's member is an element, an array
43 * is repeated elements, and anything else is its element's text - each scalar as VariantValue::As<String> writes it, so a
44 * date is ISO 8601 and a BLOB base64, as in JSON.
45 */
46 struct Writer_ {
48 bool fPrettyPrint;
49 String fIndent; // one level's
50
51 // what an element inherits from the elements it is inside
52 struct Scope_ {
53 optional<URI> fDefaultNamespace; // what a name with no namespace of its own is in
54 Mapping<URI, String> fPrefixes; // a namespace written with a prefix, not as a default one
55 const Binding* fDefaultArrayItems{&Binding::kDefaultArrayItems}; // the item Binding of an array with no fItems
56 };
57
58 void NewLine_ ()
59 {
60 if (fPrettyPrint) {
61 fOut.Write ("\n"sv);
62 }
63 }
64 void Indent_ (int indentLevel)
65 {
66 if (fPrettyPrint) {
67 for (int i = 0; i < indentLevel; ++i) {
68 fOut.Write (fIndent);
69 }
70 }
71 }
72 // a prefix names one namespace at a time, so binding it to this one unbinds any other it named
73 static void BindPrefix_ (Mapping<URI, String>* prefixes, const URI& ns, const String& prefix)
74 {
75 prefixes->RemoveAll ([&] (const Common::KeyValuePair<URI, String>& i) { return i.fValue == prefix; });
76 prefixes->Add (ns, prefix);
77 }
78 // a member whose Binding declares its type is written as no member at all where it is null (as ObjectVariantMapper omits
79 // a null field) - so it reads back absent
80 static bool OmitNull_ (const VariantValue& v, const Binding* memberBinding)
81 {
82 return v.GetType () == VariantValue::eNull and memberBinding != nullptr and memberBinding->fType;
83 }
84 // where a Binding declares a type, the value has that type - v is data, not code, so this throws
85 static void ThrowUnlessDeclaredType_ (const String& name, const VariantValue& v, const Binding* binding)
86 {
87 if (binding != nullptr and binding->fType and v.GetType () != *binding->fType) {
88 Execution::Throw (BadFormatException{"'{}' is {}, where its Binding declares {}"_f(name, v.GetType (), *binding->fType)});
89 }
90 }
91 // an object's members written as child elements, in the Binding's order - then any it does not name, as the value
92 // gives them
94 {
97 if (binding != nullptr) {
98 for (const Binding::Member& i : binding->fMembers) {
99 named.Add (i.fKey);
100 if (Binding::Kind k = binding->KindFor (i.fKey); k == Binding::Kind::eElement or k == Binding::Kind::ePresenceMarker) {
101 // a presence marker is written only where true (as an empty element): false, there is nothing to write
102 if (optional<VariantValue> v = members.Lookup (i.fKey); v and not OmitNull_ (*v, binding->MemberBinding (i.fKey)) and
103 (k != Binding::Kind::ePresenceMarker or v->As<bool> ())) {
105 }
106 }
107 }
108 }
109 for (const auto& i : members) {
110 if (not named.Contains (i.fKey)) {
111 result.Append (i);
112 }
113 }
114 return result;
115 }
116 // prefix:local, for a namespace given a prefix; else its local name
117 void WriteName_ (const NameWithNamespace& name, const Scope_& scope)
118 {
119 ThrowUnlessNCName_ (name.fName);
120 if (name.fNamespace) {
121 if (optional<String> prefix = scope.fPrefixes.Lookup (*name.fNamespace)) {
122 fOut.Write (*prefix);
123 fOut.Write (':');
124 }
125 }
126 fOut.Write (name.fName);
127 }
128 // a default namespace (xmlns=) where this element names one, and it is not the one it sits in already; a name with none
129 // inherits the namespace it is in - saying nothing is not the same as saying 'no namespace'. Returns the namespace
130 // this element's children are in
131 optional<URI> WriteNamespaceIfNew_ (const NameWithNamespace& name, const Scope_& scope)
132 {
133 if (not name.fNamespace or name.fNamespace == scope.fDefaultNamespace) {
134 return scope.fDefaultNamespace;
135 }
136 if (scope.fPrefixes.ContainsKey (*name.fNamespace)) {
137 return scope.fDefaultNamespace; // written prefix:local, so the default namespace is untouched
138 }
139 fOut.Write (" xmlns=\""sv);
140 fOut.Write (String{QuoteForXMLAttribute (name.fNamespace->As<String> (IO::Network::URI::StringPCTEncodedFlag::eDecoded))});
141 fOut.Write ('"');
142 return name.fNamespace;
143 }
144 // '<', the name, the prefixes this element declares (xmlns:p=, and now in scope) where they are not in scope already,
145 // and the default namespace where it starts one - all but the attributes and the '>'
146 void StartTag_ (const NameWithNamespace& name, const Mapping<URI, String>& declare, Scope_* scope, int indentLevel)
147 {
148 Mapping<URI, String> newPrefixes;
149 for (const auto& i : declare) {
150 if (scope->fPrefixes.Lookup (i.fKey) != i.fValue) {
151 ThrowUnlessNCName_ (i.fValue);
152 newPrefixes.Add (i.fKey, i.fValue);
153 BindPrefix_ (&scope->fPrefixes, i.fKey, i.fValue);
154 }
155 }
156 Indent_ (indentLevel);
157 fOut.Write ('<');
158 WriteName_ (name, *scope);
159 for (const auto& i : newPrefixes) {
160 fOut.Write (" xmlns:"sv);
161 fOut.Write (i.fValue);
162 fOut.Write ("=\""sv);
163 fOut.Write (String{QuoteForXMLAttribute (i.fKey.As<String> (IO::Network::URI::StringPCTEncodedFlag::eDecoded))});
164 fOut.Write ('"');
165 }
166 scope->fDefaultNamespace = WriteNamespaceIfNew_ (name, *scope);
167 }
168 void EndTag_ (const NameWithNamespace& name, const Scope_& scope)
169 {
170 fOut.Write ("</"sv);
171 WriteName_ (name, scope);
172 fOut.Write ('>');
173 NewLine_ ();
174 }
175 // the element for a member (or the document element) - binding nullptr for the default shape. outer is what the element it
176 // is inside has in scope, and alsoDeclare prefixes in scope that no element has declared yet
177 void WriteElement (const NameWithNamespace& name, const VariantValue& v, const Binding* binding, const Scope_& outer,
178 int indentLevel, const Mapping<URI, String>& alsoDeclare = {})
179 {
180 Scope_ scope = outer;
181 Mapping<URI, String> declare = alsoDeclare;
182 if (binding != nullptr) {
183 if (binding->fDefaultArrayItems != nullptr) {
184 scope.fDefaultArrayItems = binding->fDefaultArrayItems.get ();
185 }
186 for (const auto& i : binding->fNamespacePrefixes) {
187 BindPrefix_ (&declare, i.fKey, i.fValue);
188 }
189 ThrowUnlessDeclaredType_ (name.fName, v, binding);
190 if (v.GetType () == VariantValue::eMap and binding->GetUndeclaredMembers () != Binding::UndeclaredMembers::eKeep) {
191 // the members it declares are all the object has - one it does not would not read back
192 for (const auto& i : v.As<Mapping<String, VariantValue>> ()) {
193 if (not binding->fMembers.Any ([&] (const Binding::Member& m) { return m.fKey == i.fKey; })) {
194 Execution::Throw (BadFormatException{"'{}' has a member '{}', which its Binding does not declare"_f(name.fName, i.fKey)});
195 }
196 }
197 }
198 }
199 if (v.GetType () == VariantValue::eArray) {
201 const Binding* itemBinding = (binding != nullptr and binding->fItems != nullptr) ? binding->fItems.get () : scope.fDefaultArrayItems;
202 if (not itemBinding->fName) {
203 // items with no name of their own are this element, repeated - how a schema of someone else's usually has
204 // it (UPnP's <service>); then an array of one reads back as a scalar, unless a Binding says it is an array
205 if (indentLevel == 0 and items.size () != 1) {
206 static const auto kException_ =
207 BadFormatException{"a document has exactly one root element, so cannot be an array of items with no name of their own"sv};
208 Execution::Throw (kException_);
209 }
210 for (const VariantValue& i : items) {
211 WriteElement (name, i, itemBinding, scope, indentLevel, declare);
212 }
213 return;
214 }
215 // items with a name of their own are wrapped in this element: one array of a document can then be wrapped
216 // differently from another, as a schema of someone else's wants (UPnP's <actionList><action>, beside its
217 // <allowedValueList><allowedValue>)
218 StartTag_ (name, declare, &scope, indentLevel);
219 fOut.Write ('>');
220 if (not items.empty ()) {
221 NewLine_ ();
222 for (const VariantValue& i : items) {
223 WriteElement (*itemBinding->fName, i, itemBinding, scope, indentLevel + 1);
224 }
225 Indent_ (indentLevel);
226 }
227 EndTag_ (name, scope);
228 return;
229 }
230 StartTag_ (name, declare, &scope, indentLevel);
232 optional<VariantValue> textMember; // a member the Binding says is this element's text, not a child element
233 if (v.GetType () == VariantValue::eMap) {
234 members = v.As<Mapping<String, VariantValue>> ();
235 if (binding != nullptr) {
236 for (const Binding::Member& i : binding->fMembers) {
237 optional<VariantValue> mv = members.Lookup (i.fKey);
238 if (not mv or OmitNull_ (*mv, i.fBinding.get ())) {
239 continue;
240 }
241 if (binding->KindFor (i.fKey) != Binding::Kind::eElement) {
242 ThrowUnlessDeclaredType_ (i.fKey, *mv, i.fBinding.get ()); // an element member's, WriteElement checks
243 }
244 switch (binding->KindFor (i.fKey)) {
245 case Binding::Kind::eAttribute: {
246 NameWithNamespace attrName = binding->NameFor (i.fKey);
247 // an attribute in a namespace is writable only with a prefix: the Binding and its prefixes are
248 // both the caller's, so this is its contract, not anything the value could do
249 Require (not attrName.fNamespace or scope.fPrefixes.ContainsKey (*attrName.fNamespace));
250 fOut.Write (' ');
251 WriteName_ (attrName, scope);
252 fOut.Write ("=\""sv);
253 fOut.Write (String{QuoteForXMLAttribute (mv->As<String> ())});
254 fOut.Write ('"');
255 } break;
256 case Binding::Kind::eText:
257 textMember = mv;
258 break;
259 default:
260 break;
261 }
262 }
263 }
264 }
265 fOut.Write ('>');
266 // a text member is this element's text beside its attributes - an empty one written as nothing - not beside child
267 // elements: that is mixed content, which the Reader cannot read back, so v throws rather than lose them
268 String text = textMember ? textMember->As<String> () : String{};
269 if (text.empty ()) {
270 WriteContent_ (v, members, binding, scope, indentLevel);
271 }
272 else {
273 if (not ElementMembers_ (members, binding).empty ()) {
274 Execution::Throw (BadFormatException{
275 "'{}' has text (Binding::Kind::eText) beside child elements - mixed content, which cannot be read back"_f(name.fName)});
276 }
277 fOut.Write (String{QuoteForXML (text)});
278 }
279 EndTag_ (name, scope);
280 }
281 void WriteContent_ (const VariantValue& v, const Mapping<String, VariantValue>& members, const Binding* binding, const Scope_& scope, int indentLevel)
282 {
283 switch (v.GetType ()) {
284 case VariantValue::eNull:
285 break; // an empty element: XML has no null
286 case VariantValue::eMap: {
287 Sequence<Common::KeyValuePair<String, VariantValue>> elements = ElementMembers_ (members, binding);
288 if (not elements.empty ()) {
289 NewLine_ ();
290 for (const auto& i : elements) {
291 const Binding* memberBinding = binding == nullptr ? nullptr : binding->MemberBinding (i.fKey);
292 NameWithNamespace memberName = binding == nullptr ? NameWithNamespace{i.fKey} : binding->NameFor (i.fKey);
293 if (memberBinding != nullptr and memberBinding->fKind == Binding::Kind::ePresenceMarker) {
294 // a presence marker ElementMembers_ gave is true: an empty element says so by being there
295 Scope_ markerScope = scope;
296 StartTag_ (memberName, {}, &markerScope, indentLevel + 1);
297 fOut.Write ('>');
298 EndTag_ (memberName, markerScope);
299 continue;
300 }
301 WriteElement (memberName, i.fValue, memberBinding, scope, indentLevel + 1);
302 }
303 Indent_ (indentLevel);
304 }
305 } break;
306 case VariantValue::eArray:
307 AssertNotReached (); // WriteElement handles an array, needing its element's name
308 break;
309 default:
310 fOut.Write (String{QuoteForXML (v.As<String> ())});
311 break;
312 }
313 }
314 };
315}
316
317/*
318 ********************************************************************************
319 ************************** Variant::XML::Writer ********************************
320 ********************************************************************************
321 */
322class Variant::XML::Writer::Rep_ final : public Variant::Writer::_IRep, public Memory::UseBlockAllocationIfAppropriate<Rep_> {
323public:
324 Rep_ (const Options& options)
325 : fOptions_{options}
326 {
327 }
328 virtual optional<filesystem::path> GetDefaultFileSuffix () const override
329 {
330 return ".xml"sv;
331 }
332 virtual void Write (const VariantValue& v, const Streams::OutputStream::Ptr<byte>& out) const override
333 {
334 // the byte overload writes a document, so it declares the encoding it is writing in
335 OutputStream::Ptr<Character> useOut = TextToBinary::Writer::New (out, UnicodeExternalEncodings::eUTF8, ByteOrderMark::eDontInclude);
336 useOut.Write ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"sv);
337 if (fOptions_.fPrettyPrint) {
338 useOut.Write ("\n"sv);
339 }
340 Write_ (v, useOut);
341 }
342 virtual void Write (const VariantValue& v, const Streams::OutputStream::Ptr<Character>& out) const override
343 {
344 // no declaration here: the caller owns the encoding, and may be writing this into a document of its own
345 Write_ (v, out);
346 }
347 nonvirtual void Write_ (const VariantValue& v, const Streams::OutputStream::Ptr<Character>& out) const
348 {
349 StringBuilder indent;
350 for (unsigned int i = 0; i < fOptions_.fSpacesPerIndent; ++i) {
351 indent << " "sv;
352 }
353 Writer_ w{out, fOptions_.fPrettyPrint, indent.str ()};
354 Writer_::Scope_ document;
355 const Binding& binding = fOptions_.fBinding;
356 if (binding.fName) {
357 // the Binding names the document element, so it IS that element's, and the value is its content
358 w.WriteElement (*binding.fName, v, &binding, document, 0);
359 }
360 else {
361 // no name: the value's one member is the document element, and the top level Binding describes the value - so its default
362 // array items are in scope there, and its prefixes declared there. A document has exactly one root element; a value
363 // with no single member to be it throws, as any value that cannot be written does - it is data, not code
365 if (v.GetType () == VariantValue::eMap) {
366 members = v.As<Mapping<String, VariantValue>> ();
367 }
368 if (members.size () != 1) {
369 static const auto kException_ =
370 BadFormatException{"a document has exactly one root element: give the Binding an fName, or write an object of exactly one member"sv};
371 Execution::Throw (kException_);
372 }
373 if (binding.fDefaultArrayItems != nullptr) {
374 document.fDefaultArrayItems = binding.fDefaultArrayItems.get ();
375 }
376 for (const auto& i : members) {
377 w.WriteElement (binding.NameFor (i.fKey), i.fValue, binding.MemberBinding (i.fKey), document, 0, binding.fNamespacePrefixes);
378 }
379 }
380 }
381
382private:
383 Options fOptions_;
384};
385
386#if qCompilerAndStdLib_default_member_initializer_needed_within_enclosing_class_Buggy
387Variant::XML::Writer::Writer ()
388 : Writer{Options{}}
389{
390}
391#endif
392
393Variant::XML::Writer::Writer (const Options& options)
394 : inherited{MakeSharedPtr<Rep_> (options)}
395{
396}
#define AssertNotReached()
Definition Assertions.h:358
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
conditional_t< qStroika_Foundation_Memory_PreferBlockAllocation and andTrueCheck, BlockAllocationUseHelper< T >, Common::Empty > UseBlockAllocationIfAppropriate
Use this to enable block allocation for a particular class. Beware of subclassing.
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:186
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
Definition Mapping.inl:142
nonvirtual void RemoveAll()
RemoveAll removes all, or all matching (predicate, iterator range, equals comparer or whatever) items...
Definition Mapping.inl:238
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual void Append(ArgByValueType< value_type > item)
Definition Sequence.inl:615
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
Write a VariantValue as XML - an object's member is an element, an array is repeated elements.
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.
nonvirtual void Write(span< ELEMENT_TYPE2, EXTENT_2 > elts) const
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:311
nonvirtual bool empty() const
Returns true iff size() == 0.
Definition Iterable.inl:317
Ptr New(const Streams::OutputStream::Ptr< byte > &src, const Characters::CodeCvt<> &char2OutputConverter)
One member of an object: its key, and how that member differs from the default shape (nothing,...
Definition Binding.h:93
How a VariantValue is to be written as XML, and read back, where the default shape (.
Definition Binding.h:88
static const Binding kDefaultArrayItems
The Binding each item of an array is written with where neither fItems nor fDefaultArrayItems gives o...
Definition Binding.h:278
nonvirtual Kind KindFor(const String &key) const
That member's kind: its Binding's, else an element.
Definition Binding.cpp:220
nonvirtual NameWithNamespace NameFor(const String &key) const
The name that member is written with: its Binding's fName, else its own key.
Definition Binding.cpp:212
nonvirtual UndeclaredMembers GetUndeclaredMembers() const
fUndeclaredMembers, or its default: eReject where fType is declared, eKeep where not
Definition Binding.cpp:246
Mapping< URI, String > fNamespacePrefixes
A prefix for each of these namespaces, declared on this element (xmlns:p=) and used for every element...
Definition Binding.h:312
nonvirtual const Binding * MemberBinding(const String &key) const
The Binding for the member of this object with that key - nullptr where it has none,...
Definition Binding.cpp:202
shared_ptr< Binding > fDefaultArrayItems
The Binding each item of an array is written with, where the array's own Binding has no fItems: its f...
Definition Binding.h:271
An element's or attribute's name (local-name, not including a prefix), plus the namespace that name i...
Definition Namespace.h:63
String fName
The LOCAL name - "Why" for <x:Why>
Definition Namespace.h:67
optional< URI > fNamespace
The namespace fName is in, however the document said so - nullopt for a name in no namespace.
Definition Namespace.h:72
How XML is read and written - for now, with which provider - taken last by each XML function (DOM::Do...