home *** CD-ROM | disk | FTP | other *** search
/ PC World 2000 December / PCWorld_2000-12_cd.bin / Komunikace / Comanche / xuibuilder / TclXML-1.1.1 / xml.tcl < prev   
Text File  |  2000-11-02  |  13KB  |  505 lines

  1. # xml.tcl --
  2. #
  3. #    This file provides XML services.
  4. #    These services include a XML document instance and DTD parser,
  5. #    as well as support for generating XML.
  6. #
  7. # Copyright (c) 1998,1999 Zveno Pty Ltd
  8. # http://www.zveno.com/
  9. # Zveno makes this software and all associated data and documentation
  10. # ('Software') available free of charge for non-commercial purposes only. You
  11. # may make copies of the Software but you must include all of this notice on
  12. # any copy.
  13. # The Software was developed for research purposes and Zveno does not warrant
  14. # that it is error free or fit for any purpose.  Zveno disclaims any
  15. # liability for all claims, expenses, losses, damages and costs any user may
  16. # incur as a result of using, copying or modifying the Software.
  17. #
  18. # Copyright (c) 1997 Australian National University (ANU).
  19. # ANU makes this software and all associated data and documentation
  20. # ('Software') available free of charge for non-commercial purposes only. You
  21. # may make copies of the Software but you must include all of this notice on
  22. # any copy.
  23. # The Software was developed for research purposes and ANU does not warrant
  24. # that it is error free or fit for any purpose.  ANU disclaims any
  25. # liability for all claims, expenses, losses, damages and costs any user may
  26. # incur as a result of using, copying or modifying the Software.
  27. #
  28. # $Id: xml.tcl,v 1.1.1.1 1996/02/22 06:06:14 daniel Exp $
  29.  
  30. package provide xml 1.8
  31.  
  32. package require sgml 1.6
  33.  
  34. namespace eval xml {
  35.  
  36.     # Procedures for parsing XML documents
  37.     namespace export parser
  38.     # Procedures for parsing XML DTDs
  39.     namespace export DTDparser
  40.  
  41.     # Counter for creating unique parser objects
  42.     variable ParserCounter 0
  43.  
  44.     # Convenience routine
  45.     proc cl x {
  46.     return "\[$x\]"
  47.     }
  48.  
  49.     # Define various regular expressions
  50.     # white space
  51.     variable Wsp " \t\r\n"
  52.     variable noWsp [cl ^$Wsp]
  53.  
  54.     # Various XML names and tokens
  55.  
  56.     # BUG: NameChar does not include CombiningChar or Extender
  57.     variable NameChar [cl -a-zA-Z0-9._:]
  58.     variable Name [cl a-zA-Z_:]$NameChar*
  59.     variable Nmtoken $NameChar+
  60.  
  61.     # Tokenising expressions
  62.  
  63.     variable tokExpr <(/?)([cl ^$Wsp>]+)([cl $Wsp]*[cl ^>]*)>
  64.     variable substExpr "\}\n{\\2} {\\1} {} {\\3} \{"
  65.  
  66.     # table of predefined entities
  67.  
  68.     variable EntityPredef
  69.     array set EntityPredef {
  70.     lt <   gt >   amp &   quot \"   apos '
  71.     }
  72.  
  73. }
  74.  
  75.  
  76. # xml::parser --
  77. #
  78. #    Creates XML parser object.
  79. #
  80. # Arguments:
  81. #    args    Unique name for parser object
  82. #        plus option/value pairs
  83. #
  84. # Recognised Options:
  85. #    -final            Indicates end of document data
  86. #    -elementstartcommand    Called when an element starts
  87. #    -elementendcommand    Called when an element ends
  88. #    -characterdatacommand    Called when character data occurs
  89. #    -processinginstructioncommand    Called when a PI occurs
  90. #    -externalentityrefcommand    Called for an external entity reference
  91. #
  92. #    (Not compatible with expat)
  93. #    -xmldeclcommand        Called when the XML declaration occurs
  94. #    -doctypecommand        Called when the document type declaration occurs
  95. #
  96. #    -errorcommand        Script to evaluate for a fatal error
  97. #    -warningcommand        Script to evaluate for a reportable warning
  98. #    -statevariable        global state variable
  99. #    -reportempty        whether to provide empty element indication
  100. #
  101. # Results:
  102. #    The state variable is initialised.
  103.  
  104. proc xml::parser {args} {
  105.     variable ParserCounter
  106.  
  107.     if {[llength $args] > 0} {
  108.     set name [lindex $args 0]
  109.     set args [lreplace $args 0 0]
  110.     } else {
  111.     set name parser[incr ParserCounter]
  112.     }
  113.  
  114.     if {[info command [namespace current]::$name] != {}} {
  115.     return -code error "unable to create parser object \"[namespace current]::$name\" command"
  116.     }
  117.  
  118.     # Initialise state variable and object command
  119.     upvar \#0 [namespace current]::$name parser
  120.     set sgml_ns [namespace parent]::sgml
  121.     array set parser [list name $name            \
  122.     -final 1                    \
  123.     -elementstartcommand ${sgml_ns}::noop        \
  124.     -elementendcommand ${sgml_ns}::noop        \
  125.     -characterdatacommand ${sgml_ns}::noop        \
  126.     -processinginstructioncommand ${sgml_ns}::noop    \
  127.     -externalentityrefcommand ${sgml_ns}::noop    \
  128.     -xmldeclcommand ${sgml_ns}::noop        \
  129.     -doctypecommand ${sgml_ns}::noop        \
  130.     -warningcommand ${sgml_ns}::noop        \
  131.     -statevariable [namespace current]::$name    \
  132.     -reportempty 0                    \
  133.     internaldtd {}                    \
  134.     ]
  135.  
  136.     proc [namespace current]::$name {method args} \
  137.     "eval ParseCommand $name \$method \$args"
  138.  
  139.     eval ParseCommand [list $name] configure $args
  140.  
  141.     return [namespace current]::$name
  142. }
  143.  
  144. # xml::ParseCommand --
  145. #
  146. #    Handles parse object command invocations
  147. #
  148. # Valid Methods:
  149. #    cget
  150. #    configure
  151. #    parse
  152. #    reset
  153. #
  154. # Arguments:
  155. #    parser    parser object
  156. #    method    minor command
  157. #    args    other arguments
  158. #
  159. # Results:
  160. #    Depends on method
  161.  
  162. proc xml::ParseCommand {parser method args} {
  163.     upvar \#0 [namespace current]::$parser state
  164.  
  165.     switch -- $method {
  166.     cget {
  167.         return $state([lindex $args 0])
  168.     }
  169.     configure {
  170.         foreach {opt value} $args {
  171.         set state($opt) $value
  172.         }
  173.     }
  174.     parse {
  175.         ParseCommand_parse $parser [lindex $args 0]
  176.     }
  177.     reset {
  178.         if {[llength $args]} {
  179.         return -code error "too many arguments"
  180.         }
  181.         ParseCommand_reset $parser
  182.     }
  183.     default {
  184.         return -code error "unknown method \"$method\""
  185.     }
  186.     }
  187.  
  188.     return {}
  189. }
  190.  
  191. # xml::ParseCommand_parse --
  192. #
  193. #    Parses document instance data
  194. #
  195. # Arguments:
  196. #    object    parser object
  197. #    xml    data
  198. #
  199. # Results:
  200. #    Callbacks are invoked, if any are defined
  201.  
  202. proc xml::ParseCommand_parse {object xml} {
  203.     upvar \#0 [namespace current]::$object parser
  204.     variable Wsp
  205.     variable tokExpr
  206.     variable substExpr
  207.  
  208.     set parent [namespace parent]
  209.     if {![string compare :: $parent]} {
  210.     set parent {}
  211.     }
  212.  
  213.     set tokenised [lrange \
  214.         [${parent}::sgml::tokenise $xml \
  215.         $tokExpr \
  216.         $substExpr \
  217.         -internaldtdvariable [namespace current]::${object}(internaldtd)] \
  218.     5 end]
  219.  
  220.     eval ${parent}::sgml::parseEvent \
  221.     [list $tokenised \
  222.         -emptyelement [namespace code ParseEmpty] \
  223.         -parseattributelistcommand [namespace code ParseAttrs]] \
  224.     [array get parser -*command] \
  225.     [array get parser -entityvariable] \
  226.     [array get parser -reportempty] \
  227.     -normalize 0 \
  228.     -internaldtd [list $parser(internaldtd)]
  229.  
  230.     return {}
  231. }
  232.  
  233. # xml::ParseEmpty --
  234. #
  235. #    Used by parser to determine whether an element is empty.
  236. #    This should be dead easy in XML.  The only complication is
  237. #    that the RE above can't catch the trailing slash, so we have
  238. #    to dig it out of the tag name or attribute list.
  239. #
  240. #    Tcl 8.1 REs should fix this.
  241. #
  242. # Arguments:
  243. #    tag    element name
  244. #    attr    attribute list (raw)
  245. #    e    End tag delimiter.
  246. #
  247. # Results:
  248. #    "/" if the trailing slash is found.  Optionally, return a list
  249. #    containing new values for the tag name and/or attribute list.
  250.  
  251. proc xml::ParseEmpty {tag attr e} {
  252.  
  253.     if {[string match */ [string trimright $tag]] && \
  254.         ![string length $attr]} {
  255.     regsub {/$} $tag {} tag
  256.     return [list / $tag $attr]
  257.     } elseif {[string match */ [string trimright $attr]]} {
  258.     regsub {/$} [string trimright $attr] {} attr
  259.     return [list / $tag $attr]
  260.     } else {
  261.     return {}
  262.     }
  263.  
  264. }
  265.  
  266. # xml::ParseAttrs --
  267. #
  268. #    Parse element attributes.
  269. #
  270. # There are two forms for name-value pairs:
  271. #
  272. #    name="value"
  273. #    name='value'
  274. #
  275. # Watch out for the trailing slash on empty elements.
  276. #
  277. # Arguments:
  278. #    attrs    attribute string given in a tag
  279. #
  280. # Results:
  281. #    Returns a Tcl list representing the name-value pairs in the 
  282. #    attribute string
  283.  
  284. proc xml::ParseAttrs attrs {
  285.     variable Wsp
  286.     variable Name
  287.  
  288.     # First check whether there's any work to do
  289.     if {![string compare {} [string trim $attrs]]} {
  290.     return {}
  291.     }
  292.  
  293.     # Strip the trailing slash on empty elements
  294.     regsub [format {/[%s]*$} " \t\n\r"] $attrs {} atList
  295.  
  296.     set mode name
  297.     set result {}
  298.     foreach component [split $atList =] {
  299.     switch $mode {
  300.         name {
  301.         set component [string trim $component]
  302.         if {[regexp $Name $component]} {
  303.             lappend result $component
  304.         } else {
  305.             return -code error "invalid attribute name \"$component\""
  306.         }
  307.         set mode value:start
  308.         }
  309.         value:start {
  310.         set component [string trimleft $component]
  311.         set delimiter [string index $component 0]
  312.         set value {}
  313.         switch -- $delimiter {
  314.             \" -
  315.             ' {
  316.             if {[regexp [format {%s([^%s]*)%s(.*)} $delimiter $delimiter $delimiter] $component discard value remainder]} {
  317.                 lappend result $value
  318.                 set remainder [string trim $remainder]
  319.                 if {[string length $remainder]} {
  320.                 if {[regexp $Name $remainder]} {
  321.                     lappend result $remainder
  322.                     set mode value:start
  323.                 } else {
  324.                     return -code error "invalid attribute name \"$remainder\""
  325.                 }
  326.                 } else {
  327.                 set mode end
  328.                 }
  329.             } else {
  330.                 set value [string range $component 1 end]
  331.                 set mode value:continue
  332.             }
  333.             }
  334.             default {
  335.             return -code error "invalid value for attribute \"[lindex $result end]\""
  336.             }
  337.         }
  338.         }
  339.         value:continue {
  340.         if {[regexp [format {([^%s]*)%s(.*)} $delimiter $delimiter] $component discard valuepart remainder]} {
  341.             append value = $valuepart
  342.             lappend result $value
  343.             set remainder [string trim $remainder]
  344.             if {[string length $remainder]} {
  345.             if {[regexp $Name $remainder]} {
  346.                 lappend result $remainder
  347.                 set mode value:start
  348.             } else {
  349.                 return -code error "invalid attribute name \"$remainder\""
  350.             }
  351.             } else {
  352.             set mode end
  353.             }
  354.         } else {
  355.             append value = $component
  356.         }
  357.         }
  358.         end {
  359.         return -code error "unexpected data found after end of attribute list"
  360.         }
  361.     }
  362.     }
  363.  
  364.     switch $mode {
  365.     name -
  366.     end {
  367.         # This is normal
  368.     }
  369.     default {
  370.         return -code error "unexpected end of attribute list"
  371.     }
  372.     }
  373.  
  374.     return $result
  375. }
  376.  
  377. proc xml::OLDParseAttrs {attrs} {
  378.     variable Wsp
  379.     variable Name
  380.  
  381.     # First check whether there's any work to do
  382.     if {![string compare {} [string trim $attrs]]} {
  383.     return {}
  384.     }
  385.  
  386.     # Strip the trailing slash on empty elements
  387.     regsub [format {/[%s]*$} " \t\n\r"] $attrs {} atList
  388.  
  389.     # Protect Tcl special characters
  390.     #regsub -all {([[\$\\])} $atList {\\\1} atList
  391.     regsub -all & $atList {\&} atList
  392.     regsub -all {\[} $atList {\&ob;} atList
  393.     regsub -all {\]} $atList {\&cb;} atlist
  394.     # NB. sgml package delivers braces and backslashes quoted
  395.     regsub -all {\\\{} $atList {\&oc;} atList
  396.     regsub -all {\\\}} $atList {\&cc;} atlist
  397.     regsub -all {\$} $atList {\$} atList
  398.     regsub -all {\\\\} $atList {\&bs;} atList
  399.  
  400.     regsub -all [format {(%s)[%s]*=[%s]*"([^"]*)"} $Name $Wsp $Wsp] \
  401.         $atList {[set parsed(\1) {\2}; set dummy {}] } atList    ;# "
  402.     regsub -all [format {(%s)[%s]*=[%s]*'([^']*)'} $Name $Wsp $Wsp] \
  403.         $atList {[set parsed(\1) {\2}; set dummy {}] } atList
  404.  
  405.     set leftovers [subst $atList]
  406.  
  407.     if {[string length [string trim $leftovers]]} {
  408.     return -code error "syntax error in attribute list \"$attrs\""
  409.     }
  410.  
  411.     return [ParseAttrs:Deprotect [array get parsed]]
  412. }
  413.  
  414. # xml::ParseAttrs:Deprotect --
  415. #
  416. #    Reverse map Tcl special characters previously protected 
  417. #
  418. # Arguments:
  419. #    attrs    attribute list
  420. #
  421. # Results:
  422. #    Characters substituted
  423.  
  424. proc xml::ParseAttrs:Deprotect attrs {
  425.  
  426.     regsub -all &\; $attrs \\& attrs
  427.     regsub -all &ob\; $attrs \[ attrs
  428.     regsub -all &cb\; $attrs \] attrs
  429.     regsub -all &oc\; $attrs \{ attrs
  430.     regsub -all &cc\; $attrs \} attrs
  431.     regsub -all &dollar\; $attrs \$ attrs
  432.     regsub -all &bs\; $attrs \\\\ attrs
  433.  
  434.     return $attrs
  435.  
  436. }
  437.  
  438. # xml::ParseCommand_reset --
  439. #
  440. #    Initialize parser data
  441. #
  442. # Arguments:
  443. #    object    parser object
  444. #
  445. # Results:
  446. #    Parser data structure initialised
  447.  
  448. proc xml::ParseCommand_reset object {
  449.     upvar \#0 [namespace current]::$object parser
  450.  
  451.     array set parser [list \
  452.         -final 1        \
  453.         internaldtd {}    \
  454.     ]
  455. }
  456.  
  457. # xml::noop --
  458. #
  459. # A do-nothing proc
  460.  
  461. proc xml::noop args {}
  462.  
  463. ### Following procedures are based on html_library
  464.  
  465. # xml::zapWhite --
  466. #
  467. #    Convert multiple white space into a single space.
  468. #
  469. # Arguments:
  470. #    data    plain text
  471. #
  472. # Results:
  473. #    As above
  474.  
  475. proc xml::zapWhite data {
  476.     regsub -all "\[ \t\r\n\]+" $data { } data
  477.     return $data
  478. }
  479.  
  480. #
  481. # DTD parser for XML is wholly contained within the sgml.tcl package
  482. #
  483.  
  484. # xml::parseDTD --
  485. #
  486. #    Entry point to the XML DTD parser.
  487. #
  488. # Arguments:
  489. #    dtd    XML data defining the DTD to be parsed
  490. #    args    configuration options
  491. #
  492. # Results:
  493. #    Returns a three element list, first element is the content model
  494. #    for each element, second element are the attribute lists of the
  495. #    elements and the third element is the entity map.
  496.  
  497. proc xml::parseDTD {dtd args} {
  498.     return [eval [expr {[namespace parent] == {::} ? {} : [namespace parent]}]::sgml::parseDTD [list $dtd] $args]
  499. }
  500.  
  501.