module xml-text-tools
imports xml-doc 
strategies

  /**
   * @type  String -> String
   */
  string2xml-attr-value =
    string-as-chars(xml-escape-chars)

strategies // for escaping

  /**
   * @type  String -> String
   */
  xml-escape = 
    string-as-chars(xml-escape-chars)

  /**
   * @type  List(Char) -> List(Char)
   */
  xml-escape-chars =
      rec x(
          try(
            escape-amp  
          + escape-lt   
          + escape-gt   
          + escape-apos 
          + escape-quot
          )
        ; ([id | x] <+ [])
      )

  /**
   * @type  List(Char) -> List(Char)
   */
  escape-amp  : ['&'  | cs] -> ['&', 'a', 'm', 'p'     , ';' | cs]
  escape-lt   : ['<'  | cs] -> ['&', 'l', 't'          , ';' | cs]
  escape-gt   : ['>'  | cs] -> ['&', 'g', 't'          , ';' | cs]
  escape-apos : ['\'' | cs] -> ['&', 'a', 'p', 'o', 's', ';' | cs]
  escape-quot : ['"'  | cs] -> ['&', 'q', 'u', 'o', 't', ';' | cs]

strategies

  /**
   * @type String -> String
   */
  xml-unescape = 
    string-as-chars(xml-unescape-chars)

  /**
   * @type  List(Char) -> List(Char)
   */
  xml-unescape-chars =
      rec x(
          try(
            unescape-amp  
          + unescape-lt   
          + unescape-gt   
          + unescape-apos 
          + unescape-quot 
          + unescape-dec-charref
          + unescape-hex-charref
          )
        ; ([id | x] <+ [])
      )

  /**
   * @type  List(Char) -> List(Char)
   */
  unescape-amp  : ['&', 'a', 'm', 'p'     , ';' | cs] -> ['&'  | cs]
  unescape-lt   : ['&', 'l', 't'          , ';' | cs] -> ['<'  | cs]
  unescape-gt   : ['&', 'g', 't'          , ';' | cs] -> ['>'  | cs]
  unescape-apos : ['&', 'a', 'p', 'o', 's', ';' | cs] -> ['\'' | cs]
  unescape-quot : ['&', 'q', 'u', 'o', 't', ';' | cs] -> ['"'  | cs]


  /**
   * @type CharData -> CharData
   */
  xml-unescape =
    Text(
      map(
        ?Literal(_)
     <+ unescape-amp
      + unescape-lt
      + unescape-gt
      + unescape-apos
      + unescape-quot
      + unescape-dec-charref
      + unescape-hex-charref
      )
    )

  /**
   * @type  CharDataPart -> CharDataPart
   */
  unescape-amp  : EntityRef("amp")  -> Literal("&")
  unescape-lt   : EntityRef("lt")   -> Literal("<")
  unescape-gt   : EntityRef("gt")   -> Literal(">")
  unescape-apos : EntityRef("apos") -> Literal("'")
  unescape-quot : EntityRef("quot") -> Literal("\"")

  unescape-dec-charref :
    DecCharRef(s) -> Literal(<dec-char-to-string> s)

  unescape-hex-charref :
    HexCharRef(s) -> Literal(<hex-char-to-string> s)

  /*
  unescape-dec-charref : ['&', '#', x, ';' | cs] -> []
    where <is-num> x

  unescape-dec-charref : ['&', '#', x, y, ';' | cs] -> [<dec-chars-to-int> [x, y] | cs]
    where <is-num> x; <is-num> y

  unescape-dec-charref : ['&', '#', x, y, z, ';' | cs] -> [<dec-chars-to-int> [x, y, z] | cs]
    where <is-num> x; <is-num> y; <is-num> z

  unescape-hex-charref : ['&', '#', 'x', x, ';' | cs] -> [<hex-chars-to-int> [x] | cs]
    where <is-hexnum> x

  unescape-hex-charref : ['&', '#', 'x', x, y, ';' | cs] -> [<hex-chars-to-int> [x, y] | cs]
    where <is-hexnum> x; <is-hexnum> y
  */

  dec-char-to-string =
    string-as-chars(
      all(is-num)
    ; dec-chars-to-int
    ; ![<id>]
    )

  hex-char-to-string =
    string-as-chars(
      all(is-hexnum)
    ; hex-chars-to-int
    ; ![<id>]
    )

strategies // for unquoting literals
  
  /**
   * @type  String -> String
   */
  xml-unquote-attr = string-as-chars(xml-unquote-chars-attr)

  /**
   * @type  List(Char) -> List(Char)
   */
  xml-unquote-chars-attr =
    unquote-chars(?'"' + ?'\'')

  /**
   * @type AttValue -> String
   */
  xml-attr-value2string =
    ( \ DoubleQuoted(parts) -> Text(parts) \
    + \ SingleQuoted(parts) -> Text(parts) \ )
    ; chardata2string

  chardata2string =
      xml-unescape
    ; ?Text(<id>)
    ; map(?Literal(<id>) + ?CDATASection(<id>))
    ; (?[<id>] <+ concat-strings)