// ODF 1.3 Part 3 section 6.1.2: raw XML whitespace is collapsed before
// explicit text:s/tab/line-break elements are expanded. One state spans all
// nested text:span elements, and is reset only at each paragraph boundary.

///|
priv struct ReadParagraph {
  output : StringBuilder
  mut units : Int32
  mut seen : Bool
  mut pending_space : Bool
}

///|
fn ReadParagraph::emit(
  self : ReadParagraph,
  text : String,
) -> Unit raise OdsError {
  if self.units > 32767 - text.length() {
    raise Invalid("expanded cell text exceeds budget")
  }
  self.output.write_string(text)
  self.units += text.length()
}

///|
fn ReadParagraph::boundary(self : ReadParagraph) -> Unit raise OdsError {
  if self.pending_space && self.seen {
    self.emit(" ")
  }
  self.pending_space = false
  self.seen = true
}

///|
fn ReadParagraph::append_parts(
  self : ReadParagraph,
  node : ReadElement,
) -> Unit raise OdsError {
  for part in node.parts {
    match part {
      ReadText(t) =>
        for c in t {
          if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
            self.pending_space = true
          } else {
            self.boundary()
            self.emit(c.to_string())
          }
        }
      ReadElement(e) =>
        if e.name == read_text + "span" {
          e.allowed([read_text + "style-name"])
          self.append_parts(e)
        } else if e.name == read_text + "s" {
          e.allowed([read_text + "c"])
          if !e.parts.is_empty() {
            raise Invalid("text:s must be empty")
          }
          let n = match e.attribute(read_text, "c") {
            Some(raw) => read_xsd_nonnegative(raw, 32767)
            None => 1
          }
          self.boundary()
          if self.units > 32767 - n {
            raise Invalid("expanded cell text exceeds budget")
          }
          for _ in 0..