///|
/// GEDCOM 7 §MIME's specified HTML-to-text algorithm. Never evaluates markup,
/// follows links, fetches styles, or interprets it as active browser content.
pub fn html_to_plain(text : String) -> String {
  let collapsed = StringBuilder()
  let mut cursor = 0
  while cursor < text.length() {
    let space = text[cursor] == 32 ||
      text[cursor] == 9 ||
      text[cursor] == 10 ||
      text[cursor] == 13
    let start = cursor
    cursor = cursor + 1
    while cursor < text.length() &&
          (
            text[cursor] == 32 ||
            text[cursor] == 9 ||
            text[cursor] == 10 ||
            text[cursor] == 13
          ) ==
          space {
      cursor = cursor + 1
    }
    if space {
      collapsed.write_string(" ")
    } else {
      collapsed.write_string(text[start:cursor].to_owned())
    }
  }
  let s = collapsed.to_string()
  let out = StringBuilder()
  let mut i = 0
  while i < s.length() {
    if s[i] == 60 {
      let mut end = i + 1
      while end < s.length() && s[end] != 62 {
        end = end + 1
      }
      if end < s.length() {
        let tag = s[i + 1:end].to_owned().to_lower()
        if tag == "p" ||
          tag.has_prefix("p ") ||
          tag == "/p" ||
          tag.has_prefix("/p ") ||
          tag == "br" ||
          tag == "br/" ||
          tag.has_prefix("br ") {
          out.write_string("\n")
        }
        i = end + 1
        continue
      }
    }
    // Copy complete UTF-16 spans, including both halves of supplementary chars.
    let start = i
    i = i + 1
    while i < s.length() && s[i] != 60 {
      i = i + 1
    }
    out.write_string(s[start:i].to_owned())
  }
  out
  .to_string()
  .replace_all(old="<", new="<")
  .replace_all(old=">", new=">")
  .replace_all(old="&", new="&")
}

///|
fn noncore_html_entity(s : String) -> Bool {
  let mut i = 0
  while i < s.length() {
    if s[i] != 38 {
      i = i + 1
      continue
    }
    let start = i + 1
    let mut end = start
    while end < s.length() &&
          (
            (s[end] >= 65 && s[end] <= 90) ||
            (s[end] >= 97 && s[end] <= 122) ||
            (s[end] >= 48 && s[end] <= 57)
          ) {
      end = end + 1
    }
    if end > start && end < s.length() && s[end] == 59 {
      if !["amp", "lt", "gt", "quot", "apos"].contains(s[start:end].to_owned()) {
        return true
      }
      i = end + 1
    } else {
      i = i + 1
    }
  }
  false
}