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 / tcldom2.0 / dom.tcl next >
Encoding:
Text File  |  2001-10-22  |  96.9 KB  |  3,986 lines

  1. # dom.tcl --
  2. #
  3. #    This file implements the Tcl language binding for the DOM -
  4. #    the Document Object Model.  Support for the core specification
  5. #    is given here.  Layered support for specific languages, 
  6. #    such as HTML, will be in separate modules.
  7. #
  8. # Copyright (c) 1998-2000 Zveno Pty Ltd
  9. # http://www.zveno.com/
  10. #
  11. # Zveno makes this software available free of charge for any purpose.
  12. # Copies may be made of this software but all of this notice must be included
  13. # on any copy.
  14. #
  15. # The software was developed for research purposes only and Zveno does not
  16. # warrant 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 this software.
  19. #
  20. # $Id: dom.tcl,v 1.9 2001/01/23 10:56:38 balls Exp $
  21.  
  22. package provide dom::tcl 2.0
  23.  
  24. # We need the xml package, so that we get Name defined
  25.  
  26. package require xml 2.0
  27.  
  28. namespace eval dom {
  29.     namespace export DOMImplementation
  30.     namespace export document documentFragment node
  31.     namespace export element textNode attribute
  32.     namespace export processingInstruction
  33.     namespace export event
  34.  
  35.     variable maxSpecials
  36.     if {![info exists maxSpecials]} {
  37.     set maxSpecials 10
  38.     }
  39.  
  40.     variable strictDOM 0
  41.  
  42.     # Default -indentspec value
  43.     #    spaces-per-indent-level {collapse-re collapse-value}
  44.     variable indentspec [list 2 [list {        } \t]]
  45.  
  46.     # DOM Level 2 Event defaults
  47.     variable bubbles
  48.     array set bubbles {
  49.     DOMFocusIn 1
  50.     DOMFocusOut 1
  51.     DOMActivate 1
  52.     click 1
  53.     mousedown 1
  54.     mouseup 1
  55.     mouseover 1
  56.     mousemove 1
  57.     mouseout 1
  58.     DOMSubtreeModified 1
  59.     DOMNodeInserted 1
  60.     DOMNodeRemoved 1
  61.     DOMNodeInsertedIntoDocument 0
  62.     DOMNodeRemovedFromDocument 0
  63.     DOMAttrModified 1
  64.     DOMAttrRemoved 1
  65.     DOMCharacterDataModified 1
  66.     }
  67.     variable cancelable
  68.     array set cancelable {
  69.     DOMFocusIn 0
  70.     DOMFocusOut 0
  71.     DOMActivate 1
  72.     click 1
  73.     mousedown 1
  74.     mouseup 1
  75.     mouseover 1
  76.     mousemove 0
  77.     mouseout 1
  78.     DOMSubtreeModified 0
  79.     DOMNodeInserted 0
  80.     DOMNodeRemoved 0
  81.     DOMNodeInsertedIntoDocument 0
  82.     DOMNodeRemovedFromDocument 0
  83.     DOMAttrModified 0
  84.     DOMAttrRemoved 0
  85.     DOMCharacterDataModified 0
  86.     }
  87.  
  88.     # The Namespace URI for XML Namespace declarations
  89.     variable xmlnsURI http://www.w3.org/2000/xmlns/
  90.  
  91. }
  92.  
  93. # Data structure
  94. #
  95. # Documents are stored in an array within the dom namespace.
  96. # Each element of the array is indexed by a unique identifier.
  97. # Each element of the array is a key-value list with at least
  98. # the following fields:
  99. #    id docArray
  100. #    node:parentNode node:childNodes node:nodeType
  101. # Nodes of a particular type may have additional fields defined.
  102. # Note that these fields in many circumstances are configuration options
  103. # for a node type.
  104. #
  105. # "Live" data objects are stored as a separate Tcl variable.
  106. # Lists, such as child node lists, are Tcl list variables (ie scalar)
  107. # and keyed-value lists, such as attribute lists, are Tcl array
  108. # variables.  The accessor function returns the variable name,
  109. # which the application should treat as a read-only object.
  110. #
  111. # A token is a FQ array element reference for a node.
  112.  
  113. # dom::GetHandle --
  114. #
  115. #    Checks that a token is valid and sets an array variable
  116. #    in the caller to contain the node's fields.
  117. #
  118. #    This is expensive, so it is only used when called by
  119. #    the application.
  120. #
  121. # Arguments:
  122. #    type    node type (for future use)
  123. #    token    token passed in
  124. #    varName    variable name in caller to associate with node
  125. #
  126. # Results:
  127. #    Variable gets node's fields, otherwise returns error.
  128. #    Returns empty string.
  129.  
  130. proc dom::GetHandle {type token varName} {
  131.  
  132.     if {![info exists $token]} {
  133.     return -code error "invalid token \"$token\""
  134.     }
  135.  
  136.     upvar 1 $varName data
  137.     array set data [set $token]
  138.  
  139. # Type checking not implemented
  140. #    if {[string compare $data(node:nodeType) "document"]} {
  141. #    return -code error "node is not of type document"
  142. #    }
  143.  
  144.     return {}
  145. }
  146.  
  147. # dom::PutHandle --
  148. #
  149. #    Writes the values from the working copy of the node's data
  150. #    into the document's global array.
  151. #
  152. #    NB. Token checks are performed in GetHandle
  153. #    NB(2). This is still expensive, so is not used.
  154. #
  155. # Arguments:
  156. #    token    token passed in
  157. #    varName    variable name in caller to associate with node
  158. #
  159. # Results:
  160. #    Sets array element for this node to have new values.
  161. #    Returns empty string.
  162.  
  163. proc dom::PutHandle {token varName} {
  164.  
  165.     upvar 1 $varName data
  166.     set $token [array get data]
  167.  
  168.     return {}
  169. }
  170.  
  171. # dom::DOMImplementation --
  172. #
  173. #    Implementation-dependent functions.
  174. #    Most importantly, this command provides a function to
  175. #    create a document instance.
  176. #
  177. # Arguments:
  178. #    method    method to invoke
  179. #    token    token for node
  180. #    args    arguments for method
  181. #
  182. # Results:
  183. #    Depends on method used.
  184.  
  185. namespace eval dom {
  186.     variable DOMImplementationOptions {}
  187.     variable DOMImplementationCounter 0
  188. }
  189.  
  190. proc dom::DOMImplementation {method args} {
  191.     variable DOMImplementationOptions
  192.     variable DOMImplementationCounter
  193.  
  194.     switch -- $method {
  195.  
  196.     hasFeature {
  197.  
  198.         if {[llength $args] != 2} {
  199.         return -code error "wrong number of arguments"
  200.         }
  201.  
  202.         # Later on, could use Tcl package facility
  203.         if {[regexp {create|destroy|parse|query|serialize|trim|Events|UIEvents|isNode} [lindex $args 0]]} {
  204.         if {![string compare [lindex $args 1] "1.0"]} {
  205.             return 1
  206.         } else {
  207.             return 0
  208.         }
  209.         } else {
  210.         return 0
  211.         }
  212.  
  213.     }
  214.  
  215.     createDocument {
  216.         # createDocument introduced in DOM Level 2
  217.  
  218.         if {[llength $args] != 3} {
  219.         return -code error "wrong # arguments: should be DOMImplementation nsURI name doctype"
  220.         }
  221.  
  222.         set doc [DOMImplementation create]
  223.  
  224.         document createElementNS [lindex $args 0] [lindex $args 1]
  225.  
  226.         if {[string length [lindex $args 2]]} {
  227.         document configure -doctype [lindex $args 2]
  228.         }
  229.  
  230.         return $doc
  231.     }
  232.  
  233.     create {
  234.  
  235.         # Non-standard method (see createDocument)
  236.         # Bootstrap a document instance
  237.  
  238.         switch [llength $args] {
  239.         0 {
  240.             # Allocate unique document array name
  241.                 set name [namespace current]::document[incr DOMImplementationCounter]
  242.         }
  243.         1 {
  244.             # Use array name provided.  Should check that it is safe.
  245.             set name [lindex $args 0]
  246.             catch {unset $name}
  247.         }
  248.         default {
  249.             return -code error "wrong number of arguments"
  250.         }
  251.         }
  252.  
  253.         set varPrefix ${name}var
  254.         set arrayPrefix ${name}arr
  255.  
  256.         array set $name [list counter 1 \
  257.         node1 [list id node1 docArray $name        \
  258.             node:nodeType documentFragment        \
  259.             node:parentNode {}            \
  260.             node:nodeName #document            \
  261.             node:nodeValue {}            \
  262.             node:childNodes ${varPrefix}1        \
  263.             documentFragment:masterDoc node1    \
  264.             document:implementation {}        \
  265.             document:xmldecl {version 1.0}        \
  266.             document:documentElement {}        \
  267.             document:doctype {}            \
  268.         ]]
  269.  
  270.         # Initialise child node list
  271.         set ${varPrefix}1 {}
  272.  
  273.         # Return the new toplevel node
  274.         return ${name}(node1)
  275.  
  276.     }
  277.  
  278.     createDocumentType {
  279.         # Introduced in DOM Level 2
  280.  
  281.         if {[llength $args] != 3} {
  282.         return -code error "wrong number of arguments, should be: DOMImplementation createDocumentType name publicid systemid"
  283.         }
  284.  
  285.         return [CreateDocType [lindex $args 0] [list [lindex $args 1] [lindex $args 2]]]
  286.     }
  287.  
  288.     createNode {
  289.         # Non-standard method
  290.         # Creates node(s) in the given document given an XPath expression
  291.  
  292.         if {[llength $args] != 2} {
  293.         return -code error "wrong number of arguments"
  294.         }
  295.  
  296.         package require xpath
  297.  
  298.         return [XPath:CreateNode [lindex $args 0] [lindex $args 1]]
  299.     }
  300.  
  301.     destroy {
  302.  
  303.         # Free all memory associated with a node
  304.  
  305.         if {[llength $args] != 1} {
  306.         return -code error "wrong number of arguments"
  307.         }
  308.         array set node [set [lindex $args 0]]
  309.  
  310.         switch $node(node:nodeType) {
  311.  
  312.         documentFragment {
  313.  
  314.             if {[string length $node(node:parentNode)]} {
  315.             unset $node(node:childNodes)
  316.  
  317.             # Dispatch events
  318.             event postMutationEvent $node(node:parentNode) DOMSubtreeModified
  319.  
  320.             return {}
  321.             }
  322.  
  323.             # else this is the root document node,
  324.             # and we can optimize the cleanup.
  325.             # No need to dispatch events.
  326.  
  327.             # Patch from Gerald Lester
  328.  
  329.             ##
  330.             ## First release all the associated variables
  331.             ##
  332.             upvar #0 $node(docArray) docArray
  333.             for {set i 0} {$i < $docArray(counter)} {incr i} {
  334.             catch {unset ${docArrayName}var$i}
  335.             catch {unset ${docArrayName}arr$i}
  336.             }
  337.              
  338.             ##
  339.             ## Then release the main document array
  340.             ##
  341.             if {[catch {unset $node(docArray)}]} {
  342.             return -code error "unable to destroy document"
  343.             }
  344.  
  345.         }
  346.  
  347.         element {
  348.             # First make sure the node is removed from the tree
  349.             if {[string length $node(node:parentNode)]} {
  350.             node removeChild $node(node:parentNode) [lindex $args 0]
  351.             }
  352.             unset $node(node:childNodes)
  353.             unset $node(element:attributeList)
  354.             unset [lindex $args 0]
  355.  
  356.             # Dispatch events
  357.             event postMutationEvent $node(node:parentNode) DOMSubtreeModified
  358.  
  359.         }
  360.  
  361.         event {
  362.             unset [lindex $args 0]
  363.         }
  364.  
  365.         default {
  366.             # First make sure the node is removed from the tree
  367.             if {[string length $node(node:parentNode)]} {
  368.             node removeChild $node(node:parentNode) [lindex $args 0]
  369.             }
  370.             unset [lindex $args 0]
  371.  
  372.             # Dispatch events
  373.             event postMutationEvent $node(node:parentNode) DOMSubtreeModified
  374.  
  375.         }
  376.  
  377.         }
  378.  
  379.         return {}
  380.  
  381.     }
  382.  
  383.     isNode {
  384.         # isNode - non-standard method
  385.         # Sometimes it is useful to check if an arbitrary string
  386.         # refers to a DOM node
  387.  
  388.         if {[catch {GetHandle node [lindex $args 0] node}]} {
  389.         return 0
  390.         } else {
  391.         return 1
  392.         }
  393.     }
  394.  
  395.     parse {
  396.  
  397.         # This implementation uses TclXML version 2.0.
  398.         # TclXML can choose the best installed parser.
  399.  
  400.         array set opts {-parser {} -progresscommand {} -chunksize 8196}
  401.         if {[catch {array set opts [lrange $args 1 end]}]} {
  402.         return -code error "bad configuration options"
  403.         }
  404.  
  405.         # Create a state array for this parse session
  406.         set state [namespace current]::parse[incr DOMImplementationCounter]
  407.         array set $state [array get opts -*]
  408.         array set $state [list progCounter 0]
  409.         set errorCleanup {}
  410.  
  411.         if {[string length $opts(-parser)]} {
  412.         set parserOpt [list -parser $opts(-parser)]
  413.         } else {
  414.         set parserOpt {}
  415.         }
  416.         if {[catch {package require xml} version]} {
  417.         eval $errorCleanup
  418.         return -code error "unable to load XML parsing package"
  419.         }
  420.         set parser [eval xml::parser $parserOpt]
  421.  
  422.         $parser configure \
  423.         -elementstartcommand [namespace code [list ParseElementStart $state]]    \
  424.         -elementendcommand [namespace code [list ParseElementEnd $state]]    \
  425.         -characterdatacommand [namespace code [list ParseCharacterData $state]] \
  426.         -processinginstructioncommand [namespace code [list ParseProcessingInstruction $state]] \
  427.         -commentcommand [namespace code [list ParseComment $state]] \
  428.         -entityreferencecommand [namespace code [list ParseEntityReference $state]] \
  429.         -xmldeclcommand [namespace code [list ParseXMLDeclaration $state]] \
  430.         -doctypecommand [namespace code [list ParseDocType $state]] \
  431.         -final true
  432.  
  433.         # Create top-level document
  434.         array set $state [list docNode [DOMImplementation create]]
  435.         array set $state [list current [lindex [array get $state docNode] 1]]
  436.  
  437.         # Parse data
  438.         # Bug in TclExpat - doesn't handle non-final inputs
  439.         if {0 && [string length $opts(-progresscommand)]} {
  440.         $parser configure -final false
  441.         while {[string length [lindex $args 0]]} {
  442.             $parser parse [string range [lindex $args 0] 0 $opts(-chunksize)]
  443.             set args [lreplace $args 0 0 \
  444.             [string range [lindex $args 0] $opts(-chunksize) end]]
  445.             uplevel #0 $opts(-progresscommand)
  446.         }
  447.         $parser configure -final true
  448.         } elseif {[catch {$parser parse [lindex $args 0]} err]} {
  449.         catch {rename $parser {}}
  450.         catch {unset $state}
  451.         puts stderr $::errorInfo
  452.         return -code error $err
  453.         }
  454.  
  455.         # Free data structures which are no longer required
  456.         catch {rename $parser {}}
  457.  
  458.         set doc [lindex [array get $state docNode] 1]
  459.         unset $state
  460.         return $doc
  461.  
  462.     }
  463.  
  464.     query {
  465.         # Either: query token string
  466.         # or: query token ?-tagname string? ?-attrname string? ?-attrvalue string? ?-text string? ?-comment string? ?-pitarget string? ?-pidata string?
  467.  
  468.         switch [llength $args] {
  469.         0 -
  470.         1 {
  471.             return -code error "wrong number of arguments"
  472.         }
  473.  
  474.         2 {
  475.             # The query applies to the entire document
  476.             return [Query [lindex $args 0] -tagname [lindex $args 1] \
  477.             -attrname [lindex $args 1] -attrvalue [lindex $args 1] \
  478.             -text [lindex $args 1] -comment [lindex $args 1] \
  479.             -pitarget [lindex $args 1] -pidata [lindex $args 1]]
  480.         }
  481.  
  482.         default {
  483.             # Configuration options have been specified to constrain the search
  484.             if {[llength [lrange $args 1 end]] % 2} {
  485.             return -code error "no value given for option \"[lindex $args end]\""
  486.             }
  487.             set startnode [lindex $args 0]
  488.             foreach {opt value} [lrange $args 1 end] {
  489.             switch -- $opt {
  490.                 -tagname - -attrname - -attrvalue - -text - 
  491.                 -comment - -pitarget - -pidata {}
  492.                 default {
  493.                 return -code error "unknown query option \"$opt\""
  494.                 }
  495.             }
  496.             }
  497.  
  498.             return [eval Query [list $startnode] [lrange $args 1 end]]
  499.  
  500.         }
  501.  
  502.         }
  503.  
  504.     }
  505.  
  506.     selectNode {
  507.         # Non-standard method
  508.         # Returns nodeset in the given document matching an XPath expression
  509.  
  510.         if {[llength $args] != 2} {
  511.         return -code error "wrong number of arguments"
  512.         }
  513.  
  514.         package require xpath
  515.  
  516.         return [XPath:SelectNode [lindex $args 0] [lindex $args 1]]
  517.     }
  518.  
  519.     serialize {
  520.  
  521.         if {[llength $args] < 1} {
  522.         return -code error "wrong number of arguments"
  523.         }
  524.  
  525.         GetHandle documentFragment [lindex $args 0] node
  526.         return [eval [list Serialize:$node(node:nodeType)] $args]
  527.  
  528.     }
  529.  
  530.     trim {
  531.  
  532.         # Removes textNodes that only contain white space
  533.  
  534.         if {[llength $args] != 1} {
  535.         return -code error "wrong number of arguments"
  536.         }
  537.  
  538.         Trim [lindex $args 0]
  539.  
  540.         # Dispatch DOMSubtreeModified event once here?
  541.  
  542.         return {}
  543.  
  544.     }
  545.  
  546.     default {
  547.         return -code error "unknown method \"$method\""
  548.     }
  549.  
  550.     }
  551.  
  552.     return {}
  553. }
  554.  
  555. # dom::document --
  556. #
  557. #    Functions for a document node.
  558. #
  559. # Arguments:
  560. #    method    method to invoke
  561. #    token    token for node
  562. #    args    arguments for method
  563. #
  564. # Results:
  565. #    Depends on method used.
  566.  
  567. namespace eval dom {
  568.     variable documentOptionsRO doctype|implementation|documentElement
  569.     variable documentOptionsRW {}
  570. }
  571.  
  572. proc dom::document {method token args} {
  573.     variable documentOptionsRO
  574.     variable documentOptionsRW
  575.  
  576.     # GetHandle also checks token
  577.     GetHandle document $token node
  578.  
  579.     set result {}
  580.  
  581.     switch -- $method {
  582.     cget {
  583.         if {[llength $args] != 1} {
  584.         return -code error "too many arguments"
  585.         }
  586.         if {[regexp [format {^-(%s)$} $documentOptionsRO] [lindex $args 0] discard option]} {
  587.         return $node(document:$option)
  588.         } elseif {[regexp [format {^-(%s)$} $documentOptionsRW] [lindex $args 0] discard option]} {
  589.         return $node(document:$option)
  590.         } else {
  591.         return -code error "unknown option \"[lindex $args 0]\""
  592.         }
  593.     }
  594.     configure {
  595.         if {[llength $args] == 1} {
  596.         return [document cget $token [lindex $args 0]]
  597.         } elseif {[expr [llength $args] % 2]} {
  598.         return -code error "no value specified for option \"[lindex $args end]\""
  599.         } else {
  600.         foreach {option value} $args {
  601.             if {[regexp [format {^-(%s)$} $documentOptionsRW] $option discard opt]} {
  602.             set node(document:$opt) $value
  603.             } elseif {[regexp [format {^-(%s)$} $documentOptionsRO] $option discard opt]} {
  604.             return -code error "attribute \"$option\" is read-only"
  605.             } else {
  606.             return -code error "unknown option \"$option\""
  607.             }
  608.         }
  609.         }
  610.  
  611.         PutHandle $token node
  612.  
  613.     }
  614.  
  615.     createElement {
  616.         if {[llength $args] != 1} {
  617.         return -code error "wrong number of arguments"
  618.         }
  619.  
  620.         # Check that the element name is kosher
  621.         if {![regexp ^$::xml::Name\$ [lindex $args 0]]} {
  622.         return -code error "invalid element name \"[lindex $args 0]\""
  623.         }
  624.  
  625.         # Invoke internal factory function
  626.         set result [CreateElement $token [lindex $args 0] {}]
  627.  
  628.     }
  629.     createDocumentFragment {
  630.         if {[llength $args]} {
  631.         return -code error "wrong number of arguments"
  632.         }
  633.  
  634.         set result [CreateGeneric $token node:nodeType documentFragment node:nodeName #document-fragment node:nodeValue {}]
  635.     }
  636.     createTextNode {
  637.         if {[llength $args] != 1} {
  638.         return -code error "wrong number of arguments"
  639.         }
  640.  
  641.         set result [CreateTextNode $token [lindex $args 0]]
  642.     }
  643.     createComment {
  644.         if {[llength $args] != 1} {
  645.         return -code error "wrong number of arguments"
  646.         }
  647.  
  648.         set result [CreateGeneric $token node:nodeType comment node:nodeName #comment node:nodeValue [lindex $args 0]]
  649.     }
  650.     createCDATASection {
  651.         if {[llength $args] != 1} {
  652.         return -code error "wrong number of arguments"
  653.         }
  654.  
  655.         set result [CreateTextNode $token [lindex $args 0]]
  656.         node configure $result -cdatasection 1
  657.     }
  658.     createProcessingInstruction {
  659.         if {[llength $args] != 2} {
  660.         return -code error "wrong number of arguments"
  661.         }
  662.  
  663.         set result [CreateGeneric $token node:nodeType processingInstruction \
  664.             node:nodeName [lindex $args 0] node:nodeValue [lindex $args 1]]
  665.     }
  666.     createAttribute {
  667.         if {[llength $args] != 1} {
  668.         return -code error "wrong number of arguments"
  669.         }
  670.  
  671.         # Check that the attribute name is kosher
  672.         if {![regexp ^$::xml::Name\$ [lindex $args 0]]} {
  673.         return -code error "invalid attribute name \"[lindex $args 0]\""
  674.         }
  675.  
  676.         set result [CreateGeneric $token node:nodeType attribute node:nodeName [lindex $args 0]]
  677.     }
  678.     createEntity {
  679.         set result [CreateGeneric $token node:nodeType entity]
  680.     }
  681.     createEntityReference {
  682.         if {[llength $args] != 1} {
  683.         return -code error "wrong number of arguments"
  684.         }
  685.         set result [CreateGeneric $token node:nodeType entityReference node:nodeName [lindex $args 0]]
  686.     }
  687.  
  688.     createDocTypeDecl {
  689.         # This is not a standard DOM 1.0 method
  690.         # Deprecated - see DOMImplementation createDocumentType
  691.  
  692.         if {[llength $args] < 1 || [llength $args] > 5} {
  693.         return -code error "wrong number of arguments"
  694.         }
  695.  
  696.         foreach {name extid dtd entities notations} $args break
  697.         set result [CreateDocType $token $name $extid]
  698.         document configure $token -doctype $result
  699.         documenttype configure $result -internalsubset $dtd
  700.         documenttype configure $result -entities $entities
  701.         documenttype configure $result -notations $notations
  702.     }
  703.  
  704.     importNode {
  705.         # Introduced in DOM Level 2
  706.  
  707.         return -code error "not yet implemented"
  708.     }
  709.  
  710.     createElementNS {
  711.         # Introduced in DOM Level 2
  712.  
  713.         if {[llength $args] != 2} {
  714.         return -code error "wrong number of arguments, should be: createElementNS nsuri qualname"
  715.         }
  716.  
  717.         # Check that the qualified name is kosher
  718.         if {[catch {foreach {prefix localname} [::xml::qnamesplit [lindex $args 1]]} err]} {
  719.         return -code error "invalid qualified name \"[lindex $args 1]\" due to \"$err\""
  720.         }
  721.  
  722.         # Invoke internal factory function
  723.         set result [CreateElement $token [lindex $args 1] {} -prefix $prefix -namespace [lindex $args 0] -localname $localname]
  724.     }
  725.  
  726.     createAttributeNS {
  727.         # Introduced in DOM Level 2
  728.  
  729.         return -code error "not yet implemented"
  730.     }
  731.  
  732.     getElementsByTagNameNS {
  733.         # Introduced in DOM Level 2
  734.  
  735.         return -code error "not yet implemented"
  736.     }
  737.  
  738.     getElementsById {
  739.         # Introduced in DOM Level 2
  740.  
  741.         return -code error "not yet implemented"
  742.     }
  743.  
  744.     createEvent {
  745.         # Introduced in DOM Level 2
  746.  
  747.         if {[llength $args] != 1} {
  748.         return -code error "wrong number of arguments"
  749.         }
  750.  
  751.         set result [CreateEvent $token [lindex $args 0]]
  752.  
  753.     }
  754.  
  755.     getElementsByTagName {
  756.         if {[llength $args] < 1} {
  757.         return -code error "wrong number of arguments"
  758.         }
  759.  
  760.         return [eval Element:GetByTagName [list $token [lindex $args 0]] \
  761.             [lrange $args 1 end]]
  762.     }
  763.  
  764.     default {
  765.         return -code error "unknown method \"$method\""
  766.     }
  767.  
  768.     }
  769.  
  770.     # Dispatch events
  771.  
  772.     # Node insertion events are generated here instead of the
  773.     # internal factory procedures.  This is because the factory
  774.     # procedures are meant to be mean-and-lean during the parsing
  775.     # phase, and dispatching events at that time would be an
  776.     # excessive overhead.  The factory methods here are pretty
  777.     # heavyweight anyway.
  778.  
  779.     if {[string match create* $method] && [string compare $method "createEvent"]} {
  780.  
  781.     event postMutationEvent $result DOMNodeInserted -relatedNode $token
  782.     event postMutationEvent $result DOMNodeInsertedIntoDocument
  783.     event postMutationEvent $token DOMSubtreeModified
  784.  
  785.     }
  786.  
  787.     return $result
  788. }
  789.  
  790. ###    Factory methods
  791. ###
  792. ### These are lean-and-mean for fastest possible tree building
  793.  
  794. # dom::CreateElement --
  795. #
  796. #    Append an element to the given (parent) node (if any)
  797. #
  798. # Arguments:
  799. #    token    parent node
  800. #    name    element name (no checking performed here)
  801. #    aList    attribute list
  802. #    args    configuration options
  803. #
  804. # Results:
  805. #    New node created, parent optionally modified
  806.  
  807. proc dom::CreateElement {token name aList args} {
  808.     array set opts $args
  809.  
  810.     if {[string length $token]} {
  811.     array set parent [set $token]
  812.     upvar #0 $parent(docArray) docArray
  813.     set docArrayName $parent(docArray)
  814.     } else {
  815.     upvar #0 $opts(-docarray) docArray
  816.     set docArrayName $opts(-docarray)
  817.     }
  818.  
  819.     set id node[incr docArray(counter)]
  820.     set child ${docArrayName}($id)
  821.  
  822.     # Create the new node
  823.     # NB. normally we'd use Node:create here,
  824.     # but inline it instead for performance
  825.     set docArray($id) [list id $id docArray $docArrayName \
  826.         node:parentNode $token        \
  827.         node:childNodes ${docArrayName}var$docArray(counter)    \
  828.         node:nodeType element        \
  829.         node:nodeName $name            \
  830.         node:namespaceURI {}        \
  831.         node:prefix {}            \
  832.         node:localName $name        \
  833.         node:nodeValue {}            \
  834.         element:attributeList ${docArrayName}arr$docArray(counter) \
  835.         element:attributeNodes {}        \
  836.     ]
  837.  
  838.     catch {lappend docArray($id) node:namespaceURI $opts(-namespace)}
  839.     catch {lappend docArray($id) node:localName $opts(-localname)}
  840.     catch {lappend docArray($id) node:prefix $opts(-prefix)}
  841.  
  842.     # Initialise associated variables
  843.     set ${docArrayName}var$docArray(counter) {}
  844.     array set ${docArrayName}arr$docArray(counter) $aList
  845.     catch {
  846.     foreach {ns nsAttrList} $opts(-namespaceattributelists) {
  847.         foreach {attrName attrValue} $nsAttrList {
  848.         array set ${docArrayName}arr$docArray(counter) [list $ns^$attrName $attrValue]
  849.         }
  850.     }
  851.     }
  852.  
  853.     # Update parent record
  854.  
  855.     # Does this element qualify as the document element?
  856.     # If so, then has a document element already been set?
  857.  
  858.     if {[string length $token]} {
  859.  
  860.     if {![string compare $parent(node:nodeType) documentFragment]} {
  861.         if {$parent(id) == $parent(documentFragment:masterDoc)} {
  862.         if {[info exists parent(document:documentElement)] && \
  863.             [string length $parent(document:documentElement)]} {
  864.             unset docArray($id)
  865.             return -code error "document element already exists"
  866.         } else {
  867.  
  868.             # Check against document type decl
  869.             if {[string length $parent(document:doctype)]} {
  870.             array set doctypedecl [set $parent(document:doctype)]
  871.             if {[string compare $name $doctypedecl(doctype:name)]} {
  872.                 return -code error "mismatch between root element type in document type declaration \"$doctypedecl(doctype:name)\" and root element \"$name\""
  873.             }
  874.  
  875.             } else {
  876.             # Synthesize document type declaration
  877.             CreateDocType $token $name {} {}
  878.             # Resynchronise parent record
  879.             array set parent [set $token]
  880.             }
  881.  
  882.             set parent(document:documentElement) $child
  883.             set $token [array get parent]
  884.         }
  885.         }
  886.     }
  887.  
  888.     lappend $parent(node:childNodes) $child
  889.  
  890.     }
  891.  
  892.     return $child
  893. }
  894.  
  895. # dom::CreateTextNode --
  896. #
  897. #    Append a textNode node to the given (parent) node (if any).
  898. #
  899. #    This factory function can also be performed by
  900. #    CreateGeneric, but text nodes are created so often
  901. #    that this specific factory procedure speeds things up.
  902. #
  903. # Arguments:
  904. #    token    parent node
  905. #    text    initial text
  906. #    args    additional configuration options
  907. #
  908. # Results:
  909. #    New node created, parent optionally modified
  910.  
  911. proc dom::CreateTextNode {token text args} {
  912.     if {[string length $token]} {
  913.     array set parent [set $token]
  914.     upvar #0 $parent(docArray) docArray
  915.     set docArrayName $parent(docArray)
  916.     } else {
  917.     array set opts $args
  918.     upvar #0 $opts(-docarray) docArray
  919.     set docArrayName $opts(-docarray)
  920.     }
  921.  
  922.     set id node[incr docArray(counter)]
  923.     set child ${docArrayName}($id)
  924.  
  925.     # Create the new node
  926.     # NB. normally we'd use Node:create here,
  927.     # but inline it instead for performance
  928.  
  929.     # Text nodes never have children, so don't create a variable
  930.  
  931.     set docArray($id) [list id $id docArray $docArrayName \
  932.         node:parentNode $token        \
  933.         node:childNodes {}            \
  934.         node:nodeType textNode        \
  935.         node:nodeValue $text        \
  936.         node:nodeName #text            \
  937.         node:cdatasection 0            \
  938.     ]
  939.  
  940.     if {[string length $token]} {
  941.     # Update parent record
  942.     lappend $parent(node:childNodes) $child
  943.     set $token [array get parent]
  944.     }
  945.  
  946.     return $child
  947. }
  948.  
  949. # dom::CreateGeneric --
  950. #
  951. #    This is a template used for type-specific factory procedures
  952. #
  953. # Arguments:
  954. #    token    parent node
  955. #    args    optional values
  956. #
  957. # Results:
  958. #    New node created, parent modified
  959.  
  960. proc dom::CreateGeneric {token args} {
  961.     if {[string length $token]} {
  962.     array set parent [set $token]
  963.     upvar #0 $parent(docArray) docArray
  964.     set docArrayName $parent(docArray)
  965.     } else {
  966.     array set opts $args
  967.     upvar #0 $opts(-docarray) docArray
  968.     set docArrayName $opts(-docarray)
  969.     array set tmp [array get opts]
  970.     foreach opt [array names tmp -*] {
  971.         unset tmp($opt)
  972.     }
  973.     set args [array get tmp]
  974.     }
  975.  
  976.     set id node[incr docArray(counter)]
  977.     set child ${docArrayName}($id)
  978.  
  979.     # Create the new node
  980.     # NB. normally we'd use Node:create here,
  981.     # but inline it instead for performance
  982.     set docArray($id) [eval list [list id $id docArray $docArrayName    \
  983.         node:parentNode $token                    \
  984.         node:childNodes ${docArrayName}var$docArray(counter)]    \
  985.         $args
  986.     ]
  987.     set ${docArrayName}var$docArray(counter) {}
  988.  
  989.     if {[string length $token]} {
  990.     # Update parent record
  991.     lappend $parent(node:childNodes) $child
  992.     set $token [array get parent]
  993.     }
  994.  
  995.     return $child
  996. }
  997.  
  998. ### Specials
  999.  
  1000. # dom::CreateDocType --
  1001. #
  1002. #    Create a Document Type Declaration node.
  1003. #
  1004. # Arguments:
  1005. #    token    node id for the document node
  1006. #    name    root element type
  1007. #    extid    external entity id
  1008. #    dtd    internal DTD subset
  1009. #
  1010. # Results:
  1011. #    Returns node id of the newly created node.
  1012.  
  1013. proc dom::CreateDocType {token name {extid {}} {dtd {}} {entities {}} {notations {}}} {
  1014.     array set doc [set $token]
  1015.     upvar #0 $doc(docArray) docArray
  1016.  
  1017.     set id node[incr docArray(counter)]
  1018.     set child $doc(docArray)($id)
  1019.  
  1020.     set docArray($id) [list \
  1021.         id $id docArray $doc(docArray) \
  1022.         node:parentNode $token \
  1023.         node:childNodes {} \
  1024.         node:nodeType docType \
  1025.         node:nodeName {} \
  1026.         node:nodeValue {} \
  1027.         doctype:name $name \
  1028.         doctype:entities {} \
  1029.         doctype:notations {} \
  1030.         doctype:externalid $extid \
  1031.         doctype:internaldtd $dtd \
  1032.     ]
  1033.     # NB. externalid and internaldtd are not standard DOM 1.0 attributes
  1034.  
  1035.     # Update parent
  1036.  
  1037.     set doc(document:doctype) $child
  1038.  
  1039.     # Add this node to the parent's child list
  1040.     # This must come before the document element,
  1041.     # so this implementation may be buggy
  1042.     lappend $doc(node:childNodes) $child
  1043.  
  1044.     set $token [array get doc]
  1045.  
  1046.     return $child
  1047. }
  1048.  
  1049. # dom::node --
  1050. #
  1051. #    Functions for a general node.
  1052. #
  1053. #    Implements EventTarget Interface - introduced in DOM Level 2
  1054. #
  1055. # Arguments:
  1056. #    method    method to invoke
  1057. #    token    token for node
  1058. #    args    arguments for method
  1059. #
  1060. # Results:
  1061. #    Depends on method used.
  1062.  
  1063. namespace eval dom {
  1064.     variable strictDOM
  1065.  
  1066.     variable nodeOptionsRO nodeType|parentNode|childNodes|firstChild|lastChild|previousSibling|nextSibling|attributes|namespaceURI|prefix|localName
  1067.     variable nodeOptionsRW nodeValue|cdatasection
  1068.  
  1069.     # Allowing nodeName to be rw is not standard DOM.
  1070.     # A validating implementation would have to be very careful
  1071.     # in allowing this feature
  1072.     if {$strictDOM} {
  1073.     append nodeOptionsRO |nodeName
  1074.     } else {
  1075.     append nodeOptionsRW |nodeName
  1076.     }
  1077. }
  1078. # NB. cdatasection is not a standard DOM option
  1079.  
  1080. proc dom::node {method token args} {
  1081.     variable nodeOptionsRO
  1082.     variable nodeOptionsRW
  1083.  
  1084.     GetHandle node $token node
  1085.  
  1086.     set result {}
  1087.  
  1088.     switch -glob -- $method {
  1089.     cg* {
  1090.         # cget
  1091.  
  1092.         # Some read-only configuration options are computed
  1093.         if {[llength $args] != 1} {
  1094.         return -code error "too many arguments"
  1095.         }
  1096.         if {[regexp [format {^-(%s)$} $nodeOptionsRO] [lindex $args 0] discard option]} {
  1097.         switch $option {
  1098.             nodeName {
  1099.             set result $node(node:nodeName)
  1100.             switch $node(node:nodeType) {
  1101.                 textNode {
  1102.                 catch {set result [expr {$node(node:cdatasection) ? "#cdata-section" : $node(node:nodeName)}]}
  1103.                 }
  1104.                 default {
  1105.                 }
  1106.             }
  1107.             }
  1108.             childNodes {
  1109.             # How are we going to handle documentElement?
  1110.             set result $node(node:childNodes)
  1111.             }
  1112.             firstChild {
  1113.             upvar #0 $node(node:childNodes) children
  1114.             switch $node(node:nodeType) {
  1115.                 documentFragment {
  1116.                 set result [lindex $children 0]
  1117.                 catch {set result $node(document:documentElement)}
  1118.                 }
  1119.                 default {
  1120.                 set result [lindex $children 0]
  1121.                 }
  1122.             }
  1123.             }
  1124.             lastChild {
  1125.             upvar #0 $node(node:childNodes) children
  1126.             switch $node(node:nodeType) {
  1127.                 documentFragment {
  1128.                 set result [lindex $children end]
  1129.                 catch {set result $node(document:documentElement)}
  1130.                 }
  1131.                 default {
  1132.                 set result [lindex $children end]
  1133.                 }
  1134.             }
  1135.             }
  1136.             previousSibling {
  1137.             # BUG: must take documentElement into account
  1138.             # Find the parent node
  1139.             GetHandle node $node(node:parentNode) parent
  1140.             upvar #0 $parent(node:childNodes) children
  1141.             set idx [lsearch $children $token]
  1142.             if {$idx >= 0} {
  1143.                 set sib [lindex $children [incr idx -1]]
  1144.                 if {[llength $sib]} {
  1145.                 set result $sib
  1146.                 } else {
  1147.                 set result {}
  1148.                 }
  1149.             } else {
  1150.                 set result {}
  1151.             }
  1152.             }
  1153.             nextSibling {
  1154.             # BUG: must take documentElement into account
  1155.             # Find the parent node
  1156.             GetHandle node $node(node:parentNode) parent
  1157.             upvar #0 $parent(node:childNodes) children
  1158.             set idx [lsearch $children $token]
  1159.             if {$idx >= 0} {
  1160.                 set sib [lindex $children [incr idx]]
  1161.                 if {[llength $sib]} {
  1162.                 set result $sib
  1163.                 } else {
  1164.                 set result {}
  1165.                 }
  1166.             } else {
  1167.                 set result {}
  1168.             }
  1169.             }
  1170.             attributes {
  1171.             if {[string compare $node(node:nodeType) element]} {
  1172.                 set result {}
  1173.             } else {
  1174.                 set result $node(element:attributeList)
  1175.             }
  1176.             }
  1177.             default {
  1178.             return [GetField node(node:$option)]
  1179.             }
  1180.         }
  1181.         } elseif {[regexp [format {^-(%s)$} $nodeOptionsRW] [lindex $args 0] discard option]} {
  1182.         return [GetField node(node:$option)]
  1183.         } else {
  1184.         return -code error "unknown option \"[lindex $args 0]\""
  1185.         }
  1186.     }
  1187.     co* {
  1188.         # configure
  1189.  
  1190.         if {[llength $args] == 1} {
  1191.         return [document cget $token [lindex $args 0]]
  1192.         } elseif {[expr [llength $args] % 2]} {
  1193.         return -code error "no value specified for option \"[lindex $args end]\""
  1194.         } else {
  1195.         foreach {option value} $args {
  1196.             if {[regexp [format {^-(%s)$} $nodeOptionsRW] $option discard opt]} {
  1197.  
  1198.             switch $opt,$node(node:nodeType) {
  1199.                 nodeValue,textNode -
  1200.                 nodeValue,processingInstruction {
  1201.                 # Dispatch event
  1202.                 set evid [CreateEvent $token DOMCharacterDataModified]
  1203.                 event initMutationEvent $evid DOMCharacterDataModified 1 0 {} $node(node:nodeValue) $value {}
  1204.                 set node(node:nodeValue) $value
  1205.                 node dispatchEvent $token $evid
  1206.                 DOMImplementation destroy $evid
  1207.                 }
  1208.                 default {
  1209.                 set node(node:$opt) $value
  1210.                 }
  1211.             }
  1212.  
  1213.             } elseif {[regexp [format {^-(%s)$} $nodeOptionsRO] $option discard opt]} {
  1214.             return -code error "attribute \"$option\" is read-only"
  1215.             } else {
  1216.             return -code error "unknown option \"$option\""
  1217.             }
  1218.         }
  1219.         }
  1220.     }
  1221.  
  1222.     in* {
  1223.  
  1224.         # insertBefore
  1225.  
  1226.         # Previous and next sibling relationships are OK, 
  1227.         # because they are dynamically determined
  1228.  
  1229.         if {[llength $args] < 1 || [llength $args] > 2} {
  1230.         return -code error "wrong number of arguments"
  1231.         }
  1232.  
  1233.         GetHandle node [lindex $args 0] newChild
  1234.         if {[string compare $newChild(docArray) $node(docArray)]} {
  1235.         return -code error "new node must be in the same document"
  1236.         }
  1237.  
  1238.         switch [llength $args] {
  1239.         1 {
  1240.             # Append as the last node
  1241.             if {[string length $newChild(node:parentNode)]} {
  1242.             node removeChild $newChild(node:parentNode) [lindex $args 0]
  1243.             }
  1244.             lappend $node(node:childNodes) [lindex $args 0]
  1245.             set newChild(node:parentNode) $token
  1246.         }
  1247.         2 {
  1248.  
  1249.             GetHandle node [lindex $args 1] refChild
  1250.             if {[string compare $refChild(docArray) $newChild(docArray)]} {
  1251.             return -code error "nodes must be in the same document"
  1252.             }
  1253.             set idx [lsearch [set $node(node:childNodes)] [lindex $args 1]]
  1254.             if {$idx < 0} {
  1255.             return -code error "no such reference child"
  1256.             } else {
  1257.  
  1258.             # Remove from previous parent
  1259.             if {[string length $newChild(node:parentNode)]} {
  1260.                 node removeChild $newChild(node:parentNode) [lindex $args 0]
  1261.             }
  1262.  
  1263.             # Insert into new node
  1264.             set $node(node:childNodes) \
  1265.                 [linsert [set $node(node:childNodes)] $idx [lindex $args 0]]
  1266.             set newChild(node:parentNode) $token
  1267.             }
  1268.         }
  1269.         }
  1270.         PutHandle [lindex $args 0] newChild
  1271.  
  1272.         event postMutationEvent [lindex $args 0] DOMNodeInserted -relatedNode $token
  1273.         FireNodeInsertedEvents [lindex $args 0]
  1274.         event postMutationEvent $token DOMSubtreeModified
  1275.  
  1276.     }
  1277.  
  1278.     rep* {
  1279.  
  1280.         # replaceChild
  1281.  
  1282.         if {[llength $args] != 2} {
  1283.         return -code error "wrong number of arguments"
  1284.         }
  1285.  
  1286.         GetHandle node [lindex $args 0] newChild
  1287.         GetHandle node [lindex $args 1] oldChild
  1288.  
  1289.         # Find where to insert new child
  1290.         set idx [lsearch [set $node(node:childNodes)] [lindex $args 1]]
  1291.         if {$idx < 0} {
  1292.         return -code error "no such old child"
  1293.         }
  1294.  
  1295.         # Remove new child from current parent
  1296.         if {[string length $newChild(node:parentNode)]} {
  1297.         node removeChild $newChild(node:parentNode) [lindex $args 0]
  1298.         }
  1299.  
  1300.         set $node(node:childNodes) \
  1301.         [lreplace [set $node(node:childNodes)] $idx $idx [lindex $args 0]]
  1302.         set newChild(node:parentNode) $token
  1303.  
  1304.         # Update old child to reflect lack of parentage
  1305.         set oldChild(node:parentNode) {}
  1306.  
  1307.         PutHandle [lindex $args 1] oldChild
  1308.         PutHandle [lindex $args 0] newChild
  1309.  
  1310.         set result [lindex $args 0]
  1311.  
  1312.         event postMutationEvent [lindex $args 0] DOMNodeInserted -relatedNode $token
  1313.         FireNodeInsertedEvents [lindex $args 0]
  1314.         event postMutationEvent $token DOMSubtreeModified
  1315.  
  1316.     }
  1317.  
  1318.     rem* {
  1319.  
  1320.         # removeChild
  1321.  
  1322.         if {[llength $args] != 1} {
  1323.         return -code error "wrong number of arguments"
  1324.         }
  1325.         array set oldChild [set [lindex $args 0]]
  1326.         if {$oldChild(docArray) != $node(docArray)} {
  1327.         return -code error "node \"[lindex $args 0]\" is not a child"
  1328.         }
  1329.  
  1330.         # Remove the child from the parent
  1331.         upvar #0 $node(node:childNodes) myChildren
  1332.         if {[set idx [lsearch $myChildren [lindex $args 0]]] < 0} {
  1333.         return -code error "node \"[lindex $args 0]\" is not a child"
  1334.         }
  1335.         set myChildren [lreplace $myChildren $idx $idx]
  1336.  
  1337.         # Update the child to reflect lack of parentage
  1338.         set oldChild(node:parentNode) {}
  1339.         set [lindex $args 0] [array get oldChild]
  1340.  
  1341.         set result [lindex $args 0]
  1342.  
  1343.         # Event propagation has a problem here:
  1344.         # Nodes that until recently were ancestors may
  1345.         # want to capture the event, but we've just removed
  1346.         # the parentage information.  They get a DOMSubtreeModified
  1347.         # instead.
  1348.         event postMutationEvent [lindex $args 0] DOMNodeRemoved -relatedNode $token
  1349.         FireNodeRemovedEvents [lindex $args 0]
  1350.         event postMutationEvent $token DOMSubtreeModified
  1351.  
  1352.     }
  1353.  
  1354.     ap* {
  1355.  
  1356.         # appendChild
  1357.  
  1358.         if {[llength $args] != 1} {
  1359.         return -code error "wrong number of arguments"
  1360.         }
  1361.  
  1362.         # Add to new parent
  1363.         node insertBefore $token [lindex $args 0]
  1364.  
  1365.     }
  1366.  
  1367.     hasChildNodes {
  1368.         set result [Min 1 [llength [set $node(node:childNodes)]]]
  1369.     }
  1370.  
  1371.     cl* {
  1372.         # cloneNode
  1373.  
  1374.         # May need to pay closer attention to generation of events here
  1375.  
  1376.         set deep 0
  1377.         switch [llength $args] {
  1378.         0 {
  1379.         }
  1380.         1 {
  1381.             set deep [Boolean [lindex $args 0]]
  1382.         }
  1383.         default {
  1384.             return -code error "too many arguments"
  1385.         }
  1386.         }
  1387.  
  1388.         switch $node(node:nodeType) {
  1389.         element {
  1390.             set result [CreateElement {} $node(node:nodeName) [array get $node(element:attributeList)] -docarray $node(docArray)]
  1391.             if {$deep} {
  1392.             foreach child [set $node(node:childNodes)] {
  1393.                 node appendChild $result [node cloneNode $child]
  1394.             }
  1395.             }
  1396.         }
  1397.         textNode {
  1398.             set result [CreateTextNode {} $node(node:nodeValue) -docarray $node(docArray)]
  1399.         }
  1400.         document -
  1401.         documentFragment -
  1402.         default {
  1403.             set result [CreateGeneric {} node:nodeType $node(node:nodeType) -docarray $node(docArray)]
  1404.             if {$deep} {
  1405.             foreach child [set $node(node:childNodes)] {
  1406.                 node appendChild $result [node cloneNode $child]
  1407.             }
  1408.             }
  1409.         }
  1410.         }
  1411.  
  1412.     }
  1413.  
  1414.     ch* {
  1415.         # children -- non-standard method
  1416.  
  1417.         # If this is a textNode, then catch the error
  1418.         set result {}
  1419.         catch {set result [set $node(node:childNodes)]}
  1420.  
  1421.     }
  1422.  
  1423.     par* {
  1424.         # parent -- non-standard method
  1425.  
  1426.         return $node(node:parentNode)
  1427.  
  1428.     }
  1429.  
  1430.     pat* {
  1431.         # path -- non-standard method
  1432.  
  1433.         for {
  1434.         set ancestor $token
  1435.         set result {}
  1436.         catch {unset ancNode}
  1437.         array set ancNode [set $ancestor]
  1438.         } {[string length $ancNode(node:parentNode)]} {
  1439.         set ancestor $ancNode(node:parentNode)
  1440.         catch {unset ancNode}
  1441.         array set ancNode [set $ancestor]
  1442.         } {
  1443.         set result [linsert $result 0 $ancestor]
  1444.         }
  1445.         # The last node is the document node
  1446.         set result [linsert $result 0 $ancestor]
  1447.  
  1448.     }
  1449.  
  1450.     createNode {
  1451.         # createNode -- non-standard method
  1452.  
  1453.         # Creates node(s) in this document given an XPath expression.
  1454.         # Relative location paths have this node as their initial context.
  1455.  
  1456.         if {[llength $args] != 1} {
  1457.         return -code error "wrong number of arguments"
  1458.         }
  1459.  
  1460.         package require xpath
  1461.  
  1462.         return [XPath:CreateNode $token [lindex $args 0]]
  1463.     }
  1464.  
  1465.     selectNode {
  1466.         # selectNode -- non-standard method
  1467.  
  1468.         # Returns nodeset in this document matching an XPath expression.
  1469.         # Relative location paths have this node as their initial context.
  1470.  
  1471.         if {[llength $args] != 1} {
  1472.         return -code error "wrong number of arguments"
  1473.         }
  1474.  
  1475.         package require xpath
  1476.  
  1477.         return [XPath:SelectNode $token [lindex $args 0]]
  1478.     }
  1479.  
  1480.     stringValue {
  1481.         # stringValue -- non-standard method
  1482.         # Returns string value of a node, as defined by XPath Rec.
  1483.  
  1484.         switch $node(node:nodeType) {
  1485.         document -
  1486.         documentFragment -
  1487.         element {
  1488.             set value {}
  1489.             foreach child [set $node(node:childNodes)] {
  1490.             append value [node stringValue $child]
  1491.             }
  1492.             return $value
  1493.         }
  1494.         attribute -
  1495.         textNode -
  1496.         processingInstruction -
  1497.         comment {
  1498.             return $node(node:nodeValue)
  1499.         }
  1500.         default {
  1501.             return {}
  1502.         }
  1503.         }
  1504.  
  1505.     }
  1506.  
  1507.     addEv* {
  1508.         # addEventListener -- introduced in DOM Level 2
  1509.  
  1510.         if {[llength $args] < 2} {
  1511.         return -code error "wrong number of arguments"
  1512.         }
  1513.  
  1514.         set type [string tolower [lindex $args 0]]
  1515.         set listener [lindex $args 1]
  1516.         array set opts {-usecapture 0}
  1517.         array set opts [lrange $args 2 end]
  1518.         set opts(-usecapture) [Boolean $opts(-usecapture)]
  1519.         set listenerType [expr {$opts(-usecapture) ? "capturer" : "listener"}]
  1520.  
  1521.         if {![info exists node(event:$type:$listenerType)] || \
  1522.          [lsearch $node(event:$type:$listenerType) $listener] < 0} {
  1523.         lappend node(event:$type:$listenerType) $listener
  1524.         }
  1525.         # else avoid registering same listener twice
  1526.  
  1527.     }
  1528.  
  1529.     removeEv* {
  1530.         # removeEventListener -- introduced in DOM Level 2
  1531.  
  1532.         if {[llength $args] < 2} {
  1533.         return -code error "wrong number of arguments"
  1534.         }
  1535.  
  1536.         set type [string tolower [lindex $args 0]]
  1537.         set listener [lindex $args 1]
  1538.         array set opts {-usecapture 0}
  1539.         array set opts [lrange $args 2 end]
  1540.         set opts(-usecapture) [Boolean $opts(-usecapture)]
  1541.         set listenerType [expr {$opts(-usecapture) ? "capturer" : "listener"}]
  1542.  
  1543.         set idx [lsearch $node(event:$type:$listenerType) $listener]
  1544.         if {$idx >= 0} {
  1545.         set node(event:$type:$listenerType) [lreplace $node(event:$type:$listenerType) $idx $idx]
  1546.         }
  1547.  
  1548.     }
  1549.  
  1550.     disp* {
  1551.         # dispatchEvent -- introduced in DOM Level 2
  1552.  
  1553.         # This is where the fun happens!
  1554.         # Check to see if there one or more event listener,
  1555.         # if so trigger the listener(s).
  1556.         # Then pass the event up to the ancestor.
  1557.         # This may be modified by event capturing and bubbling.
  1558.  
  1559.         if {[llength $args] != 1} {
  1560.         return -code error "wrong number of arguments"
  1561.         }
  1562.  
  1563.         set eventId [lindex $args 0]
  1564.         GetHandle event $eventId event
  1565.         set type $event(type)
  1566.  
  1567.         if {![string length $event(eventPhase)]} {
  1568.  
  1569.         # This is the initial dispatch of the event.
  1570.         # First trigger any capturing event listeners
  1571.         # Starting from the root, proceed downward
  1572.  
  1573.         set event(eventPhase) capturing_phase
  1574.         set event(target) $token
  1575.         PutHandle $eventId event
  1576.  
  1577.         # DOM L2 specifies that the ancestors are determined
  1578.         # at the moment of event dispatch, so using a static
  1579.         # list is the correct thing to do
  1580.  
  1581.         foreach ancestor [lreplace [node path $token] end end] {
  1582.             GetHandle event $eventId event
  1583.             set event(currentNode) $ancestor
  1584.             PutHandle $eventId event
  1585.  
  1586.             catch {unset ancNode}
  1587.             array set ancNode [set $ancestor]
  1588.  
  1589.             if {[info exists ancNode(event:$type:capturer)]} {
  1590.             foreach capturer $ancNode(event:$type:capturer) {
  1591.                 if {[catch {uplevel #0 $capturer [list $eventId]} capturerError]} {
  1592.                 bgerror "error in capturer \"$capturerError\""
  1593.                 }
  1594.             }
  1595.  
  1596.             # A listener may stop propagation,
  1597.             # but we check here to let all of the
  1598.             # listeners at that level complete
  1599.  
  1600.             GetHandle event $eventId event
  1601.             if {$event(cancelable) && $event(stopPropagation)} {
  1602.                 break
  1603.             }
  1604.             }
  1605.         }
  1606.  
  1607.         # Prepare for next phase
  1608.         set event(eventPhase) at_target
  1609.  
  1610.         }
  1611.  
  1612.         set event(currentNode) $token
  1613.         PutHandle $eventId event
  1614.  
  1615.         if {[info exists node(event:$type:listener)]} {
  1616.         foreach listener $node(event:$type:listener) {
  1617.             if {[catch {uplevel #0 $listener [list $eventId]} listenerError]} {
  1618.             bgerror "error in listener \"$listenerError\""
  1619.             }
  1620.         }
  1621.         }
  1622.  
  1623.         GetHandle event $eventId event
  1624.         set event(eventPhase) bubbling_phase
  1625.         PutHandle $eventId event
  1626.  
  1627.         # Now propagate the event
  1628.         if {$event(cancelable) && $event(stopPropagation)} {
  1629.         # Event has been cancelled
  1630.         } elseif {[llength $node(node:parentNode)]} {
  1631.         # Go ahead and propagate
  1632.         node dispatchEvent $node(node:parentNode) $eventId
  1633.         }
  1634.  
  1635.         set event(dispatched) 1
  1636.         PutHandle $eventId event
  1637.  
  1638.     }
  1639.  
  1640.     default {
  1641.         return -code error "unknown method \"$method\""
  1642.     }
  1643.  
  1644.     }
  1645.  
  1646.     PutHandle $token node
  1647.  
  1648.     return $result
  1649. }
  1650.  
  1651. # dom::Node:create --
  1652. #
  1653. #    Generic node creation.
  1654. #    See also CreateElement, CreateTextNode, CreateGeneric.
  1655. #
  1656. # Arguments:
  1657. #    pVar    array in caller which contains parent details
  1658. #    args    configuration options
  1659. #
  1660. # Results:
  1661. #    New child node created.
  1662.  
  1663. proc dom::Node:create {pVar args} {
  1664.     upvar $pVar parent
  1665.  
  1666.     array set opts {-name {} -value {}}
  1667.     array set opts $args
  1668.  
  1669.     upvar #0 $parent(docArray) docArray
  1670.  
  1671.     # Create new node
  1672.     if {![info exists opts(-id)]} {
  1673.     set opts(-id) node[incr docArray(counter)]
  1674.     }
  1675.     set docArray($opts(-id)) [list id $opts(-id) \
  1676.         docArray $parent(docArray)        \
  1677.         node:parentNode $opts(-parent)    \
  1678.         node:childNodes $parent(docArray)var$docArray(counter)    \
  1679.         node:nodeType $opts(-type)        \
  1680.         node:nodeName $opts(-name)        \
  1681.         node:nodeValue $opts(-value)    \
  1682.         element:attributeList $parent(docArray)arr$docArray(counter) \
  1683.     ]
  1684.     set $parent(docArray)var$docArray(counter) {}
  1685.     array set $parent(docArray)arr$docArray(counter) {}
  1686.  
  1687.     # Update parent node
  1688.     if {![info exists parent(document:documentElement)]} {
  1689.     lappend parent(node:childNodes) [list [lindex $opts(-parent) 0] $opts(-id)]
  1690.     }
  1691.  
  1692.     return $parent(docArray)($opts(-id))
  1693.  
  1694. }
  1695.  
  1696. # dom::Node:set --
  1697. #
  1698. #    Generic node update
  1699. #
  1700. # Arguments:
  1701. #    token    node token
  1702. #    args    configuration options
  1703. #
  1704. # Results:
  1705. #    Node modified.
  1706.  
  1707. proc dom::Node:set {token args} {
  1708.     upvar $token node
  1709.  
  1710.     foreach {key value} $args {
  1711.     set node($key) $value
  1712.     }
  1713.  
  1714.     set $token [array get node]
  1715.  
  1716.     return {}
  1717. }
  1718.  
  1719. # dom::FireNodeInsertedEvents --
  1720. #
  1721. #    Recursively descend the tree triggering DOMNodeInserted
  1722. #    events as we go.
  1723. #
  1724. # Arguments:
  1725. #    nodeid    Node ID
  1726. #
  1727. # Results:
  1728. #    DOM L2 DOMNodeInserted events posted
  1729.  
  1730. proc dom::FireNodeInsertedEvents nodeid {
  1731.     event postMutationEvent $nodeid DOMNodeInsertedIntoDocument
  1732.     foreach child [node children $nodeid] {
  1733.     FireNodeInsertedEvents $child
  1734.     }
  1735.  
  1736.     return {}
  1737. }
  1738.  
  1739. # dom::FireNodeRemovedEvents --
  1740. #
  1741. #    Recursively descend the tree triggering DOMNodeRemoved
  1742. #    events as we go.
  1743. #
  1744. # Arguments:
  1745. #    nodeid    Node ID
  1746. #
  1747. # Results:
  1748. #    DOM L2 DOMNodeRemoved events posted
  1749.  
  1750. proc dom::FireNodeRemovedEvents nodeid {
  1751.     event postMutationEvent $nodeid DOMNodeRemovedFromDocument
  1752.     foreach child [node children $nodeid] {
  1753.     FireNodeRemovedEvents $child
  1754.     }
  1755.  
  1756.     return {}
  1757. }
  1758.  
  1759. # dom::element --
  1760. #
  1761. #    Functions for an element.
  1762. #
  1763. # Arguments:
  1764. #    method    method to invoke
  1765. #    token    token for node
  1766. #    args    arguments for method
  1767. #
  1768. # Results:
  1769. #    Depends on method used.
  1770.  
  1771. namespace eval dom {
  1772.     variable elementOptionsRO tagName|empty
  1773.     variable elementOptionsRW {}
  1774. }
  1775.  
  1776. proc dom::element {method token args} {
  1777.     variable elementOptionsRO
  1778.     variable elementOptionsRW
  1779.  
  1780.     GetHandle node $token node
  1781.  
  1782.     if {[string compare $node(node:nodeType) "element"]} {
  1783.     return -code error "not an element type node"
  1784.     }
  1785.     set result {}
  1786.  
  1787.     switch -- $method {
  1788.  
  1789.     cget {
  1790.         # Some read-only configuration options are computed
  1791.         if {[llength $args] != 1} {
  1792.         return -code error "too many arguments"
  1793.         }
  1794.         if {[regexp [format {^-(%s)$} $elementOptionsRO] [lindex $args 0] discard option]} {
  1795.         switch $option {
  1796.             tagName {
  1797.             set result [lindex $node(node:nodeName) 0]
  1798.             }
  1799.             empty {
  1800.             if {![info exists node(element:empty)]} {
  1801.                 return 0
  1802.             } else {
  1803.                 return $node(element:empty)
  1804.             }
  1805.             }
  1806.             default {
  1807.             return $node(node:$option)
  1808.             }
  1809.         }
  1810.         } elseif {[regexp [format {^-(%s)$} $elementOptionsRW] [lindex $args 0] discard option]} {
  1811.         return $node(node:$option)
  1812.         } else {
  1813.         return -code error "unknown option \"[lindex $args 0]\""
  1814.         }
  1815.     }
  1816.     configure {
  1817.         if {[llength $args] == 1} {
  1818.         return [document cget $token [lindex $args 0]]
  1819.         } elseif {[expr [llength $args] % 2]} {
  1820.         return -code error "no value specified for option \"[lindex $args end]\""
  1821.         } else {
  1822.         foreach {option value} $args {
  1823.             if {[regexp [format {^-(%s)$} $elementOptionsRO] $option discard opt]} {
  1824.             return -code error "attribute \"$option\" is read-only"
  1825.             } elseif {[regexp [format {^-(%s)$} $elementOptionsRW] $option discard opt]} {
  1826.             return -code error "not implemented"
  1827.             } else {
  1828.             return -code error "unknown option \"$option\""
  1829.             }
  1830.         }
  1831.         }
  1832.     }
  1833.  
  1834.     getAttribute {
  1835.         if {[llength $args] != 1} {
  1836.         return -code error "wrong number of arguments"
  1837.         }
  1838.  
  1839.         set result {}
  1840.  
  1841.         upvar #0 $node(element:attributeList) attrList
  1842.         catch {set result $attrList([lindex $args 0])}
  1843.  
  1844.         return $result
  1845.  
  1846.     }
  1847.  
  1848.     setAttribute {
  1849.         if {[llength $args] != 2} {
  1850.         return -code error "wrong number of arguments"
  1851.         }
  1852.  
  1853.         # Check that the attribute name is kosher
  1854.         if {![regexp ^$::xml::Name\$ [lindex $args 0]]} {
  1855.         return -code error "invalid attribute name \"[lindex $args 0]\""
  1856.         }
  1857.  
  1858.         upvar #0 $node(element:attributeList) attrList
  1859.         set evid [CreateEvent $token DOMAttrModified]
  1860.         set oldValue {}
  1861.         catch {set oldValue $attrList([lindex $args 0])}
  1862.         event initMutationEvent $evid DOMAttrModified 1 0 $token $oldValue [lindex $args 1] [lindex $args 0]
  1863.         set result [set attrList([lindex $args 0]) [lindex $args 1]]
  1864.         node dispatchEvent $token $evid
  1865.         DOMImplementation destroy $evid
  1866.  
  1867.     }
  1868.  
  1869.     removeAttribute {
  1870.         if {[llength $args] != 1} {
  1871.         return -code error "wrong number of arguments"
  1872.         }
  1873.  
  1874.         upvar #0 $node(element:attributeList) attrList
  1875.         catch {unset attrList([lindex $args 0])}
  1876.  
  1877.         event postMutationEvent $token DOMAttrRemoved -attrName [lindex $args 0]
  1878.  
  1879.     }
  1880.  
  1881.     getAttributeNS {
  1882.         if {[llength $args] != 2} {
  1883.         return -code error "wrong number of arguments"
  1884.         }
  1885.  
  1886.         set result {}
  1887.         upvar #0 $node(element:attributeList) attrList
  1888.         catch {set result $attrList([lindex $args 0]^[lindex $args 1])}
  1889.  
  1890.         return $result
  1891.  
  1892.     }
  1893.  
  1894.     setAttributeNS {
  1895.         if {[llength $args] != 3} {
  1896.         return -code error "wrong number of arguments"
  1897.         }
  1898.  
  1899.         # Check that the attribute name is kosher
  1900.         if {![regexp ^$::xml::QName\$ [lindex $args 1] discard prefix localName]} {
  1901.         return -code error "invalid qualified attribute name \"[lindex $args 1]\""
  1902.         }
  1903.  
  1904.         # BUG: At the moment the prefix is ignored
  1905.  
  1906.         upvar #0 $node(element:attributeList) attrList
  1907.         set evid [CreateEvent $token DOMAttrModified]
  1908.         set oldValue {}
  1909.         catch {set oldValue $attrList([lindex $args 0]^$localName)}
  1910.         event initMutationEvent $evid DOMAttrModified 1 0 $token $oldValue [lindex $args 2] [lindex $args 0]^localName
  1911.         set result [set attrList([lindex $args 0]^$localName) [lindex $args 2]]
  1912.         node dispatchEvent $token $evid
  1913.         DOMImplementation destroy $evid
  1914.  
  1915.     }
  1916.  
  1917.     removeAttributeNS {
  1918.         if {[llength $args] != 2} {
  1919.         return -code error "wrong number of arguments"
  1920.         }
  1921.  
  1922.         upvar #0 $node(element:attributeList) attrList
  1923.         catch {unset attrList([lindex $args 0]^[lindex $args 1])}
  1924.  
  1925.         event postMutationEvent $token DOMAttrRemoved -attrName [lindex $args 0]^[lindex $args 1]
  1926.  
  1927.     }
  1928.  
  1929.     getAttributeNode {
  1930.         array set tmp [array get $node(element:attributeList)]
  1931.         if {![info exists tmp([lindex $args 0])]} {
  1932.         return {}
  1933.         }
  1934.  
  1935.         # Synthesize an attribute node if one doesn't already exist
  1936.         array set attrNodes $node(element:attributeNodes)
  1937.         if {[catch {set result $attrNodes([lindex $args 0])}]} {
  1938.         set result [CreateGeneric $token node:nodeType attribute node:nodeName [lindex $args 0] node:nodeValue $tmp([lindex $args 0])]
  1939.         lappend node(element:attributeNodes) [lindex $args 0] $result
  1940.         }
  1941.     }
  1942.  
  1943.     setAttributeNode -
  1944.     removeAttributeNode -
  1945.     getAttributeNodeNS -
  1946.     setAttributeNodeNS -
  1947.     removeAttributeNodeNS {
  1948.         return -code error "not yet implemented"
  1949.     }
  1950.  
  1951.     getElementsByTagName {
  1952.         if {[llength $args] < 1} {
  1953.         return -code error "wrong number of arguments"
  1954.         }
  1955.  
  1956.         return [eval Element:GetByTagName [list $token [lindex $args 0]] \
  1957.             [lrange $args 1 end]]
  1958.     }
  1959.  
  1960.     normalize {
  1961.         if {[llength $args]} {
  1962.         return -code error "wrong number of arguments"
  1963.         }
  1964.  
  1965.         Element:Normalize node [set $node(node:childNodes)]
  1966.     }
  1967.  
  1968.     default {
  1969.         return -code error "unknown method \"$method\""
  1970.     }
  1971.  
  1972.     }
  1973.  
  1974.     PutHandle $token node
  1975.  
  1976.     return $result
  1977. }
  1978.  
  1979. # dom::Element:GetByTagName --
  1980. #
  1981. #    Search for (child) elements
  1982. #
  1983. #    This used to be non-recursive, but then I read the DOM spec
  1984. #    properly and discovered that it should recurse.  The -deep
  1985. #    option allows for backward-compatibility, and defaults to the
  1986. #    DOM-specified value of true.
  1987. #
  1988. # Arguments:
  1989. #    token    parent node
  1990. #    name    element type to search for
  1991. #    args    configuration options
  1992. #
  1993. # Results:
  1994. #    List of matching node tokens
  1995.  
  1996. proc dom::Element:GetByTagName {token name args} {
  1997.     array set node [set $token]
  1998.  
  1999.     array set cfg {-deep 1}
  2000.     array set cfg $args
  2001.     set cfg(-deep) [Boolean $cfg(-deep)]
  2002.  
  2003.     # Guard against arbitrary glob characters
  2004.     # Probably should check that name is a legal XML Name
  2005.     if {[regexp {[][*?\\]} $name] && [string compare $name "*"]} {
  2006.     return -code error "invalid element name"
  2007.     }
  2008.  
  2009.     set result {}
  2010.  
  2011.     if {[string compare $node(node:nodeType) "documentFragment"]} {
  2012.     return [Element:GetByTagName:Search [set $node(node:childNodes)] $name $cfg(-deep)]
  2013.     } elseif {[llength $node(document:documentElement)]} {
  2014.     # Document Element must exist and must be an element type node
  2015.     return [Element:GetByTagName:Search $node(document:documentElement) $name $cfg(-deep)]
  2016.     }
  2017.  
  2018.     return $result
  2019. }
  2020.  
  2021. # dom::Element:GetByTagName:Search --
  2022. #
  2023. #    Search for elements.  This does the real work.
  2024. #
  2025. # Arguments:
  2026. #    tokens    nodes to search (inclusive)
  2027. #    name    element type to search for
  2028. #    deep    whether to search recursively
  2029. #
  2030. # Results:
  2031. #    List of matching node tokens
  2032.  
  2033. proc dom::Element:GetByTagName:Search {tokens name deep} {
  2034.     set result {}
  2035.  
  2036.     foreach tok $tokens {
  2037.     catch {unset nodeInfo}
  2038.     array set nodeInfo [set $tok]
  2039.     switch -- $nodeInfo(node:nodeType) {
  2040.         element {
  2041.         if {[string match $name [GetField nodeInfo(node:nodeName)]]} {
  2042.             lappend result $tok
  2043.         }
  2044.         if {$deep} {
  2045.             set childResult [Element:GetByTagName:Search [set $nodeInfo(node:childNodes)] $name $deep]
  2046.             if {[llength $childResult]} {
  2047.             eval lappend result $childResult
  2048.             }
  2049.         }
  2050.         }
  2051.     }
  2052.     }
  2053.  
  2054.     return $result
  2055. }
  2056.  
  2057. # dom::Element:Normalize --
  2058. #
  2059. #    Normalize the text nodes
  2060. #
  2061. # Arguments:
  2062. #    pVar    parent array variable in caller
  2063. #    nodes    list of node tokens
  2064. #
  2065. # Results:
  2066. #    Adjacent text nodes are coalesced
  2067.  
  2068. proc dom::Element:Normalize {pVar nodes} {
  2069.     upvar $pVar parent
  2070.  
  2071.     set textNode {}
  2072.  
  2073.     foreach n $nodes {
  2074.     GetHandle node $n child
  2075.     set cleanup {}
  2076.  
  2077.     switch $child(node:nodeType) {
  2078.         textNode {
  2079.         if {[llength $textNode]} {
  2080.  
  2081.             # Coalesce into previous node
  2082.             set evid [CreateEvent $n DOMCharacterDataModified]
  2083.             event initMutationEvent $evid DOMCharacterDataModified 1 0 {} $text(node:nodeValue) $text(node:nodeValue)$child(node:nodeValue) {}
  2084.             append text(node:nodeValue) $child(node:nodeValue)
  2085.             node dispatchEvent $n $evid
  2086.             DOMImplementation destroy $evid
  2087.  
  2088.             # Remove this child
  2089.             upvar #0 $parent(node:childNodes) childNodes
  2090.             set idx [lsearch $childNodes $n]
  2091.             set childNodes [lreplace $childNodes $idx $idx]
  2092.             unset $n
  2093.             set cleanup [list event postMutationEvent [node parent $n] DOMSubtreeModified]
  2094.             event postMutationEvent $n DOMNodeRemoved
  2095.  
  2096.             PutHandle $textNode text
  2097.         } else {
  2098.             set textNode $n
  2099.             catch {unset text}
  2100.             array set text [array get child]
  2101.         }
  2102.         }
  2103.         element -
  2104.         document -
  2105.         documentFragment {
  2106.         set textNode {}
  2107.         Element:Normalize child [set $child(node:childNodes)]
  2108.         }
  2109.         default {
  2110.         set textNode {}
  2111.         }
  2112.     }
  2113.  
  2114.     eval $cleanup
  2115.     }
  2116.  
  2117.     return {}
  2118. }
  2119.  
  2120. # dom::processinginstruction --
  2121. #
  2122. #    Functions for a processing intruction.
  2123. #
  2124. # Arguments:
  2125. #    method    method to invoke
  2126. #    token    token for node
  2127. #    args    arguments for method
  2128. #
  2129. # Results:
  2130. #    Depends on method used.
  2131.  
  2132. namespace eval dom {
  2133.     variable piOptionsRO target
  2134.     variable piOptionsRW data
  2135. }
  2136.  
  2137. proc dom::processinginstruction {method token args} {
  2138.     variable piOptionsRO
  2139.     variable piOptionsRW
  2140.  
  2141.     GetHandle node $token node
  2142.  
  2143.     set result {}
  2144.  
  2145.     switch -- $method {
  2146.  
  2147.     cget {
  2148.         # Some read-only configuration options are computed
  2149.         if {[llength $args] != 1} {
  2150.         return -code error "too many arguments"
  2151.         }
  2152.         if {[regexp [format {^-(%s)$} $elementOptionsRO] [lindex $args 0] discard option]} {
  2153.         switch $option {
  2154.             target {
  2155.             set result [lindex $node(node:nodeName) 0]
  2156.             }
  2157.             default {
  2158.             return $node(node:$option)
  2159.             }
  2160.         }
  2161.         } elseif {[regexp [format {^-(%s)$} $elementOptionsRW] [lindex $args 0] discard option]} {
  2162.         switch $option {
  2163.             data {
  2164.             return $node(node:nodeValue)
  2165.             }
  2166.             default {
  2167.             return $node(node:$option)
  2168.             }
  2169.         }
  2170.         } else {
  2171.         return -code error "unknown option \"[lindex $args 0]\""
  2172.         }
  2173.     }
  2174.     configure {
  2175.         if {[llength $args] == 1} {
  2176.         return [document cget $token [lindex $args 0]]
  2177.         } elseif {[expr [llength $args] % 2]} {
  2178.         return -code error "no value specified for option \"[lindex $args end]\""
  2179.         } else {
  2180.         foreach {option value} $args {
  2181.             if {[regexp [format {^-(%s)$} $elementOptionsRO] $option discard opt]} {
  2182.             return -code error "attribute \"$option\" is read-only"
  2183.             } elseif {[regexp [format {^-(%s)$} $elementOptionsRW] $option discard opt]} {
  2184.             switch $opt {
  2185.                 data {
  2186.                 set evid [CreateEvent $token DOMCharacterDataModified]
  2187.                 event initMutationEvent $evid DOMCharacterModified 1 0 {} $node(node:nodeValue) $value {}
  2188.                 set node(node:nodeValue) $value
  2189.                 node dispatchEvent $token $evid
  2190.                 DOMImplementation destroy $evid
  2191.                 }
  2192.                 default {
  2193.                 set node(node:$opt) $value
  2194.                 }
  2195.             }
  2196.             } else {
  2197.             return -code error "unknown option \"$option\""
  2198.             }
  2199.         }
  2200.         }
  2201.     }
  2202.  
  2203.     default {
  2204.         return -code error "unknown method \"$method\""
  2205.     }
  2206.  
  2207.     }
  2208.  
  2209.     PutHandle $token node
  2210.  
  2211.     return $result
  2212. }
  2213.  
  2214. #################################################
  2215. #
  2216. # DOM Level 2 Interfaces
  2217. #
  2218. #################################################
  2219.  
  2220. # dom::event --
  2221. #
  2222. #    Implements Event Interface
  2223. #
  2224. #    Subclassed Interfaces are also defined here,
  2225. #    such as UIEvents.
  2226. #
  2227. # Arguments:
  2228. #    method    method to invoke
  2229. #    token    token for event
  2230. #    args    arguments for method
  2231. #
  2232. # Results:
  2233. #    Depends on method used.
  2234.  
  2235. namespace eval dom {
  2236.     variable eventOptionsRO type|target|currentNode|eventPhase|bubbles|cancelable|timeStamp|detail|view|screenX|screenY|clientX|clientY|ctrlKey|shiftKey|altKey|metaKey|button|relatedNode|prevValue|newValue|attrName
  2237.     variable eventOptionsRW {}
  2238.  
  2239.     # Issue: should the attributes belonging to the subclassed Interface
  2240.     # be separated out?
  2241.  
  2242.     variable uieventOptionsRO detail|view
  2243.     variable uieventOptionsRW {}
  2244.  
  2245.     variable mouseeventOptionsRO screenX|screenY|clientX|clientY|ctrlKey|shiftKey|altKey|metaKey|button|relatedNode
  2246.     variable mouseeventOptionsRW {}
  2247.  
  2248.     variable mutationeventOptionsRO relatedNode|prevValue|newValue|attrName
  2249.     variable mutationeventOptionsRW {}
  2250. }
  2251.  
  2252. proc dom::event {method token args} {
  2253.     variable eventOptionsRO
  2254.     variable eventOptionsRW
  2255.     variable bubbles
  2256.     variable cancelable
  2257.  
  2258.     GetHandle event $token event
  2259.  
  2260.     set result {}
  2261.  
  2262.     switch -glob -- $method {
  2263.  
  2264.     cg* {
  2265.         # cget
  2266.  
  2267.         if {[llength $args] != 1} {
  2268.         return -code error "too many arguments"
  2269.         }
  2270.         if {[regexp [format {^-(%s)$} $eventOptionsRO] [lindex $args 0] discard option]} {
  2271.         return $event($option)
  2272.         } elseif {[regexp [format {^-(%s)$} $eventOptionsRW] [lindex $args 0] discard option]} {
  2273.         return $event($option)
  2274.         } else {
  2275.         return -code error "unknown option \"[lindex $args 0]\""
  2276.         }
  2277.     }
  2278.  
  2279.     co* {
  2280.         # configure
  2281.  
  2282.         if {[llength $args] == 1} {
  2283.         return [event cget $token [lindex $args 0]]
  2284.         } elseif {[expr [llength $args] % 2]} {
  2285.         return -code error "no value specified for option \"[lindex $args end]\""
  2286.         } else {
  2287.         foreach {option value} $args {
  2288.             if {[regexp [format {^-(%s)$} $eventOptionsRW] $option discard opt]} {
  2289.             set event($opt) $value
  2290.             } elseif {[regexp [format {^-(%s)$} $eventOptionsRO] $option discard opt]} {
  2291.             return -code error "attribute \"$option\" is read-only"
  2292.             } else {
  2293.             return -code error "unknown option \"$option\""
  2294.             }
  2295.         }
  2296.         }
  2297.  
  2298.         PutHandle $token event
  2299.  
  2300.     }
  2301.  
  2302.     st* {
  2303.         # stopPropagation
  2304.  
  2305.         set event(stopPropagation) 1
  2306.         PutHandle $token event
  2307.  
  2308.     }
  2309.  
  2310.     pr* {
  2311.         # preventDefault
  2312.  
  2313.         set event(preventDefault) 1
  2314.         PutHandle $token event
  2315.  
  2316.     }
  2317.  
  2318.     initE* {
  2319.         # initEvent
  2320.  
  2321.         if {[llength $args] != 3} {
  2322.         return -code error "wrong number of arguments"
  2323.         }
  2324.  
  2325.         if {$event(dispatched)} {
  2326.         return -code error "event has been dispatched"
  2327.         }
  2328.  
  2329.         foreach {event(type) event(bubbles) event(cancelable)} $args break
  2330.         set event(type) [string tolower $event(type)]
  2331.  
  2332.         PutHandle $token event
  2333.  
  2334.     }
  2335.  
  2336.     initU* {
  2337.         # initUIEvent
  2338.  
  2339.         if {[llength $args] < 4 || [llength $args] > 5} {
  2340.         return -code error "wrong number of arguments"
  2341.         }
  2342.  
  2343.         if {$event(dispatched)} {
  2344.         return -code error "event has been dispatched"
  2345.         }
  2346.  
  2347.         set event(detail) 0
  2348.         foreach {event(type) event(bubbles) event(cancelable) event(view) event(detail)} $args break
  2349.         set event(type) [string tolower $event(type)]
  2350.  
  2351.         PutHandle $token event
  2352.  
  2353.     }
  2354.  
  2355.     initMo* {
  2356.         # initMouseEvent
  2357.  
  2358.         if {[llength $args] != 15} {
  2359.         return -code error "wrong number of arguments"
  2360.         }
  2361.  
  2362.         if {$event(dispatched)} {
  2363.         return -code error "event has been dispatched"
  2364.         }
  2365.  
  2366.         set event(detail) 1
  2367.         foreach {event(type) event(bubbles) event(cancelable) event(view) event(detail) event(screenX) event(screenY) event(clientX) event(clientY) event(ctrlKey) event(altKey) event(shiftKey) event(metaKey) event(button) event(relatedNode)} $args break
  2368.         set event(type) [string tolower $event(type)]
  2369.  
  2370.         PutHandle $token event
  2371.  
  2372.     }
  2373.  
  2374.     initMu* {
  2375.         # initMutationEvent
  2376.  
  2377.         if {[llength $args] != 7} {
  2378.         return -code error "wrong number of arguments"
  2379.         }
  2380.  
  2381.         if {$event(dispatched)} {
  2382.         return -code error "event has been dispatched"
  2383.         }
  2384.  
  2385.         foreach {event(type) event(bubbles) event(cancelable) event(relatedNode) event(prevValue) event(newValue) event(attrName)} $args break
  2386.         set event(type) [string tolower $event(type)]
  2387.  
  2388.         PutHandle $token event
  2389.  
  2390.     }
  2391.  
  2392.     postUI* {
  2393.         # postUIEvent, non-standard convenience method
  2394.  
  2395.         set evType [lindex $args 0]
  2396.         array set evOpts [list \
  2397.             -bubbles $bubbles($evType) -cancelable $cancelable($evType)    \
  2398.             -view {}            \
  2399.             -detail {}            \
  2400.         ]
  2401.         array set evOpts [lrange $args 1 end]
  2402.  
  2403.         set evid [CreateEvent $token $evType]
  2404.         event initUIEvent $evid $evType $evOpts(-bubbles) $evOpts(-cancelable) $evOpts(-view) $evOpts(-detail)
  2405.         node dispatchEvent $token $evid
  2406.         DOMImplementation destroy $evid
  2407.  
  2408.     }
  2409.  
  2410.     postMo* {
  2411.         # postMouseEvent, non-standard convenience method
  2412.  
  2413.         set evType [lindex $args 0]
  2414.         array set evOpts [list \
  2415.             -bubbles $bubbles($evType) -cancelable $cancelable($evType)    \
  2416.             -view {}            \
  2417.             -detail {}            \
  2418.             -screenX {}            \
  2419.             -screenY {}            \
  2420.             -clientX {}            \
  2421.             -clientY {}            \
  2422.             -ctrlKey {}            \
  2423.             -altKey {}            \
  2424.             -shiftKey {}        \
  2425.             -metaKey {}            \
  2426.             -button {}            \
  2427.             -relatedNode {}        \
  2428.         ]
  2429.         array set evOpts [lrange $args 1 end]
  2430.  
  2431.         set evid [CreateEvent $token $evType]
  2432.         event initMouseEvent $evid $evType $evOpts(-bubbles) $evOpts(-cancelable) $evOpts(-view) $evOpts(-detail) $evOpts(-screenX) $evOpts(-screenY) $evOpts(-clientX) $evOpts(-clientY) $evOpts(-ctrlKey) $evOpts(-altKey) $evOpts(-shiftKey) $evOpts(-metaKey) $evOpts(-button) $evOpts(-relatedNode)
  2433.         node dispatchEvent $token $evid
  2434.         DOMImplementation destroy $evid
  2435.  
  2436.     }
  2437.  
  2438.     postMu* {
  2439.         # postMutationEvent, non-standard convenience method
  2440.  
  2441.         set evType [lindex $args 0]
  2442.         array set evOpts [list \
  2443.             -bubbles $bubbles($evType) -cancelable $cancelable($evType)    \
  2444.             -relatedNode {}            \
  2445.             -prevValue {} -newValue {}        \
  2446.             -attrName {}            \
  2447.         ]
  2448.         array set evOpts [lrange $args 1 end]
  2449.  
  2450.         set evid [CreateEvent $token $evType]
  2451.         event initMutationEvent $evid $evType $evOpts(-bubbles) $evOpts(-cancelable) $evOpts(-relatedNode) $evOpts(-prevValue) $evOpts(-newValue) $evOpts(-attrName)
  2452.         node dispatchEvent $token $evid
  2453.         DOMImplementation destroy $evid
  2454.  
  2455.     }
  2456.  
  2457.     default {
  2458.         return -code error "unknown method \"$method\""
  2459.     }
  2460.     }
  2461.  
  2462.     return $result
  2463. }
  2464.  
  2465. # dom::CreateEvent --
  2466. #
  2467. #    Create an event object
  2468. #
  2469. # Arguments:
  2470. #    token    parent node
  2471. #    type    event type
  2472. #    args    configuration options
  2473. #
  2474. # Results:
  2475. #    Returns event token
  2476.  
  2477. proc dom::CreateEvent {token type args} {
  2478.     if {[string length $token]} {
  2479.     array set parent [set $token]
  2480.     upvar #0 $parent(docArray) docArray
  2481.     set docArrayName $parent(docArray)
  2482.     } else {
  2483.     array set opts $args
  2484.     upvar #0 $opts(-docarray) docArray
  2485.     set docArrayName $opts(-docarray)
  2486.     }
  2487.  
  2488.     set id event[incr docArray(counter)]
  2489.     set child ${docArrayName}($id)
  2490.  
  2491.     # Create the event
  2492.     set docArray($id) [list id $id docArray $docArrayName \
  2493.         node:nodeType event    \
  2494.         type $type        \
  2495.         cancelable 1    \
  2496.         stopPropagation 0    \
  2497.         preventDefault 0    \
  2498.         dispatched 0    \
  2499.         bubbles 1        \
  2500.         eventPhase {}    \
  2501.         timeStamp [clock clicks -milliseconds]    \
  2502.         ]
  2503.  
  2504.     return $child
  2505. }
  2506.  
  2507. #################################################
  2508. #
  2509. # Serialisation
  2510. #
  2511. #################################################
  2512.  
  2513. # dom::Serialize:documentFragment --
  2514. #
  2515. #    Produce text for documentFragment.
  2516. #
  2517. # Arguments:
  2518. #    token    node token
  2519. #    args    configuration options
  2520. #
  2521. # Results:
  2522. #    XML format text.
  2523.  
  2524. proc dom::Serialize:documentFragment {token args} {
  2525.     array set node [set $token]
  2526.  
  2527.     if {[string compare "node1" $node(documentFragment:masterDoc)]} {
  2528.     return [eval [list Serialize:node $token] $args]
  2529.     } else {
  2530.     if {[string compare {} [GetField node(document:documentElement)]]} {
  2531.         return [eval Serialize:document [list $token] $args]
  2532.     } else {
  2533.         return -code error "document has no document element"
  2534.     }
  2535.     }
  2536.  
  2537. }
  2538.  
  2539. # dom::Serialize:document --
  2540. #
  2541. #    Produce text for document.
  2542. #
  2543. # Arguments:
  2544. #    token    node token
  2545. #    args    configuration options
  2546. #
  2547. # Results:
  2548. #    XML format text.
  2549.  
  2550. proc dom::Serialize:document {token args} {
  2551.     array set node [set $token]
  2552.     array set opts {
  2553.     -showxmldecl 1
  2554.     -showdoctypedecl 1
  2555.     }
  2556.     array set opts $args
  2557.  
  2558.     if {![info exists node(document:documentElement)]} {
  2559.     return -code error "document has no document element"
  2560.     } elseif {![string length node(document:doctype)]} {
  2561.     return -code error "no document type declaration given"
  2562.     } else {
  2563.  
  2564.     array set doctype [set $node(document:doctype)]
  2565.  
  2566.     # BUG: Want to serialize all children except for the 
  2567.     # document element, and then do the document element.
  2568.  
  2569.     # Bug fix: can't use Serialize:attributeList for XML declaration,
  2570.     # since attributes must occur in a given order (XML 2.8 [23])
  2571.  
  2572.     set result {}
  2573.  
  2574.     if {$opts(-showxmldecl)} {
  2575.         append result <?xml[Serialize:XMLDecl version $node(document:xmldecl)][Serialize:XMLDecl encoding $node(document:xmldecl)][Serialize:XMLDecl standalone $node(document:xmldecl)]?>\n
  2576.     }
  2577.     if {$opts(-showdoctypedecl)} {
  2578.         append result <!DOCTYPE\ $doctype(doctype:name)[Serialize:ExternalID $doctype(doctype:externalid)][expr {[string length $doctype(doctype:internaldtd)] ? " \[$doctype(doctype:internaldtd)\]" : {}}]>\n
  2579.     }
  2580.  
  2581.     return $result[eval Serialize:element [list $node(document:documentElement)] $args]
  2582.     }
  2583.  
  2584. }
  2585.  
  2586. # dom::Serialize:ExternalID --
  2587. #
  2588. #    Returned appropriately quoted external identifiers
  2589. #
  2590. # Arguments:
  2591. #    id    external indentifiers
  2592. #
  2593. # Results:
  2594. #    text
  2595.  
  2596. proc dom::Serialize:ExternalID id {
  2597.     set publicid {}
  2598.     set systemid {}
  2599.     foreach {publicid systemid} $id break
  2600.  
  2601.     switch -glob -- [string length $publicid],[string length $systemid] {
  2602.     0,0 {
  2603.         return {}
  2604.     }
  2605.     0,* {
  2606.         return " SYSTEM \"$systemid\""
  2607.     }
  2608.     *,* {
  2609.         return " PUBLIC \"$publicid\" \"systemid\""
  2610.     }
  2611.     }
  2612.  
  2613.     return {}
  2614. }
  2615.  
  2616. # dom::Serialize:XMLDecl --
  2617. #
  2618. #    Produce text for XML Declaration attribute.
  2619. #    Order is determine by document serialisation procedure.
  2620. #
  2621. # Arguments:
  2622. #    attr    required attribute
  2623. #    attList    attribute list
  2624. #
  2625. # Results:
  2626. #    XML format text.
  2627.  
  2628. proc dom::Serialize:XMLDecl {attr attrList} {
  2629.     array set data $attrList
  2630.     if {![info exists data($attr)]} {
  2631.     return {}
  2632.     } elseif {[string length $data($attr)]} {
  2633.     return " $attr='$data($attr)'"
  2634.     } else {
  2635.     return {}
  2636.     }
  2637. }
  2638.  
  2639. # dom::Serialize:node --
  2640. #
  2641. #    Produce text for an arbitrary node.
  2642. #    This simply serializes the child nodes of the node.
  2643. #
  2644. # Arguments:
  2645. #    token    node token
  2646. #    args    configuration options
  2647. #
  2648. # Results:
  2649. #    XML format text.
  2650.  
  2651. proc dom::Serialize:node {token args} {
  2652.     array set node [set $token]
  2653.     array set opts $args
  2654.  
  2655.     if {[info exists opts(-indent)]} {
  2656.     # NB. 0|1 cannot be used as booleans - mention this in docn
  2657.     if {[regexp {^false|no|off$} $opts(-indent)]} {
  2658.         # No action required
  2659.     } elseif {[regexp {^true|yes|on$} $opts(-indent)]} {
  2660.         set opts(-indent) 1
  2661.     } else {
  2662.         incr opts(-indent)
  2663.     }
  2664.     }
  2665.  
  2666.     set result {}
  2667.     foreach childToken [set $node(node:childNodes)] {
  2668.     catch {unset child}
  2669.     array set child [set $childToken]
  2670.     append result [eval [list Serialize:$child(node:nodeType) $childToken] [array get opts]]
  2671.     }
  2672.  
  2673.     return $result
  2674. }
  2675.  
  2676. # dom::Serialize:element --
  2677. #
  2678. #    Produce text for an element.
  2679. #
  2680. # Arguments:
  2681. #    token    node token
  2682. #    args    configuration options
  2683. #
  2684. # Results:
  2685. #    XML format text.
  2686.  
  2687. proc dom::Serialize:element {token args} {
  2688.     array set node [set $token]
  2689.     array set opts {-newline {}}
  2690.     array set opts $args
  2691.  
  2692.     set result {}
  2693.     set newline {}
  2694.     if {[lsearch $opts(-newline) $node(node:nodeName)] >= 0} {
  2695.     append result \n
  2696.     set newline \n
  2697.     }
  2698.     append result [eval Serialize:Indent [array get opts]]
  2699.     append result "<$node(node:nodeName)"
  2700.     append result [Serialize:attributeList [array get $node(element:attributeList)]]
  2701.  
  2702.     if {![llength [set $node(node:childNodes)]]} {
  2703.  
  2704.     append result />$newline
  2705.  
  2706.     } else {
  2707.  
  2708.     append result >$newline
  2709.  
  2710.     # Do the children
  2711.     if {[hasmixedcontent $token]} {
  2712.         set opts(-indent) no
  2713.     }
  2714.     append result [eval Serialize:node [list $token] [array get opts]]
  2715.  
  2716.     append result [eval Serialize:Indent [array get opts]]
  2717.     append result "$newline</$node(node:nodeName)>$newline"
  2718.  
  2719.     }
  2720.  
  2721.     return $result
  2722. }
  2723.  
  2724. # dom::Serialize:textNode --
  2725. #
  2726. #    Produce text for a text node.  This procedure may
  2727. #    return a CDATA section where appropriate.
  2728. #
  2729. # Arguments:
  2730. #    token    node token
  2731. #    args    configuration options
  2732. #
  2733. # Results:
  2734. #    XML format text.
  2735.  
  2736. proc dom::Serialize:textNode {token args} {
  2737.     array set node [set $token]
  2738.  
  2739.     if {$node(node:cdatasection)} {
  2740.     return [Serialize:CDATASection $node(node:nodeValue)]
  2741.     } elseif {[Serialize:ExceedsThreshold $node(node:nodeValue)]} {
  2742.     return [Serialize:CDATASection $node(node:nodeValue)]
  2743.     } else {
  2744.     return [Encode $node(node:nodeValue)]
  2745.     }
  2746. }
  2747.  
  2748. # dom::Serialize:ExceedsThreshold --
  2749. #
  2750. #    Applies heuristic(s) to determine whether a text node
  2751. #    should be formatted as a CDATA section.
  2752. #
  2753. # Arguments:
  2754. #    text    node text
  2755. #
  2756. # Results:
  2757. #    Boolean.
  2758.  
  2759. proc dom::Serialize:ExceedsThreshold {text} {
  2760.     variable maxSpecials
  2761.  
  2762.     return [expr {[regsub -all {[<>&]} $text {} discard] > $maxSpecials}]
  2763. }
  2764.  
  2765. # dom::Serialize:CDATASection --
  2766. #
  2767. #    Formats a CDATA section.
  2768. #
  2769. # Arguments:
  2770. #    text    node text
  2771. #
  2772. # Results:
  2773. #    XML text.
  2774.  
  2775. proc dom::Serialize:CDATASection {text} {
  2776.     set result {}
  2777.     while {[regexp {(.*)]]>(.*)} $text discard text trailing]} {
  2778.     set result \]\]>\;<!\[CDATA\[$trailing\]\]>$result
  2779.     }
  2780.     return <!\[CDATA\[$text\]\]>$result
  2781. }
  2782.  
  2783. # dom::Serialize:processingInstruction --
  2784. #
  2785. #    Produce text for a PI node.
  2786. #
  2787. # Arguments:
  2788. #    token    node token
  2789. #    args    configuration options
  2790. #
  2791. # Results:
  2792. #    XML format text.
  2793.  
  2794. proc dom::Serialize:processingInstruction {token args} {
  2795.     array set node [set $token]
  2796.  
  2797.     return "[eval Serialize:Indent $args]<?$node(node:nodeName)[expr {$node(node:nodeValue) == "" ? "" : " $node(node:nodeValue)"}]?>"
  2798. }
  2799.  
  2800. # dom::Serialize:comment --
  2801. #
  2802. #    Produce text for a comment node.
  2803. #
  2804. # Arguments:
  2805. #    token    node token
  2806. #    args    configuration options
  2807. #
  2808. # Results:
  2809. #    XML format text.
  2810.  
  2811. proc dom::Serialize:comment {token args} {
  2812.     array set node [set $token]
  2813.  
  2814.     return [eval Serialize:Indent $args]<!--$node(node:nodeValue)-->
  2815. }
  2816.  
  2817. # dom::Serialize:entityReference --
  2818. #
  2819. #    Produce text for an entity reference.
  2820. #
  2821. # Arguments:
  2822. #    token    node token
  2823. #    args    configuration options
  2824. #
  2825. # Results:
  2826. #    XML format text.
  2827.  
  2828. proc dom::Serialize:entityReference {token args} {
  2829.     array set node [set $token]
  2830.  
  2831.     return &$node(node:nodeName)\;
  2832. }
  2833.  
  2834. # dom::Encode --
  2835. #
  2836. #    Encode special characters
  2837. #
  2838. # Arguments:
  2839. #    value    text value
  2840. #
  2841. # Results:
  2842. #    XML format text.
  2843.  
  2844. proc dom::Encode value {
  2845.     array set Entity {
  2846.     $ $
  2847.     < <
  2848.     > >
  2849.     & &
  2850.     \" "
  2851.     ' '
  2852.     }
  2853.  
  2854.     regsub -all {([$<>&"'])} $value {$Entity(\1)} value
  2855.  
  2856.     return [subst -nocommand -nobackslash $value]
  2857. }
  2858.  
  2859. # dom::Serialize:attributeList --
  2860. #
  2861. #    Produce text for an attribute list.
  2862. #
  2863. # Arguments:
  2864. #    l    name/value paired list
  2865. #
  2866. # Results:
  2867. #    XML format text.
  2868.  
  2869. proc dom::Serialize:attributeList {l} {
  2870.  
  2871.     set result {}
  2872.     foreach {name value} $l {
  2873.  
  2874.     append result { } $name =
  2875.  
  2876.     # Handle special characters
  2877.     regsub -all < $value {\<} value
  2878.     regsub -all & $value {\&} value
  2879.  
  2880.     if {![string match *\"* $value]} {
  2881.         append result \"$value\"
  2882.     } elseif {![string match *'* $value]} {
  2883.         append result '$value'
  2884.     } else {
  2885.         regsub -all \" $value {\"} value
  2886.         append result \"$value\"
  2887.     }
  2888.  
  2889.     }
  2890.  
  2891.     return $result
  2892. }
  2893.  
  2894. # dom::Serialize:Indent --
  2895. #
  2896. #    Calculate the indentation required, if any
  2897. #
  2898. # Arguments:
  2899. #    args    configuration options, which may specify -indent
  2900. #
  2901. # Results:
  2902. #    May return white space
  2903.  
  2904. proc dom::Serialize:Indent args {
  2905.     variable indentspec
  2906.     array set opts [list -indentspec $indentspec]
  2907.     array set opts $args
  2908.  
  2909.     if {![info exists opts(-indent)] || \
  2910.         [regexp {^false|no|off$} $opts(-indent)]} {
  2911.     return {}
  2912.     }
  2913.  
  2914.     if {[regexp {^true|yes|on$} $opts(-indent)]} {
  2915.     # Default indent level is 0
  2916.     return \n
  2917.     }
  2918.  
  2919.     if {!$opts(-indent)} {
  2920.     return \n
  2921.     }
  2922.  
  2923.     set ws [format \n%\ [expr $opts(-indent) * [lindex $opts(-indentspec) 0]]s { }]
  2924.     regsub -all [lindex [lindex $opts(-indentspec) 1] 0] $ws [lindex [lindex $opts(-indentspec) 1] 1] ws
  2925.  
  2926.     return $ws
  2927.  
  2928. }
  2929.  
  2930. #################################################
  2931. #
  2932. # Parsing
  2933. #
  2934. #################################################
  2935.  
  2936. # ParseElementStart --
  2937. #
  2938. #    Push a new element onto the stack.
  2939. #
  2940. # Arguments:
  2941. #    stateVar    global state array variable
  2942. #    name        element name
  2943. #    attrList    attribute list
  2944. #    args        configuration options
  2945. #
  2946. # Results:
  2947. #    An element is created within the currently open element.
  2948.  
  2949. proc dom::ParseElementStart {stateVar name attrList args} {
  2950.     variable xmlnsURI
  2951.  
  2952.     upvar #0 $stateVar state
  2953.     array set opts $args
  2954.  
  2955.     # Push namespace declarations
  2956.     # We need to be able to map namespaceURI's back to prefixes
  2957.     set nsattrlists {}
  2958.     catch {
  2959.     foreach {namespaceURI prefix} $opts(-namespacedecls) {
  2960.         lappend state(NS:$namespaceURI) $prefix
  2961.  
  2962.         # Also, synthesize namespace declaration attributes
  2963.         # TclXML is a little too clever when it parses them away!
  2964.  
  2965.         lappend nsattrlists $prefix $namespaceURI
  2966.     }
  2967.     lappend opts(-namespaceattributelists) $xmlnsURI $nsattrlists
  2968.  
  2969.     }
  2970.  
  2971.     set nsarg {}
  2972.     catch {
  2973.     lappend nsarg -namespace $opts(-namespace)
  2974.     lappend nsarg -localname $name
  2975.     lappend nsarg -prefix [lindex $state(NS:$opts(-namespace)) end]
  2976.     }
  2977.  
  2978.     lappend state(current) \
  2979.     [eval CreateElement [list [lindex $state(current) end] $name $attrList] $nsarg [array get opts -namespaceattributelists]]
  2980.  
  2981.     if {[info exists opts(-empty)] && $opts(-empty)} {
  2982.     # Flag this node as being an empty element
  2983.     array set node [set [lindex $state(current) end]]
  2984.     set node(element:empty) 1
  2985.     set [lindex $state(current) end] [array get node]
  2986.     }
  2987.  
  2988.     # Temporary: implement -progresscommand here, because of broken parser
  2989.     if {[string length $state(-progresscommand)]} {
  2990.     if {!([incr state(progCounter)] % $state(-chunksize))} {
  2991.         uplevel #0 $state(-progresscommand)
  2992.     }
  2993.     }
  2994. }
  2995.  
  2996. # ParseElementEnd --
  2997. #
  2998. #    Pop an element from the stack.
  2999. #
  3000. # Arguments:
  3001. #    stateVar    global state array variable
  3002. #    name        element name
  3003. #    args        configuration options
  3004. #
  3005. # Results:
  3006. #    Currently open element is closed.
  3007.  
  3008. proc dom::ParseElementEnd {stateVar name args} {
  3009.     upvar #0 $stateVar state
  3010.  
  3011.     set state(current) [lreplace $state(current) end end]
  3012. }
  3013.  
  3014. # ParseCharacterData --
  3015. #
  3016. #    Add a textNode to the currently open element.
  3017. #
  3018. # Arguments:
  3019. #    stateVar    global state array variable
  3020. #    data        character data
  3021. #
  3022. # Results:
  3023. #    A textNode is created.
  3024.  
  3025. proc dom::ParseCharacterData {stateVar data} {
  3026.     upvar #0 $stateVar state
  3027.  
  3028.     CreateTextNode [lindex $state(current) end] $data
  3029. }
  3030.  
  3031. # ParseProcessingInstruction --
  3032. #
  3033. #    Add a PI to the currently open element.
  3034. #
  3035. # Arguments:
  3036. #    stateVar    global state array variable
  3037. #    name        PI name
  3038. #    target        PI target
  3039. #
  3040. # Results:
  3041. #    A processingInstruction node is created.
  3042.  
  3043. proc dom::ParseProcessingInstruction {stateVar name target} {
  3044.     upvar #0 $stateVar state
  3045.  
  3046.     CreateGeneric [lindex $state(current) end] node:nodeType processingInstruction node:nodeName $name node:nodeValue $target
  3047. }
  3048.  
  3049. # ParseXMLDeclaration --
  3050. #
  3051. #    Add information from the XML Declaration to the document.
  3052. #
  3053. # Arguments:
  3054. #    stateVar    global state array variable
  3055. #    version        version identifier
  3056. #    encoding    character encoding
  3057. #    standalone    standalone document declaration
  3058. #
  3059. # Results:
  3060. #    Document node modified.
  3061.  
  3062. proc dom::ParseXMLDeclaration {stateVar version encoding standalone} {
  3063.     upvar #0 $stateVar state
  3064.  
  3065.     array set node [set $state(docNode)]
  3066.     array set xmldecl $node(document:xmldecl)
  3067.  
  3068.     array set xmldecl [list version $version    \
  3069.         standalone $standalone        \
  3070.         encoding $encoding            \
  3071.     ]
  3072.  
  3073.     set node(document:xmldecl) [array get xmldecl]
  3074.     set $state(docNode) [array get node]
  3075.  
  3076.     return {}
  3077. }
  3078.  
  3079. # ParseDocType --
  3080. #
  3081. #    Add a Document Type Declaration node to the document.
  3082. #
  3083. # Arguments:
  3084. #    stateVar    global state array variable
  3085. #    root        root element type
  3086. #    publit        public identifier literal
  3087. #    systemlist    system identifier literal
  3088. #    dtd        internal DTD subset
  3089. #
  3090. # Results:
  3091. #    DocType node added
  3092.  
  3093. proc dom::ParseDocType {stateVar root {publit {}} {systemlit {}} {dtd {}} args} {
  3094.     upvar #0 $stateVar state
  3095.  
  3096.     CreateDocType $state(docNode) $root [list $publit $systemlit] $dtd {} {}
  3097.     # Last two are entities and notaions (as namedNodeMap's)
  3098.  
  3099.     return {}
  3100. }
  3101.  
  3102. # dom::ParseComment --
  3103. #
  3104. #    Parse comment
  3105. #
  3106. # Arguments:
  3107. #    stateVar    state array
  3108. #    data        comment data
  3109. #
  3110. # Results:
  3111. #    Comment node added to DOM tree
  3112.  
  3113. proc dom::ParseComment {stateVar data} {
  3114.     upvar #0 $stateVar state
  3115.  
  3116.     CreateGeneric [lindex $state(current) end] node:nodeType comment node:nodeValue $data
  3117.  
  3118.     return {}
  3119. }
  3120.  
  3121. # dom::ParseEntityReference --
  3122. #
  3123. #    Parse an entity reference
  3124. #
  3125. # Arguments:
  3126. #    stateVar    state variable
  3127. #    ref        entity
  3128. #
  3129. # Results:
  3130. #    Entity reference node added to DOM tree
  3131.  
  3132. proc dom::ParseEntityReference {stateVar ref} {
  3133.     upvar #0 $stateVar state
  3134.  
  3135.     CreateGeneric [lindex $state(current) end] node:nodeType entityReference node:nodeName $ref
  3136.  
  3137.     return {}
  3138. }
  3139.  
  3140. #################################################
  3141. #
  3142. # Trim white space
  3143. #
  3144. #################################################
  3145.  
  3146. # dom::Trim --
  3147. #
  3148. #    Remove textNodes that only contain white space
  3149. #
  3150. # Arguments:
  3151. #    nodeid    node to trim
  3152. #
  3153. # Results:
  3154. #    textNode nodes may be removed (from descendants)
  3155.  
  3156. proc dom::Trim nodeid {
  3157.     array set node [set $nodeid]
  3158.  
  3159.     switch $node(node:nodeType) {
  3160.  
  3161.     textNode {
  3162.         if {![string length [string trim $node(node:nodeValue)]]} {
  3163.         node removeChild $node(node:parentNode) $nodeid
  3164.         }
  3165.     }
  3166.  
  3167.     default {
  3168.         # Some nodes have no child list.  Reported by Jim Hollister <jhollister@objectspace.com>
  3169.         set children {}
  3170.         catch {set children [set $node(node:childNodes)]}
  3171.         foreach child $children {
  3172.         Trim $child
  3173.         }
  3174.     }
  3175.  
  3176.     }
  3177.  
  3178.     return {}
  3179. }
  3180.  
  3181. #################################################
  3182. #
  3183. # Query function
  3184. #
  3185. #################################################
  3186.  
  3187. # dom::Query --
  3188. #
  3189. #    Search DOM.
  3190. #
  3191. # DEPRECATED: This will be obsoleted by XPath.
  3192. #
  3193. # Arguments:
  3194. #    token    node to search
  3195. #    args    query options
  3196. #
  3197. # Results:
  3198. #    If query is found, return the node ID of the containing node.
  3199. #    Otherwise, return empty string
  3200.  
  3201. proc dom::Query {token args} {
  3202.     array set node [set $token]
  3203.     array set query $args
  3204.  
  3205.     set found 0
  3206.     switch $node(node:nodeType) {
  3207.     document -
  3208.     documentFragment {
  3209.         foreach child [set $node(node:childNodes)] {
  3210.         if {[llength [set result [eval Query [list $child] $args]]]} {
  3211.             return $result
  3212.         }
  3213.         }
  3214.     }
  3215.     element {
  3216.         catch {set found [expr ![string compare $node(node:nodeName) $query(-tagname)]]}
  3217.         if {$found} {
  3218.         return $token
  3219.         }
  3220.         if {![catch {array set attributes [set $node(element:attributeList)]}]} {
  3221.         catch {set found [expr [lsearch [array names attributes] $query(-attrname)] >= 0]}
  3222.         catch {set found [expr $found || [lsearch [array get attributes] $query(-attrvalue)] >= 0]}
  3223.         }
  3224.  
  3225.         if {$found} {
  3226.         return $token
  3227.         }
  3228.  
  3229.         foreach child [set $node(node:childNodes)] {
  3230.         if {[llength [set result [eval Query [list $child] $args]]]} {
  3231.             return $result
  3232.         }
  3233.         }
  3234.  
  3235.     }
  3236.     textNode -
  3237.     comment {
  3238.         catch {
  3239.         set querytext [expr {$node(node:nodeType) == "textNode" ? $query(-text) : $query(-comment)}]
  3240.         set found [expr [string match $node(node:nodeValue) $querytext] >= 0]
  3241.         }
  3242.  
  3243.         if {$found} {
  3244.         return $token
  3245.         }
  3246.     }
  3247.     processingInstruction {
  3248.         catch {set found [expr ![string compare $node(node:nodeName) $query(-pitarget)]]}
  3249.         catch {set found [expr $found || ![string compare $node(node:nodeValue) $query(-pidata)]]}
  3250.  
  3251.         if {$found} {
  3252.         return $token
  3253.         }
  3254.     }
  3255.     }
  3256.  
  3257.     if {$found} {
  3258.     return $token
  3259.     }
  3260.  
  3261.     return {}
  3262. }
  3263.  
  3264. #################################################
  3265. #
  3266. # XPath support
  3267. #
  3268. #################################################
  3269.  
  3270. # dom::XPath:CreateNode --
  3271. #
  3272. #    Given an XPath expression, create the node
  3273. #    referred to by the expression.  Nodes required
  3274. #    as steps of the path are created if they do
  3275. #    not exist.
  3276. #
  3277. # Arguments:
  3278. #    node    context node
  3279. #    path    location path
  3280. #
  3281. # Results:
  3282. #    Node(s) created in the DOM tree.
  3283. #    Returns token for deepest node in the expression.
  3284.  
  3285. proc dom::XPath:CreateNode {node path} {
  3286.  
  3287.     if {[string length [node parent $node]]} {
  3288.     array set nodearr [set $node]
  3289.     set root $nodearr(docArray)(node1)
  3290.     } else {
  3291.     set root $node
  3292.     }
  3293.  
  3294.     set spath [xpath::split $path]
  3295.  
  3296.     if {[llength $spath] <= 1} {
  3297.     # / - do nothing
  3298.     return $root
  3299.     }
  3300.  
  3301.     if {![llength [lindex $spath 0]]} {
  3302.     # Absolute location path
  3303.     set context $root
  3304.     set spath [lrange $spath 1 end]
  3305.     set contexttype document
  3306.     } else {
  3307.     set context $node
  3308.     set contexttype [node cget $node -nodeType]
  3309.     }
  3310.  
  3311.     foreach step $spath {
  3312.  
  3313.     # Sanity check on path
  3314.     switch $contexttype {
  3315.         document -
  3316.         documentFragment -
  3317.         element {}
  3318.         default {
  3319.         return -code error "node type \"$contexttype\" have no children"
  3320.         }
  3321.     }
  3322.  
  3323.     switch [lindex $step 0] {
  3324.  
  3325.         child {
  3326.         if {[llength [lindex $step 1]] > 1} {
  3327.             foreach {nodetype discard} [lindex $step 1] break
  3328.  
  3329.             switch -- $nodetype {
  3330.             text {
  3331.                 set posn [CreateNode:FindPosition [lindex $step 2]]
  3332.  
  3333.                 set count 0
  3334.                 set targetNode {}
  3335.                 foreach child [node children $context] {
  3336.                 switch [node cget $child -nodeType] {
  3337.                     textNode {
  3338.                     incr count
  3339.                     if {$count == $posn} {
  3340.                         set targetNode $child
  3341.                         break
  3342.                     }
  3343.                     }
  3344.                     default {}
  3345.                 }
  3346.                 }
  3347.  
  3348.                 if {[string length $targetNode]} {
  3349.                 set context $targetNode
  3350.                 } else {
  3351.                 # Creating sequential textNodes doesn't make sense
  3352.                 set context [document createTextNode $context {}]
  3353.                 }
  3354.                 set contexttype textNode
  3355.             }
  3356.             default {
  3357.                 return -code error "node type test \"${nodetype}()\" not supported"
  3358.             }
  3359.             }
  3360.         } else {
  3361.             # Find the child element
  3362.             set posn [CreateNode:FindPosition [lindex $step 2]]
  3363.  
  3364.             set count 0
  3365.             set targetNode {}
  3366.             foreach child [node children $context] {
  3367.             switch [node cget $child -nodeType] {
  3368.                 element {
  3369.                 if {![string compare [lindex $step 1] [node cget $child -nodeName]]} {
  3370.                     incr count
  3371.                     if {$count == $posn} {
  3372.                     set targetNode $child
  3373.                     break
  3374.                     }
  3375.                 }
  3376.                 }
  3377.                 default {}
  3378.             }
  3379.             }
  3380.  
  3381.             if {[string length $targetNode]} {
  3382.             set context $targetNode
  3383.             } else {
  3384.             # Didn't find it so create required elements
  3385.             while {$count < $posn} {
  3386.                 set child [document createElement $context [lindex $step 1]]
  3387.                 incr count
  3388.             }
  3389.             set context $child
  3390.             }
  3391.             set contexttype element
  3392.  
  3393.         }
  3394.         }
  3395.  
  3396.         default {
  3397.         return -code error "axis \"[lindex $step 0]\" is not supported"
  3398.         }
  3399.     }
  3400.     }
  3401.  
  3402.     return $context
  3403. }
  3404.  
  3405. # dom::CreateNode:FindPosition --
  3406.  
  3407. proc dom::CreateNode:FindPosition predicates {
  3408.     switch [llength $predicates] {
  3409.     0 {
  3410.         return 1
  3411.     }
  3412.     1 {
  3413.         # Fall-through
  3414.     }
  3415.     default {
  3416.         return -code error "multiple predicates not supported"
  3417.     }
  3418.     }
  3419.     set predicate [lindex $predicates 0]
  3420.  
  3421.     switch -- [lindex [lindex $predicate 0] 0] {
  3422.     function {
  3423.         switch -- [lindex [lindex $predicate 0] 1] {
  3424.         position {
  3425.             if {[lindex $predicate 1] == "="} {
  3426.             if {[string compare [lindex [lindex $predicate 2] 0] "number"]} {
  3427.                 return -code error "operand must be a number"
  3428.             } else {
  3429.                 set posn [lindex [lindex $predicate 2] 1]
  3430.             }
  3431.             } else {
  3432.             return -code error "operator must be \"=\""
  3433.             }
  3434.         }
  3435.         default {
  3436.             return -code error "predicate function \"[lindex [lindex $predicate 0] 1]\" not supported"
  3437.         }
  3438.         }
  3439.     }
  3440.     default {
  3441.         return -code error "predicate must be position() function"
  3442.     }
  3443.     }
  3444.  
  3445.     return $posn
  3446. }
  3447.  
  3448. # dom::XPath:SelectNode --
  3449. #
  3450. #    Match nodes with an XPath location path
  3451. #
  3452. # Arguments:
  3453. #    ctxt    context - Tcl list
  3454. #    path    location path
  3455. #
  3456. # Results:
  3457. #    Returns Tcl list of matching nodes
  3458.  
  3459. proc dom::XPath:SelectNode {ctxt path} {
  3460.  
  3461.     if {![llength $ctxt]} {
  3462.     return {}
  3463.     }
  3464.  
  3465.     set spath [xpath::split $path]
  3466.  
  3467.     if {[string length [node parent [lindex $ctxt 0]]]} {
  3468.     array set nodearr [set [lindex $ctxt 0]]
  3469.     set root $nodearr(docArray)(node1)
  3470.     } else {
  3471.     set root [lindex $ctxt 0]
  3472.     }
  3473.  
  3474.     if {[llength $spath] <= 1} {
  3475.     return $root
  3476.     }
  3477.  
  3478.     if {![llength [lindex $spath 0]]} {
  3479.     set ctxt $root
  3480.     set spath [lrange $spath 1 end]
  3481.     }
  3482.  
  3483.     return [XPath:SelectNode:Rel $ctxt $spath]
  3484. }
  3485.  
  3486. # dom::XPath:SelectNode:Rel --
  3487. #
  3488. #    Match nodes with an XPath location path
  3489. #
  3490. # Arguments:
  3491. #    ctxt    context - Tcl list
  3492. #    path    split location path
  3493. #
  3494. # Results:
  3495. #    Returns Tcl list of matching nodes
  3496.  
  3497. proc dom::XPath:SelectNode:Rel {ctxt spath} {
  3498.     if {![llength $spath]} {
  3499.     return $ctxt
  3500.     }
  3501.  
  3502.     set step [lindex $spath 0]
  3503.     set result {}
  3504.     switch [lindex $step 0] {
  3505.  
  3506.     child {
  3507.         # All children are candidates
  3508.         set children {}
  3509.         foreach node [XPath:SN:GetElementTypeNodes $ctxt] {
  3510.         eval lappend children [node children $node]
  3511.         }
  3512.  
  3513.         # Now apply node test to each child
  3514.         foreach node $children {
  3515.         if {[XPath:SN:ApplyNodeTest $node [lindex $step 1]]} {
  3516.             lappend result $node
  3517.         }
  3518.         }
  3519.  
  3520.     }
  3521.  
  3522.     descendant-or-self {
  3523.         foreach node $ctxt {
  3524.         if {[XPath:SN:ApplyNodeTest $node [lindex $step 1]]} {
  3525.             lappend result $node
  3526.         }
  3527.         eval lappend result [XPath:SN:DescendAndTest [node children $node] [lindex $step 1]]
  3528.         }
  3529.     }
  3530.  
  3531.     descendant {
  3532.         foreach node $ctxt {
  3533.         eval lappend result [XPath:SN:DescendAndTest [node children $node] [lindex $step 1]]
  3534.         }
  3535.     }
  3536.  
  3537.     attribute {
  3538.         if {[string compare [lindex $step 1] "*"]} {
  3539.         foreach node $ctxt {
  3540.             set attrNode [element getAttributeNode $node [lindex $step 1]]
  3541.             if {[llength $attrNode]} {
  3542.             lappend result $attrNode
  3543.             }
  3544.         }
  3545.         } else {
  3546.         # All attributes are returned
  3547.         foreach node $ctxt {
  3548.             foreach attrName [array names [node cget $node -attributes]] {
  3549.             set attrNode [element getAttributeNode $node $attrName]
  3550.             if {[llength $attrNode]} {
  3551.                 lappend result $attrNode
  3552.             }
  3553.             }
  3554.         }
  3555.         }
  3556.     }
  3557.  
  3558.     default {
  3559.         return -code error "axis \"[lindex $step 0]\" is not supported"
  3560.     }
  3561.     }
  3562.  
  3563.     # Now apply predicates
  3564.     set result [XPath:ApplyPredicates $result [lindex $step 2]]
  3565.  
  3566.     # Apply the next location step
  3567.     return [XPath:SelectNode:Rel $result [lrange $spath 1 end]]
  3568. }
  3569.  
  3570. # dom::XPath:SN:GetElementTypeNodes --
  3571. #
  3572. #    Reduce nodeset to those nodes of element type
  3573. #
  3574. # Arguments:
  3575. #    nodeset    set of nodes
  3576. #
  3577. # Results:
  3578. #    Returns nodeset in which all nodes are element type
  3579.  
  3580. proc dom::XPath:SN:GetElementTypeNodes nodeset {
  3581.     set result {}
  3582.     foreach node $nodeset {
  3583.     switch [node cget $node -nodeType] {
  3584.         documentFragment -
  3585.         element {
  3586.         lappend result $node
  3587.         }
  3588.         default {}
  3589.     }
  3590.     }
  3591.     return $result
  3592. }
  3593.  
  3594. # dom::XPath:SN:ApplyNodeTest --
  3595. #
  3596. #    Apply the node test to a node
  3597. #
  3598. # Arguments:
  3599. #    node    DOM node to test
  3600. #    test    node test
  3601. #
  3602. # Results:
  3603. #    1 if node passes, 0 otherwise
  3604.  
  3605. proc dom::XPath:SN:ApplyNodeTest {node test} {
  3606.     if {[llength $test] > 1} {
  3607.     foreach {name typetest} $test break
  3608.     # Node type test
  3609.     switch -glob -- $name,[node cget $node -nodeType] {
  3610.         node,* {
  3611.         return 1
  3612.         }
  3613.         text,textNode -
  3614.         comment,comment -
  3615.         processing-instruction,processingInstruction {
  3616.         return 1
  3617.         }
  3618.         text,* -
  3619.         comment,* -
  3620.         processing-instruction,* {
  3621.         return 0
  3622.         }
  3623.         default {
  3624.         return -code error "illegal node type test \"[lindex $step 1]\""
  3625.         }
  3626.     }
  3627.     } else {
  3628.     # Node name test
  3629.     switch -glob -- $test,[node cget $node -nodeType],[node cget $node -nodeName] \
  3630.         \\*,element,* {
  3631.         return 1
  3632.     } \
  3633.         \\*,* {
  3634.         return 0
  3635.     } \
  3636.         *,element,$test {
  3637.         return 1
  3638.     }
  3639.     }
  3640.  
  3641.     return 0
  3642. }
  3643.  
  3644. # dom::XPath:SN:DescendAndTest --
  3645. #
  3646. #    Descend the element hierarchy,
  3647. #    apply the node test as we go
  3648. #
  3649. # Arguments:
  3650. #    nodeset    nodes to be tested and descended
  3651. #    test    node test
  3652. #
  3653. # Results:
  3654. #    Returned nodeset of nodes which pass the test
  3655.  
  3656. proc dom::XPath:SN:DescendAndTest {nodeset test} {
  3657.     set result {}
  3658.  
  3659.     foreach node $nodeset {
  3660.     if {[XPath:SN:ApplyNodeTest $node $test]} {
  3661.         lappend result $node
  3662.     }
  3663.     switch [node cget $node -nodeType] {
  3664.         documentFragment -
  3665.         element {
  3666.         eval lappend result [XPath:SN:DescendAndTest [node children $node] $test]
  3667.         }
  3668.     }
  3669.     }
  3670.  
  3671.     return $result
  3672. }
  3673.  
  3674. # dom::XPath:ApplyPredicates --
  3675. #
  3676. #    Filter a nodeset with predicates
  3677. #
  3678. # Arguments:
  3679. #    ctxt    current context nodeset
  3680. #    preds    list of predicates
  3681. #
  3682. # Results:
  3683. #    Returns new (possibly reduced) context nodeset
  3684.  
  3685. proc dom::XPath:ApplyPredicates {ctxt preds} {
  3686.  
  3687.     set result {}
  3688.     foreach node $ctxt {
  3689.     set passed 1
  3690.     foreach predicate $preds {
  3691.         if {![XPath:ApplyPredicate $node $predicate]} {
  3692.         set passed 0
  3693.         break
  3694.         }
  3695.     }
  3696.     if {$passed} {
  3697.         lappend result $node
  3698.     }
  3699.     }
  3700.  
  3701.     return $result
  3702. }
  3703.  
  3704. # dom::XPath:ApplyPredicate --
  3705. #
  3706. #    Filter a node with a single predicate
  3707. #
  3708. # Arguments:
  3709. #    node    current context node
  3710. #    pred    predicate
  3711. #
  3712. # Results:
  3713. #    Returns boolean
  3714.  
  3715. proc dom::XPath:ApplyPredicate {node pred} {
  3716.  
  3717.     switch -- [lindex $pred 0] {
  3718.     = -
  3719.     != -
  3720.     >= -
  3721.     <= -
  3722.     > -
  3723.     > {
  3724.  
  3725.         if {[llength $pred] != 3} {
  3726.         return -code error "malformed expression"
  3727.         }
  3728.  
  3729.         set operand1 [XPath:Pred:ResolveExpr $node [lindex $pred 1]]
  3730.         set operand2 [XPath:Pred:ResolveExpr $node [lindex $pred 2]]
  3731.  
  3732.         # Convert operands to the correct type, if necessary
  3733.         switch -glob [lindex $operand1 0],[lindex $operand2 0] {
  3734.         literal,literal {
  3735.             return [XPath:Pred:CompareLiterals [lindex $pred 0] [lindex $operand1 1] [lindex $operand2 1]]
  3736.         }
  3737.  
  3738.         number,number -
  3739.         literal,number -
  3740.         number,literal {
  3741.             # Compare as numbers
  3742.             return [XPath:Pred:CompareNumbers [lindex $pred 0] [lindex $operand1 1] [lindex $operand2 1]]
  3743.         }
  3744.  
  3745.         boolean,boolean {
  3746.             # Compare as booleans
  3747.             return -code error "boolean comparison not yet implemented"
  3748.         }
  3749.  
  3750.         node,node {
  3751.             # Nodeset comparison
  3752.             return -code error "nodeset comparison not yet implemented"
  3753.         }
  3754.  
  3755.         node,* {
  3756.             set value {}
  3757.             if {[llength [lindex $operand1 1]]} {
  3758.             set value [node stringValue [lindex [lindex $operand1 1] 0]]
  3759.             }
  3760.             return [XPath:Pred:CompareLiterals [lindex $pred 0] $value [lindex $operand2 1]]
  3761.         }
  3762.         *,node {
  3763.             set value {}
  3764.             if {[llength [lindex $operand2 1]]} {
  3765.             set value [node stringValue [lindex [lindex $operand2 1] 0]]
  3766.             }
  3767.             return [XPath:Pred:CompareLiterals [lindex $pred 0] $value [lindex $operand1 1]]
  3768.         }
  3769.  
  3770.         default {
  3771.             return -code error "can't compare [lindex $operand1 0] to [lindex $operand2 0]"
  3772.         }
  3773.         }
  3774.     }
  3775.  
  3776.     function {
  3777.         return -code error "invalid predicate"
  3778.     }
  3779.     number -
  3780.     literal {
  3781.         return -code error "invalid predicate"
  3782.     }
  3783.  
  3784.     path {
  3785.         set nodeset [XPath:SelectNode:Rel $node [lindex $pred 1]]
  3786.         return [expr {[llength $nodeset] > 0 ? 1 : 0}]
  3787.     }
  3788.  
  3789.     }
  3790.  
  3791.     return 1
  3792. }
  3793.  
  3794. # dom::XPath:Pred:Compare --
  3795.  
  3796. proc dom::XPath:Pred:CompareLiterals {op operand1 operand2} {
  3797.     set result [string compare $operand1 $operand2]
  3798.  
  3799.     # The obvious:
  3800.     #return [expr {$result $opMap($op) 0}]
  3801.     # doesn't compile
  3802.     
  3803.     switch $op {
  3804.     = {
  3805.         return [expr {$result == 0}]
  3806.     }
  3807.     != {
  3808.         return [expr {$result != 0}]
  3809.     }
  3810.     <= {
  3811.         return [expr {$result <= 0}]
  3812.     }
  3813.     >= {
  3814.         return [expr {$result >= 0}]
  3815.     }
  3816.     < {
  3817.         return [expr {$result < 0}]
  3818.     }
  3819.     > {
  3820.         return [expr {$result > 0}]
  3821.     }
  3822.     }
  3823.     return -code error "internal error"
  3824. }
  3825.  
  3826. # dom::XPath:Pred:ResolveExpr --
  3827.  
  3828. proc dom::XPath:Pred:ResolveExpr {node expr} {
  3829.  
  3830.     switch [lindex $expr 0] {
  3831.     path {
  3832.         return [list node [XPath:SelectNode:Rel $node [lindex $expr 1]]]
  3833.     }
  3834.  
  3835.     function -
  3836.     group {
  3837.         return -code error "[lindex $expr 0] not yet implemented"
  3838.     }
  3839.     literal -
  3840.     number -
  3841.     boolean {
  3842.         return $expr
  3843.     }
  3844.  
  3845.     default {
  3846.         return -code error "internal error"
  3847.     }
  3848.     }
  3849.  
  3850.     return {}
  3851. }
  3852.  
  3853. #################################################
  3854. #
  3855. # Miscellaneous
  3856. #
  3857. #################################################
  3858.  
  3859. # dom::hasmixedcontent --
  3860. #
  3861. #    Determine whether an element contains mixed content
  3862. #
  3863. # Arguments:
  3864. #    token    dom node
  3865. #
  3866. # Results:
  3867. #    Returns 1 if element contains mixed content,
  3868. #    0 otherwise
  3869.  
  3870. proc dom::hasmixedcontent token {
  3871.     array set node [set $token]
  3872.  
  3873.     if {[string compare $node(node:nodeType) "element"]} {
  3874.     # Really undefined
  3875.     return 0
  3876.     }
  3877.  
  3878.     foreach child [set $node(node:childNodes)] {
  3879.     catch {unset childnode}
  3880.     array set childnode [set $child]
  3881.     if {![string compare $childnode(node:nodeType) "textNode"]} {
  3882.         return 1
  3883.     }
  3884.     }
  3885.  
  3886.     return 0
  3887. }
  3888.  
  3889. # dom::prefix2namespaceURI --
  3890. #
  3891. #    Given an XML Namespace prefix, find the corresponding Namespace URI
  3892. #
  3893. # Arguments:
  3894. #    node    DOM Node
  3895. #    prefix    XML Namespace prefix
  3896. #
  3897. # Results:
  3898. #    Returns URI
  3899.  
  3900. proc dom::prefix2namespaceURI {node prefix} {
  3901.     variable xmlnsURI
  3902.  
  3903.     # Search this node and its ancestors for the appropriate
  3904.     # XML Namespace declaration
  3905.  
  3906.     set parent [dom::node parent $node]
  3907.     set nsuri [dom::element getAttributeNS $node $xmlnsURI $prefix]
  3908.     if {[string length $parent] && ![string length $nsuri]} {
  3909.     set nsuri [dom::element getAttributeNS $parent $::dom::xmlnsURI $prefix]
  3910.     set parent [dom::node parent $parent]
  3911.     }
  3912.  
  3913.     if {[string length $nsuri]} {
  3914.     return $nsuri
  3915.     } else {
  3916.     return -code error "unable to find namespace URI for prefix \"$prefix\""
  3917.     }
  3918.  
  3919. }
  3920.  
  3921. # dom::GetField --
  3922. #
  3923. #    Return a value, or empty string if not defined
  3924. #
  3925. # Arguments:
  3926. #    var    name of variable to return
  3927. #
  3928. # Results:
  3929. #    Returns the value, or empty string if variable is not defined.
  3930.  
  3931. proc dom::GetField var {
  3932.     upvar $var v
  3933.     if {[info exists v]} {
  3934.     return $v
  3935.     } else {
  3936.     return {}
  3937.     }
  3938. }
  3939.  
  3940. # dom::Min --
  3941. #
  3942. #    Return the minimum of two numeric values
  3943. #
  3944. # Arguments:
  3945. #    a    a value
  3946. #    b    another value
  3947. #
  3948. # Results:
  3949. #    Returns the value which is lower than the other.
  3950.  
  3951. proc dom::Min {a b} {
  3952.     return [expr {$a < $b ? $a : $b}]
  3953. }
  3954.  
  3955. # dom::Max --
  3956. #
  3957. #    Return the maximum of two numeric values
  3958. #
  3959. # Arguments:
  3960. #    a    a value
  3961. #    b    another value
  3962. #
  3963. # Results:
  3964. #    Returns the value which is greater than the other.
  3965.  
  3966. proc dom::Max {a b} {
  3967.     return [expr {$a > $b ? $a : $b}]
  3968. }
  3969.  
  3970. # dom::Boolean --
  3971. #
  3972. #    Return a boolean value
  3973. #
  3974. # Arguments:
  3975. #    b    value
  3976. #
  3977. # Results:
  3978. #    Returns 0 or 1
  3979.  
  3980. proc dom::Boolean b {
  3981.     regsub -nocase {^(true|yes|1|on)$} $b 1 b
  3982.     regsub -nocase {^(false|no|0|off)$} $b 0 b
  3983.     return $b
  3984. }
  3985.  
  3986.