/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 "&" |
311 | 0 | final_attribute_value = MUST(final_attribute_value.replace("&"sv, "&"sv, ReplaceMode::All)); |
312 | | |
313 | | // 2. """ with """ |
314 | 0 | final_attribute_value = MUST(final_attribute_value.replace("\""sv, """sv, ReplaceMode::All)); |
315 | | |
316 | | // 3. "<" with "<" |
317 | 0 | final_attribute_value = MUST(final_attribute_value.replace("<"sv, "<"sv, ReplaceMode::All)); |
318 | | |
319 | | // 4. ">" with ">" |
320 | 0 | final_attribute_value = MUST(final_attribute_value.replace(">"sv, ">"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 "&". |
775 | 0 | markup = MUST(markup.replace("&"sv, "&"sv, ReplaceMode::All)); |
776 | | |
777 | | // 4. Replace any occurrences of "<" in markup by "<". |
778 | 0 | markup = MUST(markup.replace("<"sv, "<"sv, ReplaceMode::All)); |
779 | | |
780 | | // 5. Replace any occurrences of ">" in markup by ">". |
781 | 0 | markup = MUST(markup.replace(">"sv, ">"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 | | } |