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.1.tcl < prev    next >
Encoding:
Text File  |  2001-10-22  |  12.0 KB  |  488 lines

  1. # tclparser-8.1.tcl --
  2. #
  3. #    This file provides a Tcl implementation of a XML parser.
  4. #    This file supports Tcl 8.1.
  5. #
  6. #    See xml-8.[01].tcl for definitions of character sets and
  7. #    regular expressions.
  8. #
  9. # Copyright (c) 1998-2001 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.1.tcl,v 1.12 2001/02/26 02:11:11 doss Exp $
  30.  
  31. package require Tcl 8.1
  32.  
  33. package provide xml::tclparser 2.0
  34.  
  35. package require xmldefs 2.0
  36.  
  37. package require sgmlparser 1.0
  38.  
  39. namespace eval xml::tclparser {
  40.  
  41.     namespace export create createexternal externalentity parse configure get delete
  42.  
  43.     # Tokenising expressions
  44.  
  45.     variable tokExpr $::xml::tokExpr
  46.     variable substExpr $::xml::substExpr
  47.  
  48.     # Register this parser class
  49.  
  50.     ::xml::parserclass create tcl \
  51.         -createcommand [namespace code create] \
  52.         -createentityparsercommand [namespace code createentityparser] \
  53.         -parsecommand [namespace code parse] \
  54.         -configurecommand [namespace code configure] \
  55.         -deletecommand [namespace code delete]
  56. }
  57.  
  58. # xml::tclparser::create --
  59. #
  60. #    Creates XML parser object.
  61. #
  62. # Arguments:
  63. #    name    unique identifier for this instance
  64. #
  65. # Results:
  66. #    The state variable is initialised.
  67.  
  68. proc xml::tclparser::create name {
  69.  
  70.     # Initialise state variable
  71.     upvar \#0 [namespace current]::$name parser
  72.     array set parser [list -name $name            \
  73.     -final 1                    \
  74.     -validate 0                    \
  75.     -statevariable [namespace current]::$name    \
  76.     -baseurl {}                    \
  77.     internaldtd {}                    \
  78.     entities [namespace current]::Entities$name    \
  79.     extentities [namespace current]::ExtEntities$name    \
  80.     parameterentities [namespace current]::PEntities$name    \
  81.     externalparameterentities [namespace current]::ExtPEntities$name    \
  82.     elementdecls [namespace current]::ElDecls$name    \
  83.     attlistdecls [namespace current]::AttlistDecls$name    \
  84.     notationdecls [namespace current]::NotDecls$name    \
  85.     depth 0                        \
  86.     ]
  87.  
  88.     # Initialise entities with predefined set
  89.     array set [namespace current]::Entities$name [array get ::sgml::EntityPredef]
  90.  
  91.     return $name
  92. }
  93.  
  94. # xml::tclparser::createentityparser --
  95. #
  96. #    Creates XML parser object for an entity.
  97. #
  98. # Arguments:
  99. #    name    name for the new parser
  100. #    parent    name of parent parser
  101. #
  102. # Results:
  103. #    The state variable is initialised.
  104.  
  105. proc xml::tclparser::createentityparser {parent name} {
  106.     upvar #0 [namespace current]::$parent p
  107.  
  108.     # Initialise state variable
  109.     upvar \#0 [namespace current]::$name external
  110.     array set external [array get p]
  111.  
  112.     array set external [list -name $name            \
  113.     -statevariable [namespace current]::$name    \
  114.     internaldtd {}                    \
  115.     line 0                        \
  116.     ]
  117.     incr external(depth)
  118.  
  119.     return $name
  120. }
  121.  
  122. # xml::tclparser::configure --
  123. #
  124. #    Configures a XML parser object.
  125. #
  126. # Arguments:
  127. #    name    unique identifier for this instance
  128. #    args    option name/value pairs
  129. #
  130. # Results:
  131. #    May change values of config options
  132.  
  133. proc xml::tclparser::configure {name args} {
  134.     upvar \#0 [namespace current]::$name parser
  135.  
  136.     # BUG: very crude, no checks for illegal args
  137.     array set parser $args
  138.  
  139.     return {}
  140. }
  141.  
  142. # xml::tclparser::parse --
  143. #
  144. #    Parses document instance data
  145. #
  146. # Arguments:
  147. #    name    parser object
  148. #    xml    data
  149. #    args    configuration options
  150. #
  151. # Results:
  152. #    Callbacks are invoked
  153.  
  154. proc xml::tclparser::parse {name xml args} {
  155.  
  156.     array set options $args
  157.     upvar \#0 [namespace current]::$name parser
  158.     variable tokExpr
  159.     variable substExpr
  160.  
  161.     set parseOptions [list \
  162.         -emptyelement [namespace code ParseEmpty] \
  163.         -parseattributelistcommand [namespace code ParseAttrs] \
  164.         -parseentitydeclcommand [namespace code ParseEntity] \
  165.         -normalize 0]
  166.     eval lappend parseOptions \
  167.         [array get parser -*command] \
  168.         [array get parser -reportempty] \
  169.         [array get parser -name] \
  170.         [array get parser -baseurl] \
  171.         [array get parser -validate] \
  172.         [array get parser -final] \
  173.         [array get parser -defaultexpandinternalentities] \
  174.         [array get parser entities] \
  175.         [array get parser extentities] \
  176.         [array get parser parameterentities] \
  177.         [array get parser externalparameterentities] \
  178.         [array get parser elementdecls] \
  179.         [array get parser attlistdecls] \
  180.         [array get parser notationdecls]
  181.  
  182.     set dtdsubset no
  183.     catch {set dtdsubset $options(-dtdsubset)}
  184.     switch -- $dtdsubset {
  185.     internal {
  186.         # Bypass normal parsing
  187.         lappend parseOptions -statevariable $parser(-statevariable)
  188.         array set intOptions [array get ::sgml::StdOptions]
  189.         array set intOptions $parseOptions
  190.         ::sgml::ParseDTD:Internal [array get intOptions] $xml
  191.         return {}
  192.     }
  193.     external {
  194.         # Bypass normal parsing
  195.         lappend parseOptions -statevariable $parser(-statevariable)
  196.         array set intOptions [array get ::sgml::StdOptions]
  197.         array set intOptions $parseOptions
  198.         ::sgml::ParseDTD:External [array get intOptions] $xml
  199.         return {}
  200.     }
  201.     default {
  202.         # Pass through to normal processing
  203.     }
  204.     }
  205.  
  206.     set tokenised [lrange \
  207.         [::sgml::tokenise $xml \
  208.         $tokExpr \
  209.         $substExpr \
  210.         -internaldtdvariable [namespace current]::${name}(internaldtd)] \
  211.     4 end]
  212.  
  213.     lappend parseOptions -internaldtd [list $parser(internaldtd)]
  214.     eval ::sgml::parseEvent [list $tokenised] $parseOptions
  215.  
  216.     return {}
  217. }
  218.  
  219. # xml::tclparser::ParseEmpty --  Tcl 8.1+ version
  220. #
  221. #    Used by parser to determine whether an element is empty.
  222. #    This is usually dead easy in XML, but as always not quite.
  223. #    Have to watch out for empty element syntax
  224. #
  225. # Arguments:
  226. #    tag    element name
  227. #    attr    attribute list (raw)
  228. #    e    End tag delimiter.
  229. #
  230. # Results:
  231. #    Return value of e
  232.  
  233. proc xml::tclparser::ParseEmpty {tag attr e} {
  234.     switch -glob [string length $e],[regexp "/[::xml::cl $::xml::Wsp]*$" $attr] {
  235.     0,0 {
  236.         return {}
  237.     }
  238.     0,* {
  239.         return /
  240.     }
  241.     default {
  242.         return $e
  243.     }
  244.     }
  245. }
  246.  
  247. # xml::tclparser::ParseAttrs -- Tcl 8.1+ version
  248. #
  249. #    Parse element attributes.
  250. #
  251. # There are two forms for name-value pairs:
  252. #
  253. #    name="value"
  254. #    name='value'
  255. #
  256. # Arguments:
  257. #    attrs    attribute string given in a tag
  258. #
  259. # Results:
  260. #    Returns a Tcl list representing the name-value pairs in the 
  261. #    attribute string
  262. #
  263. #    A ">" occurring in the attribute list causes problems when parsing
  264. #    the XML.  This manifests itself by an unterminated attribute value
  265. #    and a ">" appearing the element text.
  266. #    In this case return a three element list;
  267. #    the message "unterminated attribute value", the attribute list it
  268. #    did manage to parse and the remainder of the attribute list.
  269.  
  270. proc xml::tclparser::ParseAttrs attrs {
  271.  
  272.     set result {}
  273.  
  274.     while {[string length [string trim $attrs]]} {
  275.     if {[regexp ($::xml::Name)[::sgml::cl $::xml::Wsp]*=[::sgml::cl $::xml::Wsp]*("|')([::sgml::cl ^<]*?)\\2(.*) $attrs discard attrName delimiter value attrs]} {
  276.         lappend result $attrName [NormalizeAttValue $value]
  277.     } elseif {[regexp $::xml::Name[::sgml::cl $::xml::Wsp]*=[::sgml::cl $::xml::Wsp]*("|')[::sgml::cl ^<]*\$ $attrs]} {
  278.         return -code error [list {unterminated attribute value} $result $attrs]
  279.     } else {
  280.         return -code error "invalid attribute list"
  281.     }
  282.     }
  283.  
  284.     return $result
  285. }
  286.  
  287. # xml::tclparser::NormalizeAttValue --
  288. #
  289. #    Perform attribute value normalisation.  This involves:
  290. #    . character references are appended to the value
  291. #    . entity references are recursively processed and replacement value appended
  292. #    . whitespace characters cause a space to be appended
  293. #    . other characters appended as-is
  294. #
  295. #    Because no state is passed in here, it's a bit difficult
  296. #    to pass entity references back into the parser for further
  297. #    replacement.  I'll just punt on the whole thing for now and do
  298. #    basic normalisation - char refs, pre-defined entities and ws.
  299. #
  300. # Arguments:
  301. #    value    unparsed attribute value
  302. #
  303. # Results:
  304. #    Normalised value returned.
  305.  
  306. proc xml::tclparser::NormalizeAttValue value {
  307.  
  308.     # sgmlparser already has backslashes protected
  309.     # Protect Tcl specials
  310.     regsub -all {([][$])} $value {\\\1} value
  311.  
  312.     # Deal with white space
  313.     regsub -all "\[$::xml::Wsp\]" $value { } value
  314.  
  315.     # Find entity refs
  316.     regsub -all {&([^;]+);} $value {[NormalizeAttValue:DeRef {\1}]} value
  317.  
  318.     return [subst $value]
  319. }
  320.  
  321. # xml::tclparser::NormalizeAttValue:DeRef --
  322. #
  323. #    Simplistic handler to normalize attribute values
  324. #
  325. # Arguments:
  326. #    ref    entity reference
  327. #
  328. # Results:
  329. #    Returns character
  330.  
  331. proc xml::tclparser::NormalizeAttValue:DeRef ref {
  332.     switch -glob -- $ref {
  333.     #x* {
  334.         scan [string range 2 $ref] %x value
  335.         return $value
  336.     }
  337.     #* {
  338.         scan [string range 1 $ref] %d value
  339.         return $value
  340.     }
  341.     lt -
  342.     gt -
  343.     amp -
  344.     quot -
  345.     apos {
  346.         array set map {lt < gt > amp & quot \" apos '}
  347.         return $map($ref)
  348.     }
  349.     default {
  350.         return -code error "unable to resolve entity reference \"$ref\""
  351.     }
  352.     }
  353. }
  354.  
  355. # xml::tclparser::ParseEntity --
  356. #
  357. #    Parse general entity declaration
  358. #
  359. # Arguments:
  360. #    data    text to parse
  361. #
  362. # Results:
  363. #    Tcl list containing entity declaration
  364.  
  365. proc xml::tclparser::ParseEntity data {
  366.     set data [string trim $data]
  367.     if {[regexp $::sgml::ExternalEntityExpr $data discard type delimiter1 id1 discard delimiter2 id2 optNDATA ndata]} {
  368.     switch $type {
  369.         PUBLIC {
  370.         return [list external $id2 $id1 $ndata]
  371.         }
  372.         SYSTEM {
  373.         return [list external $id1 {} $ndata]
  374.         }
  375.     }
  376.     } elseif {[regexp {^("|')(.*?)\1$} $data discard delimiter value]} {
  377.     return [list internal $value]
  378.     } else {
  379.     return -code error "badly formed entity declaration"
  380.     }
  381. }
  382.  
  383. # xml::tclparser::delete --
  384. #
  385. #    Destroy parser data
  386. #
  387. # Arguments:
  388. #    name    parser object
  389. #
  390. # Results:
  391. #    Parser data structure destroyed
  392.  
  393. proc xml::tclparser::delete name {
  394.     upvar \#0 [namespace current]::$name parser
  395.     catch {::sgml::ParserDelete $parser(-statevariable)}
  396.     catch {unset parser}
  397.     return {}
  398. }
  399.  
  400. # xml::tclparser::get --
  401. #
  402. #    Retrieve additional information from the parser
  403. #
  404. # Arguments:
  405. #    name    parser object
  406. #    method    info to retrieve
  407. #    args    additional arguments for method
  408. #
  409. # Results:
  410. #    Depends on method
  411.  
  412. proc xml::tclparser::get {name method args} {
  413.     upvar #0 [namespace current]::$name parser
  414.  
  415.     switch -- $method {
  416.  
  417.     elementdecl {
  418.         switch [llength $args] {
  419.  
  420.         0 {
  421.             # Return all element declarations
  422.             upvar #0 $parser(elementdecls) elements
  423.             return [array get elements]
  424.         }
  425.  
  426.         1 {
  427.             # Return specific element declaration
  428.             upvar #0 $parser(elementdecls) elements
  429.             if {[info exists elements([lindex $args 0])]} {
  430.             return [array get elements [lindex $args 0]]
  431.             } else {
  432.             return -code error "element \"[lindex $args 0]\" not declared"
  433.             }
  434.         }
  435.  
  436.         default {
  437.             return -code error "wrong number of arguments: should be \"elementdecl ?element?\""
  438.         }
  439.         }
  440.     }
  441.  
  442.     attlist {
  443.         if {[llength $args] != 1} {
  444.         return -code error "wrong number of arguments: should be \"get attlist element\""
  445.         }
  446.  
  447.         upvar #0 $parser(attlistdecls)
  448.  
  449.         return {}
  450.     }
  451.  
  452.     entitydecl {
  453.     }
  454.  
  455.     parameterentitydecl {
  456.     }
  457.  
  458.     notationdecl {
  459.     }
  460.  
  461.     default {
  462.         return -code error "unknown method \"$method\""
  463.     }
  464.     }
  465.  
  466.     return {}
  467. }
  468.  
  469. # xml::tclparser::ExternalEntity --
  470. #
  471. #    Resolve and parse external entity
  472. #
  473. # Arguments:
  474. #    name    parser object
  475. #    base    base URL
  476. #    sys    system identifier
  477. #    pub    public identifier
  478. #
  479. # Results:
  480. #    External entity is fetched and parsed
  481.  
  482. proc xml::tclparser::ExternalEntity {name base sys pub} {
  483. }
  484.