4#include "Stroika/Foundation/StroikaPreComp.h"
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"
25using namespace Stroika::Foundation::DataExchange::XML;
26using namespace Stroika::Foundation::DataExchange::Variant::XML;
27using namespace Stroika::Foundation::Streams;
29using Memory::MakeSharedPtr;
34 void ThrowUnlessNCName_ (
const String& name)
36 if (not IsNCName (name)) {
37 Execution::Throw (
BadFormatException{
"'{}' is not an XML name, so cannot be written as one"_f(name)});
53 optional<URI> fDefaultNamespace;
64 void Indent_ (
int indentLevel)
67 for (
int i = 0; i < indentLevel; ++i) {
76 prefixes->
Add (ns, prefix);
82 return v.GetType () == VariantValue::eNull and memberBinding !=
nullptr and memberBinding->
fType;
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)});
97 if (binding !=
nullptr) {
100 if (
Binding::Kind k = binding->
KindFor (i.fKey); k == Binding::Kind::eElement or k == Binding::Kind::ePresenceMarker) {
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> ())) {
109 for (
const auto& i : members) {
110 if (not named.Contains (i.fKey)) {
119 ThrowUnlessNCName_ (name.
fName);
121 if (optional<String> prefix = scope.fPrefixes.Lookup (*name.
fNamespace)) {
122 fOut.
Write (*prefix);
131 optional<URI> WriteNamespaceIfNew_ (
const NameWithNamespace& name,
const Scope_& scope)
134 return scope.fDefaultNamespace;
136 if (scope.fPrefixes.ContainsKey (*name.
fNamespace)) {
137 return scope.fDefaultNamespace;
139 fOut.
Write (
" xmlns=\""sv);
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);
156 Indent_ (indentLevel);
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))});
166 scope->fDefaultNamespace = WriteNamespaceIfNew_ (name, *scope);
171 WriteName_ (name, scope);
180 Scope_ scope = outer;
182 if (binding !=
nullptr) {
186 for (
const auto& i : binding->fNamespacePrefixes) {
187 BindPrefix_ (&declare, i.fKey, i.fValue);
189 ThrowUnlessDeclaredType_ (name.
fName, v, binding);
190 if (v.GetType () == VariantValue::eMap and binding->
GetUndeclaredMembers () != Binding::UndeclaredMembers::eKeep) {
194 Execution::Throw (
BadFormatException{
"'{}' has a member '{}', which its Binding does not declare"_f(name.
fName, i.fKey)});
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) {
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_);
211 WriteElement (name, i, itemBinding, scope, indentLevel, declare);
218 StartTag_ (name, declare, &scope, indentLevel);
220 if (not items.
empty ()) {
223 WriteElement (*itemBinding->fName, i, itemBinding, scope, indentLevel + 1);
225 Indent_ (indentLevel);
227 EndTag_ (name, scope);
230 StartTag_ (name, declare, &scope, indentLevel);
232 optional<VariantValue> textMember;
233 if (v.GetType () == VariantValue::eMap) {
235 if (binding !=
nullptr) {
237 optional<VariantValue> mv = members.
Lookup (i.fKey);
238 if (not mv or OmitNull_ (*mv, i.fBinding.get ())) {
241 if (binding->
KindFor (i.fKey) != Binding::Kind::eElement) {
242 ThrowUnlessDeclaredType_ (i.fKey, *mv, i.fBinding.get ());
244 switch (binding->
KindFor (i.fKey)) {
245 case Binding::Kind::eAttribute: {
251 WriteName_ (attrName, scope);
252 fOut.
Write (
"=\""sv);
256 case Binding::Kind::eText:
270 WriteContent_ (v, members, binding, scope, indentLevel);
273 if (not ElementMembers_ (members, binding).empty ()) {
275 "'{}' has text (Binding::Kind::eText) beside child elements - mixed content, which cannot be read back"_f(name.
fName)});
279 EndTag_ (name, scope);
283 switch (v.GetType ()) {
284 case VariantValue::eNull:
286 case VariantValue::eMap: {
288 if (not elements.
empty ()) {
290 for (
const auto& i : elements) {
293 if (memberBinding !=
nullptr and memberBinding->fKind == Binding::Kind::ePresenceMarker) {
295 Scope_ markerScope = scope;
296 StartTag_ (memberName, {}, &markerScope, indentLevel + 1);
298 EndTag_ (memberName, markerScope);
301 WriteElement (memberName, i.fValue, memberBinding, scope, indentLevel + 1);
303 Indent_ (indentLevel);
306 case VariantValue::eArray:
328 virtual optional<filesystem::path> GetDefaultFileSuffix ()
const override
336 useOut.
Write (
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"sv);
337 if (fOptions_.fPrettyPrint) {
338 useOut.
Write (
"\n"sv);
350 for (
unsigned int i = 0; i < fOptions_.fSpacesPerIndent; ++i) {
353 Writer_ w{out, fOptions_.fPrettyPrint, indent.
str ()};
354 Writer_::Scope_ document;
355 const Binding& binding = fOptions_.fBinding;
358 w.WriteElement (*binding.
fName, v, &binding, document, 0);
365 if (v.GetType () == VariantValue::eMap) {
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_);
376 for (
const auto& i : members) {
386#if qCompilerAndStdLib_default_member_initializer_needed_within_enclosing_class_Buggy
387Variant::XML::Writer::Writer ()
393Variant::XML::Writer::Writer (
const Options& options)
#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.
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
nonvirtual String str() const
String is like std::u32string, except it is much easier to use, often much more space efficient,...
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)
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
nonvirtual void RemoveAll()
RemoveAll removes all, or all matching (predicate, iterator range, equals comparer or whatever) items...
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual void Append(ArgByValueType< value_type > item)
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
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 ...
nonvirtual RETURNTYPE As() const
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.
nonvirtual bool empty() const
Returns true iff size() == 0.
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,...
How a VariantValue is to be written as XML, and read back, where the default shape (.
static const Binding kDefaultArrayItems
The Binding each item of an array is written with where neither fItems nor fDefaultArrayItems gives o...
Sequence< Member > fMembers
nonvirtual Kind KindFor(const String &key) const
That member's kind: its Binding's, else an element.
nonvirtual NameWithNamespace NameFor(const String &key) const
The name that member is written with: its Binding's fName, else its own key.
optional< VariantValue::Type > fType
nonvirtual UndeclaredMembers GetUndeclaredMembers() const
fUndeclaredMembers, or its default: eReject where fType is declared, eKeep where not
Mapping< URI, String > fNamespacePrefixes
A prefix for each of these namespaces, declared on this element (xmlns:p=) and used for every element...
shared_ptr< Binding > fItems
optional< NameWithNamespace > fName
nonvirtual const Binding * MemberBinding(const String &key) const
The Binding for the member of this object with that key - nullptr where it has none,...
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...
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.
How XML is read and written - for now, with which provider - taken last by each XML function (DOM::Do...