blob: de6937918917e2fe9c6d8db16e8668ddfa64dc1a [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2012 Apple Inc. All rights
* reserved.
* Copyright (C) 2009, 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/editing/serializers/markup_accumulator.h"
#include "third_party/blink/renderer/core/dom/attr.h"
#include "third_party/blink/renderer/core/dom/cdata_section.h"
#include "third_party/blink/renderer/core/dom/comment.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/document_fragment.h"
#include "third_party/blink/renderer/core/dom/document_type.h"
#include "third_party/blink/renderer/core/dom/processing_instruction.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_template_element.h"
#include "third_party/blink/renderer/core/xlink_names.h"
#include "third_party/blink/renderer/core/xml_names.h"
#include "third_party/blink/renderer/core/xmlns_names.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
namespace blink {
MarkupAccumulator::MarkupAccumulator(EAbsoluteURLs resolve_urls_method,
SerializationType serialization_type)
: formatter_(resolve_urls_method, serialization_type) {}
MarkupAccumulator::~MarkupAccumulator() = default;
void MarkupAccumulator::AppendString(const String& string) {
markup_.Append(string);
}
void MarkupAccumulator::AppendEndTag(const Element& element) {
AppendEndMarkup(element);
}
void MarkupAccumulator::AppendStartMarkup(const Node& node) {
switch (node.getNodeType()) {
case Node::kTextNode:
formatter_.AppendText(markup_, ToText(node));
break;
case Node::kElementNode:
AppendElement(ToElement(node));
break;
case Node::kAttributeNode:
// Only XMLSerializer can pass an Attr. So, |documentIsHTML| flag is
// false.
formatter_.AppendAttributeValue(markup_, ToAttr(node).value(), false);
break;
default:
formatter_.AppendStartMarkup(markup_, node);
break;
}
}
void MarkupAccumulator::AppendEndMarkup(const Element& element) {
formatter_.AppendEndMarkup(markup_, element);
}
void MarkupAccumulator::AppendCustomAttributes(const Element&) {}
bool MarkupAccumulator::ShouldIgnoreAttribute(
const Element& element,
const Attribute& attribute) const {
return false;
}
bool MarkupAccumulator::ShouldIgnoreElement(const Element& element) const {
return false;
}
void MarkupAccumulator::AppendElement(const Element& element) {
// https://html.spec.whatwg.org/multipage/parsing.html#html-fragment-serialisation-algorithm
AppendStartTagOpen(element);
AttributeCollection attributes = element.Attributes();
if (SerializeAsHTMLDocument(element)) {
// 3.2. Element: If current node's is value is not null, and the
// element does not have an is attribute in its attribute list, ...
const AtomicString& is_value = element.IsValue();
if (!is_value.IsNull() && !attributes.Find(html_names::kIsAttr)) {
AppendAttribute(element, Attribute(html_names::kIsAttr, is_value));
}
}
for (const auto& attribute : attributes) {
if (!ShouldIgnoreAttribute(element, attribute))
AppendAttribute(element, attribute);
}
// Give an opportunity to subclasses to add their own attributes.
AppendCustomAttributes(element);
AppendStartTagClose(element);
}
void MarkupAccumulator::AppendStartTagOpen(const Element& element) {
formatter_.AppendStartTagOpen(markup_, element);
if (!SerializeAsHTMLDocument(element) && ShouldAddNamespaceElement(element)) {
AppendNamespace(element.prefix(), element.namespaceURI());
}
}
void MarkupAccumulator::AppendStartTagClose(const Element& element) {
formatter_.AppendStartTagClose(markup_, element);
}
void MarkupAccumulator::AppendAttribute(const Element& element,
const Attribute& attribute) {
String value = formatter_.ResolveURLIfNeeded(element, attribute);
if (SerializeAsHTMLDocument(element)) {
MarkupFormatter::AppendAttributeAsHTML(markup_, attribute, value);
} else {
AppendAttributeAsXMLWithNamespace(element, attribute, value);
}
}
void MarkupAccumulator::AppendAttributeAsXMLWithNamespace(
const Element& element,
const Attribute& attribute,
const String& value) {
// https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes
// 3.3. Let attribute namespace be the value of attr's namespaceURI value.
const AtomicString& attribute_namespace = attribute.NamespaceURI();
// 3.4. Let candidate prefix be null.
AtomicString candidate_prefix;
// 3.5. If attribute namespace is not null, then run these sub-steps:
// 3.5.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.
// TODO(tkent): Implement it. crbug.com/906807
candidate_prefix = attribute.Prefix();
// 3.5.2. If the value of attribute namespace is the XMLNS namespace, then
// run these steps:
if (attribute_namespace == xmlns_names::kNamespaceURI) {
if (!attribute.Prefix() && attribute.LocalName() != g_xmlns_atom)
candidate_prefix = g_xmlns_atom;
// Account for the namespace attribute we're about to append.
AddPrefix(attribute.Prefix() ? attribute.LocalName() : g_empty_atom,
attribute.Value());
} else if (attribute_namespace == xml_names::kNamespaceURI) {
// TODO(tkent): Remove this block when we implement 'retrieving a
// preferred prefix string'.
if (!candidate_prefix)
candidate_prefix = g_xml_atom;
} else {
// TODO(tkent): Remove this block. The standard and Firefox don't
// have this behavior.
if (attribute_namespace == xlink_names::kNamespaceURI) {
if (!candidate_prefix)
candidate_prefix = g_xlink_atom;
}
// 3.5.3. Otherwise, the attribute namespace in not the XMLNS namespace.
// Run these steps:
if (ShouldAddNamespaceAttribute(attribute, element)) {
if (!candidate_prefix) {
// 3.5.3.1. Let candidate prefix be the result of generating a prefix
// providing map, attribute namespace, and prefix index as input.
candidate_prefix = GeneratePrefix(attribute_namespace);
// 3.5.3.2. Append the following to result, in the order listed:
MarkupFormatter::AppendAttribute(markup_, g_xmlns_atom,
candidate_prefix, attribute_namespace,
false);
} else {
DCHECK(candidate_prefix);
AppendNamespace(candidate_prefix, attribute_namespace);
}
}
}
MarkupFormatter::AppendAttribute(markup_, candidate_prefix,
attribute.LocalName(), value, false);
}
bool MarkupAccumulator::ShouldAddNamespaceAttribute(const Attribute& attribute,
const Element& element) {
// xmlns and xmlns:prefix attributes should be handled by another branch in
// AppendAttributeAsXMLWithNamespace().
DCHECK_NE(attribute.NamespaceURI(), xmlns_names::kNamespaceURI);
// Attributes are in the null namespace by default.
if (!attribute.NamespaceURI())
return false;
// Attributes without a prefix will need one generated for them, and an xmlns
// attribute for that prefix.
if (!attribute.Prefix())
return true;
return !element.hasAttribute(WTF::g_xmlns_with_colon + attribute.Prefix());
}
void MarkupAccumulator::AppendNamespace(const AtomicString& prefix,
const AtomicString& namespace_uri) {
AtomicString found_uri = LookupNamespaceURI(prefix);
if (!EqualIgnoringNullity(found_uri, namespace_uri)) {
AddPrefix(prefix, namespace_uri);
if (prefix.IsEmpty()) {
MarkupFormatter::AppendAttribute(markup_, g_null_atom, g_xmlns_atom,
namespace_uri, false);
} else {
MarkupFormatter::AppendAttribute(markup_, g_xmlns_atom, prefix,
namespace_uri, false);
}
}
}
EntityMask MarkupAccumulator::EntityMaskForText(const Text& text) const {
return formatter_.EntityMaskForText(text);
}
void MarkupAccumulator::PushNamespaces(const Node& node) {
if (!node.IsElementNode() || SerializeAsHTMLDocument(node))
return;
DCHECK_GT(namespace_stack_.size(), 0u);
// TODO(tkent): Avoid to copy the whole map.
// We can't do |namespace_stack_.emplace_back(namespace_stack_.back())|
// because back() returns a reference in the vector backing, and
// emplace_back() can reallocate it.
namespace_stack_.push_back(Namespaces(namespace_stack_.back()));
}
void MarkupAccumulator::PopNamespaces(const Node& node) {
if (!node.IsElementNode() || SerializeAsHTMLDocument(node))
return;
namespace_stack_.pop_back();
}
// https://w3c.github.io/DOM-Parsing/#dfn-add
void MarkupAccumulator::AddPrefix(const AtomicString& prefix,
const AtomicString& namespace_uri) {
namespace_stack_.back().Set(prefix ? prefix : g_empty_atom, namespace_uri);
}
AtomicString MarkupAccumulator::LookupNamespaceURI(const AtomicString& prefix) {
return namespace_stack_.back().at(prefix ? prefix : g_empty_atom);
}
// https://w3c.github.io/DOM-Parsing/#dfn-generating-a-prefix
AtomicString MarkupAccumulator::GeneratePrefix(
const AtomicString& new_namespace) {
// 1. Let generated prefix be the concatenation of the string "ns" and the
// current numerical value of prefix index.
AtomicString generated_prefix = "ns" + String::Number(prefix_index_);
// 2. Let the value of prefix index be incremented by one.
++prefix_index_;
// 3. Add to map the generated prefix given the new namespace namespace.
AddPrefix(generated_prefix, new_namespace);
// 4. Return the value of generated prefix.
return generated_prefix;
}
bool MarkupAccumulator::SerializeAsHTMLDocument(const Node& node) const {
return formatter_.SerializeAsHTMLDocument(node);
}
bool MarkupAccumulator::ShouldAddNamespaceElement(const Element& element) {
// Don't add namespace attribute if it is already defined for this elem.
const AtomicString& prefix = element.prefix();
if (prefix.IsEmpty()) {
if (element.hasAttribute(g_xmlns_atom)) {
AddPrefix(g_empty_atom, element.namespaceURI());
return false;
}
return true;
}
return !element.hasAttribute(WTF::g_xmlns_with_colon + prefix);
}
std::pair<Node*, Element*> MarkupAccumulator::GetAuxiliaryDOMTree(
const Element& element) const {
return std::pair<Node*, Element*>();
}
template <typename Strategy>
void MarkupAccumulator::SerializeNodesWithNamespaces(
const Node& target_node,
EChildrenOnly children_only) {
if (target_node.IsElementNode() &&
ShouldIgnoreElement(ToElement(target_node))) {
return;
}
PushNamespaces(target_node);
if (!children_only)
AppendStartMarkup(target_node);
if (!(SerializeAsHTMLDocument(target_node) &&
ElementCannotHaveEndTag(target_node))) {
Node* current = IsHTMLTemplateElement(target_node)
? Strategy::FirstChild(
*ToHTMLTemplateElement(target_node).content())
: Strategy::FirstChild(target_node);
for (; current; current = Strategy::NextSibling(*current))
SerializeNodesWithNamespaces<Strategy>(*current, kIncludeNode);
// Traverses other DOM tree, i.e., shadow tree.
if (target_node.IsElementNode()) {
std::pair<Node*, Element*> auxiliary_pair =
GetAuxiliaryDOMTree(ToElement(target_node));
Node* auxiliary_tree = auxiliary_pair.first;
Element* enclosing_element = auxiliary_pair.second;
if (auxiliary_tree) {
if (auxiliary_pair.second)
AppendStartMarkup(*enclosing_element);
current = Strategy::FirstChild(*auxiliary_tree);
for (; current; current = Strategy::NextSibling(*current)) {
SerializeNodesWithNamespaces<Strategy>(*current, kIncludeNode);
}
if (enclosing_element)
AppendEndTag(*enclosing_element);
}
}
}
if ((!children_only && target_node.IsElementNode()) &&
!(SerializeAsHTMLDocument(target_node) &&
ElementCannotHaveEndTag(target_node)))
AppendEndTag(ToElement(target_node));
PopNamespaces(target_node);
}
template <typename Strategy>
String MarkupAccumulator::SerializeNodes(const Node& target_node,
EChildrenOnly children_only) {
if (!SerializeAsHTMLDocument(target_node)) {
// https://w3c.github.io/DOM-Parsing/#dfn-xml-serialization
DCHECK_EQ(namespace_stack_.size(), 0u);
// 2. Let prefix map be a new namespace prefix map.
namespace_stack_.emplace_back();
// 3. Add the XML namespace with prefix value "xml" to prefix map.
AddPrefix(g_xml_atom, xml_names::kNamespaceURI);
// 4. Let prefix index be a generated namespace prefix index with value 1.
prefix_index_ = 1;
}
SerializeNodesWithNamespaces<Strategy>(target_node, children_only);
return ToString();
}
template String MarkupAccumulator::SerializeNodes<EditingStrategy>(
const Node&,
EChildrenOnly);
} // namespace blink