// Bounded XML reader for ODF data import with internal DTD/entity processing.
// No external entity I/O or recovery. Comments and processing instructions are ignored.

///|
priv enum ReadPart {
  ReadText(String)
  ReadElement(ReadElement)
}

///|
priv struct ReadElement {
  name : String
  attrs : Map[String, String]
  parts : Array[ReadPart]
  ns : Map[String, String]
}

///|
priv struct ReadXml {
  chars : Array[Char]
  mut at : Int32
  mut nodes : Int32
  normalize_lines : Bool
  document_part : String
  mut location : String
  events : Array[XmlProcessorEvent]
  mut standalone : Bool
  mut dtd : DtdInfo?
  expansion : ReadEntityExpansion
  declared_types : Map[(Int32, String), String]
  other_ids : Array[(Int32, String, String)]
}

///|
priv struct ReadEntityExpansion {
  mut units : Int32
  active : Array[String]
}

///|
fn ReadXml::peek(self : ReadXml) -> Char {
  if self.at < self.chars.length() {
    self.chars[self.at]
  } else {
    '\u0000'
  }
}

///|
fn ReadXml::take(self : ReadXml) -> Char raise OdsError {
  if self.at >= self.chars.length() {
    raise Invalid("truncated XML")
  }
  let c = self.chars[self.at]
  self.at += 1
  c
}

///|
fn ReadXml::expect(self : ReadXml, c : Char) -> Unit raise OdsError {
  if self.take() != c {
    raise Invalid("malformed XML delimiter")
  }
}

///|
fn ReadXml::space(self : ReadXml) -> Unit {
  while self.peek() == ' ' ||
        self.peek() == '\n' ||
        self.peek() == '\r' ||
        self.peek() == '\t' {
    self.at += 1
  }
}

///|
fn xml_name_start(c : Char) -> Bool {
  let n = c.to_int()
  c == ':' ||
  c == '_' ||
  (n >= 0x41 && n <= 0x5A) ||
  (n >= 0x61 && n <= 0x7A) ||
  (n >= 0xC0 && n <= 0xD6) ||
  (n >= 0xD8 && n <= 0xF6) ||
  (n >= 0xF8 && n <= 0x2FF) ||
  (n >= 0x370 && n <= 0x37D) ||
  (n >= 0x37F && n <= 0x1FFF) ||
  (n >= 0x200C && n <= 0x200D) ||
  (n >= 0x2070 && n <= 0x218F) ||
  (n >= 0x2C00 && n <= 0x2FEF) ||
  (n >= 0x3001 && n <= 0xD7FF) ||
  (n >= 0xF900 && n <= 0xFDCF) ||
  (n >= 0xFDF0 && n <= 0xFFFD) ||
  (n >= 0x10000 && n <= 0xEFFFF)
}

///|
fn xml_name_char(c : Char) -> Bool {
  let n = c.to_int()
  xml_name_start(c) ||
  c == '-' ||
  c == '.' ||
  (c >= '0' && c <= '9') ||
  n == 0xB7 ||
  (n >= 0x300 && n <= 0x36F) ||
  (n >= 0x203F && n <= 0x2040)
}

///|
fn ReadXml::name(self : ReadXml) -> String raise OdsError {
  let b = StringBuilder()
  let first = self.peek()
  if !xml_name_start(first) {
    raise Invalid("unsupported XML name")
  }
  while xml_name_char(self.peek()) {
    b.write_char(self.take())
    if b.to_string().length() > 256 {
      raise Invalid("XML name limit")
    }
  }
  b.to_string()
}

///|
fn ReadXml::text(
  self : ReadXml,
  stop : Char,
  attribute : Bool,
) -> String raise OdsError {
  let b = StringBuilder()
  let mut units = 0
  while self.at < self.chars.length() && self.peek() != stop {
    if !attribute && self.general_entity_ahead() {
      break
    }
    let c = self.take()
    if !attribute &&
      c == ']' &&
      self.at + 1 < self.chars.length() &&
      self.chars[self.at] == ']' &&
      self.chars[self.at + 1] == '>' {
      raise Invalid("raw ]]> is forbidden in XML text")
    }
    if attribute && c == '<' {
      raise Invalid("raw less-than in XML attribute")
    }
    if c == '&' {
      let e = StringBuilder()
      while self.peek() != ';' {
        e.write_char(self.take())
        if e.to_string().length() > 16 {
          raise Invalid("entity length limit")
        }
      }
      self.expect(';')
      let entity = e.to_string()
      let decoded = match entity {
        "amp" => '&'
        "lt" => '<'
        "gt" => '>'
        "quot" => '"'
        "apos" => '\''
        _ => {
          let mut n = 0
          let mut digits = 0
          let mut base = 10
          let mut index = 0
          for d in entity {
            if index == 0 {
              if d != '#' {
                raise Invalid("custom XML entities forbidden")
              }
            } else if index == 1 && d == 'x' {
              base = 16
            } else {
              let v = if d >= '0' && d <= '9' {
                d.to_int() - 48
              } else if d >= 'a' && d <= 'f' {
                d.to_int() - 87
              } else if d >= 'A' && d <= 'F' {
                d.to_int() - 55
              } else {
                -1
              }
              if v < 0 || v >= base || n > (0x10FFFF - v) / base {
                raise Invalid("invalid numeric XML entity")
              }
              n = n * base + v
              digits += 1
            }
            index += 1
          }
          if digits == 0 || n > 0x10FFFF || (n >= 0xD800 && n <= 0xDFFF) {
            raise Invalid("invalid XML code point")
          }
          n.unsafe_to_char()
        }
      }
      b.write_char(decoded)
    } else if c == '\r' && self.normalize_lines {
      if self.peek() == '\n' {
        self.at += 1
      }
      b.write_char(if attribute { ' ' } else { '\n' })
    } else {
      b.write_char(if attribute && (c == '\n' || c == '\t') { ' ' } else { c })
    }
    units += 1
    if units > 32767 {
      raise Invalid("XML text/attribute exceeds limit")
    }
  }
  let s = b.to_string()
  validate_text(s)
  s
}

///|
fn ReadXml::starts_with(self : ReadXml, expected : String) -> Bool {
  let mut offset = 0
  for c in expected {
    if self.at + offset >= self.chars.length() ||
      self.chars[self.at + offset] != c {
      return false
    }
    offset += 1
  }
  true
}

///|
fn ReadXml::cdata(self : ReadXml) -> String raise OdsError {
  for c in "") {
    let c = self.take()
    if c == '\r' && self.normalize_lines {
      if self.peek() == '\n' {
        self.at += 1
      }
      b.write_char('\n')
    } else {
      b.write_char(c)
    }
    units += 1
    if units > 32767 {
      raise Invalid("CDATA text exceeds limit")
    }
  }
  self.at += 3
  let value = b.to_string()
  validate_text(value)
  value
}

///|
fn ReadXml::comment(self : ReadXml) -> Unit raise OdsError {
  for c in "") {
    if self.starts_with("--") {
      raise Invalid("double hyphen in XML comment")
    }
    b.write_char(self.take())
    units += 1
    if units > 32767 {
      raise Invalid("XML comment limit")
    }
  }
  self.at += 3
  validate_text(b.to_string())
}

///|
fn ReadXml::instruction(self : ReadXml) -> Unit raise OdsError {
  self.expect('<')
  self.expect('?')
  let target = self.name()
  if !xml_id_ncname(target) {
    raise Invalid("PI target must be an NCName")
  }
  match target {
    "xml" | "xmL" | "xMl" | "xML" | "Xml" | "XmL" | "XMl" | "XML" =>
      raise Invalid("reserved XML PI target")
    _ => ()
  }
  if !self.starts_with("?>") &&
    self.peek() != ' ' &&
    self.peek() != '\t' &&
    self.peek() != '\r' &&
    self.peek() != '\n' {
    raise Invalid("XML PI target separator required")
  }
  self.space()
  let b = StringBuilder()
  let mut units = 0
  while !self.starts_with("?>") {
    let c = self.take()
    if c == '\r' && self.normalize_lines {
      if self.peek() == '\n' {
        self.at += 1
      }
      b.write_char('\n')
    } else {
      b.write_char(c)
    }
    units += 1
    if units > 32767 {
      raise Invalid("XML PI limit")
    }
  }
  self.at += 2
  validate_text(b.to_string())
  self.event("PROCESSING_INSTRUCTION", target, b.to_string())
}

///|
fn ReadXml::misc(self : ReadXml) -> Unit raise OdsError {
  while true {
    self.space()
    if self.starts_with("