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