home *** CD-ROM | disk | FTP | other *** search
/ PC World 2002 May / PCWorld_2002-05_cd.bin / Software / TemaCD / activetcltk / ActiveTcl8.3.4.1-8.win32-ix86.exe / ActiveTcl8.3.4.1-win32-ix86 / lib / tclxml2.0 / tclparser-8.0.tcl < prev    next >
Encoding:
Text File  |  2001-10-22  |  9.3 KB  |  376 lines

  1. # tclparser-8.0.tcl --
  2. #
  3. #    This file provides a Tcl implementation of a XML parser.
  4. #    This file supports Tcl 8.0.
  5. #
  6. #    See xml-8.[01].tcl for definitions of character sets and
  7. #    regular expressions.
  8. #
  9. # Copyright (c) 1998,1999 Zveno Pty Ltd
  10. # http://www.zveno.com/
  11. # Zveno makes this software and all associated data and documentation
  12. # ('Software') available free of charge for any purpose.
  13. # Copies may be made of this Software but all of this notice must be included
  14. # on any copy.
  15. # The Software was developed for research purposes and Zveno does not warrant
  16. # that it is error free or fit for any purpose.  Zveno disclaims any
  17. # liability for all claims, expenses, losses, damages and costs any user may
  18. # incur as a result of using, copying or modifying the Software.
  19. #
  20. # Copyright (c) 1997 Australian National University (ANU).
  21. # ANU makes this software and all associated data and documentation
  22. # ('Software') available free of charge for any purpose. You may make copies
  23. # of the Software but you must include all of this notice on any copy.
  24. # The Software was developed for research purposes and ANU does not warrant
  25. # that it is error free or fit for any purpose.  ANU disclaims any
  26. # liability for all claims, expenses, losses, damages and costs any user may
  27. # incur as a result of using, copying or modifying the Software.
  28. #
  29. # $Id: tclparser-8.0.tcl,v 1.3 2001/02/26 02:11:11 doss Exp $
  30.  
  31. package require -exact Tcl 8.0
  32.  
  33. package require xmldefs 1.10
  34.  
  35. package require sgmlparser 1.0
  36.  
  37. package provide xml::tclparser 2.0
  38.  
  39. namespace eval xml {
  40.  
  41.     # Procedures for parsing XML documents
  42.     namespace export parser
  43.     # Procedures for parsing XML DTDs
  44.     namespace export DTDparser
  45.  
  46.     # Counter for creating unique parser objects
  47.     variable ParserCounter 0
  48.  
  49. }
  50.  
  51. # xml::parser --
  52. #
  53. #    Creates XML parser object.
  54. #
  55. # Arguments:
  56. #    args    Unique name for parser object
  57. #        plus option/value pairs
  58. #
  59. # Recognised Options:
  60. #    -final            Indicates end of document data
  61. #    -elementstartcommand    Called when an element starts
  62. #    -elementendcommand    Called when an element ends
  63. #    -characterdatacommand    Called when character data occurs
  64. #    -processinginstructioncommand    Called when a PI occurs
  65. #    -externalentityrefcommand    Called for an external entity reference
  66. #
  67. #    (Not compatible with expat)
  68. #    -xmldeclcommand        Called when the XML declaration occurs
  69. #    -doctypecommand        Called when the document type declaration occurs
  70. #
  71. #    -errorcommand        Script to evaluate for a fatal error
  72. #    -warningcommand        Script to evaluate for a reportable warning
  73. #    -statevariable        global state variable
  74. #    -reportempty        whether to provide empty element indication
  75. #
  76. # Results:
  77. #    The state variable is initialised.
  78.  
  79. proc xml::parser {args} {
  80.     variable ParserCounter
  81.  
  82.     if {[llength $args] > 0} {
  83.     set name [lindex $args 0]
  84.     set args [lreplace $args 0 0]
  85.     } else {
  86.     set name parser[incr ParserCounter]
  87.     }
  88.  
  89.     if {[info command [namespace current]::$name] != {}} {
  90.     return -code error "unable to create parser object \"[namespace current]::$name\" command"
  91.     }
  92.  
  93.     # Initialise state variable and object command
  94.     upvar \#0 [namespace current]::$name parser
  95.     set sgml_ns [namespace parent]::sgml
  96.     array set parser [list name $name            \
  97.     -final 1                    \
  98.     -elementstartcommand ${sgml_ns}::noop        \
  99.     -elementendcommand ${sgml_ns}::noop        \
  100.     -characterdatacommand ${sgml_ns}::noop        \
  101.     -processinginstructioncommand ${sgml_ns}::noop    \
  102.     -externalentityrefcommand ${sgml_ns}::noop    \
  103.     -xmldeclcommand ${sgml_ns}::noop        \
  104.     -doctypecommand ${sgml_ns}::noop        \
  105.     -warningcommand ${sgml_ns}::noop        \
  106.     -statevariable [namespace current]::$name    \
  107.     -reportempty 0                    \
  108.     internaldtd {}                    \
  109.     ]
  110.  
  111.     proc [namespace current]::$name {method args} \
  112.     "eval ParseCommand $name \$method \$args"
  113.  
  114.     eval ParseCommand [list $name] configure $args
  115.  
  116.     return [namespace current]::$name
  117. }
  118.  
  119. # xml::ParseCommand --
  120. #
  121. #    Handles parse object command invocations
  122. #
  123. # Valid Methods:
  124. #    cget
  125. #    configure
  126. #    parse
  127. #    reset
  128. #
  129. # Arguments:
  130. #    parser    parser object
  131. #    method    minor command
  132. #    args    other arguments
  133. #
  134. # Results:
  135. #    Depends on method
  136.  
  137. proc xml::ParseCommand {parser method args} {
  138.     upvar \#0 [namespace current]::$parser state
  139.  
  140.     switch -- $method {
  141.     cget {
  142.         return $state([lindex $args 0])
  143.     }
  144.     configure {
  145.         foreach {opt value} $args {
  146.         set state($opt) $value
  147.         }
  148.     }
  149.     parse {
  150.         ParseCommand_parse $parser [lindex $args 0]
  151.     }
  152.     reset {
  153.         if {[llength $args]} {
  154.         return -code error "too many arguments"
  155.         }
  156.         ParseCommand_reset $parser
  157.     }
  158.     default {
  159.         return -code error "unknown method \"$method\""
  160.     }
  161.     }
  162.  
  163.     return {}
  164. }
  165.  
  166. # xml::ParseCommand_parse --
  167. #
  168. #    Parses document instance data
  169. #
  170. # Arguments:
  171. #    object    parser object
  172. #    xml    data
  173. #
  174. # Results:
  175. #    Callbacks are invoked, if any are defined
  176.  
  177. proc xml::ParseCommand_parse {object xml} {
  178.     upvar \#0 [namespace current]::$object parser
  179.     variable Wsp
  180.     variable tokExpr
  181.     variable substExpr
  182.  
  183.     set parent [namespace parent]
  184.     if {![string compare :: $parent]} {
  185.     set parent {}
  186.     }
  187.  
  188.     set tokenised [lrange \
  189.         [${parent}::sgml::tokenise $xml \
  190.         $tokExpr \
  191.         $substExpr \
  192.         -internaldtdvariable [namespace current]::${object}(internaldtd)] \
  193.     4 end]
  194.  
  195.     eval ${parent}::sgml::parseEvent \
  196.     [list $tokenised \
  197.         -emptyelement [namespace code ParseEmpty] \
  198.         -parseattributelistcommand [namespace code ParseAttrs]] \
  199.     [array get parser -*command] \
  200.     [array get parser -entityvariable] \
  201.     [array get parser -reportempty] \
  202.     [array get parser -final] \
  203.     -normalize 0 \
  204.     -internaldtd [list $parser(internaldtd)]
  205.  
  206.     return {}
  207. }
  208.  
  209. # xml::ParseEmpty --  Tcl 8.0 version
  210. #
  211. #       Used by parser to determine whether an element is empty.
  212. #       This should be dead easy in XML.  The only complication is
  213. #       that the RE above can't catch the trailing slash, so we have
  214. #       to dig it out of the tag name or attribute list.
  215. #
  216. #       Tcl 8.1 REs should fix this.
  217. #
  218. # Arguments:
  219. #       tag     element name
  220. #       attr    attribute list (raw)
  221. #       e       End tag delimiter.
  222. #
  223. # Results:
  224. #       "/" if the trailing slash is found.  Optionally, return a list
  225. #       containing new values for the tag name and/or attribute list.
  226.  
  227. proc xml::ParseEmpty {tag attr e} {
  228.  
  229.     if {[string match */ [string trimright $tag]] && \
  230.             ![string length $attr]} {
  231.         regsub {/$} $tag {} tag
  232.         return [list / $tag $attr]
  233.     } elseif {[string match */ [string trimright $attr]]} {
  234.         regsub {/$} [string trimright $attr] {} attr
  235.         return [list / $tag $attr]
  236.     } else {
  237.         return {}
  238.     }
  239.  
  240. }
  241.  
  242. # xml::ParseAttrs --
  243. #
  244. #    Parse element attributes.
  245. #
  246. # There are two forms for name-value pairs:
  247. #
  248. #    name="value"
  249. #    name='value'
  250. #
  251. # Watch out for the trailing slash on empty elements.
  252. #
  253. # Arguments:
  254. #    attrs    attribute string given in a tag
  255. #
  256. # Results:
  257. #    Returns a Tcl list representing the name-value pairs in the 
  258. #    attribute string
  259.  
  260. proc xml::ParseAttrs attrs {
  261.     variable Wsp
  262.     variable Name
  263.  
  264.     # First check whether there's any work to do
  265.     if {![string compare {} [string trim $attrs]]} {
  266.     return {}
  267.     }
  268.  
  269.     # Strip the trailing slash on empty elements
  270.     regsub [format {/[%s]*$} " \t\n\r"] $attrs {} atList
  271.  
  272.     set mode name
  273.     set result {}
  274.     foreach component [split $atList =] {
  275.     switch $mode {
  276.         name {
  277.         set component [string trim $component]
  278.         if {[regexp $Name $component]} {
  279.             lappend result $component
  280.         } else {
  281.             return -code error "invalid attribute name \"$component\""
  282.         }
  283.         set mode value:start
  284.         }
  285.         value:start {
  286.         set component [string trimleft $component]
  287.         set delimiter [string index $component 0]
  288.         set value {}
  289.         switch -- $delimiter {
  290.             \" -
  291.             ' {
  292.             if {[regexp [format {%s([^%s]*)%s(.*)} $delimiter $delimiter $delimiter] $component discard value remainder]} {
  293.                 lappend result $value
  294.                 set remainder [string trim $remainder]
  295.                 if {[string length $remainder]} {
  296.                 if {[regexp $Name $remainder]} {
  297.                     lappend result $remainder
  298.                     set mode value:start
  299.                 } else {
  300.                     return -code error "invalid attribute name \"$remainder\""
  301.                 }
  302.                 } else {
  303.                 set mode end
  304.                 }
  305.             } else {
  306.                 set value [string range $component 1 end]
  307.                 set mode value:continue
  308.             }
  309.             }
  310.             default {
  311.             return -code error "invalid value for attribute \"[lindex $result end]\""
  312.             }
  313.         }
  314.         }
  315.         value:continue {
  316.         if {[regexp [format {([^%s]*)%s(.*)} $delimiter $delimiter] $component discard valuepart remainder]} {
  317.             append value = $valuepart
  318.             lappend result $value
  319.             set remainder [string trim $remainder]
  320.             if {[string length $remainder]} {
  321.             if {[regexp $Name $remainder]} {
  322.                 lappend result $remainder
  323.                 set mode value:start
  324.             } else {
  325.                 return -code error "invalid attribute name \"$remainder\""
  326.             }
  327.             } else {
  328.             set mode end
  329.             }
  330.         } else {
  331.             append value = $component
  332.         }
  333.         }
  334.         end {
  335.         return -code error "unexpected data found after end of attribute list"
  336.         }
  337.     }
  338.     }
  339.  
  340.     switch $mode {
  341.     name -
  342.     end {
  343.         # This is normal
  344.     }
  345.     default {
  346.         return -code error "unexpected end of attribute list"
  347.     }
  348.     }
  349.  
  350.     return $result
  351. }
  352.  
  353. # xml::ParseCommand_reset --
  354. #
  355. #    Initialize parser data
  356. #
  357. # Arguments:
  358. #    object    parser object
  359. #
  360. # Results:
  361. #    Parser data structure initialised
  362.  
  363. proc xml::ParseCommand_reset object {
  364.     upvar \#0 [namespace current]::$object parser
  365.  
  366.     array set parser [list \
  367.         -final 1        \
  368.         internaldtd {}    \
  369.     ]
  370. }
  371.  
  372.