///|
fn is_table_text_foster_target(name : StringView) -> Bool {
match name {
"table" | "tbody" | "tfoot" | "thead" | "tr" => true
_ => false
}
}
///|
fn Parser::report_foster_parenting_character_errors(
self : Parser,
value : StringView,
start : Int,
) -> Int {
let mut count = 0
let mut pos = 0
while pos < value.length() {
let ch = value.get_char(pos).unwrap()
if !ch.is_ascii_whitespace() {
self.error_at("foster-parenting-character", start + pos)
count += 1
}
pos += ch.utf16_len()
}
count
}
///|
fn Parser::foster_parent_table_text_with_formatting(
self : Parser,
cleaned : StringView,
start : Int,
table_index : Int,
formatting_index : Int,
) -> Bool {
let entry = self.active_formatting[formatting_index]
let node = element_from_formatting_entry(entry)
node.append_child(
self.text_at(@tok.decode_entities(cleaned, in_attribute=false), start),
)
if self.table_index_is_fragment_context_table(table_index) {
self.current_node().append_child(node)
} else {
self.insert_node_before_open_table(node, table_index)
}
self.active_formatting[formatting_index] = formatting_entry_with_node(
entry, node,
)
self.pending_table_view_implied_end_tag = true
true
}
///|
fn Parser::foster_parent_table_text(
self : Parser,
cleaned : StringView,
start : Int,
) -> Bool {
if !text_has_non_ascii_whitespace(cleaned) {
return false
}
let table_index = self.last_open_table_index()
if table_index < 0 || self.table_text_is_inside_cell_or_caption(table_index) {
return false
}
let current = self.current_node()
if current.kind != Element || !is_table_text_foster_target(current.name) {
return false
}
let formatting_index = self.active_formatting_orphan_anchor_index()
if formatting_index >= 0 {
return self.foster_parent_table_text_with_formatting(
cleaned, start, table_index, formatting_index,
)
}
// Fostered characters process in body: reconstruction (itself
// foster-aware) reopens formatting before the table and the text goes
// inside it.
let stack_before = self.stack.length()
self.reconstruct_active_formatting_elements()
if self.stack.length() > stack_before {
ignore(self.report_foster_parenting_character_errors(cleaned, start))
let fostered = self.text_at(
@tok.decode_entities(cleaned, in_attribute=false),
start,
)
self.current_node().append_child(fostered)
return true
}
ignore(self.report_foster_parenting_character_errors(cleaned, start))
let fostered = self.text_at(
@tok.decode_entities(cleaned, in_attribute=false),
start,
)
if self.table_index_is_fragment_context_table(table_index) {
self.current_node().append_child(fostered)
return true
}
let table = self.stack[table_index]
match table.parent {
Some(parent) => {
// The appropriate place for inserting fostered characters is
// immediately before the table; when a text node already sits
// there, the characters merge into it ("A" and "C" around a cell
// become one "AC" node). Materialize any batched trailing text
// first so the sibling's data is current.
self.flush_pending_text()
let mut table_pos = -1
let mut sibling_index = 0
while sibling_index < parent.children.length() {
if physical_equal(parent.children[sibling_index], table) {
table_pos = sibling_index
break
}
sibling_index += 1
}
if table_pos > 0 &&
parent.children[table_pos - 1].kind == Text &&
!parent.children[table_pos - 1].sanitize_escape_only &&
!self.foster_merge_crosses_body_boundary(
parent,
parent.children[table_pos - 1],
) {
let previous = parent.children[table_pos - 1]
let merged = @dom.text(previous.data + fostered.data)
merged.parsed_from_source = previous.parsed_from_source
merged.origin_offset = previous.origin_offset
merged.origin_line = previous.origin_line
merged.origin_col = previous.origin_col
merged.parent = Some(parent)
parent.children[table_pos - 1] = merged
previous.parent = None
if self.remove_foster_parented_text(previous) {
self.foster_parented_texts.push(merged)
}
if self.remove_forced_body_node(previous) {
self.forced_body_nodes.push(merged)
}
return true
}
parent.insert_before(fostered, Some(table))
}
None => self.root.append_child(fostered)
}
self.foster_parented_texts.push(fostered)
true
}
///|
fn Parser::foster_parent_template_table_text(
self : Parser,
cleaned : StringView,
start : Int,
) -> Bool {
if !self.in_template_content_without_inner_table() ||
!text_has_non_ascii_whitespace(cleaned) {
return false
}
let current = self.current_node()
if current.kind != Element || !is_table_text_foster_target(current.name) {
return false
}
let template_index = self.last_open_template_index()
ignore(self.report_foster_parenting_character_errors(cleaned, start))
self.stack[template_index].append_child(
self.text_at(@tok.decode_entities(cleaned, in_attribute=false), start),
)
true
}
///|
fn Parser::report_foster_parented_table_context_text(
self : Parser,
cleaned : StringView,
start : Int,
) -> Unit {
if !text_has_non_ascii_whitespace(cleaned) {
return
}
let table_index = self.table_context_without_cell_or_caption()
if table_index < 0 {
return
}
let current = self.current_node()
if current.kind == Element && is_table_text_foster_target(current.name) {
return
}
ignore(self.report_foster_parenting_character_errors(cleaned, start))
}
///|
/// A top-level whitespace-only run that is not already marked as body or
/// fostered content belongs between the head and the body; fostered
/// characters must not merge into it (same boundary as the trailing-text
/// merge).
fn Parser::foster_merge_crosses_body_boundary(
self : Parser,
parent : @dom.Node,
previous : @dom.Node,
) -> Bool {
if self.is_fragment_parser() && !self.fragment_context_html {
return false
}
if !(parent.kind == Document ||
(parent.kind == Element && parent.name == "html")) {
return false
}
if !is_whitespace_text_node(previous) {
return false
}
if self.node_is_forced_body(previous) {
return false
}
!self.foster_parented_texts[:].any(candidate => {
physical_equal(candidate, previous)
})
}