#
# $Id: xmlformatter.icn,v 1.2 2006-07-10 12:55:15 rparlett Exp $
#
# This file is in the public domain.
#
# Author: Robert Parlett (parlett@dial.pipex.com)
#

package xml

#
# A formatter for XML documents.  The default formatter behaviour is to output the
# string content as held in the children array (this may or may not have had whitespace
# removed depending on whether validation against a DTD occured), and without
# indentation.
#
class XmlFormatter : Formatter(indent, no_whitespace, text_trim, as_read)
   #
   # Ensure that each opening element appears with an indent of n chars.  Whitespace
   # will be inserted as appropriate.  By default, no indentation is done.
   #
   method set_indent(n)
      indent := n
   end

   #
   # Configure the formatter so that whitespace-only string content will be discarded.
   #
   method set_no_whitespace()
      no_whitespace := 1
   end

   #
   # Clear the no_whitespace option (the default).
   #
   method clear_no_whitespace()
      no_whitespace := &null
   end

   #
   # Configure the formatter so that whitespace-only string content will be discarded,
   # and in addition any other string content will be trimmed at both ends.
   #
   method set_text_trim()
      text_trim := 1
   end

   #
   # Clear the text_trim option (the default).
   #
   method clear_text_trim()
      text_trim := &null
   end

   #
   # Configure the formatter so that the string content will be output as read from
   # the input; ie the formatter uses the whitespace_children list rather than the children
   # list.
   #
   method set_as_read()
      as_read := 1
   end

   #
   # Clear the as_read option (the default).
   #
   method clear_as_read()
      as_read := &null
   end

   method format_document(n, level)
      local s
      s := ""
      every s ||:= format(!n.children, level + 1) || "\n"
      return s
   end

   method format_content(s, level)
      return s
   end

   method format_doctype(n, level)
      local s, t, x
      s := "<!DOCTYPE "
      s ||:= \n.name
      x := n.external_id
      if \x then {
         if \x.public_id then
            s ||:= " PUBLIC \"" || x.public_id || "\" \"" || x.system_id || "\""
         else
            s ||:= " SYSTEM \"" || x.system_id || "\""
      }
      return s || ">"
   end

   method format_comment(n, level)
      return "<!--" || n.comment || "-->"
   end

   method format_pi(n, level)
      local s
      s := "<?" || n.target
      if \n.content then
         s ||:= " " || n.content 
      return s || "?>"
   end

   method format_element(n, level)
      local s, istr1, istr2, x, l, e

      if \indent then {
         istr1 := "\n" || repl(" ", indent * level)
         istr2 := "\n" || repl(" ", indent * (level - 1))
      }

      s := "<" || n.name
      if *n.attributes > 0 then {
         s ||:= " "
         every x := !sort(n.attributes) do {
            s ||:= x[1] || "=\"" || xml_escape(x[2], '&<>\"') || "\" "
         }
      }
      if *n.children = 0 then
         s ||:= "/>"
      else {
         s ||:= ">"
         l := get_children(n)
         if *l = 1 & string(l[1]) then {
            s ||:= xml_escape(l[1], '&<>\"')
         } else {
            every e := !l do {
               if \indent then {
                  s ||:= istr1
               }
               if string(e) then
                  s ||:= xml_escape(e, '&<>\"')
               else
                  s ||:= format(e, level + 1)
            }
            if \indent then {
               s ||:= istr2
            }
         }
         s ||:= "</" || n.name || ">"
      }
      return s
   end

   method get_children(el)
      if \as_read then
         return \el.whitespace_children | el.children
      if \no_whitespace then
         return el.get_children_no_whitespace()
      if \text_trim then {
         return el.get_trimmed_children()
      }
      return el.children
   end

   method format_cdata(n, level)
      return "<![CDATA[" || n.content || "]]>"
   end

   method format_xmldecl(n, level)
      local s
      s := "<?xml "
      if \n.version then
         s ||:= "version='" || n.version || "' "
      if \n.encoding then
         s ||:= "encoding='" || n.encoding || "' "
      if \n.standalone then
         s ||:= "standalone='" || n.standalone || "' "
      return s || "?>"
   end
end
