Updated 2005-02-17 10:18:10 by NEM

MAK A simple set of functions for piecewise generation of formatted XML without a lot of fluff.
 # Map special characters into their XML entity equivalents.  The optional
 # "attribute" pararameter instructs the mapping to also treat quotes as
 # special entities for use in element attributes, which isn't necessary
 # for element body text.

 proc XMLEntities { text {attribute 0} } {
     if {$attribute} {
         return [string map {& &amp; < &lt; > &gt; \' &apos; \" &quot;} $text]
     } else {
         return [string map {& &amp; < &lt; > &gt;} $text]

 # Turn a given body of text into XML comments indented the specified
 # number of spaces.

 proc XMLComment { indent {text ""} } {
     set result ""

     if {$text != ""} {
         append result "\n"

         foreach line [split $text \n] {
             append result [format "%*s<!-- %*s -->\n" \
                 $indent "" -[expr {60 - $indent}] [XMLEntities $line]]

         append result "\n"

     return $result

 # Generate an XML element indented the specified number of spaces.
 # Paramters:
 # name    - the name of the element to be generated.
 # attribs - a list of name value name value ... to be output as
 #           attributes of the element.  They should not be pre-mapped
 #           into XML entities, since this will be handled for you.
 # content - the body of the element (i.e., what is to be inserted
 #           in between the start and end tags, if anything).  Generally
 #           this would be either the result of earlier calls to XMLElement
 #           to generate deeper structures, the result of calling
 #           XMLEntities on a piece of text data.

 proc XMLElement { indent name attribs {content ""} } {
     set result [format "%*s<%s" $indent "" $name]

     foreach {attrib value} $attribs {
         append result " $attrib=\"[XMLEntities $value 1]\""

     if {$content != ""} {
         append result ">"

         set headlen [string length $result]
         set contlen [string length $content]
         set taglen  [string length $name]

         if {(($headlen + $contlen + $taglen + 3) > 60) ||
                 ([string first "\n" $content] != -1)} {
             append result "\n"
             append result $content
             append result [format "%*s</%s>\n" $indent "" $name]
         } else {
             append result $content
             append result "</$name>\n"
     } else {
         append result "/>\n"

     return $result

XMLComment example:
 % XMLComment 2 "The quick brown dog\njumped over\nthe lazy dog"

   <!-- The quick brown dog                                        -->
   <!-- jumped over                                                -->
   <!-- the lazy dog                                               -->

XMLElement example:
 % set attributes [list foo 1 bar asdf baz <whee>]
 foo 1 bar asdf baz <whee>
 % set body       [XMLElement 3 element2 "" "just some text"]
    <element2>just some text</element2>

 % XMLElement 2 element1 $attributes $body
   <element1 foo="1" bar="asdf" baz="&lt;whee&gt;">
    <element2>just some text</element2>

Of course, you need to deal with XML header, namespace declaration, etc. As well, you may want to be careful of encoding. If you don't specify an encoding in the XML headers, XML defaults to UTF-8, which is not the encoding that Tcl normally uses. So if saving to a file, you will probably want to use fconfigure to change the file encoding to UTF-8, or whatever encoding you specify when you add the header.

See also: xmlgen / htmlgen

[Category XML]