///|
pub(all) struct XmlAttribute {
  uri : String
  name : String
  value : String
} derive(Debug, Eq, ToJson)

///|
pub(all) struct XmlElement {
  uri : String
  name : String
  attributes : Array[XmlAttribute]
  content : Array[XmlContent]
} derive(Debug, Eq, ToJson)

///|
pub(all) enum XmlContent {
  Text(String)
  Element(XmlElement)
} derive(Debug, Eq, ToJson)

///|
priv struct XmlReader {
  cs : Array[Char]
  mut i : Int
  mut nodes : Int
}

///|
fn xml_char(n : Int) -> Bool {
  n == 9 ||
  n == 10 ||
  n == 13 ||
  (n >= 32 && n <= 55295) ||
  (n >= 57344 && n <= 65533) ||
  (n >= 65536 && n <= 1114111)
}

///|
fn xml_text(s : String) -> Unit raise DavError {
  if !s.iter().all(c => xml_char(c.to_int())) {
    raise Invalid("invalid XML character")
  }
}

///|
fn name_start(c : Char) -> Bool {
  let n = c.to_int()
  c == '_' ||
  (c >= 'A' && c <= 'Z') ||
  (c >= 'a' && c <= 'z') ||
  (n >= 192 && n <= 214) ||
  (n >= 216 && n <= 246) ||
  (n >= 248 && n <= 767) ||
  (n >= 880 && n <= 893) ||
  (n >= 895 && n <= 8191) ||
  (n >= 8204 && n <= 8205) ||
  (n >= 8304 && n <= 8591) ||
  (n >= 11264 && n <= 12271) ||
  (n >= 12289 && n <= 55295) ||
  (n >= 63744 && n <= 64975) ||
  (n >= 65008 && n <= 65533) ||
  (n >= 65536 && n <= 983039)
}

///|
fn name_char(c : Char) -> Bool {
  let n = c.to_int()
  name_start(c) ||
  c == '-' ||
  c == '.' ||
  (c >= '0' && c <= '9') ||
  n == 183 ||
  (n >= 768 && n <= 879) ||
  (n >= 8255 && n <= 8256)
}

///|
fn ncname(s : String) -> Unit raise DavError {
  let cs = s.to_array()
  if cs.is_empty() || !name_start(cs[0]) || !cs.iter().all(name_char) {
    raise Invalid("invalid XML local name")
  }
}

///|
fn XmlReader::starts(self : XmlReader, text : String) -> Bool {
  let cs = text.to_array()
  if self.i + cs.length() > self.cs.length() {
    return false
  }
  for j, c in cs {
    if self.cs[self.i + j] != c {
      return false
    }
  }
  true
}

///|
fn XmlReader::space(self : XmlReader) -> Bool {
  let start = self.i
  while self.i < self.cs.length() &&
        (
          self.cs[self.i] == ' ' ||
          self.cs[self.i] == '\n' ||
          self.cs[self.i] == '\r' ||
          self.cs[self.i] == '\t'
        ) {
    self.i += 1
  }
  self.i != start
}

///|
fn XmlReader::need(self : XmlReader, text : String) -> Unit raise DavError {
  if !self.starts(text) {
    raise Invalid("malformed XML near offset " + self.i.to_string())
  }
  self.i += text.length()
}

///|
fn XmlReader::name(self : XmlReader) -> String raise DavError {
  let out = StringBuilder()
  if self.i >= self.cs.length() ||
    !(name_start(self.cs[self.i]) || self.cs[self.i] == ':') {
    raise Invalid("XML name")
  }
  while self.i < self.cs.length() &&
        (name_char(self.cs[self.i]) || self.cs[self.i] == ':') {
    out.write_char(self.cs[self.i])
    self.i += 1
  }
  out.to_string()
}

///|
fn split_name(s : String) -> (String, String) raise DavError {
  let parts = s.split(":").collect()
  if parts.length() == 1 {
    ncname(s)
    return ("", s)
  }
  if parts.length() != 2 {
    raise Invalid("invalid XML QName")
  }
  let prefix = parts[0].to_owned()
  let local_name = parts[1].to_owned()
  ncname(prefix)
  ncname(local_name)
  (prefix, local_name)
}

///|
fn XmlReader::until(self : XmlReader, stop : String) -> String raise DavError {
  let out = StringBuilder()
  while !self.starts(stop) {
    if self.i >= self.cs.length() {
      raise Invalid("unterminated XML construct")
    }
    out.write_char(self.cs[self.i])
    self.i += 1
  }
  self.need(stop)
  out.to_string()
}

///|
fn XmlReader::reference(self : XmlReader) -> Char raise DavError {
  self.need("&")
  let out = StringBuilder()
  let mut count = 0
  while !self.starts(";") {
    if self.i >= self.cs.length() || count >= 16 {
      raise Invalid("invalid XML entity")
    }
    out.write_char(self.cs[self.i])
    self.i += 1
    count += 1
  }
  self.need(";")
  let value = out.to_string()
  match value {
    "amp" => '&'
    "lt" => '<'
    "gt" => '>'
    "quot" => '"'
    "apos" => '\''
    _ => {
      let cs = value.to_array()
      if cs.length() < 2 || cs[0] != '#' {
        raise Invalid("unknown XML entity")
      }
      let hex = cs[1] == 'x'
      let start = if hex { 2 } else { 1 }
      if start >= cs.length() {
        raise Invalid("empty character reference")
      }
      let base = if hex { 16 } else { 10 }
      let mut number = 0
      for i in start..= '0' && c <= '9' {
          c.to_int() - 48
        } else if hex && c >= 'A' && c <= 'F' {
          c.to_int() - 55
        } else if hex && c >= 'a' && c <= 'f' {
          c.to_int() - 87
        } else {
          -1
        }
        if digit < 0 || digit >= base || number > (1114111 - digit) / base {
          raise Invalid("invalid character reference")
        }
        number = number * base + digit
      }
      if !xml_char(number) {
        raise Invalid("forbidden XML character reference")
      }
      number.unsafe_to_char()
    }
  }
}

///|
fn XmlReader::attribute_value(
  self : XmlReader,
  declaration? : Bool = false,
) -> String raise DavError {
  if self.i >= self.cs.length() {
    raise Invalid("missing XML attribute value")
  }
  let quote = self.cs[self.i]
  if quote != '"' && quote != '\'' {
    raise Invalid("XML attribute quote")
  }
  self.i += 1
  let out = StringBuilder()
  while self.i < self.cs.length() && self.cs[self.i] != quote {
    let c = self.cs[self.i]
    if c == '<' || (declaration && c == '&') {
      raise Invalid("invalid attribute or declaration value")
    }
    if c == '&' {
      out.write_char(self.reference())
    } else {
      out.write_char(if c == '\t' || c == '\n' || c == '\r' { ' ' } else { c })
      self.i += 1
    }
  }
  self.need(quote.to_string())
  out.to_string()
}

///|
fn XmlReader::comment(self : XmlReader) -> Unit raise DavError {
  self.need("")
  if text.contains("--") || text.has_suffix("-") {
    raise Invalid("invalid XML comment")
  }
}

///|
fn XmlReader::instruction(self : XmlReader) -> Unit raise DavError {
  self.need("") && !self.space() {
    raise Invalid("invalid XML instruction")
  }
  ignore(self.until("?>"))
}

///|
fn XmlReader::misc(self : XmlReader) -> Unit raise DavError {
  while true {
    ignore(self.space())
    if self.starts("