///|
fn SourceTokenizer::decoded_special_text(
  self : SourceTokenizer,
  name : StringView,
  raw : StringView,
  start : Int,
  error_pos : Int,
) -> String {
  let raw = self.replace_special_text_nulls(raw, start)
  let data = if @impl.special_text_decodes_entities(name) {
    let decoded = @impl.decode_entities_with_errors(raw, in_attribute=false)
    for error in decoded.errors {
      self.error_at(error.code, error_pos)
    }
    decoded.data
  } else {
    @syn.normalize_newlines(raw)
  }
  if self.xml_coercion {
    @syn.coerce_text_for_xml(data)
  } else {
    data.to_owned()
  }
}

///|
fn SourceTokenizer::replace_special_text_nulls(
  self : SourceTokenizer,
  raw : StringView,
  start : Int,
) -> StringView {
  // The common case has no nulls: the input view passes through
  // unchanged.
  if !raw.contains_char('\u{0000}') {
    return raw
  }
  let out = StringBuilder(size_hint=raw.length())
  let mut pos = 0
  for ch in raw {
    if ch == '\u{0000}' {
      self.error_at("unexpected-null-character", start + pos)
      out.write_char('\u{FFFD}')
    } else {
      out.write_char(ch)
    }
    pos += ch.utf16_len()
  }
  out.to_string()
}

///|
fn SourceTokenizer::consume_special_text_tokens(
  self : SourceTokenizer,
  name : String,
) -> Unit {
  let content_start = self.pos
  if name == "plaintext" {
    match self.input.get_view(start=content_start, end=self.input.length()) {
      Some(view) if !view.is_empty() =>
        self.tokens.push(
          Characters(
            self.decoded_special_text(
              name,
              view,
              content_start,
              @syn.eof_error_pos(self.input),
            ),
          ),
        )
      _ => ()
    }
    self.pos = self.input.length()
    return
  }
  if name == "script" {
    let scan = @impl.scan_script_text(self.input, self.pos)
    for error in scan.errors {
      self.error_at(error.code, error.offset)
    }
    if !scan.data.is_empty() {
      let data = if self.xml_coercion {
        @syn.coerce_text_for_xml(scan.data)
      } else {
        scan.data
      }
      self.tokens.push(Characters(data))
    }
    match scan.end_start {
      Some(end_start) => {
        self.pos = end_start
        self.parse_end_tag()
      }
      None => self.pos = self.input.length()
    }
    return
  }
  match @impl.find_matching_end_tag(self.input, self.pos, name) {
    Some(end_start) => {
      let error_pos = @impl.matching_end_tag_error_pos(self.input, end_start)
      match self.input.get_view(start=content_start, end=end_start) {
        Some(view) if !view.is_empty() =>
          self.tokens.push(
            Characters(
              self.decoded_special_text(name, view, content_start, error_pos),
            ),
          )
        _ => ()
      }
      self.pos = end_start
      self.parse_end_tag()
    }
    None => {
      let error_pos = @syn.eof_error_pos(self.input)
      match self.input.get_view(start=content_start, end=self.input.length()) {
        Some(view) if !view.is_empty() =>
          self.tokens.push(
            Characters(
              self.decoded_special_text(name, view, content_start, error_pos),
            ),
          )
        _ => ()
      }
      self.pos = self.input.length()
    }
  }
}