Coverage Report

Created: 2026-09-26 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/DOMParsing/XMLSerializer.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibWeb/Bindings/XMLSerializerPrototype.h>
8
#include <LibWeb/DOM/Attr.h>
9
#include <LibWeb/DOM/CDATASection.h>
10
#include <LibWeb/DOM/Comment.h>
11
#include <LibWeb/DOM/Document.h>
12
#include <LibWeb/DOM/DocumentFragment.h>
13
#include <LibWeb/DOM/DocumentType.h>
14
#include <LibWeb/DOM/Element.h>
15
#include <LibWeb/DOM/NamedNodeMap.h>
16
#include <LibWeb/DOM/Node.h>
17
#include <LibWeb/DOM/ProcessingInstruction.h>
18
#include <LibWeb/DOM/Text.h>
19
#include <LibWeb/DOMParsing/XMLSerializer.h>
20
#include <LibWeb/HTML/HTMLTemplateElement.h>
21
#include <LibWeb/Infra/Strings.h>
22
#include <LibWeb/Namespace.h>
23
#include <LibWeb/WebIDL/ExceptionOr.h>
24
25
namespace Web::DOMParsing {
26
27
JS_DEFINE_ALLOCATOR(XMLSerializer);
28
29
WebIDL::ExceptionOr<JS::NonnullGCPtr<XMLSerializer>> XMLSerializer::construct_impl(JS::Realm& realm)
30
0
{
31
0
    return realm.heap().allocate<XMLSerializer>(realm, realm);
32
0
}
33
34
XMLSerializer::XMLSerializer(JS::Realm& realm)
35
0
    : PlatformObject(realm)
36
0
{
37
0
}
38
39
0
XMLSerializer::~XMLSerializer() = default;
40
41
void XMLSerializer::initialize(JS::Realm& realm)
42
0
{
43
0
    Base::initialize(realm);
44
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(XMLSerializer);
45
0
}
46
47
// https://w3c.github.io/DOM-Parsing/#dom-xmlserializer-serializetostring
48
WebIDL::ExceptionOr<String> XMLSerializer::serialize_to_string(JS::NonnullGCPtr<DOM::Node const> root)
49
0
{
50
    // The serializeToString(root) method must produce an XML serialization of root passing a value of false for the require well-formed parameter, and return the result.
51
0
    return serialize_node_to_xml_string(root, RequireWellFormed::No);
52
0
}
53
54
// https://w3c.github.io/DOM-Parsing/#dfn-add
55
static void add_prefix_to_namespace_prefix_map(HashMap<FlyString, Vector<Optional<FlyString>>>& prefix_map, Optional<FlyString> const& prefix, Optional<FlyString> const& namespace_)
56
0
{
57
    // 1. Let candidates list be the result of retrieving a list from map where there exists a key in map that matches the value of ns or if there is no such key, then let candidates list be null.
58
0
    auto candidates_list_iterator = namespace_.has_value() ? prefix_map.find(*namespace_) : prefix_map.end();
59
60
    // 2. If candidates list is null, then create a new list with prefix as the only item in the list, and associate that list with a new key ns in map.
61
0
    if (candidates_list_iterator == prefix_map.end()) {
62
0
        Vector<Optional<FlyString>> new_list;
63
0
        new_list.append(prefix);
64
0
        prefix_map.set(*namespace_, move(new_list));
65
0
        return;
66
0
    }
67
68
    // 3. Otherwise, append prefix to the end of candidates list.
69
0
    candidates_list_iterator->value.append(prefix);
70
0
}
71
72
// https://w3c.github.io/DOM-Parsing/#dfn-retrieving-a-preferred-prefix-string
73
static Optional<FlyString> retrieve_a_preferred_prefix_string(Optional<FlyString> const& preferred_prefix, HashMap<FlyString, Vector<Optional<FlyString>>> const& namespace_prefix_map, Optional<FlyString> const& namespace_)
74
0
{
75
    // 1. Let candidates list be the result of retrieving a list from map where there exists a key in map that matches the value of ns or if there is no such key,
76
    //    then stop running these steps, and return the null value.
77
0
    if (!namespace_.has_value())
78
0
        return {};
79
0
    auto candidates_list_iterator = namespace_prefix_map.find(*namespace_);
80
0
    if (candidates_list_iterator == namespace_prefix_map.end())
81
0
        return {};
82
83
    // 2. Otherwise, for each prefix value prefix in candidates list, iterating from beginning to end:
84
0
    for (size_t prefix_index = 0; prefix_index < candidates_list_iterator->value.size(); ++prefix_index) {
85
0
        auto const& prefix = candidates_list_iterator->value.at(prefix_index);
86
87
        // 1. If prefix matches preferred prefix, then stop running these steps and return prefix.
88
0
        if (prefix == preferred_prefix)
89
0
            return prefix;
90
91
        // 2. If prefix is the last item in the candidates list, then stop running these steps and return prefix.
92
0
        if (prefix_index == candidates_list_iterator->value.size() - 1)
93
0
            return prefix;
94
0
    }
95
96
    // Spec Note: There will always be at least one prefix value in the list.
97
0
    VERIFY_NOT_REACHED();
98
0
}
99
100
// https://w3c.github.io/DOM-Parsing/#dfn-generating-a-prefix
101
static FlyString generate_a_prefix(HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, Optional<FlyString> const& new_namespace, u64& prefix_index)
102
0
{
103
    // 1. Let generated prefix be the concatenation of the string "ns" and the current numerical value of prefix index.
104
0
    auto generated_prefix = FlyString(MUST(String::formatted("ns{}", prefix_index)));
105
106
    // 2. Let the value of prefix index be incremented by one.
107
0
    ++prefix_index;
108
109
    // 3. Add to map the generated prefix given the new namespace namespace.
110
0
    add_prefix_to_namespace_prefix_map(namespace_prefix_map, generated_prefix, new_namespace);
111
112
    // 4. Return the value of generated prefix.
113
0
    return generated_prefix;
114
0
}
115
116
// https://w3c.github.io/DOM-Parsing/#dfn-found
117
static bool prefix_is_in_prefix_map(FlyString const& prefix, HashMap<FlyString, Vector<Optional<FlyString>>> const& namespace_prefix_map, Optional<FlyString> const& namespace_)
118
0
{
119
    // 1. Let candidates list be the result of retrieving a list from map where there exists a key in map that matches the value of ns
120
    //    or if there is no such key, then stop running these steps, and return false.
121
0
    if (!namespace_.has_value())
122
0
        return false;
123
0
    auto candidates_list_iterator = namespace_prefix_map.find(*namespace_);
124
0
    if (candidates_list_iterator == namespace_prefix_map.end())
125
0
        return false;
126
127
    // 2. If the value of prefix occurs at least once in candidates list, return true, otherwise return false.
128
0
    return candidates_list_iterator->value.contains_slow(prefix);
129
0
}
130
131
WebIDL::ExceptionOr<String> serialize_node_to_xml_string_impl(JS::NonnullGCPtr<DOM::Node const> root, Optional<FlyString>& namespace_, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, RequireWellFormed require_well_formed);
132
133
// https://w3c.github.io/DOM-Parsing/#dfn-xml-serialization
134
WebIDL::ExceptionOr<String> serialize_node_to_xml_string(JS::NonnullGCPtr<DOM::Node const> root, RequireWellFormed require_well_formed)
135
0
{
136
    // 1. Let namespace be a context namespace with value null. The context namespace tracks the XML serialization algorithm's current default namespace.
137
    //    The context namespace is changed when either an Element Node has a default namespace declaration, or the algorithm generates a default namespace declaration
138
    //    for the Element Node to match its own namespace. The algorithm assumes no namespace (null) to start.
139
0
    Optional<FlyString> namespace_;
140
141
    // 2. Let prefix map be a new namespace prefix map.
142
0
    HashMap<FlyString, Vector<Optional<FlyString>>> prefix_map;
143
144
    // 3. Add the XML namespace with prefix value "xml" to prefix map.
145
0
    add_prefix_to_namespace_prefix_map(prefix_map, "xml"_fly_string, Namespace::XML);
146
147
    // 4. Let prefix index be a generated namespace prefix index with value 1. The generated namespace prefix index is used to generate a new unique prefix value
148
    //    when no suitable existing namespace prefix is available to serialize a node's namespaceURI (or the namespaceURI of one of node's attributes).
149
0
    u64 prefix_index = 1;
150
151
    // 5. Return the result of running the XML serialization algorithm on node passing the context namespace namespace, namespace prefix map prefix map,
152
    //    generated namespace prefix index reference to prefix index, and the flag require well-formed. If an exception occurs during the execution of the algorithm,
153
    //    then catch that exception and throw an "InvalidStateError" DOMException.
154
    // NOTE: InvalidStateError exceptions will be created when needed, as this also allows us to have a specific error message for the exception.
155
0
    return serialize_node_to_xml_string_impl(root, namespace_, prefix_map, prefix_index, require_well_formed);
156
0
}
157
158
static WebIDL::ExceptionOr<String> serialize_element(DOM::Element const& element, Optional<FlyString>& namespace_, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, RequireWellFormed require_well_formed);
159
static WebIDL::ExceptionOr<String> serialize_document(DOM::Document const& document, Optional<FlyString>& namespace_, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, RequireWellFormed require_well_formed);
160
static WebIDL::ExceptionOr<String> serialize_comment(DOM::Comment const& comment, RequireWellFormed require_well_formed);
161
static WebIDL::ExceptionOr<String> serialize_text(DOM::Text const& text, RequireWellFormed require_well_formed);
162
static WebIDL::ExceptionOr<String> serialize_document_fragment(DOM::DocumentFragment const& document_fragment, Optional<FlyString>& namespace_, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, RequireWellFormed require_well_formed);
163
static WebIDL::ExceptionOr<String> serialize_document_type(DOM::DocumentType const& document_type, RequireWellFormed require_well_formed);
164
static WebIDL::ExceptionOr<String> serialize_processing_instruction(DOM::ProcessingInstruction const& processing_instruction, RequireWellFormed require_well_formed);
165
static WebIDL::ExceptionOr<String> serialize_cdata_section(DOM::CDATASection const& cdata_section, RequireWellFormed require_well_formed);
166
167
// https://w3c.github.io/DOM-Parsing/#dfn-xml-serialization-algorithm
168
WebIDL::ExceptionOr<String> serialize_node_to_xml_string_impl(JS::NonnullGCPtr<DOM::Node const> root, Optional<FlyString>& namespace_, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, RequireWellFormed require_well_formed)
169
0
{
170
    // Each of the following algorithms for producing an XML serialization of a DOM node take as input a node to serialize and the following arguments:
171
    // - A context namespace namespace
172
    // - A namespace prefix map prefix map
173
    // - A generated namespace prefix index prefix index
174
    // - The require well-formed flag
175
176
    // The XML serialization algorithm produces an XML serialization of an arbitrary DOM node node based on the node's interface type.
177
    // Each referenced algorithm is to be passed the arguments as they were received by the caller and return their result to the caller.
178
    // Re-throw any exceptions.
179
    // If node's interface is:
180
181
0
    if (is<DOM::Element>(*root)) {
182
        // -> Element
183
        //    Run the algorithm for XML serializing an Element node node.
184
0
        return serialize_element(static_cast<DOM::Element const&>(*root), namespace_, namespace_prefix_map, prefix_index, require_well_formed);
185
0
    }
186
187
0
    if (is<DOM::Document>(*root)) {
188
        // -> Document
189
        //    Run the algorithm for XML serializing a Document node node.
190
0
        return serialize_document(static_cast<DOM::Document const&>(*root), namespace_, namespace_prefix_map, prefix_index, require_well_formed);
191
0
    }
192
193
0
    if (is<DOM::Comment>(*root)) {
194
        // -> Comment
195
        //    Run the algorithm for XML serializing a Comment node node.
196
0
        return serialize_comment(static_cast<DOM::Comment const&>(*root), require_well_formed);
197
0
    }
198
199
0
    if (is<DOM::Text>(*root)) {
200
        // -> Text
201
        //    Run the algorithm for XML serializing a Text node node.
202
0
        return serialize_text(static_cast<DOM::Text const&>(*root), require_well_formed);
203
0
    }
204
205
0
    if (is<DOM::DocumentFragment>(*root)) {
206
        // -> DocumentFragment
207
        //    Run the algorithm for XML serializing a DocumentFragment node node.
208
0
        return serialize_document_fragment(static_cast<DOM::DocumentFragment const&>(*root), namespace_, namespace_prefix_map, prefix_index, require_well_formed);
209
0
    }
210
211
0
    if (is<DOM::DocumentType>(*root)) {
212
        // -> DocumentType
213
        //    Run the algorithm for XML serializing a DocumentType node node.
214
0
        return serialize_document_type(static_cast<DOM::DocumentType const&>(*root), require_well_formed);
215
0
    }
216
217
0
    if (is<DOM::ProcessingInstruction>(*root)) {
218
        // -> ProcessingInstruction
219
        //    Run the algorithm for XML serializing a ProcessingInstruction node node.
220
0
        return serialize_processing_instruction(static_cast<DOM::ProcessingInstruction const&>(*root), require_well_formed);
221
0
    }
222
223
0
    if (is<DOM::CDATASection>(*root)) {
224
        // Note: Serialization of CDATASection nodes is not mentioned in the specification, but treating CDATASection nodes as
225
        // text leads to incorrect serialization.
226
0
        return serialize_cdata_section(static_cast<DOM::CDATASection const&>(*root), require_well_formed);
227
0
    }
228
229
0
    if (is<DOM::Attr>(*root)) {
230
        // -> An Attr object
231
        //    Return an empty string.
232
0
        return String {};
233
0
    }
234
235
    // -> Anything else
236
    //    Throw a TypeError. Only Nodes and Attr objects can be serialized by this algorithm.
237
0
    return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Can only serialize Nodes or Attributes."sv };
238
0
}
239
240
// https://w3c.github.io/DOM-Parsing/#dfn-recording-the-namespace-information
241
static Optional<FlyString> record_namespace_information(DOM::Element const& element, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, HashMap<FlyString, Optional<FlyString>>& local_prefix_map)
242
0
{
243
    // 1. Let default namespace attr value be null.
244
0
    Optional<FlyString> default_namespace_attribute_value;
245
246
    // 2. Main: For each attribute attr in element's attributes, in the order they are specified in the element's attribute list:
247
0
    for (size_t attribute_index = 0; attribute_index < element.attributes()->length(); ++attribute_index) {
248
0
        auto const* attribute = element.attributes()->item(attribute_index);
249
0
        VERIFY(attribute);
250
251
        // 1. Let attribute namespace be the value of attr's namespaceURI value.
252
0
        auto const& attribute_namespace = attribute->namespace_uri();
253
254
        // 2. Let attribute prefix be the value of attr's prefix.
255
0
        auto const& attribute_prefix = attribute->prefix();
256
257
        // 3. If the attribute namespace is the XMLNS namespace, then:
258
0
        if (attribute_namespace == Namespace::XMLNS) {
259
            // 1. If attribute prefix is null, then attr is a default namespace declaration. Set the default namespace attr value to attr's value and stop running these steps,
260
            //    returning to Main to visit the next attribute.
261
0
            if (!attribute_prefix.has_value()) {
262
0
                default_namespace_attribute_value = attribute->value();
263
0
                continue;
264
0
            }
265
266
            // 2. Otherwise, the attribute prefix is not null and attr is a namespace prefix definition. Run the following steps:
267
            // 1. Let prefix definition be the value of attr's localName.
268
0
            auto const& prefix_definition = attribute->local_name();
269
270
            // 2. Let namespace definition be the value of attr's value.
271
0
            Optional<FlyString> namespace_definition = attribute->value();
272
273
            // 3. If namespace definition is the XML namespace, then stop running these steps, and return to Main to visit the next attribute.
274
0
            if (namespace_definition == Namespace::XML)
275
0
                continue;
276
277
            // 4. If namespace definition is the empty string (the declarative form of having no namespace), then let namespace definition be null instead.
278
0
            if (namespace_definition == ""sv)
279
0
                namespace_definition = {};
280
281
            // 5. If prefix definition is found in map given the namespace namespace definition, then stop running these steps, and return to Main to visit the next attribute.
282
0
            if (prefix_is_in_prefix_map(prefix_definition, namespace_prefix_map, namespace_definition))
283
0
                continue;
284
285
            // 6. Add the prefix prefix definition to map given namespace namespace definition.
286
0
            add_prefix_to_namespace_prefix_map(namespace_prefix_map, prefix_definition, namespace_definition);
287
288
            // 7. Add the value of prefix definition as a new key to the local prefixes map, with the namespace definition as the key's value replacing the value of null with the empty string if applicable.
289
0
            local_prefix_map.set(prefix_definition, namespace_definition.value_or(FlyString {}));
290
0
        }
291
0
    }
292
293
    // 3. Return the value of default namespace attr value.
294
0
    return default_namespace_attribute_value;
295
0
}
296
297
// https://w3c.github.io/DOM-Parsing/#dfn-serializing-an-attribute-value
298
static WebIDL::ExceptionOr<String> serialize_an_attribute_value(Optional<FlyString> const& attribute_value, [[maybe_unused]] RequireWellFormed require_well_formed)
299
0
{
300
    // FIXME: 1. If the require well-formed flag is set (its value is true), and attribute value contains characters that are not matched by the XML Char production,
301
    //           then throw an exception; the serialization of this attribute value would fail to produce a well-formed element serialization.
302
303
    // 2. If attribute value is null, then return the empty string.
304
0
    if (!attribute_value.has_value())
305
0
        return String {};
306
307
    // 3. Otherwise, attribute value is a string. Return the value of attribute value, first replacing any occurrences of the following:
308
0
    auto final_attribute_value = attribute_value->to_string();
309
310
    // 1. "&" with "&amp;"
311
0
    final_attribute_value = MUST(final_attribute_value.replace("&"sv, "&amp;"sv, ReplaceMode::All));
312
313
    // 2. """ with "&quot;"
314
0
    final_attribute_value = MUST(final_attribute_value.replace("\""sv, "&quot;"sv, ReplaceMode::All));
315
316
    // 3. "<" with "&lt;"
317
0
    final_attribute_value = MUST(final_attribute_value.replace("<"sv, "&lt;"sv, ReplaceMode::All));
318
319
    // 4. ">" with "&gt;"
320
0
    final_attribute_value = MUST(final_attribute_value.replace(">"sv, "&gt;"sv, ReplaceMode::All));
321
322
0
    return final_attribute_value;
323
0
}
324
325
struct LocalNameSetEntry {
326
    Optional<FlyString> namespace_uri;
327
    FlyString local_name;
328
};
329
330
// https://w3c.github.io/DOM-Parsing/#dfn-xml-serialization-of-the-attributes
331
static WebIDL::ExceptionOr<String> serialize_element_attributes(DOM::Element const& element, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, HashMap<FlyString, Optional<FlyString>> const& local_prefixes_map, bool ignore_namespace_definition_attribute, RequireWellFormed require_well_formed)
332
0
{
333
0
    auto& realm = element.realm();
334
335
    // 1. Let result be the empty string.
336
0
    StringBuilder result;
337
338
    // 2. Let localname set be a new empty namespace localname set. This localname set will contain tuples of unique attribute namespaceURI and localName pairs, and is populated as each attr is processed.
339
    // Spec Note: This set is used to [optionally] enforce the well-formed constraint that an element cannot have two attributes with the same namespaceURI and localName.
340
    //            This can occur when two otherwise identical attributes on the same element differ only by their prefix values.
341
0
    Vector<LocalNameSetEntry> local_name_set;
342
343
    // 3. Loop: For each attribute attr in element's attributes, in the order they are specified in the element's attribute list:
344
0
    for (size_t attribute_index = 0; attribute_index < element.attributes()->length(); ++attribute_index) {
345
0
        auto const* attribute = element.attributes()->item(attribute_index);
346
0
        VERIFY(attribute);
347
348
        // 1. If the require well-formed flag is set (its value is true), and the localname set contains a tuple whose values match those of a new tuple consisting of attr's namespaceURI attribute and localName attribute,
349
        //      then throw an exception; the serialization of this attr would fail to produce a well-formed element serialization.
350
0
        if (require_well_formed == RequireWellFormed::Yes) {
351
0
            auto local_name_set_iterator = local_name_set.find_if([&attribute](LocalNameSetEntry const& entry) {
352
0
                return entry.namespace_uri == attribute->namespace_uri() && entry.local_name == attribute->local_name();
353
0
            });
354
355
0
            if (local_name_set_iterator != local_name_set.end())
356
0
                return WebIDL::InvalidStateError::create(realm, "Element contains two attributes with identical namespaces and local names"_string);
357
0
        }
358
359
        // 2. Create a new tuple consisting of attr's namespaceURI attribute and localName attribute, and add it to the localname set.
360
0
        LocalNameSetEntry new_local_name_set_entry {
361
0
            .namespace_uri = attribute->namespace_uri(),
362
0
            .local_name = attribute->local_name(),
363
0
        };
364
365
0
        local_name_set.append(move(new_local_name_set_entry));
366
367
        // 3. Let attribute namespace be the value of attr's namespaceURI value.
368
0
        auto const& attribute_namespace = attribute->namespace_uri();
369
370
        // 4. Let candidate prefix be null.
371
0
        Optional<FlyString> candidate_prefix;
372
373
        // 5. If attribute namespace is not null, then run these sub-steps:
374
0
        if (attribute_namespace.has_value()) {
375
            // 1. Let candidate prefix be the result of retrieving a preferred prefix string from map given namespace attribute namespace with preferred prefix being attr's prefix value.
376
0
            candidate_prefix = retrieve_a_preferred_prefix_string(attribute->prefix(), namespace_prefix_map, attribute->namespace_uri());
377
378
            // 2. If the value of attribute namespace is the XMLNS namespace, then run these steps:
379
0
            if (attribute_namespace == Namespace::XMLNS) {
380
                // 1. If any of the following are true, then stop running these steps and goto Loop to visit the next attribute:
381
                // - the attr's value is the XML namespace;
382
0
                if (attribute->value() == Namespace::XML)
383
0
                    continue;
384
385
                // - the attr's prefix is null and the ignore namespace definition attribute flag is true (the Element's default namespace attribute should be skipped);
386
0
                if (!attribute->prefix().has_value() && ignore_namespace_definition_attribute)
387
0
                    continue;
388
389
                // - the attr's prefix is not null and either
390
0
                if (attribute->prefix().has_value()) {
391
                    // - the attr's localName is not a key contained in the local prefixes map, or
392
0
                    auto name_in_local_prefix_map_iterator = local_prefixes_map.find(attribute->local_name());
393
0
                    if (name_in_local_prefix_map_iterator == local_prefixes_map.end())
394
0
                        continue;
395
396
                    // - the attr's localName is present in the local prefixes map but the value of the key does not match attr's value
397
0
                    if (name_in_local_prefix_map_iterator->value != attribute->value())
398
0
                        continue;
399
0
                }
400
401
                // and furthermore that the attr's localName (as the prefix to find) is found in the namespace prefix map given the namespace consisting of the attr's value
402
                // (the current namespace prefix definition was exactly defined previously--on an ancestor element not the current element whose attributes are being processed).
403
0
                if (prefix_is_in_prefix_map(attribute->local_name(), namespace_prefix_map, attribute->value()))
404
0
                    continue;
405
406
                // 2. If the require well-formed flag is set (its value is true), and the value of attr's value attribute matches the XMLNS namespace,
407
                //    then throw an exception; the serialization of this attribute would produce invalid XML because the XMLNS namespace is reserved and cannot be applied as an element's namespace via XML parsing.
408
0
                if (require_well_formed == RequireWellFormed::Yes && attribute->value() == Namespace::XMLNS)
409
0
                    return WebIDL::InvalidStateError::create(realm, "The XMLNS namespace cannot be used as an element's namespace"_string);
410
411
                // 3. If the require well-formed flag is set (its value is true), and the value of attr's value attribute is the empty string,
412
                //    then throw an exception; namespace prefix declarations cannot be used to undeclare a namespace (use a default namespace declaration instead).
413
0
                if (require_well_formed == RequireWellFormed::Yes && attribute->value().is_empty())
414
0
                    return WebIDL::InvalidStateError::create(realm, "Attribute's value is empty"_string);
415
416
                // 4. [If] the attr's prefix matches the string "xmlns", then let candidate prefix be the string "xmlns".
417
0
                if (attribute->prefix() == "xmlns"sv)
418
0
                    candidate_prefix = "xmlns"_fly_string;
419
0
            }
420
421
            // 3. Otherwise, the attribute namespace in not the XMLNS namespace. Run these steps:
422
0
            else {
423
                // 1. Let candidate prefix be the result of generating a prefix providing map, attribute namespace, and prefix index as input.
424
0
                candidate_prefix = generate_a_prefix(namespace_prefix_map, attribute->namespace_uri(), prefix_index);
425
426
                // 2. Append the following to result, in the order listed:
427
                // 1. " " (U+0020 SPACE);
428
                // 2. The string "xmlns:";
429
0
                result.append(" xmlns:"sv);
430
431
                // 3. The value of candidate prefix;
432
0
                VERIFY(candidate_prefix.has_value());
433
0
                result.append(candidate_prefix.value());
434
435
                // 4. "="" (U+003D EQUALS SIGN, U+0022 QUOTATION MARK);
436
0
                result.append("=\""sv);
437
438
                // 5. The result of serializing an attribute value given attribute namespace and the require well-formed flag as input
439
0
                result.append(TRY(serialize_an_attribute_value(attribute->namespace_uri(), require_well_formed)));
440
441
                // 6. """ (U+0022 QUOTATION MARK).
442
0
                result.append('"');
443
0
            }
444
0
        }
445
446
        // 6. Append a " " (U+0020 SPACE) to result.
447
0
        result.append(' ');
448
449
        // 7. If candidate prefix is not null, then append to result the concatenation of candidate prefix with ":" (U+003A COLON).
450
0
        if (candidate_prefix.has_value())
451
0
            result.appendff("{}:", candidate_prefix.value());
452
453
        // 8. If the require well-formed flag is set (its value is true), and this attr's localName attribute contains the character ":" (U+003A COLON)
454
        //    or does not match the XML Name production or equals "xmlns" and attribute namespace is null, then throw an exception; the serialization of this attr would not be a well-formed attribute.
455
0
        if (require_well_formed == RequireWellFormed::Yes) {
456
0
            if (attribute->local_name().bytes_as_string_view().contains(':'))
457
0
                return WebIDL::InvalidStateError::create(realm, "Attribute's local name contains a colon"_string);
458
459
            // FIXME: Check attribute's local name against the XML Name production.
460
461
0
            if (attribute->local_name() == "xmlns"sv && !attribute->namespace_uri().has_value())
462
0
                return WebIDL::InvalidStateError::create(realm, "Attribute's local name is 'xmlns' and the attribute has no namespace"_string);
463
0
        }
464
465
        // 9. Append the following strings to result, in the order listed:
466
        // 1. The value of attr's localName;
467
0
        result.append(attribute->local_name());
468
469
        // 2. "="" (U+003D EQUALS SIGN, U+0022 QUOTATION MARK);
470
0
        result.append("=\""sv);
471
472
        // 3. The result of serializing an attribute value given attr's value attribute and the require well-formed flag as input;
473
0
        result.append(TRY(serialize_an_attribute_value(attribute->value(), require_well_formed)));
474
475
        // 4. """ (U+0022 QUOTATION MARK).
476
0
        result.append('"');
477
0
    }
478
479
    // 4. Return the value of result.
480
0
    return MUST(result.to_string());
481
0
}
482
483
// https://w3c.github.io/DOM-Parsing/#xml-serializing-an-element-node
484
static WebIDL::ExceptionOr<String> serialize_element(DOM::Element const& element, Optional<FlyString>& namespace_, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, RequireWellFormed require_well_formed)
485
0
{
486
0
    auto& realm = element.realm();
487
488
    // 1. If the require well-formed flag is set (its value is true), and this node's localName attribute contains the character ":" (U+003A COLON) or does not match the XML Name production,
489
    //    then throw an exception; the serialization of this node would not be a well-formed element.
490
0
    if (require_well_formed == RequireWellFormed::Yes) {
491
0
        if (element.local_name().bytes_as_string_view().contains(':'))
492
0
            return WebIDL::InvalidStateError::create(realm, "Element's local name contains a colon"_string);
493
494
        // FIXME: Check element's local name against the XML Char production.
495
0
    }
496
497
    // 2. Let markup be the string "<" (U+003C LESS-THAN SIGN).
498
0
    StringBuilder markup;
499
0
    markup.append('<');
500
501
    // 3. Let qualified name be an empty string.
502
0
    StringBuilder qualified_name;
503
504
    // 4. Let skip end tag be a boolean flag with value false.
505
0
    bool skip_end_tag = false;
506
507
    // 5. Let ignore namespace definition attribute be a boolean flag with value false.
508
0
    bool ignore_namespace_definition_attribute = false;
509
510
    // 6. Given prefix map, copy a namespace prefix map and let map be the result.
511
0
    HashMap<FlyString, Vector<Optional<FlyString>>> map;
512
513
    // https://w3c.github.io/DOM-Parsing/#dfn-copy-a-namespace-prefix-map
514
    // NOTE: This is only used here.
515
    // To copy a namespace prefix map map means to copy the map's keys into a new empty namespace prefix map,
516
    // and to copy each of the values in the namespace prefix list associated with each keys' value into a new list
517
    // which should be associated with the respective key in the new map.
518
0
    for (auto const& map_entry : namespace_prefix_map)
519
0
        map.set(map_entry.key, map_entry.value);
520
521
    // 7. Let local prefixes map be an empty map. The map has unique Node prefix strings as its keys, with corresponding namespaceURI Node values
522
    //    as the map's key values (in this map, the null namespace is represented by the empty string).
523
0
    HashMap<FlyString, Optional<FlyString>> local_prefixes_map;
524
525
    // 8. Let local default namespace be the result of recording the namespace information for node given map and local prefixes map.
526
0
    auto local_default_namespace = record_namespace_information(element, map, local_prefixes_map);
527
528
    // 9. Let inherited ns be a copy of namespace.
529
0
    auto inherited_ns = namespace_;
530
531
    // 10. Let ns be the value of node's namespaceURI attribute.
532
0
    auto const& ns = element.namespace_uri();
533
534
    // 11. If inherited ns is equal to ns, then:
535
0
    if (inherited_ns == ns) {
536
        // 1. If local default namespace is not null, then set ignore namespace definition attribute to true.
537
0
        if (local_default_namespace.has_value())
538
0
            ignore_namespace_definition_attribute = true;
539
540
        // 2. If ns is the XML namespace, then append to qualified name the concatenation of the string "xml:" and the value of node's localName.
541
0
        if (ns == Namespace::XML)
542
0
            qualified_name.appendff("xml:{}", element.local_name());
543
544
        // 3. Otherwise, append to qualified name the value of node's localName.
545
0
        else
546
0
            qualified_name.append(element.local_name());
547
548
        // 4. Append the value of qualified name to markup.
549
0
        markup.append(qualified_name.string_view());
550
0
    }
551
552
    // 12. Otherwise, inherited ns is not equal to ns (the node's own namespace is different from the context namespace of its parent). Run these sub-steps:
553
0
    else {
554
        // 1. Let prefix be the value of node's prefix attribute.
555
0
        auto prefix = element.prefix();
556
557
        // 2. Let candidate prefix be the result of retrieving a preferred prefix string prefix from map given namespace ns.
558
0
        auto candidate_prefix = retrieve_a_preferred_prefix_string(prefix, map, ns);
559
560
        // 3. If the value of prefix matches "xmlns", then run the following steps:
561
0
        if (prefix == "xmlns"sv) {
562
            // 1. If the require well-formed flag is set, then throw an error. An Element with prefix "xmlns" will not legally round-trip in a conforming XML parser.
563
0
            if (require_well_formed == RequireWellFormed::Yes)
564
0
                return WebIDL::InvalidStateError::create(realm, "Elements prefix is 'xmlns'"_string);
565
566
            // 2. Let candidate prefix be the value of prefix.
567
0
            candidate_prefix = prefix;
568
0
        }
569
570
        // 4. Found a suitable namespace prefix: if candidate prefix is not null (a namespace prefix is defined which maps to ns), then:
571
0
        if (candidate_prefix.has_value()) {
572
            // 1. Append to qualified name the concatenation of candidate prefix, ":" (U+003A COLON), and node's localName.
573
0
            qualified_name.appendff("{}:{}", candidate_prefix.value(), element.local_name());
574
575
            // 2. If the local default namespace is not null (there exists a locally-defined default namespace declaration attribute) and its value is not the XML namespace,
576
            //   then let inherited ns get the value of local default namespace unless the local default namespace is the empty string in which case let it get null
577
            //   (the context namespace is changed to the declared default, rather than this node's own namespace).
578
0
            if (local_default_namespace.has_value() && local_default_namespace.value() != Namespace::XML) {
579
0
                if (!local_default_namespace.value().is_empty())
580
0
                    inherited_ns = local_default_namespace.value();
581
0
                else
582
0
                    inherited_ns = {};
583
0
            }
584
585
            // 3. Append the value of qualified name to markup.
586
0
            markup.append(qualified_name.string_view());
587
0
        }
588
589
        // 5. Otherwise, if prefix is not null, then:
590
0
        else if (prefix.has_value()) {
591
            // 1. If the local prefixes map contains a key matching prefix, then let prefix be the result of generating a prefix providing as input map, ns, and prefix index.
592
0
            if (local_prefixes_map.contains(*prefix))
593
0
                prefix = generate_a_prefix(map, ns, prefix_index);
594
595
            // 2. Add prefix to map given namespace ns.
596
0
            add_prefix_to_namespace_prefix_map(map, prefix, ns);
597
598
            // 3. Append to qualified name the concatenation of prefix, ":" (U+003A COLON), and node's localName.
599
0
            qualified_name.appendff("{}:{}", prefix, element.local_name());
600
601
            // 4. Append the value of qualified name to markup.
602
0
            markup.append(qualified_name.string_view());
603
604
            // 5. Append the following to markup, in the order listed:
605
            // 1. " " (U+0020 SPACE);
606
            // 2. The string "xmlns:";
607
0
            markup.append(" xmlns:"sv);
608
609
            // 3. The value of prefix;
610
0
            markup.append(prefix.value());
611
612
            // 4. "="" (U+003D EQUALS SIGN, U+0022 QUOTATION MARK);
613
0
            markup.append("=\""sv);
614
615
            // 5. The result of serializing an attribute value given ns and the require well-formed flag as input;
616
0
            markup.append(TRY(serialize_an_attribute_value(ns, require_well_formed)));
617
618
            // 6. """ (U+0022 QUOTATION MARK).
619
0
            markup.append('"');
620
621
            // 7. If local default namespace is not null (there exists a locally-defined default namespace declaration attribute),
622
            //   then let inherited ns get the value of local default namespace unless the local default namespace is the empty string in which case let it get null.
623
0
            if (local_default_namespace.has_value()) {
624
0
                if (!local_default_namespace.value().is_empty())
625
0
                    inherited_ns = local_default_namespace.value();
626
0
                else
627
0
                    inherited_ns = {};
628
0
            }
629
0
        }
630
631
        // 6. Otherwise, if local default namespace is null, or local default namespace is not null and its value is not equal to ns, then:
632
0
        else if (!local_default_namespace.has_value() || local_default_namespace.value() != ns) {
633
            // 1. Set the ignore namespace definition attribute flag to true.
634
0
            ignore_namespace_definition_attribute = true;
635
636
            // 2. Append to qualified name the value of node's localName.
637
0
            qualified_name.append(element.local_name());
638
639
            // 3. Let the value of inherited ns be ns.
640
0
            inherited_ns = ns;
641
642
            // 4. Append the value of qualified name to markup.
643
0
            markup.append(qualified_name.string_view());
644
645
            // 5. Append the following to markup, in the order listed:
646
            // 1. " " (U+0020 SPACE);
647
            // 2. The string "xmlns";
648
            // 3. "="" (U+003D EQUALS SIGN, U+0022 QUOTATION MARK);
649
0
            markup.append(" xmlns=\""sv);
650
651
            // 4. The result of serializing an attribute value given ns and the require well-formed flag as input;
652
0
            markup.append(TRY(serialize_an_attribute_value(ns, require_well_formed)));
653
654
            // 5. """ (U+0022 QUOTATION MARK).
655
0
            markup.append('"');
656
0
        }
657
658
0
        else {
659
            // 7. Otherwise, the node has a local default namespace that matches ns.
660
            //    Append to qualified name the value of node's localName, let the value of inherited ns be ns, and append the value of qualified name to markup.
661
0
            VERIFY(local_default_namespace.has_value());
662
0
            VERIFY(local_default_namespace.value() == ns);
663
664
0
            qualified_name.append(element.local_name());
665
0
            inherited_ns = ns;
666
0
            markup.append(qualified_name.string_view());
667
0
        }
668
0
    }
669
670
    // 13. Append to markup the result of the XML serialization of node's attributes given map, prefix index, local prefixes map, ignore namespace definition attribute flag, and require well-formed flag.
671
0
    markup.append(TRY(serialize_element_attributes(element, map, prefix_index, local_prefixes_map, ignore_namespace_definition_attribute, require_well_formed)));
672
673
    // 14. If ns is the HTML namespace, and the node's list of children is empty, and the node's localName matches any one of the following void elements:
674
    //    "area", "base", "basefont", "bgsound", "br", "col", "embed", "frame", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr";
675
    //    then append the following to markup, in the order listed:
676
0
    if (ns == Namespace::HTML && !element.has_children() && element.local_name().is_one_of(HTML::TagNames::area, HTML::TagNames::area, HTML::TagNames::base, HTML::TagNames::basefont, HTML::TagNames::bgsound, HTML::TagNames::br, HTML::TagNames::col, HTML::TagNames::embed, HTML::TagNames::frame, HTML::TagNames::hr, HTML::TagNames::img, HTML::TagNames::input, HTML::TagNames::keygen, HTML::TagNames::link, HTML::TagNames::menuitem, HTML::TagNames::meta, HTML::TagNames::param, HTML::TagNames::source, HTML::TagNames::track, HTML::TagNames::wbr)) {
677
        // 1. " " (U+0020 SPACE);
678
        // 2. "/" (U+002F SOLIDUS).
679
0
        markup.append(" /"sv);
680
681
        // and set the skip end tag flag to true.
682
0
        skip_end_tag = true;
683
0
    }
684
685
    // 15. If ns is not the HTML namespace, and the node's list of children is empty, then append "/" (U+002F SOLIDUS) to markup and set the skip end tag flag to true.
686
0
    if (ns != Namespace::HTML && !element.has_children()) {
687
0
        markup.append('/');
688
0
        skip_end_tag = true;
689
0
    }
690
691
    // 16. Append ">" (U+003E GREATER-THAN SIGN) to markup.
692
0
    markup.append('>');
693
694
    // 17. If the value of skip end tag is true, then return the value of markup and skip the remaining steps. The node is a leaf-node.
695
0
    if (skip_end_tag)
696
0
        return MUST(markup.to_string());
697
698
    // 18. If ns is the HTML namespace, and the node's localName matches the string "template", then this is a template element.
699
0
    if (ns == Namespace::HTML && element.local_name() == HTML::TagNames::template_) {
700
        // Append to markup the result of XML serializing a DocumentFragment node given the template element's template contents (a DocumentFragment), providing inherited ns, map, prefix index, and the require well-formed flag.
701
0
        auto const& template_element = verify_cast<HTML::HTMLTemplateElement>(element);
702
0
        markup.append(TRY(serialize_document_fragment(template_element.content(), inherited_ns, map, prefix_index, require_well_formed)));
703
0
    }
704
705
    // 19. Otherwise, append to markup the result of running the XML serialization algorithm on each of node's children, in tree order, providing inherited ns, map, prefix index, and the require well-formed flag.
706
0
    else {
707
0
        for (auto const* element_child = element.first_child(); element_child; element_child = element_child->next_sibling())
708
0
            markup.append(TRY(serialize_node_to_xml_string_impl(*element_child, inherited_ns, map, prefix_index, require_well_formed)));
709
0
    }
710
711
    // 20. Append the following to markup, in the order listed:
712
    // 1. "</" (U+003C LESS-THAN SIGN, U+002F SOLIDUS);
713
0
    markup.append("</"sv);
714
715
    // 2. The value of qualified name;
716
0
    markup.append(qualified_name.string_view());
717
718
    // 3. ">" (U+003E GREATER-THAN SIGN).
719
0
    markup.append('>');
720
721
    // 21. Return the value of markup.
722
0
    return MUST(markup.to_string());
723
0
}
724
725
// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-document-node
726
static WebIDL::ExceptionOr<String> serialize_document(DOM::Document const& document, Optional<FlyString>& namespace_, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, RequireWellFormed require_well_formed)
727
0
{
728
    // If the require well-formed flag is set (its value is true), and this node has no documentElement (the documentElement attribute's value is null),
729
    // then throw an exception; the serialization of this node would not be a well-formed document.
730
0
    if (require_well_formed == RequireWellFormed::Yes && !document.document_element())
731
0
        return WebIDL::InvalidStateError::create(document.realm(), "Document has no document element"_string);
732
733
    // Otherwise, run the following steps:
734
    // 1. Let serialized document be an empty string.
735
0
    StringBuilder serialized_document;
736
737
    // 2. For each child child of node, in tree order, run the XML serialization algorithm on the child passing along the provided arguments, and append the result to serialized document.
738
0
    for (auto const* child = document.first_child(); child; child = child->next_sibling())
739
0
        serialized_document.append(TRY(serialize_node_to_xml_string_impl(*child, namespace_, namespace_prefix_map, prefix_index, require_well_formed)));
740
741
    // 3. Return the value of serialized document.
742
0
    return MUST(serialized_document.to_string());
743
0
}
744
745
// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-comment-node
746
static WebIDL::ExceptionOr<String> serialize_comment(DOM::Comment const& comment, RequireWellFormed require_well_formed)
747
0
{
748
    // If the require well-formed flag is set (its value is true), and node's data contains characters that are not matched by the XML Char production
749
    // or contains "--" (two adjacent U+002D HYPHEN-MINUS characters) or that ends with a "-" (U+002D HYPHEN-MINUS) character, then throw an exception;
750
    // the serialization of this node's data would not be well-formed.
751
0
    if (require_well_formed == RequireWellFormed::Yes) {
752
        // FIXME: Check comment's data against the XML Char production.
753
754
0
        if (comment.data().contains("--"sv))
755
0
            return WebIDL::InvalidStateError::create(comment.realm(), "Comment data contains two adjacent hyphens"_string);
756
757
0
        if (comment.data().ends_with('-'))
758
0
            return WebIDL::InvalidStateError::create(comment.realm(), "Comment data ends with a hyphen"_string);
759
0
    }
760
761
    // Otherwise, return the concatenation of "<!--", node's data, and "-->".
762
0
    return MUST(String::formatted("<!--{}-->", comment.data()));
763
0
}
764
765
// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node
766
static WebIDL::ExceptionOr<String> serialize_text(DOM::Text const& text, [[maybe_unused]] RequireWellFormed require_well_formed)
767
0
{
768
    // FIXME: 1. If the require well-formed flag is set (its value is true), and node's data contains characters that are not matched by the XML Char production,
769
    //           then throw an exception; the serialization of this node's data would not be well-formed.
770
771
    // 2. Let markup be the value of node's data.
772
0
    auto markup = text.data();
773
774
    // 3. Replace any occurrences of "&" in markup by "&amp;".
775
0
    markup = MUST(markup.replace("&"sv, "&amp;"sv, ReplaceMode::All));
776
777
    // 4. Replace any occurrences of "<" in markup by "&lt;".
778
0
    markup = MUST(markup.replace("<"sv, "&lt;"sv, ReplaceMode::All));
779
780
    // 5. Replace any occurrences of ">" in markup by "&gt;".
781
0
    markup = MUST(markup.replace(">"sv, "&gt;"sv, ReplaceMode::All));
782
783
    // 6. Return the value of markup.
784
0
    return markup;
785
0
}
786
787
// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-documentfragment-node
788
static WebIDL::ExceptionOr<String> serialize_document_fragment(DOM::DocumentFragment const& document_fragment, Optional<FlyString>& namespace_, HashMap<FlyString, Vector<Optional<FlyString>>>& namespace_prefix_map, u64& prefix_index, RequireWellFormed require_well_formed)
789
0
{
790
    // 1. Let markup the empty string.
791
0
    StringBuilder markup;
792
793
    // 2. For each child child of node, in tree order, run the XML serialization algorithm on the child given namespace, prefix map, a reference to prefix index,
794
    //    and flag require well-formed. Concatenate the result to markup.
795
0
    for (auto const* child = document_fragment.first_child(); child; child = child->next_sibling())
796
0
        markup.append(TRY(serialize_node_to_xml_string_impl(*child, namespace_, namespace_prefix_map, prefix_index, require_well_formed)));
797
798
    // 3. Return the value of markup.
799
0
    return MUST(markup.to_string());
800
0
}
801
802
// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-documenttype-node
803
static WebIDL::ExceptionOr<String> serialize_document_type(DOM::DocumentType const& document_type, RequireWellFormed require_well_formed)
804
0
{
805
0
    if (require_well_formed == RequireWellFormed::Yes) {
806
        // FIXME: 1. If the require well-formed flag is true and the node's publicId attribute contains characters that are not matched by the XML PubidChar production,
807
        //           then throw an exception; the serialization of this node would not be a well-formed document type declaration.
808
809
        // 2. If the require well-formed flag is true and the node's systemId attribute contains characters that are not matched by the XML Char production or that contains
810
        //    both a """ (U+0022 QUOTATION MARK) and a "'" (U+0027 APOSTROPHE), then throw an exception; the serialization of this node would not be a well-formed document type declaration.
811
        // FIXME: Check systemId against the XML Char production.
812
0
        if (document_type.system_id().contains('"') && document_type.system_id().contains('\''))
813
0
            return WebIDL::InvalidStateError::create(document_type.realm(), "Document type system ID contains both a quotation mark and an apostrophe"_string);
814
0
    }
815
816
    // 3. Let markup be an empty string.
817
0
    StringBuilder markup;
818
819
    // 4. Append the string "<!DOCTYPE" to markup.
820
    // 5. Append " " (U+0020 SPACE) to markup.
821
0
    markup.append("<!DOCTYPE "sv);
822
823
    // 6. Append the value of the node's name attribute to markup. For a node belonging to an HTML document, the value will be all lowercase.
824
0
    markup.append(document_type.name());
825
826
    // 7. If the node's publicId is not the empty string then append the following, in the order listed, to markup:
827
0
    if (!document_type.public_id().is_empty()) {
828
        // 1. " " (U+0020 SPACE);
829
        // 2. The string "PUBLIC";
830
        // 3. " " (U+0020 SPACE);
831
        // 4. """ (U+0022 QUOTATION MARK);
832
0
        markup.append(" PUBLIC \""sv);
833
834
        // 5. The value of the node's publicId attribute;
835
0
        markup.append(document_type.public_id());
836
837
        // 6. """ (U+0022 QUOTATION MARK).
838
0
        markup.append('"');
839
0
    }
840
841
    // 8. If the node's systemId is not the empty string and the node's publicId is set to the empty string, then append the following, in the order listed, to markup:
842
0
    if (!document_type.system_id().is_empty() && document_type.public_id().is_empty()) {
843
        // 1. " " (U+0020 SPACE);
844
        // 2. The string "SYSTEM".
845
0
        markup.append(" SYSTEM"sv);
846
0
    }
847
848
    // 9. If the node's systemId is not the empty string then append the following, in the order listed, to markup:
849
0
    if (!document_type.system_id().is_empty()) {
850
        // 1. " " (U+0020 SPACE);
851
        // 2. """ (U+0022 QUOTATION MARK);
852
0
        markup.append(" \""sv);
853
854
        // 3. The value of the node's systemId attribute;
855
0
        markup.append(document_type.system_id());
856
857
        // 4. """ (U+0022 QUOTATION MARK).
858
0
        markup.append('"');
859
0
    }
860
861
    // 10. Append ">" (U+003E GREATER-THAN SIGN) to markup.
862
0
    markup.append('>');
863
864
    // 11. Return the value of markup.
865
0
    return MUST(markup.to_string());
866
0
}
867
868
// https://w3c.github.io/DOM-Parsing/#dfn-xml-serializing-a-processinginstruction-node
869
static WebIDL::ExceptionOr<String> serialize_processing_instruction(DOM::ProcessingInstruction const& processing_instruction, RequireWellFormed require_well_formed)
870
0
{
871
0
    if (require_well_formed == RequireWellFormed::Yes) {
872
        // 1. If the require well-formed flag is set (its value is true), and node's target contains a ":" (U+003A COLON) character
873
        //    or is an ASCII case-insensitive match for the string "xml", then throw an exception; the serialization of this node's target would not be well-formed.
874
0
        if (processing_instruction.target().contains(':'))
875
0
            return WebIDL::InvalidStateError::create(processing_instruction.realm(), "Processing instruction target contains a colon"_string);
876
877
0
        if (Infra::is_ascii_case_insensitive_match(processing_instruction.target(), "xml"sv))
878
0
            return WebIDL::InvalidStateError::create(processing_instruction.realm(), "Processing instruction target is equal to 'xml'"_string);
879
880
        // 2. If the require well-formed flag is set (its value is true), and node's data contains characters that are not matched by the XML Char production or contains
881
        //    the string "?>" (U+003F QUESTION MARK, U+003E GREATER-THAN SIGN), then throw an exception; the serialization of this node's data would not be well-formed.
882
        // FIXME: Check data against the XML Char production.
883
0
        if (processing_instruction.data().contains("?>"sv))
884
0
            return WebIDL::InvalidStateError::create(processing_instruction.realm(), "Processing instruction data contains a terminator"_string);
885
0
    }
886
887
    // 3. Let markup be the concatenation of the following, in the order listed:
888
0
    StringBuilder markup;
889
890
    // 1. "<?" (U+003C LESS-THAN SIGN, U+003F QUESTION MARK);
891
0
    markup.append("<?"sv);
892
893
    // 2. The value of node's target;
894
0
    markup.append(processing_instruction.target());
895
896
    // 3. " " (U+0020 SPACE);
897
0
    markup.append(' ');
898
899
    // 4. The value of node's data;
900
0
    markup.append(processing_instruction.data());
901
902
    // 5. "?>" (U+003F QUESTION MARK, U+003E GREATER-THAN SIGN).
903
0
    markup.append("?>"sv);
904
905
    // 4. Return the value of markup.
906
0
    return MUST(markup.to_string());
907
0
}
908
909
// FIXME: This is ad-hoc
910
static WebIDL::ExceptionOr<String> serialize_cdata_section(DOM::CDATASection const& cdata_section, RequireWellFormed require_well_formed)
911
0
{
912
0
    if (require_well_formed == RequireWellFormed::Yes && cdata_section.data().contains("]]>"sv))
913
0
        return WebIDL::InvalidStateError::create(cdata_section.realm(), "CDATA section data contains a CDATA section end delimiter"_string);
914
915
0
    StringBuilder markup;
916
0
    markup.append("<![CDATA["sv);
917
0
    markup.append(cdata_section.data());
918
0
    markup.append("]]>"sv);
919
920
0
    return MUST(markup.to_string());
921
0
}
922
923
}