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

  1. # sgmlparser.tcl --
  2. #
  3. #    This file provides the generic part of a parser for SGML-based
  4. #    languages, namely HTML and XML.
  5. #
  6. #    NB.  It is a misnomer.  There is no support for parsing
  7. #    arbitrary SGML as such.
  8. #
  9. #    See sgml.tcl for variable definitions.
  10. #
  11. # Copyright (c) 1998-2001 Zveno Pty Ltd
  12. # http://www.zveno.com/
  13. #
  14. # Zveno makes this software available free of charge for any purpose.
  15. # Copies may be made of this software but all of this notice must be included
  16. # on any copy.
  17. #
  18. # The software was developed for research purposes only and Zveno does not
  19. # warrant that it is error free or fit for any purpose.  Zveno disclaims any
  20. # liability for all claims, expenses, losses, damages and costs any user may
  21. # incur as a result of using, copying or modifying this software.
  22. #
  23. # Copyright (c) 1997 ANU and CSIRO on behalf of the
  24. # participants in the CRC for Advanced Computational Systems ('ACSys').
  25. # ACSys makes this software and all associated data and documentation 
  26. # ('Software') available free of charge for any purpose.  You may make copies 
  27. # of the Software but you must include all of this notice on any copy.
  28. # The Software was developed for research purposes and ACSys does not warrant
  29. # that it is error free or fit for any purpose.  ACSys disclaims any
  30. # liability for all claims, expenses, losses, damages and costs any user may
  31. # incur as a result of using, copying or modifying the Software.
  32. #
  33. # $Id: sgmlparser.tcl,v 1.17 2001/08/02 05:11:53 balls Exp $
  34.  
  35. package require sgml 1.8
  36.  
  37. package provide sgmlparser 1.0
  38.  
  39. namespace eval sgml {
  40.     namespace export tokenise parseEvent
  41.  
  42.     namespace export parseDTD
  43.  
  44.     # NB. Most namespace variables are defined in sgml-8.[01].tcl
  45.     # to account for differences between versions of Tcl.
  46.     # This especially includes the regular expressions used.
  47.  
  48.     variable ParseEventNum
  49.     if {![info exists ParseEventNum]} {
  50.     set ParseEventNum 0
  51.     }
  52.     variable ParseDTDnum
  53.     if {![info exists ParseDTDNum]} {
  54.     set ParseDTDNum 0
  55.     }
  56.  
  57.     variable declExpr [cl $::sgml::Wsp]*([cl ^$::sgml::Wsp]+)[cl $::sgml::Wsp]*([cl ^>]*)
  58.     variable EntityExpr [cl $::sgml::Wsp]*(%[cl $::sgml::Wsp])?[cl $::sgml::Wsp]*($::sgml::Name)[cl $::sgml::Wsp]+(.*)
  59.  
  60.     #variable MarkupDeclExpr <([cl ^$::sgml::Wsp>]+)[cl $::sgml::Wsp]*([cl ^$::sgml::Wsp]+)[cl $::sgml::Wsp]*([cl ^>]*)>
  61.     #variable MarkupDeclSub "} {\\1} {\\2} {\\3} {"
  62.     variable MarkupDeclExpr <[cl $::sgml::Wsp]*([cl ^$::sgml::Wsp>]+)[cl $::sgml::Wsp]*([cl ^>]*)>
  63.     variable MarkupDeclSub "\} {\\1} {\\2} \{"
  64.  
  65.     variable ExternalEntityExpr ^(PUBLIC|SYSTEM)[cl $::sgml::Wsp]+("|')(.*?)\\2([cl $::sgml::Wsp]+("|')(.*?)\\2)?([cl $::sgml::Wsp]+NDATA[cl $::sgml::Wsp]+($::xml::Name))?\$
  66.  
  67.     variable StdOptions
  68.     array set StdOptions [list \
  69.     -elementstartcommand        [namespace current]::noop    \
  70.     -elementendcommand        [namespace current]::noop    \
  71.     -characterdatacommand        [namespace current]::noop    \
  72.     -processinginstructioncommand    [namespace current]::noop    \
  73.     -externalentitycommand        {}                \
  74.     -xmldeclcommand            [namespace current]::noop    \
  75.     -doctypecommand            [namespace current]::noop    \
  76.     -commentcommand            [namespace current]::noop    \
  77.     -entitydeclcommand        [namespace current]::noop    \
  78.     -unparsedentitydeclcommand    [namespace current]::noop    \
  79.     -parameterentitydeclcommand    [namespace current]::noop    \
  80.     -notationdeclcommand        [namespace current]::noop    \
  81.     -elementdeclcommand        [namespace current]::noop    \
  82.     -attlistdeclcommand        [namespace current]::noop    \
  83.     -paramentityparsing        1                \
  84.     -defaultexpandinternalentities    1                \
  85.     -startdoctypedeclcommand    [namespace current]::noop    \
  86.     -enddoctypedeclcommand        [namespace current]::noop    \
  87.     -entityreferencecommand        {}                \
  88.     -warningcommand            [namespace current]::noop    \
  89.     -errorcommand            [namespace current]::Error    \
  90.     -final                1                \
  91.     -validate            0                \
  92.     -baseurl            {}                \
  93.     -name                {}                \
  94.     -emptyelement            [namespace current]::EmptyElement    \
  95.     -parseattributelistcommand    [namespace current]::noop    \
  96.     -parseentitydeclcommand        [namespace current]::noop    \
  97.     -normalize            1                \
  98.     -internaldtd            {}                \
  99.     -reportempty            0                \
  100.     ]
  101. }
  102.  
  103. # sgml::tokenise --
  104. #
  105. #    Transform the given HTML/XML text into a Tcl list.
  106. #
  107. # Arguments:
  108. #    sgml        text to tokenize
  109. #    elemExpr    RE to recognise tags
  110. #    elemSub        transform for matched tags
  111. #    args        options
  112. #
  113. # Valid Options:
  114. #    -final        boolean        True if no more data is to be supplied
  115. #    -statevariable    varName        Name of a variable used to store info
  116. #
  117. # Results:
  118. #    Returns a Tcl list representing the document.
  119.  
  120. proc sgml::tokenise {sgml elemExpr elemSub args} {
  121.     array set options {-final 1}
  122.     catch {array set options $args}
  123.     set options(-final) [Boolean $options(-final)]
  124.  
  125.     # If the data is not final then there must be a variable to store
  126.     # unused data.
  127.     if {!$options(-final) && ![info exists options(-statevariable)]} {
  128.     return -code error {option "-statevariable" required if not final}
  129.     }
  130.  
  131.     # Pre-process stage
  132.     #
  133.     # Extract the internal DTD subset, if any
  134.  
  135.     catch {upvar #0 $options(-internaldtdvariable) dtd}
  136.     if {[regexp {<!DOCTYPE[^[<]+\[([^]]+)\]} $sgml discard dtd]} {
  137.     regsub {(<!DOCTYPE[^[<]+)(\[[^]]+\])} $sgml {\1\&xml:intdtd;} sgml
  138.     }
  139.  
  140.     # Protect Tcl special characters
  141.     regsub -all {([{}\\])} $sgml {\\\1} sgml
  142.  
  143.     # Do the translation
  144.  
  145.     if {[info exists options(-statevariable)]} {
  146.     upvar #0 $opts(-statevariable) unused
  147.     if {[info exists unused]} {
  148.         regsub -all $elemExpr $unused$sgml $elemSub sgml
  149.         unset unused
  150.     } else {
  151.         regsub -all $elemExpr $sgml $elemSub sgml
  152.     }
  153.     set sgml "{} {} {} \{$sgml\}"
  154.  
  155.     # Performance note (Tcl 8.0):
  156.     #    Use of lindex, lreplace will cause parsing to list object
  157.  
  158.     if {[regexp {^([^<]*)(<[^>]*$)} [lindex $sgml end] x text unused]} {
  159.         set sgml [lreplace $sgml end end $text]
  160.     }
  161.  
  162.     } else {
  163.  
  164.     # Performance note (Tcl 8.0):
  165.     #    In this case, no conversion to list object is performed
  166.  
  167.     regsub -all $elemExpr $sgml $elemSub sgml
  168.     set sgml "{} {} {} \{$sgml\}"
  169.     }
  170.  
  171.     return $sgml
  172.  
  173. }
  174.  
  175. # sgml::parseEvent --
  176. #
  177. #    Produces an event stream for a XML/HTML document,
  178. #    given the Tcl list format returned by tokenise.
  179. #
  180. #    This procedure checks that the document is well-formed,
  181. #    and throws an error if the document is found to be not
  182. #    well formed.  Warnings are passed via the -warningcommand script.
  183. #
  184. #    The procedure only check for well-formedness,
  185. #    no DTD is required.  However, facilities are provided for entity expansion.
  186. #
  187. # Arguments:
  188. #    sgml        Instance data, as a Tcl list.
  189. #    args        option/value pairs
  190. #
  191. # Valid Options:
  192. #    -final            Indicates end of document data
  193. #    -validate        Boolean to enable validation
  194. #    -baseurl        URL for resolving relative URLs
  195. #    -elementstartcommand    Called when an element starts
  196. #    -elementendcommand    Called when an element ends
  197. #    -characterdatacommand    Called when character data occurs
  198. #    -entityreferencecommand    Called when an entity reference occurs
  199. #    -processinginstructioncommand    Called when a PI occurs
  200. #    -externalentitycommand    Called for an external entity reference
  201. #
  202. #    -xmldeclcommand        Called when the XML declaration occurs
  203. #    -doctypecommand        Called when the document type declaration occurs
  204. #    -commentcommand        Called when a comment occurs
  205. #    -entitydeclcommand    Called when a parsed entity is declared
  206. #    -unparsedentitydeclcommand    Called when an unparsed external entity is declared
  207. #    -parameterentitydeclcommand    Called when a parameter entity is declared
  208. #    -notationdeclcommand    Called when a notation is declared
  209. #    -elementdeclcommand    Called when an element is declared
  210. #    -attlistdeclcommand    Called when an attribute list is declared
  211. #    -paramentityparsing    Boolean to enable/disable parameter entity substitution
  212. #    -defaultexpandinternalentities    Boolean to enable/disable expansion of entities declared in internal DTD subset
  213. #
  214. #    -startdoctypedeclcommand    Called when the Doc Type declaration starts (see also -doctypecommand)
  215. #    -enddoctypedeclcommand    Called when the Doc Type declaration ends (see also -doctypecommand)
  216. #
  217. #    -errorcommand        Script to evaluate for a fatal error
  218. #    -warningcommand        Script to evaluate for a reportable warning
  219. #    -statevariable        global state variable
  220. #    -normalize        whether to normalize names
  221. #    -reportempty        whether to include an indication of empty elements
  222. #
  223. # Results:
  224. #    The various callback scripts are invoked.
  225. #    Returns empty string.
  226. #
  227. # BUGS:
  228. #    If command options are set to empty string then they should not be invoked.
  229.  
  230. proc sgml::parseEvent {sgml args} {
  231.     variable Wsp
  232.     variable noWsp
  233.     variable Nmtoken
  234.     variable Name
  235.     variable ParseEventNum
  236.     variable StdOptions
  237.  
  238.     array set options [array get StdOptions]
  239.     catch {array set options $args}
  240.  
  241.     foreach {opt value} [array get options *command] {
  242.     if {[string compare $opt "-externalentitycommand"] && ![string length $value]} {
  243.         set options($opt) [namespace current]::noop
  244.     }
  245.     }
  246.  
  247.     if {![info exists options(-statevariable)]} {
  248.     set options(-statevariable) [namespace current]::ParseEvent[incr ParseEventNum]
  249.     }
  250.     if {![info exists options(entities)]} {
  251.     set options(entities) [namespace current]::Entities$ParseEventNum
  252.     array set $options(entities) [array get [namespace current]::EntityPredef]
  253.     }
  254.     if {![info exists options(extentities)]} {
  255.     set options(extentities) [namespace current]::ExtEntities$ParseEventNum
  256.     }
  257.     if {![info exists options(parameterentities)]} {
  258.     set options(parameterentities) [namespace current]::ParamEntities$ParseEventNum
  259.     }
  260.     if {![info exists options(externalparameterentities)]} {
  261.     set options(externalparameterentities) [namespace current]::ExtParamEntities$ParseEventNum
  262.     }
  263.     if {![info exists options(elementdecls)]} {
  264.     set options(elementdecls) [namespace current]::ElementDecls$ParseEventNum
  265.     }
  266.     if {![info exists options(attlistdecls)]} {
  267.     set options(attlistdecls) [namespace current]::AttListDecls$ParseEventNum
  268.     }
  269.     if {![info exists options(notationdecls)]} {
  270.     set options(notationdecls) [namespace current]::NotationDecls$ParseEventNum
  271.     }
  272.     if {![info exists options(namespaces)]} {
  273.     set options(namespaces) [namespace current]::Namespaces$ParseEventNum
  274.     }
  275.  
  276.     # Choose an external entity resolver
  277.  
  278.     if {![string length $options(-externalentitycommand)]} {
  279.     if {$options(-validate)} {
  280.         set options(-externalentitycommand) [namespace code ResolveEntity]
  281.     } else {
  282.         set options(-externalentitycommand) [namespace code noop]
  283.     }
  284.     }
  285.  
  286.     upvar #0 $options(-statevariable) state
  287.     upvar #0 $options(entities) entities
  288.  
  289.     if {![info exists state]} {
  290.     # Initialise the state variable
  291.     array set state {
  292.         mode normal
  293.         haveXMLDecl 0
  294.         haveDocElement 0
  295.         inDTD 0
  296.         context {}
  297.         stack {}
  298.         line 0
  299.         defaultNS {}
  300.         defaultNSURI {}
  301.     }
  302.     }
  303.  
  304.     foreach {tag close param text} $sgml {
  305.  
  306.     # Keep track of lines in the input
  307.     incr state(line) [regsub -all \n $param {} discard]
  308.     incr state(line) [regsub -all \n $text {} discard]
  309.  
  310.     # If the current mode is cdata or comment then we must undo what the
  311.     # regsub has done to reconstitute the data
  312.  
  313.     set empty {}
  314.     switch $state(mode) {
  315.         comment {
  316.         # This had "[string length $param] && " as a guard -
  317.         # can't remember why :-(
  318.         if {[regexp ([cl ^-]*)--\$ $tag discard comm1]} {
  319.             # end of comment (in tag)
  320.             set tag {}
  321.             set close {}
  322.             set state(mode) normal
  323.             uplevel #0 $options(-commentcommand) [list $state(commentdata)<$comm1]
  324.             unset state(commentdata)
  325.         } elseif {[regexp ([cl ^-]*)--\$ $param discard comm1]} {
  326.             # end of comment (in attributes)
  327.             uplevel #0 $options(-commentcommand) [list $state(commentdata)<$close$tag>$comm1]
  328.             unset state(commentdata)
  329.             set tag {}
  330.             set param {}
  331.             set close {}
  332.             set state(mode) normal
  333.         } elseif {[regexp ([cl ^-]*)-->(.*) $text discard comm1 text]} {
  334.             # end of comment (in text)
  335.             uplevel #0 $options(-commentcommand) [list $state(commentdata)<$close$tag$param>$comm1]
  336.             unset state(commentdata)
  337.             set tag {}
  338.             set param {}
  339.             set close {}
  340.             set state(mode) normal
  341.         } else {
  342.             # comment continues
  343.             append state(commentdata) <$close$tag$param>$text
  344.             continue
  345.         }
  346.         }
  347.         cdata {
  348.         if {[string length $param] && [regexp ([cl ^\]]*)\]\][cl $Wsp]*\$ $tag discard cdata1]} {
  349.             # end of CDATA (in tag)
  350.             uplevel #0 $options(-characterdatacommand) [list $state(cdata)<[subst -nocommand -novariable $close$cdata1]]
  351.             set text [subst -novariable -nocommand $text]
  352.             set tag {}
  353.             unset state(cdata)
  354.             set state(mode) normal
  355.         } elseif {[regexp ([cl ^\]]*)\]\][cl $Wsp]*\$ $param discard cdata1]} {
  356.             # end of CDATA (in attributes)
  357.             uplevel #0 $options(-characterdatacommand) [list $state(cdata)<[subst -nocommand -novariable $close$tag$cdata1]]
  358.             set text [subst -novariable -nocommand $text]
  359.             set tag {}
  360.             set param {}
  361.             unset state(cdata)
  362.             set state(mode) normal
  363.         } elseif {[regexp (.*)\]\][cl $Wsp]*>(.*) $text discard cdata1 text]} {
  364.             # end of CDATA (in text)
  365.             uplevel #0 $options(-characterdatacommand) [list $state(cdata)<[subst -nocommand -novariable $close$tag$param>$cdata1]]
  366.             set text [subst -novariable -nocommand $text]
  367.             set tag {}
  368.             set param {}
  369.             set close {}
  370.             unset state(cdata)
  371.             set state(mode) normal
  372.         } else {
  373.             # CDATA continues
  374.             append state(cdata) [subst -nocommand -novariable <$close$tag$param>$text]
  375.             continue
  376.         }
  377.         }
  378.         continue {
  379.         # We're skipping elements looking for the close tag
  380.         switch -glob -- [string length $tag],[regexp {^\?|!.*} $tag],$close {
  381.             0,* {
  382.             continue
  383.             }
  384.             *,0, {
  385.             if {![string compare $tag $state(continue:tag)]} {
  386.                 set empty [uplevel #0 $options(-emptyelement) [list $tag $param $empty]]
  387.                 if {![string length $empty]} {
  388.                 incr state(continue:level)
  389.                 }
  390.             }
  391.             continue
  392.             }
  393.             *,0,/ {
  394.             if {![string compare $tag $state(continue:tag)]} {
  395.                 incr state(continue:level) -1
  396.             }
  397.             if {!$state(continue:level)} {
  398.                 unset state(continue:tag)
  399.                 unset state(continue:level)
  400.                 set state(mode) {}
  401.             }
  402.             }
  403.             default {
  404.             continue
  405.             }
  406.         }
  407.         }
  408.         default {
  409.         # The trailing slash on empty elements can't be automatically separated out
  410.         # in the RE, so we must do it here.
  411.         regexp (.*)(/)[cl $Wsp]*$ $param discard param empty
  412.         }
  413.     }
  414.  
  415.     # default: normal mode
  416.  
  417.     # Bug: if the attribute list has a right angle bracket then the empty
  418.     # element marker will not be seen
  419.  
  420.     set empty [uplevel #0 $options(-emptyelement) [list $tag $param $empty]]
  421.  
  422.     switch -glob -- [string length $tag],[regexp {^\?|!.*} $tag],$close,$empty {
  423.  
  424.         0,0,, {
  425.         # Ignore empty tag - dealt with non-normal mode above
  426.         }
  427.         *,0,, {
  428.  
  429.         # Start tag for an element.
  430.  
  431.         # Check if the internal DTD entity is in an attribute value
  432.         regsub -all &xml:intdtd\; $param \[$options(-internaldtd)\] param
  433.  
  434.         set code [catch {ParseEvent:ElementOpen $tag $param [array get options]} msg]
  435.         set state(haveDocElement) 1
  436.         switch $code {
  437.             0 {# OK}
  438.             3 {
  439.             # break
  440.             return {}
  441.             }
  442.             4 {
  443.             # continue
  444.             # Remember this tag and look for its close
  445.             set state(continue:tag) $tag
  446.             set state(continue:level) 1
  447.             set state(mode) continue
  448.             continue
  449.             }
  450.             default {
  451.             return -code $code -errorinfo $::errorInfo $msg
  452.             }
  453.         }
  454.  
  455.         }
  456.  
  457.         *,0,/, {
  458.  
  459.         # End tag for an element.
  460.  
  461.         set code [catch {ParseEvent:ElementClose $tag [array get options]} msg]
  462.         switch $code {
  463.             0 {# OK}
  464.             3 {
  465.             # break
  466.             return {}
  467.             }
  468.             4 {
  469.             # continue
  470.             # skip sibling nodes
  471.             set state(continue:tag) [lindex $state(stack) end]
  472.             set state(continue:level) 1
  473.             set state(mode) continue
  474.             continue
  475.             }
  476.             default {
  477.             return -code $code -errorinfo $::errorInfo $msg
  478.             }
  479.         }
  480.  
  481.         }
  482.  
  483.         *,0,,/ {
  484.  
  485.         # Empty element
  486.  
  487.         # The trailing slash sneaks through into the param variable
  488.         regsub -all /[cl $::sgml::Wsp]*\$ $param {} param
  489.  
  490.         set code [catch {ParseEvent:ElementOpen $tag $param [array get options] -empty 1} msg]
  491.         set state(haveDocElement) 1
  492.         switch $code {
  493.             0 {# OK}
  494.             3 {
  495.             # break
  496.             return {}
  497.             }
  498.             4 {
  499.             # continue
  500.             # Pretty useless since it closes straightaway
  501.             }
  502.             default {
  503.             return -code $code -errorinfo $::errorInfo $msg
  504.             }
  505.         }
  506.         set code [catch {ParseEvent:ElementClose $tag [array get options] -empty 1} msg]
  507.         switch $code {
  508.             0 {# OK}
  509.             3 {
  510.             # break
  511.             return {}
  512.             }
  513.             4 {
  514.             # continue
  515.             # skip sibling nodes
  516.             set state(continue:tag) [lindex $state(stack) end]
  517.             set state(continue:level) 1
  518.             set state(mode) continue
  519.             continue
  520.             }
  521.             default {
  522.             return -code $code -errorinfo $::errorInfo $msg
  523.             }
  524.         }
  525.  
  526.         }
  527.  
  528.         *,1,* {
  529.         # Processing instructions or XML declaration
  530.         switch -glob -- $tag {
  531.  
  532.             {\?xml} {
  533.             # XML Declaration
  534.             if {$state(haveXMLDecl)} {
  535.                 uplevel #0 $options(-errorcommand) "unexpected characters \"<$tag\" around line $state(line)"
  536.             } elseif {![regexp {\?$} $param]} {
  537.                 uplevel #0 $options(-errorcommand) "XML Declaration missing characters \"?>\" around line $state(line)"
  538.             } else {
  539.  
  540.                 # We can do the parsing in one step with Tcl 8.1 RE's
  541.                 # This has the benefit of performing better WF checking
  542.  
  543.                 set adv_re [format {^[%s]*version[%s]*=[%s]*("|')(-+|[a-zA-Z0-9_.:]+)\1([%s]+encoding[%s]*=[%s]*("|')([A-Za-z][-A-Za-z0-9._]*)\4)?([%s]*standalone[%s]*=[%s]*("|')(yes|no)\7)?[%s]*\?$} $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp]
  544.  
  545.                 if {[catch {regexp $adv_re $param discard delimiter version discard delimiter encoding discard delimiter standalone} matches]} {
  546.                 # Otherwise we must fallback to 8.0.
  547.                 # This won't detect certain well-formedness errors
  548.  
  549.                 # Get the version number
  550.                 if {[regexp [format {[%s]*version[%s]*=[%s]*"(-+|[a-zA-Z0-9_.:]+)"[%s]*} $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp] $param discard version] || [regexp [format {[%s]*version[%s]*=[%s]*'(-+|[a-zA-Z0-9_.:]+)'[%s]*} $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp] $param discard version]} {
  551.                     if {[string compare $version "1.0"]} {
  552.                     # Should we support future versions?
  553.                     # At least 1.X?
  554.                     uplevel #0 $options(-errorcommand) "document XML version \"$version\" is incompatible with XML version 1.0"
  555.                     }
  556.                 } else {
  557.                     uplevel #0 $options(-errorcommand) "XML Declaration missing version information around line $state(line)"
  558.                 }
  559.  
  560.                 # Get the encoding declaration
  561.                 set encoding {}
  562.                 regexp [format {[%s]*encoding[%s]*=[%s]*"([A-Za-z]([A-Za-z0-9._]|-)*)"[%s]*} $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp] $param discard encoding
  563.                 regexp [format {[%s]*encoding[%s]*=[%s]*'([A-Za-z]([A-Za-z0-9._]|-)*)'[%s]*} $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp] $param discard encoding
  564.  
  565.                 # Get the standalone declaration
  566.                 set standalone {}
  567.                 regexp [format {[%s]*standalone[%s]*=[%s]*"(yes|no)"[%s]*} $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp] $param discard standalone
  568.                 regexp [format {[%s]*standalone[%s]*=[%s]*'(yes|no)'[%s]*} $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp $::sgml::Wsp] $param discard standalone
  569.  
  570.                 # Invoke the callback
  571.                 uplevel #0 $options(-xmldeclcommand) [list $version $encoding $standalone]
  572.  
  573.                 } elseif {$matches == 0} {
  574.                 uplevel #0 $options(-errorcommand) "XML Declaration not well-formed around line $state(line)"
  575.                 } else {
  576.  
  577.                 # Invoke the callback
  578.                 uplevel #0 $options(-xmldeclcommand) [list $version $encoding $standalone]
  579.  
  580.                 }
  581.  
  582.             }
  583.  
  584.             }
  585.  
  586.             {\?*} {
  587.             # Processing instruction
  588.             set tag [string range $tag 1 end]
  589.             if {[regsub {\?$} $tag {} tag]} {
  590.                 if {[string length [string trim $param]]} {
  591.                 uplevel #0 $options(-errorcommand) [list "unexpected text \"$param\" in processing instruction around line $state(line)"]
  592.                 }
  593.             } elseif {![regexp ^$Name\$ $tag]} {
  594.                 uplevel #0 $options(-errorcommand) [list "illegal character in processing instruction target \"$tag\""]
  595.             } elseif {[regexp {[xX][mM][lL]} $tag]} {
  596.                 uplevel #0 $options(-errorcommand) [list "characters \"xml\" not permitted in processing instruction target \"$tag\""]
  597.             } elseif {![regsub {\?$} $param {} param]} {
  598.                 uplevel #0 $options(-errorcommand) "PI: expected '?' character around line $state(line)"
  599.             }
  600.             set code [catch {uplevel #0 $options(-processinginstructioncommand) [list $tag [string trimleft $param]]} msg]
  601.             switch $code {
  602.                 0 {# OK}
  603.                 3 {
  604.                 # break
  605.                 return {}
  606.                 }
  607.                 4 {
  608.                 # continue
  609.                 # skip sibling nodes
  610.                 set state(continue:tag) [lindex $state(stack) end]
  611.                 set state(continue:level) 1
  612.                 set state(mode) continue
  613.                 continue
  614.                 }
  615.                 default {
  616.                 return -code $code -errorinfo $::errorInfo $msg
  617.                 }
  618.             }
  619.             }
  620.  
  621.             !DOCTYPE {
  622.             # External entity reference
  623.             # This should move into xml.tcl
  624.             # Parse the params supplied.  Looking for Name, ExternalID and MarkupDecl
  625.             set matched [regexp ^[cl $Wsp]*($Name)[cl $Wsp]*(.*) $param x state(doc_name) param]
  626.             set state(doc_name) [Normalize $state(doc_name) $options(-normalize)]
  627.             set externalID {}
  628.             set pubidlit {}
  629.             set systemlit {}
  630.             set externalID {}
  631.             if {[regexp -nocase ^[cl $Wsp]*(SYSTEM|PUBLIC)(.*) $param x id param]} {
  632.                 switch [string toupper $id] {
  633.                 SYSTEM {
  634.                     if {[regexp ^[cl $Wsp]+"([cl ^"]*)"(.*) $param x systemlit param] || [regexp ^[cl $Wsp]+'([cl ^']*)'(.*) $param x systemlit param]} {
  635.                     set externalID [list SYSTEM $systemlit] ;# "
  636.                     } else {
  637.                     uplevel #0 $options(-errorcommand) {{syntax error: SYSTEM identifier not followed by literal}}
  638.                     }
  639.                 }
  640.                 PUBLIC {
  641.                     if {[regexp ^[cl $Wsp]+"([cl ^"]*)"(.*) $param x pubidlit param] || [regexp ^[cl $Wsp]+'([cl ^']*)'(.*) $param x pubidlit param]} {
  642.                     if {[regexp ^[cl $Wsp]+"([cl ^"]*)"(.*) $param x systemlit param] || [regexp ^[cl $Wsp]+'([cl ^']*)'(.*) $param x systemlit param]} {
  643.                         set externalID [list PUBLIC $pubidlit $systemlit]
  644.                     } else {
  645.                         uplevel #0 $options(-errorcommand) "syntax error: PUBLIC identifier not followed by system literal around line $state(line)"
  646.                     }
  647.                     } else {
  648.                     uplevel #0 $options(-errorcommand) "syntax error: PUBLIC identifier not followed by literal around line $state(line)"
  649.                     }
  650.                 }
  651.                 }
  652.                 if {[regexp -nocase ^[cl $Wsp]+NDATA[cl $Wsp]+($Name)(.*) $param x notation param]} {
  653.                 lappend externalID $notation
  654.                 }
  655.             }
  656.  
  657.             set state(inDTD) 1
  658.  
  659.             ParseEvent:DocTypeDecl [array get options] $state(doc_name) $pubidlit $systemlit $options(-internaldtd)
  660.  
  661.             set state(inDTD) 0
  662.  
  663.             }
  664.  
  665.             !--* {
  666.  
  667.             # Start of a comment
  668.             # See if it ends in the same tag, otherwise change the
  669.             # parsing mode
  670.  
  671.             regexp {!--(.*)} $tag discard comm1
  672.             if {[regexp ([cl ^-]*)--[cl $Wsp]*\$ $comm1 discard comm1_1]} {
  673.                 # processed comment (end in tag)
  674.                 uplevel #0 $options(-commentcommand) [list $comm1_1]
  675.             } elseif {[regexp ([cl ^-]*)--[cl $Wsp]*\$ $param discard comm2]} {
  676.                 # processed comment (end in attributes)
  677.                 uplevel #0 $options(-commentcommand) [list $comm1$comm2]
  678.             } elseif {[regexp ([cl ^-]*)-->(.*) $text discard comm2 text]} {
  679.                 # processed comment (end in text)
  680.                 uplevel #0 $options(-commentcommand) [list $comm1$param$empty>$comm2]
  681.             } else {
  682.                 # start of comment
  683.                 set state(mode) comment
  684.                 set state(commentdata) "$comm1$param$empty>$text"
  685.                 continue
  686.             }
  687.             }
  688.  
  689.             {!\[CDATA\[*} {
  690.  
  691.             regexp {!\[CDATA\[(.*)} $tag discard cdata1
  692.             if {[regexp {(.*)]]$} $cdata1 discard cdata2]} {
  693.                 # processed CDATA (end in tag)
  694.                 uplevel #0 $options(-characterdatacommand) [list [subst -novariable -nocommand $cdata2]]
  695.                 set text [subst -novariable -nocommand $text]
  696.             } elseif {[regexp {(.*)]]$} $param discard cdata2]} {
  697.                 # processed CDATA (end in attribute)
  698.                 # Backslashes in param are quoted at this stage
  699.                 uplevel #0 $options(-characterdatacommand) [list $cdata1[subst -novariable -nocommand $cdata2]]
  700.                 set text [subst -novariable -nocommand $text]
  701.             } elseif {[regexp {(.*)]]>(.*)} $text discard cdata2 text]} {
  702.                 # processed CDATA (end in text)
  703.                 # Backslashes in param and text are quoted at this stage
  704.                 uplevel #0 $options(-characterdatacommand) [list $cdata1[subst -novariable -nocommand $param]$empty>[subst -novariable -nocommand $cdata2]]
  705.                 set text [subst -novariable -nocommand $text]
  706.             } else {
  707.                 # start CDATA
  708.                 set state(cdata) "$cdata1$param>$text"
  709.                 set state(mode) cdata
  710.                 continue
  711.             }
  712.  
  713.             }
  714.  
  715.             !ELEMENT -
  716.             !ATTLIST -
  717.             !ENTITY -
  718.             !NOTATION {
  719.             uplevel #0 $options(-errorcommand) "[string range $tag 1 end] declaration not expected in document instance around line $state(line)"
  720.             }
  721.  
  722.             default {
  723.             uplevel #0 $options(-errorcommand) [list "unknown processing instruction \"<$tag>\" around line $state(line)"]
  724.             }
  725.         }
  726.         }
  727.         *,1,* -
  728.         *,0,/,/ {
  729.         # Syntax error
  730.             uplevel #0 $options(-errorcommand) [list [list syntax error: closed/empty tag: tag $tag param $param empty $empty close $close around line $state(line)]]
  731.         }
  732.     }
  733.  
  734.     # Process character data
  735.  
  736.     if {$state(haveDocElement) && [llength $state(stack)]} {
  737.  
  738.         # Check if the internal DTD entity is in the text
  739.         regsub -all &xml:intdtd\; $text \[$options(-internaldtd)\] text
  740.  
  741.         # Look for entity references
  742.         if {([array size entities] || \
  743.             [string length $options(-entityreferencecommand)]) && \
  744.             $options(-defaultexpandinternalentities) && \
  745.             [regexp {&[^;]+;} $text]} {
  746.  
  747.         # protect Tcl specials
  748.         # NB. braces and backslashes may already be protected
  749.         regsub -all {\\({|}|\\)} $text {\1} text
  750.         regsub -all {([][$\\{}])} $text {\\\1} text
  751.  
  752.         # Mark entity references
  753.         regsub -all {&([^;]+);} $text [format {%s; %s {\1} ; %s %s} \}\} [namespace code [list Entity [array get options] $options(-entityreferencecommand) $options(-characterdatacommand) $options(entities)]] [namespace code [list DeProtect $options(-characterdatacommand)]] \{\{] text
  754.         set text "uplevel #0 [namespace code [list DeProtect1 $options(-characterdatacommand)]] {{$text}}"
  755.         eval $text
  756.         } else {
  757.         # Restore protected special characters
  758.         regsub -all {\\([][{}\\])} $text {\1} text
  759.         uplevel #0 $options(-characterdatacommand) [list $text]
  760.         }
  761.     } elseif {[string length [string trim $text]]} {
  762.         uplevel #0 $options(-errorcommand) "unexpected text \"$text\" in document prolog around line $state(line)"
  763.     }
  764.  
  765.     }
  766.  
  767.     # If this is the end of the document, close all open containers
  768.     if {$options(-final) && [llength $state(stack)]} {
  769.     eval $options(-errorcommand) [list [list element [lindex $state(stack) end] remains unclosed around line $state(line)]]
  770.     }
  771.  
  772.     return {}
  773. }
  774.  
  775. # sgml::DeProtect --
  776. #
  777. #    Invoke given command after removing protecting backslashes
  778. #    from given text.
  779. #
  780. # Arguments:
  781. #    cmd    Command to invoke
  782. #    text    Text to deprotect
  783. #
  784. # Results:
  785. #    Depends on command
  786.  
  787. proc sgml::DeProtect1 {cmd text} {
  788.     if {[string compare {} $text]} {
  789.     regsub -all {\\([][{}\\])} $text {\1} text
  790.     uplevel #0 $cmd [list $text]
  791.     }
  792. }
  793. proc sgml::DeProtect {cmd text} {
  794.     set text [lindex $text 0]
  795.     if {[string compare {} $text]} {
  796.     regsub -all {\\([][{}\\])} $text {\1} text
  797.     uplevel #0 $cmd [list $text]
  798.     }
  799. }
  800.  
  801. # sgml::ParserDelete --
  802. #
  803. #    Free all memory associated with parser
  804. #
  805. # Arguments:
  806. #    var    global state array
  807. #
  808. # Results:
  809. #    Variables unset
  810.  
  811. proc sgml::ParserDelete var {
  812.     upvar #0 $var state
  813.  
  814.     if {![info exists state]} {
  815.     return -code error "unknown parser"
  816.     }
  817.  
  818.     catch {unset $state(entities)}
  819.     catch {unset $state(parameterentities)}
  820.     catch {unset $state(elementdecls)}
  821.     catch {unset $state(attlistdecls)}
  822.     catch {unset $state(notationdecls)}
  823.     catch {unset $state(namespaces)}
  824.  
  825.     unset state
  826.  
  827.     return {}
  828. }
  829.  
  830. # sgml::ParseEvent:ElementOpen --
  831. #
  832. #    Start of an element.
  833. #
  834. # Arguments:
  835. #    tag    Element name
  836. #    attr    Attribute list
  837. #    opts    Options
  838. #    args    further configuration options
  839. #
  840. # Options:
  841. #    -empty boolean
  842. #        indicates whether the element was an empty element
  843. #
  844. # Results:
  845. #    Modify state and invoke callback
  846.  
  847. proc sgml::ParseEvent:ElementOpen {tag attr opts args} {
  848.     variable Name
  849.     variable Wsp
  850.  
  851.     array set options $opts
  852.     upvar #0 $options(-statevariable) state
  853.     array set cfg {-empty 0}
  854.     array set cfg $args
  855.  
  856.     if {$options(-normalize)} {
  857.     set tag [string toupper $tag]
  858.     }
  859.  
  860.     # Update state
  861.     lappend state(stack) $tag
  862.  
  863.     # Parse attribute list into a key-value representation
  864.     if {[string compare $options(-parseattributelistcommand) {}]} {
  865.     if {[catch {uplevel #0 $options(-parseattributelistcommand) [list $attr]} attr]} {
  866.         if {[string compare [lindex $attr 0] "unterminated attribute value"]} {
  867.         uplevel #0 $options(-errorcommand) "$attr around line $state(line)"
  868.         set attr {}
  869.         } else {
  870.  
  871.         # It is most likely that a ">" character was in an attribute value.
  872.         # This manifests itself by ">" appearing in the element's text.
  873.         # In this case the callback should return a three element list;
  874.         # the message "unterminated attribute value", the attribute list it
  875.         # did manage to parse and the remainder of the attribute list.
  876.  
  877.         foreach {msg attlist brokenattr} $attr break
  878.  
  879.         upvar text elemText
  880.         if {[string first > $elemText] >= 0} {
  881.  
  882.             # Now piece the attribute list back together
  883.             regexp ($Name)[cl $Wsp]*=[cl $Wsp]*("|')(.*) $brokenattr discard attname delimiter attvalue
  884.             regexp (.*)>([cl ^>]*)\$ $elemText discard remattlist elemText
  885.             regexp ([cl ^$delimiter]*)${delimiter}(.*) $remattlist discard remattvalue remattlist
  886.  
  887.             append attvalue >$remattvalue
  888.             lappend attlist $attname $attvalue
  889.  
  890.             # Complete parsing the attribute list
  891.             if {[catch {uplevel #0 $options(-parseattributelistcommand) [list $remattlist]} attr]} {
  892.             uplevel #0 $options(-errorcommand) [list $attr around line $state(line)]
  893.             set attr {}
  894.             set attlist {}
  895.             } else {
  896.             eval lappend attlist $attr
  897.             }
  898.  
  899.             set attr $attlist
  900.  
  901.         } else {
  902.             uplevel #0 $options(-errorcommand) [list $attr around line $state(line)]
  903.             set attr {}
  904.         }
  905.         }
  906.     }
  907.     }
  908.  
  909.     set empty {}
  910.     if {$cfg(-empty) && $options(-reportempty)} {
  911.     set empty {-empty 1}
  912.     }
  913.  
  914.     # Check for namespace declarations
  915.     upvar #0 $options(namespaces) namespaces
  916.     set nsdecls {}
  917.     if {[llength $attr]} {
  918.     array set attrlist $attr
  919.     foreach {attrName attrValue} [array get attrlist xmlns*] {
  920.         unset attrlist($attrName)
  921.         set colon [set prefix {}]
  922.         if {[regexp {^xmlns(:(.+))?$} $attrName discard colon prefix]} {
  923.         switch -glob [string length $colon],[string length $prefix] {
  924.             0,0 {
  925.             # default NS declaration
  926.             lappend state(defaultNSURI) $attrValue
  927.             lappend state(defaultNS) [llength $state(stack)]
  928.             lappend nsdecls $attrValue {}
  929.             }
  930.             0,* {
  931.             # Huh?
  932.             }
  933.             *,0 {
  934.             # Error
  935.             uplevel #0 $state(-warningcommand) "no prefix specified for namespace URI \"$attrValue\" in element \"$tag\""
  936.             }
  937.             default {
  938.             set namespaces($prefix,[llength $state(stack)]) $attrValue
  939.             lappend nsdecls $attrValue $prefix
  940.             }
  941.         }
  942.         }
  943.     }
  944.     if {[llength $nsdecls]} {
  945.         set nsdecls [list -namespacedecls $nsdecls]
  946.     }
  947.     set attr [array get attrlist]
  948.     }
  949.  
  950.     # Check whether this element has an expanded name
  951.     set ns {}
  952.     if {[regexp {([^:]+):(.*)$} $tag discard prefix tag]} {
  953.     set nsspec [lsort -dictionary -decreasing [array names namespaces $prefix,*]]
  954.     if {[llength $nsspec]} {
  955.         set nsuri $namespaces([lindex $nsspec 0])
  956.         set ns [list -namespace $nsuri]
  957.     } else {
  958.         uplevel #0 $options(-errorcommand) "no namespace declared for prefix \"$prefix\" in element $tag"
  959.     }
  960.     } elseif {[llength $state(defaultNSURI)]} {
  961.     set ns [list -namespace [lindex $state(defaultNSURI) end]]
  962.     }
  963.  
  964.     # Invoke callback
  965.     set code [catch {uplevel #0 $options(-elementstartcommand) [list $tag $attr] $empty $ns $nsdecls} msg]
  966.     return -code $code -errorinfo $::errorInfo $msg
  967. }
  968.  
  969. # sgml::ParseEvent:ElementClose --
  970. #
  971. #    End of an element.
  972. #
  973. # Arguments:
  974. #    tag    Element name
  975. #    opts    Options
  976. #    args    further configuration options
  977. #
  978. # Options:
  979. #    -empty boolean
  980. #        indicates whether the element as an empty element
  981. #
  982. # Results:
  983. #    Modify state and invoke callback
  984.  
  985. proc sgml::ParseEvent:ElementClose {tag opts args} {
  986.     array set options $opts
  987.     upvar #0 $options(-statevariable) state
  988.     array set cfg {-empty 0}
  989.     array set cfg $args
  990.  
  991.     # WF check
  992.     if {[string compare $tag [lindex $state(stack) end]]} {
  993.     uplevel #0 $options(-errorcommand) [list "end tag \"$tag\" does not match open element \"[lindex $state(stack) end]\" around line $state(line)"]
  994.     return
  995.     }
  996.  
  997.     # Check whether this element has an expanded name
  998.     upvar #0 $options(namespaces) namespaces
  999.     set ns {}
  1000.     if {[regexp {([^:]+):(.*)$} $tag discard prefix tag]} {
  1001.     set nsuri $namespaces([lindex [lsort -dictionary -decreasing [array names namespaces $prefix,*]] 0])
  1002.     set ns [list -namespace $nsuri]
  1003.     } elseif {[llength $state(defaultNSURI)]} {
  1004.     set ns [list -namespace [lindex $state(defaultNSURI) end]]
  1005.     }
  1006.  
  1007.     # Pop namespace stacks, if any
  1008.     if {[llength $state(defaultNS)]} {
  1009.     if {[llength $state(stack)] == [lindex $state(defaultNS) end]} {
  1010.         set state(defaultNS) [lreplace $state(defaultNS) end end]
  1011.     }
  1012.     }
  1013.     foreach nsspec [array names namespaces *,[llength $state(stack)]] {
  1014.     unset namespaces($nsspec)
  1015.     }
  1016.  
  1017.     # Update state
  1018.     set state(stack) [lreplace $state(stack) end end]
  1019.  
  1020.     set empty {}
  1021.     if {$cfg(-empty) && $options(-reportempty)} {
  1022.     set empty {-empty 1}
  1023.     }
  1024.  
  1025.     # Invoke callback
  1026.     uplevel #0 $options(-elementendcommand) [list $tag] $empty $ns
  1027.  
  1028.     return {}
  1029. }
  1030.  
  1031. # sgml::Normalize --
  1032. #
  1033. #    Perform name normalization if required
  1034. #
  1035. # Arguments:
  1036. #    name    name to normalize
  1037. #    req    normalization required
  1038. #
  1039. # Results:
  1040. #    Name returned as upper-case if normalization required
  1041.  
  1042. proc sgml::Normalize {name req} {
  1043.     if {$req} {
  1044.     return [string toupper $name]
  1045.     } else {
  1046.     return $name
  1047.     }
  1048. }
  1049.  
  1050. # sgml::Entity --
  1051. #
  1052. #    Resolve XML entity references (syntax: &xxx;).
  1053. #
  1054. # Arguments:
  1055. #    opts        options
  1056. #    entityrefcmd    application callback for entity references
  1057. #    pcdatacmd    application callback for character data
  1058. #    entities    name of array containing entity definitions.
  1059. #    ref        entity reference (the "xxx" bit)
  1060. #
  1061. # Results:
  1062. #    Returns substitution text for given entity.
  1063.  
  1064. proc sgml::Entity {opts entityrefcmd pcdatacmd entities ref} {
  1065.     array set options $opts
  1066.     upvar #0 $options(-statevariable) state
  1067.  
  1068.     if {![string length $entities]} {
  1069.     set entities [namespace current EntityPredef]
  1070.     }
  1071.  
  1072.     switch -glob -- $ref {
  1073.     %* {
  1074.         # Parameter entity - not recognised outside of a DTD
  1075.     }
  1076.     #x* {
  1077.         # Character entity - hex
  1078.         if {[catch {format %c [scan [string range $ref 2 end] %x tmp; set tmp]} char]} {
  1079.         return -code error "malformed character entity \"$ref\""
  1080.         }
  1081.         uplevel #0 $pcdatacmd [list $char]
  1082.  
  1083.         return {}
  1084.  
  1085.     }
  1086.     #* {
  1087.         # Character entity - decimal
  1088.         if {[catch {format %c [scan [string range $ref 1 end] %d tmp; set tmp]} char]} {
  1089.         return -code error "malformed character entity \"$ref\""
  1090.         }
  1091.         uplevel #0 $pcdatacmd [list $char]
  1092.  
  1093.         return {}
  1094.  
  1095.     }
  1096.     default {
  1097.         # General entity
  1098.         upvar #0 $entities map
  1099.         if {[info exists map($ref)]} {
  1100.  
  1101.         if {![regexp {<|&} $map($ref)]} {
  1102.  
  1103.             # Simple text replacement - optimise
  1104.             uplevel #0 $pcdatacmd [list $map($ref)]
  1105.  
  1106.             return {}
  1107.  
  1108.         }
  1109.  
  1110.         # Otherwise an additional round of parsing is required.
  1111.         # This only applies to XML, since HTML doesn't have general entities
  1112.  
  1113.         # Must parse the replacement text for start & end tags, etc
  1114.         # This text must be self-contained: balanced closing tags, and so on
  1115.  
  1116.         set tokenised [tokenise $map($ref) $::xml::tokExpr $::xml::substExpr]
  1117.         set options(-final) 0
  1118.         eval parseEvent [list $tokenised] [array get options]
  1119.  
  1120.         return {}
  1121.  
  1122.         } elseif {[string compare $entityrefcmd "::sgml::noop"]} {
  1123.  
  1124.         set result [uplevel #0 $entityrefcmd [list $ref]]
  1125.  
  1126.         if {[string length $result]} {
  1127.             uplevel #0 $pcdatacmd [list $result]
  1128.         }
  1129.  
  1130.         return {}
  1131.  
  1132.         } else {
  1133.  
  1134.         # Reconstitute entity reference
  1135.  
  1136.         uplevel #0 $options(-errorcommand) [list "undefined entity reference \"$ref\""]
  1137.  
  1138.         return {}
  1139.  
  1140.         }
  1141.     }
  1142.     }
  1143.  
  1144.     # If all else fails leave the entity reference untouched
  1145.     uplevel #0 $pcdatacmd [list &$ref\;]
  1146.  
  1147.     return {}
  1148. }
  1149.  
  1150. ####################################
  1151. #
  1152. # DTD parser for SGML (XML).
  1153. #
  1154. # This DTD actually only handles XML DTDs.  Other language's
  1155. # DTD's, such as HTML, must be written in terms of a XML DTD.
  1156. #
  1157. ####################################
  1158.  
  1159. # sgml::ParseEvent:DocTypeDecl --
  1160. #
  1161. #    Entry point for DTD parsing
  1162. #
  1163. # Arguments:
  1164. #    opts    configuration options
  1165. #    docEl    document element name
  1166. #    pubId    public identifier
  1167. #    sysId    system identifier (a URI)
  1168. #    intSSet    internal DTD subset
  1169.  
  1170. proc sgml::ParseEvent:DocTypeDecl {opts docEl pubId sysId intSSet} {
  1171.     array set options {}
  1172.     array set options $opts
  1173.  
  1174.     set code [catch {uplevel #0 $options(-doctypecommand) [list $docEl $pubId $sysId $intSSet]} err]
  1175.     switch $code {
  1176.     3 {
  1177.         # break
  1178.         return {}
  1179.     }
  1180.     0 -
  1181.     4 {
  1182.         # continue
  1183.     }
  1184.     default {
  1185.         return -code $code $err
  1186.     }
  1187.     }
  1188.  
  1189.     # Otherwise we'll parse the DTD and report it piecemeal
  1190.  
  1191.     # The internal DTD subset is processed first (XML 2.8)
  1192.     # During this stage, parameter entities are only allowed
  1193.     # between markup declarations
  1194.  
  1195.     ParseDTD:Internal [array get options] $intSSet
  1196.  
  1197.     # The external DTD subset is processed last (XML 2.8)
  1198.     # During this stage, parameter entities may occur anywhere
  1199.  
  1200.     # We must resolve the external identifier to obtain the
  1201.     # DTD data.  The application may supply its own resolver.
  1202.  
  1203.     if {[string length $pubId] || [string length $sysId]} {
  1204.     uplevel #0 $options(-externalentitycommand) [list $options(-name) $options(-baseurl) $sysId $pubId]
  1205.     }
  1206.  
  1207.     return {}
  1208. }
  1209.  
  1210. # sgml::ParseDTD:Internal --
  1211. #
  1212. #    Parse the internal DTD subset.
  1213. #
  1214. #    Parameter entities are only allowed between markup declarations.
  1215. #
  1216. # Arguments:
  1217. #    opts    configuration options
  1218. #    dtd    DTD data
  1219. #
  1220. # Results:
  1221. #    Markup declarations parsed may cause callback invocation
  1222.  
  1223. proc sgml::ParseDTD:Internal {opts dtd} {
  1224.     variable MarkupDeclExpr
  1225.     variable MarkupDeclSub
  1226.  
  1227.     array set options {}
  1228.     array set options $opts
  1229.  
  1230.     upvar #0 $options(-statevariable) state
  1231.     upvar #0 $options(parameterentities) PEnts
  1232.     upvar #0 $options(externalparameterentities) ExtPEnts
  1233.  
  1234.     # Tokenize the DTD
  1235.  
  1236.     # Protect Tcl special characters
  1237.     regsub -all {([{}\\])} $dtd {\\\1} dtd
  1238.  
  1239.     regsub -all $MarkupDeclExpr $dtd $MarkupDeclSub dtd
  1240.  
  1241.     # Entities may have angle brackets in their replacement
  1242.     # text, which breaks the RE processing.  So, we must
  1243.     # use a similar technique to processing doc instances
  1244.     # to rebuild the declarations from the pieces
  1245.  
  1246.     set mode {} ;# normal
  1247.     set delimiter {}
  1248.     set name {}
  1249.     set param {}
  1250.  
  1251.     set state(inInternalDTD) 1
  1252.  
  1253.     # Process the tokens
  1254.     foreach {decl value text} [lrange "{} {} \{$dtd\}" 3 end] {
  1255.  
  1256.     # Keep track of line numbers
  1257.     incr state(line) [regsub -all \n $text {} discard]
  1258.  
  1259.     ParseDTD:EntityMode [array get options] mode replText decl value text $delimiter $name $param
  1260.  
  1261.     ParseDTD:ProcessMarkupDecl [array get options] decl value delimiter name mode replText text param
  1262.  
  1263.     # There may be parameter entity references between markup decls
  1264.  
  1265.     if {[regexp {%.*;} $text]} {
  1266.  
  1267.         # Protect Tcl special characters
  1268.         regsub -all {([{}\\])} $text {\\\1} text
  1269.  
  1270.         regsub -all %($::sgml::Name)\; $text "\} {\\1} \{" text
  1271.  
  1272.         set PElist "\{$text\}"
  1273.         set PElist [lreplace $PElist end end]
  1274.         foreach {text entref} $PElist {
  1275.         if {[string length [string trim $text]]} {
  1276.             uplevel #0 $options(-errorcommand) "unexpected text in internal DTD subset around line $state(line)"
  1277.         }
  1278.  
  1279.         # Expand parameter entity and recursively parse
  1280.         # BUG: no checks yet for recursive entity references
  1281.  
  1282.         if {[info exists PEnts($entref)]} {
  1283.             set externalParser [$options(-name) entityparser]
  1284.             $externalParser parse $PEnts($entref) -dtdsubset internal
  1285.         } elseif {[info exists ExtPEnts($entref)]} {
  1286.             set externalParser [$options(-name) entityparser]
  1287.             $externalParser parse $ExtPEnts($entref) -dtdsubset external
  1288.             #$externalParser free
  1289.         } else {
  1290.             uplevel #0 $options(-errorcommand) "reference to undeclared parameter entity \"$entref\""
  1291.         }
  1292.         }
  1293.  
  1294.     }
  1295.  
  1296.     }
  1297.  
  1298.     return {}
  1299. }
  1300.  
  1301. # sgml::ParseDTD:EntityMode --
  1302. #
  1303. #    Perform special processing for various parser modes
  1304. #
  1305. # Arguments:
  1306. #    opts    configuration options
  1307. #    modeVar    pass-by-reference mode variable
  1308. #    replTextVar    pass-by-ref
  1309. #    declVar    pass-by-ref
  1310. #    valueVar    pass-by-ref
  1311. #    textVar    pass-by-ref
  1312. #    delimiter    delimiter currently in force
  1313. #    name
  1314. #    param
  1315. #
  1316. # Results:
  1317. #    Depends on current mode
  1318.  
  1319. proc sgml::ParseDTD:EntityMode {opts modeVar replTextVar declVar valueVar textVar delimiter name param} {
  1320.     upvar 1 $modeVar mode
  1321.     upvar 1 $replTextVar replText
  1322.     upvar 1 $declVar decl
  1323.     upvar 1 $valueVar value
  1324.     upvar 1 $textVar text
  1325.     array set options $opts
  1326.  
  1327.     switch $mode {
  1328.     {} {
  1329.         # Pass through to normal processing section
  1330.     }
  1331.     entity {
  1332.         # Look for closing delimiter
  1333.         if {[regexp ([cl ^$delimiter]*)${delimiter}(.*) $decl discard val1 remainder]} {
  1334.         append replText <$val1
  1335.         DTD:ENTITY [array get options] $name [string trim $param] $delimiter$replText$delimiter
  1336.         set decl /
  1337.         set text $remainder\ $value>$text
  1338.         set value {}
  1339.         set mode {}
  1340.         } elseif {[regexp ([cl ^$delimiter]*)${delimiter}(.*) $value discard val2 remainder]} {
  1341.         append replText <$decl\ $val2
  1342.         DTD:ENTITY [array get options] $name [string trim $param] $delimiter$replText$delimiter
  1343.         set decl /
  1344.         set text $remainder>$text
  1345.         set value {}
  1346.         set mode {}
  1347.         } elseif {[regexp ([cl ^$delimiter]*)${delimiter}(.*) $text discard val3 remainder]} {
  1348.         append replText <$decl\ $value>$val3
  1349.         DTD:ENTITY [array get options] $name [string trim $param] $delimiter$replText$delimiter
  1350.         set decl /
  1351.         set text $remainder
  1352.         set value {}
  1353.         set mode {}
  1354.         } else {
  1355.  
  1356.         # Remain in entity mode
  1357.         append replText <$decl\ $value>$text
  1358.         return -code continue
  1359.  
  1360.         }
  1361.     }
  1362.  
  1363.     ignore {
  1364.         upvar #0 $options(-statevariable) state
  1365.  
  1366.         if {[regexp {]](.*)$} $decl discard remainder]} {
  1367.         set state(condSections) [lreplace $state(condSections) end end]
  1368.         set decl $remainder
  1369.         set mode {}
  1370.         } elseif {[regexp {]](.*)$} $value discard remainder]} {
  1371.         set state(condSections) [lreplace $state(condSections) end end]
  1372.         regexp <[cl $::sgml::Wsp]*($::sgml::Name)(.*) $remainder discard decl value
  1373.         set mode {}
  1374.         } elseif {[regexp {]]>(.*)$} $text discard remainder]} {
  1375.         set state(condSections) [lreplace $state(condSections) end end]
  1376.         set decl /
  1377.         set value {}
  1378.         set text $remainder
  1379.         #regexp <[cl $::sgml::Wsp]*($::sgml::Name)([cl ^>]*)>(.*) $remainder discard decl value text
  1380.         set mode {}
  1381.         } else {
  1382.         set decl /
  1383.         }
  1384.  
  1385.     }
  1386.  
  1387.     comment {
  1388.         # Look for closing comment delimiter
  1389.  
  1390.         upvar #0 $options(-statevariable) state
  1391.  
  1392.         if {[regexp (.*?)--(.*)\$ $decl discard data1 remainder]} {
  1393.         } elseif {[regexp (.*?)--(.*)\$ $value discard data1 remainder]} {
  1394.         } elseif {[regexp (.*?)--(.*)\$ $text discard data1 remainder]} {
  1395.         } else {
  1396.         # comment continues
  1397.         append state(commentdata) <$decl\ $value>$text
  1398.         set decl /
  1399.         set value {}
  1400.         set text {}
  1401.         }
  1402.     }
  1403.  
  1404.     }
  1405.  
  1406.     return {}
  1407. }
  1408.  
  1409. # sgml::ParseDTD:ProcessMarkupDecl --
  1410. #
  1411. #    Process a single markup declaration
  1412. #
  1413. # Arguments:
  1414. #    opts    configuration options
  1415. #    declVar    pass-by-ref
  1416. #    valueVar    pass-by-ref
  1417. #    delimiterVar    pass-by-ref for current delimiter in force
  1418. #    nameVar    pass-by-ref
  1419. #    modeVar    pass-by-ref for current parser mode
  1420. #    replTextVar    pass-by-ref
  1421. #    textVar    pass-by-ref
  1422. #    paramVar    pass-by-ref
  1423. #
  1424. # Results:
  1425. #    Depends on markup declaration.  May change parser mode
  1426.  
  1427. proc sgml::ParseDTD:ProcessMarkupDecl {opts declVar valueVar delimiterVar nameVar modeVar replTextVar textVar paramVar} {
  1428.     upvar 1 $modeVar mode
  1429.     upvar 1 $replTextVar replText
  1430.     upvar 1 $textVar text
  1431.     upvar 1 $declVar decl
  1432.     upvar 1 $valueVar value
  1433.     upvar 1 $nameVar name
  1434.     upvar 1 $delimiterVar delimiter
  1435.     upvar 1 $paramVar param
  1436.  
  1437.     variable declExpr
  1438.     variable ExternalEntityExpr
  1439.  
  1440.     array set options $opts
  1441.     upvar #0 $options(-statevariable) state
  1442.  
  1443.     switch -glob -- $decl {
  1444.  
  1445.     / {
  1446.         # continuation from entity processing
  1447.     }
  1448.  
  1449.     !ELEMENT {
  1450.         # Element declaration
  1451.         if {[regexp $declExpr $value discard tag cmodel]} {
  1452.         DTD:ELEMENT [array get options] $tag $cmodel
  1453.         } else {
  1454.         uplevel #0 $options(-errorcommand) "malformed element declaration around line $state(line)"
  1455.         }
  1456.     }
  1457.  
  1458.     !ATTLIST {
  1459.         # Attribute list declaration
  1460.         variable declExpr
  1461.         if {[regexp $declExpr $value discard tag attdefns]} {
  1462.         if {[catch {DTD:ATTLIST [array get options] $tag $attdefns} err]} {
  1463.             #puts stderr "Stack trace: $::errorInfo\n***\n"
  1464.             # Atttribute parsing has bugs at the moment
  1465.             #return -code error "$err around line $state(line)"
  1466.             return {}
  1467.         }
  1468.         } else {
  1469.         uplevel #0 $options(-errorcommand) "malformed attribute list declaration around line $state(line)"
  1470.         }
  1471.     }
  1472.  
  1473.     !ENTITY {
  1474.         # Entity declaration
  1475.         variable EntityExpr
  1476.  
  1477.         if {[regexp $EntityExpr $value discard param name value]} {
  1478.  
  1479.         # Entity replacement text may have a '>' character.
  1480.         # In this case, the real delimiter will be in the following
  1481.         # text.  This is complicated by the possibility of there
  1482.         # being several '<','>' pairs in the replacement text.
  1483.         # At this point, we are searching for the matching quote delimiter.
  1484.  
  1485.         if {[regexp $ExternalEntityExpr $value]} {
  1486.             DTD:ENTITY [array get options] $name [string trim $param] $value
  1487.         } elseif {[regexp ("|')(.*?)\\1(.*) $value discard delimiter replText value]} {
  1488.  
  1489.             if {[string length [string trim $value]]} {
  1490.             uplevel #0 $options(-errorcommand) "malformed entity declaration around line $state(line)"
  1491.             } else {
  1492.             DTD:ENTITY [array get options] $name [string trim $param] $delimiter$replText$delimiter
  1493.             }
  1494.         } elseif {[regexp ("|')(.*) $value discard delimiter replText]} {
  1495.             append replText >$text
  1496.             set text {}
  1497.             set mode entity
  1498.         } else {
  1499.             uplevel #0 $options(-errorcommand) "no delimiter for entity declaration around line $state(line)"
  1500.         }
  1501.  
  1502.         } else {
  1503.         uplevel #0 $options(-errorcommand) "malformed entity declaration around line $state(line)"
  1504.         }
  1505.     }
  1506.  
  1507.     !NOTATION {
  1508.         # Notation declaration
  1509.         if {[regexp $declExpr param discard tag notation]} {
  1510.         DTD:ENTITY [array get options] $tag $notation
  1511.         } else {
  1512.         uplevel #0 $options(-errorcommand) "malformed entity declaration around line $state(line)"
  1513.         }
  1514.     }
  1515.  
  1516.     !--* {
  1517.         # Start of a comment
  1518.  
  1519.         if {[regexp !--(.*?)--\$ $decl discard data]} {
  1520.         if {[string length [string trim $value]]} {
  1521.             uplevel #0 $options(-errorcommand) [list "unexpected text \"$value\""]
  1522.         }
  1523.         uplevel #0 $options(-commentcommand) [list $data]
  1524.         set decl /
  1525.         set value {}
  1526.         } elseif {[regexp -- ^(.*?)--\$ $value discard data2]} {
  1527.         regexp !--(.*)\$ $decl discard data1
  1528.         uplevel #0 $options(-commentcommand) [list $data1\ $data2]
  1529.         set decl /
  1530.         set value {}
  1531.         } elseif {[regexp (.*?)-->(.*)\$ $text discard data3 remainder]} {
  1532.         regexp !--(.*)\$ $decl discard data1
  1533.         uplevel #0 $options(-commentcommand) [list $data1\ $value>$data3]
  1534.         set decl /
  1535.         set value {}
  1536.         set text $remainder
  1537.         } else {
  1538.         regexp !--(.*)\$ $decl discard data1
  1539.         set state(commentdata) $data1\ $value>$text
  1540.         set decl /
  1541.         set value {}
  1542.         set text {}
  1543.         set mode comment
  1544.         }
  1545.     }
  1546.  
  1547.     !*INCLUDE* -
  1548.     !*IGNORE* {
  1549.         if {$state(inInternalDTD)} {
  1550.         uplevel #0 $options(-errorcommand) "conditional section not permitted in internal DTD subset around line $state(line)"
  1551.         }
  1552.  
  1553.         if {[regexp {^!\[INCLUDE\[(.*)} $decl discard remainder]} {
  1554.         # Push conditional section stack, popped by ]]> sequence
  1555.  
  1556.         if {[regexp {(.*?)]]$} $remainder discard r2]} {
  1557.             # section closed immediately
  1558.             if {[string length [string trim $r2]]} {
  1559.             uplevel #0 $options(-errorcommand) [list "unexpected text \"$r2\" in conditional section"]
  1560.             }
  1561.         } elseif {[regexp {(.*?)]](.*)} $value discard r2 r3]} {
  1562.             # section closed immediately
  1563.             if {[string length [string trim $r2]]} {
  1564.             uplevel #0 $options(-errorcommand) [list "unexpected text \"$r2\" in conditional section"]
  1565.             }
  1566.             if {[string length [string trim $r3]]} {
  1567.             uplevel #0 $options(-errorcommand) [list "unexpected text \"$r3\" in conditional section"]
  1568.             }
  1569.         } else {
  1570.  
  1571.             lappend state(condSections) INCLUDE
  1572.  
  1573.             set parser [$options(-name) entityparser]
  1574.             $parser parse $remainder\ $value> -dtdsubset external
  1575.             #$parser free
  1576.  
  1577.             if {[regexp {(.*?)]]>(.*)} $text discard t1 t2]} {
  1578.             if {[string length [string trim $t1]]} {
  1579.                 uplevel #0 $options(-errorcommand) [list "unexpected text \"$t1\""]
  1580.             }
  1581.             if {![llength $state(condSections)]} {
  1582.                 uplevel #0 $options(-errorcommand) [list "extraneous conditional section close"]
  1583.             }
  1584.             set state(condSections) [lreplace $state(condSections) end end]
  1585.             set text $t2
  1586.             }
  1587.  
  1588.         }
  1589.         } elseif {[regexp {^!\[IGNORE\[(.*)} $decl discard remainder]} {
  1590.         # Set ignore mode.  Still need a stack
  1591.         set mode ignore
  1592.  
  1593.         if {[regexp {(.*?)]]$} $remainder discard r2]} {
  1594.             # section closed immediately
  1595.             if {[string length [string trim $r2]]} {
  1596.             uplevel #0 $options(-errorcommand) [list "unexpected text \"$r2\" in conditional section"]
  1597.             }
  1598.         } elseif {[regexp {(.*?)]](.*)} $value discard r2 r3]} {
  1599.             # section closed immediately
  1600.             if {[string length [string trim $r2]]} {
  1601.             uplevel #0 $options(-errorcommand) [list "unexpected text \"$r2\" in conditional section"]
  1602.             }
  1603.             if {[string length [string trim $r3]]} {
  1604.             uplevel #0 $options(-errorcommand) [list "unexpected text \"$r3\" in conditional section"]
  1605.             }
  1606.         } else {
  1607.             
  1608.             lappend state(condSections) IGNORE
  1609.  
  1610.             if {[regexp {(.*?)]]>(.*)} $text discard t1 t2]} {
  1611.             if {[string length [string trim $t1]]} {
  1612.                 uplevel #0 $options(-errorcommand) [list "unexpected text \"$t1\""]
  1613.             }
  1614.             if {![llength $state(condSections)]} {
  1615.                 uplevel #0 $options(-errorcommand) [list "extraneous conditional section close"]
  1616.             }
  1617.             set state(condSections) [lreplace $state(condSections) end end]
  1618.             set text $t2
  1619.             }
  1620.  
  1621.         }
  1622.         } else {
  1623.         uplevel #0 $options(-errorcommand) [list "illegal markup declaration \"$decl\" around line $state(line)"]
  1624.         }
  1625.  
  1626.     }
  1627.  
  1628.     default {
  1629.         if {[regexp {^\?(.*)} $decl discard target]} {
  1630.         # Processing instruction
  1631.         } else {
  1632.         uplevel #0 $options(-errorcommand) [list "illegal markup declaration \"$decl\""]
  1633.         }
  1634.     }
  1635.     }
  1636.  
  1637.     return {}
  1638. }
  1639.  
  1640. # sgml::ParseDTD:External --
  1641. #
  1642. #    Parse the external DTD subset.
  1643. #
  1644. #    Parameter entities are allowed anywhere.
  1645. #
  1646. # Arguments:
  1647. #    opts    configuration options
  1648. #    dtd    DTD data
  1649. #
  1650. # Results:
  1651. #    Markup declarations parsed may cause callback invocation
  1652.  
  1653. proc sgml::ParseDTD:External {opts dtd} {
  1654.     variable MarkupDeclExpr
  1655.     variable MarkupDeclSub
  1656.     variable declExpr
  1657.  
  1658.     array set options $opts
  1659.     upvar #0 $options(parameterentities) PEnts
  1660.     upvar #0 $options(externalparameterentities) ExtPEnts
  1661.     upvar #0 $options(-statevariable) state
  1662.  
  1663.     # As with the internal DTD subset, watch out for
  1664.     # entities with angle brackets
  1665.     set mode {} ;# normal
  1666.     set delimiter {}
  1667.     set name {}
  1668.     set param {}
  1669.  
  1670.     set oldState 0
  1671.     catch {set oldState $state(inInternalDTD)}
  1672.     set state(inInternalDTD) 0
  1673.  
  1674.     # Initialise conditional section stack
  1675.     if {![info exists state(condSections)]} {
  1676.     set state(condSections) {}
  1677.     }
  1678.     set startCondSectionDepth [llength $state(condSections)]
  1679.  
  1680.     while {[string length $dtd]} {
  1681.     set progress 0
  1682.     set PEref {}
  1683.     if {![string compare $mode "ignore"]} {
  1684.         set progress 1
  1685.         if {[regexp {]]>(.*)} $dtd discard dtd]} {
  1686.         set remainder {}
  1687.         set mode {} ;# normal
  1688.         set state(condSections) [lreplace $state(condSections) end end]
  1689.         continue
  1690.         } else {
  1691.         uplevel #0 $options(-errorcommand) [list "IGNORE conditional section closing delimiter not found"]
  1692.         }
  1693.     } elseif {[regexp ^(.*?)%($::sgml::Name)\;(.*)\$ $dtd discard data PEref remainder]} {
  1694.         set progress 1
  1695.     } else {
  1696.         set data $dtd
  1697.         set dtd {}
  1698.         set remainder {}
  1699.     }
  1700.  
  1701.     # Tokenize the DTD (so far)
  1702.  
  1703.     # Protect Tcl special characters
  1704.     regsub -all {([{}\\])} $data {\\\1} dataP
  1705.  
  1706.     set n [regsub -all $MarkupDeclExpr $dataP $MarkupDeclSub dataP]
  1707.  
  1708.     if {$n} {
  1709.         set progress 1
  1710.         # All but the last markup declaration should have no text
  1711.         set dataP [lrange "{} {} \{$dataP\}" 3 end]
  1712.         if {[llength $dataP] > 3} {
  1713.         foreach {decl value text} [lrange $dataP 0 [expr [llength $dataP] - 4]] {
  1714.             ParseDTD:EntityMode [array get options] mode replText decl value text $delimiter $name $param
  1715.             ParseDTD:ProcessMarkupDecl [array get options] decl value delimiter name mode repltextVar text param
  1716.  
  1717.             if {[string length [string trim $text]]} {
  1718.             # check for conditional section close
  1719.             if {[regexp {]]>(.*)$} $text discard text]} {
  1720.                 if {[string length [string trim $text]]} {
  1721.                 uplevel #0 $options(-errorcommand) [list "unexpected text \"$text\""]
  1722.                 }
  1723.                 if {![llength $state(condSections)]} {
  1724.                 uplevel #0 $options(-errorcommand) [list "extraneous conditional section close"]
  1725.                 }
  1726.                 set state(condSections) [lreplace $state(condSections) end end]
  1727.                 if {![string compare $mode "ignore"]} {
  1728.                 set mode {} ;# normal
  1729.                 }
  1730.             } else {
  1731.                 uplevel #0 $options(-errorcommand) [list "unexpected text \"$text\""]
  1732.             }
  1733.             }
  1734.         }
  1735.         }
  1736.         # Do the last declaration
  1737.         foreach {decl value text} [lrange $dataP [expr [llength $dataP] - 3] end] {
  1738.         ParseDTD:EntityMode [array get options] mode replText decl value text $delimiter $name $param
  1739.         ParseDTD:ProcessMarkupDecl [array get options] decl value delimiter name mode repltextVar text param
  1740.         }
  1741.     }
  1742.  
  1743.     # Now expand the PE reference, if any
  1744.     switch -glob $mode,[string length $PEref],$n {
  1745.         ignore,0,* {
  1746.         set dtd $text
  1747.         }
  1748.         ignore,*,* {
  1749.         set dtd $text$remainder
  1750.         }
  1751.         *,0,0 {
  1752.         set dtd $data
  1753.         }
  1754.         *,0,* {
  1755.         set dtd $text
  1756.         }
  1757.         *,*,0 {
  1758.         if {[catch {append data $PEnts($PEref)}]} {
  1759.             if {[info exists ExtPEnts($PEref)]} {
  1760.             set externalParser [$options(-name) entityparser]
  1761.             $externalParser parse $ExtPEnts($PEref) -dtdsubset external
  1762.             #$externalParser free
  1763.             } else {
  1764.             uplevel #0 $options(-errorcommand) "parameter entity \"$PEref\" not declared"
  1765.             }
  1766.         }
  1767.         set dtd $data$remainder
  1768.         }
  1769.         default {
  1770.         if {[catch {append text $PEnts($PEref)}]} {
  1771.             if {[info exists ExtPEnts($PEref)]} {
  1772.             set externalParser [$options(-name) entityparser]
  1773.             $externalParser parse $ExtPEnts($PEref) -dtdsubset external
  1774.             #$externalParser free
  1775.             } else {
  1776.             uplevel #0 $options(-errorcommand) "parameter entity \"$PEref\" not declared"
  1777.             }
  1778.         }
  1779.         set dtd $text$remainder
  1780.         }
  1781.     }
  1782.  
  1783.     # Check whether a conditional section has been terminated
  1784.     if {[regexp {^(.*?)]]>(.*)$} $dtd discard t1 t2]} {
  1785.         if {![regexp <.*> $t1]} {
  1786.         if {[string length [string trim $t1]]} {
  1787.             uplevel #0 $options(-errorcommand) [list "unexpected text \"$t1\""]
  1788.         }
  1789.         if {![llength $state(condSections)]} {
  1790.             uplevel #0 $options(-errorcommand) [list "extraneous conditional section close"]
  1791.         }
  1792.         set state(condSections) [lreplace $state(condSections) end end]
  1793.         if {![string compare $mode "ignore"]} {
  1794.             set mode {} ;# normal
  1795.         }
  1796.         set dtd $t2
  1797.         set progress 1
  1798.         }
  1799.     }
  1800.  
  1801.     if {!$progress} {
  1802.         # No parameter entity references were found and 
  1803.         # the text does not contain a well-formed markup declaration
  1804.         # Avoid going into an infinite loop
  1805.         upvar #0 $options(-errorcommand) "external entity does not contain well-formed markup declaration"
  1806.         break
  1807.     }
  1808.     }
  1809.  
  1810.     set state(inInternalDTD) $oldState
  1811.  
  1812.     # Check that conditional sections have been closed properly
  1813.     if {[llength $state(condSections)] > $startCondSectionDepth} {
  1814.     uplevel #0 $options(-errorcommand) [list "[lindex $state(condSections) end] conditional section not closed"]
  1815.     }
  1816.     if {[llength $state(condSections)] < $startCondSectionDepth} {
  1817.     uplevel #0 $options(-errorcommand) [list "too many conditional section closures"]
  1818.     }
  1819.  
  1820.     return {}
  1821. }
  1822.  
  1823. # Procedures for handling the various declarative elements in a DTD.
  1824. # New elements may be added by creating a procedure of the form
  1825. # parse:DTD:_element_
  1826.  
  1827. # For each of these procedures, the various regular expressions they use
  1828. # are created outside of the proc to avoid overhead at runtime
  1829.  
  1830. # sgml::DTD:ELEMENT --
  1831. #
  1832. #    <!ELEMENT ...> defines an element.
  1833. #
  1834. #    The content model for the element is stored in the contentmodel array,
  1835. #    indexed by the element name.  The content model is parsed into the
  1836. #    following list form:
  1837. #
  1838. #        {}    Content model is EMPTY.
  1839. #            Indicated by an empty list.
  1840. #        *    Content model is ANY.
  1841. #            Indicated by an asterix.
  1842. #        {ELEMENT ...}
  1843. #            Content model is element-only.
  1844. #        {MIXED {element1 element2 ...}}
  1845. #            Content model is mixed (PCDATA and elements).
  1846. #            The second element of the list contains the 
  1847. #            elements that may occur.  #PCDATA is assumed 
  1848. #            (ie. the list is normalised).
  1849. #
  1850. # Arguments:
  1851. #    opts    configuration options
  1852. #    name    element GI
  1853. #    modspec    unparsed content model specification
  1854.  
  1855. proc sgml::DTD:ELEMENT {opts name modspec} {
  1856.     variable Wsp
  1857.     array set options $opts
  1858.  
  1859.     upvar #0 $options(elementdecls) elements
  1860.  
  1861.     if {$options(-validate) && [info exists elements($name)]} {
  1862.     eval $options(-errorcommand) element [list "element \"$name\" already declared"]
  1863.     } else {
  1864.     switch -- $modspec {
  1865.         EMPTY {
  1866.             set elements($name) {}
  1867.         uplevel #0 $options(-elementdeclcommand) $name {{}}
  1868.         }
  1869.         ANY {
  1870.             set elements($name) *
  1871.         uplevel #0 $options(-elementdeclcommand) $name *
  1872.         }
  1873.         default {
  1874.         # Don't parse the content model for now,
  1875.         # just pass the model to the application
  1876.         if {0 && [regexp [format {^\([%s]*#PCDATA[%s]*(\|([^)]+))?[%s]*\)*[%s]*$} $Wsp $Wsp $Wsp $Wsp] discard discard mtoks]} {
  1877.             set cm($name) [list MIXED [split $mtoks |]]
  1878.         } elseif {0} {
  1879.             if {[catch {CModelParse $state(state) $value} result]} {
  1880.             eval $options(-errorcommand) element [list $result]
  1881.             } else {
  1882.             set cm($id) [list ELEMENT $result]
  1883.             }
  1884.         } else {
  1885.             set elements($name) $modspec
  1886.             uplevel #0 $options(-elementdeclcommand) $name [list $modspec]
  1887.         }
  1888.         }
  1889.     }
  1890.     }
  1891. }
  1892.  
  1893. # sgml::CModelParse --
  1894. #
  1895. #    Parse an element content model (non-mixed).
  1896. #    A syntax tree is constructed.
  1897. #    A transition table is built next.
  1898. #
  1899. #    This is going to need alot of work!
  1900. #
  1901. # Arguments:
  1902. #    state    state array variable
  1903. #    value    the content model data
  1904. #
  1905. # Results:
  1906. #    A Tcl list representing the content model.
  1907.  
  1908. proc sgml::CModelParse {state value} {
  1909.     upvar #0 $state var
  1910.  
  1911.     # First build syntax tree
  1912.     set syntaxTree [CModelMakeSyntaxTree $state $value]
  1913.  
  1914.     # Build transition table
  1915.     set transitionTable [CModelMakeTransitionTable $state $syntaxTree]
  1916.  
  1917.     return [list $syntaxTree $transitionTable]
  1918. }
  1919.  
  1920. # sgml::CModelMakeSyntaxTree --
  1921. #
  1922. #    Construct a syntax tree for the regular expression.
  1923. #
  1924. #    Syntax tree is represented as a Tcl list:
  1925. #    rep {:choice|:seq {{rep list1} {rep list2} ...}}
  1926. #    where:    rep is repetition character, *, + or ?. {} for no repetition
  1927. #        listN is nested expression or Name
  1928. #
  1929. # Arguments:
  1930. #    spec    Element specification
  1931. #
  1932. # Results:
  1933. #    Syntax tree for element spec as nested Tcl list.
  1934. #
  1935. #    Examples:
  1936. #    (memo)
  1937. #        {} {:seq {{} memo}}
  1938. #    (front, body, back?)
  1939. #        {} {:seq {{} front} {{} body} {? back}}
  1940. #    (head, (p | list | note)*, div2*)
  1941. #        {} {:seq {{} head} {* {:choice {{} p} {{} list} {{} note}}} {* div2}}
  1942. #    (p | a | ul)+
  1943. #        + {:choice {{} p} {{} a} {{} ul}}
  1944.  
  1945. proc sgml::CModelMakeSyntaxTree {state spec} {
  1946.     upvar #0 $state var
  1947.     variable Wsp
  1948.     variable name
  1949.  
  1950.     # Translate the spec into a Tcl list.
  1951.  
  1952.     # None of the Tcl special characters are allowed in a content model spec.
  1953.     if {[regexp {\$|\[|\]|\{|\}} $spec]} {
  1954.     return -code error "illegal characters in specification"
  1955.     }
  1956.  
  1957.     regsub -all [format {(%s)[%s]*(\?|\*|\+)?[%s]*(,|\|)?} $name $Wsp $Wsp] $spec [format {%sCModelSTname %s {\1} {\2} {\3}} \n $state] spec
  1958.     regsub -all {\(} $spec "\nCModelSTopenParen $state " spec
  1959.     regsub -all [format {\)[%s]*(\?|\*|\+)?[%s]*(,|\|)?} $Wsp $Wsp] $spec [format {%sCModelSTcloseParen %s {\1} {\2}} \n $state] spec
  1960.  
  1961.     array set var {stack {} state start}
  1962.     eval $spec
  1963.  
  1964.     # Peel off the outer seq, its redundant
  1965.     return [lindex [lindex $var(stack) 1] 0]
  1966. }
  1967.  
  1968. # sgml::CModelSTname --
  1969. #
  1970. #    Processes a name in a content model spec.
  1971. #
  1972. # Arguments:
  1973. #    state    state array variable
  1974. #    name    name specified
  1975. #    rep    repetition operator
  1976. #    cs    choice or sequence delimiter
  1977. #
  1978. # Results:
  1979. #    See CModelSTcp.
  1980.  
  1981. proc sgml::CModelSTname {state name rep cs args} {
  1982.     if {[llength $args]} {
  1983.     return -code error "syntax error in specification: \"$args\""
  1984.     }
  1985.  
  1986.     CModelSTcp $state $name $rep $cs
  1987. }
  1988.  
  1989. # sgml::CModelSTcp --
  1990. #
  1991. #    Process a content particle.
  1992. #
  1993. # Arguments:
  1994. #    state    state array variable
  1995. #    name    name specified
  1996. #    rep    repetition operator
  1997. #    cs    choice or sequence delimiter
  1998. #
  1999. # Results:
  2000. #    The content particle is added to the current group.
  2001.  
  2002. proc sgml::CModelSTcp {state cp rep cs} {
  2003.     upvar #0 $state var
  2004.  
  2005.     switch -glob -- [lindex $var(state) end]=$cs {
  2006.     start= {
  2007.         set var(state) [lreplace $var(state) end end end]
  2008.         # Add (dummy) grouping, either choice or sequence will do
  2009.         CModelSTcsSet $state ,
  2010.         CModelSTcpAdd $state $cp $rep
  2011.     }
  2012.     :choice= -
  2013.     :seq= {
  2014.         set var(state) [lreplace $var(state) end end end]
  2015.         CModelSTcpAdd $state $cp $rep
  2016.     }
  2017.     start=| -
  2018.     start=, {
  2019.         set var(state) [lreplace $var(state) end end [expr {$cs == "," ? ":seq" : ":choice"}]]
  2020.         CModelSTcsSet $state $cs
  2021.         CModelSTcpAdd $state $cp $rep
  2022.     }
  2023.     :choice=| -
  2024.     :seq=, {
  2025.         CModelSTcpAdd $state $cp $rep
  2026.     }
  2027.     :choice=, -
  2028.     :seq=| {
  2029.         return -code error "syntax error in specification: incorrect delimiter after \"$cp\", should be \"[expr {$cs == "," ? "|" : ","}]\""
  2030.     }
  2031.     end=* {
  2032.         return -code error "syntax error in specification: no delimiter before \"$cp\""
  2033.     }
  2034.     default {
  2035.         return -code error "syntax error"
  2036.     }
  2037.     }
  2038.     
  2039. }
  2040.  
  2041. # sgml::CModelSTcsSet --
  2042. #
  2043. #    Start a choice or sequence on the stack.
  2044. #
  2045. # Arguments:
  2046. #    state    state array
  2047. #    cs    choice oir sequence
  2048. #
  2049. # Results:
  2050. #    state is modified: end element of state is appended.
  2051.  
  2052. proc sgml::CModelSTcsSet {state cs} {
  2053.     upvar #0 $state var
  2054.  
  2055.     set cs [expr {$cs == "," ? ":seq" : ":choice"}]
  2056.  
  2057.     if {[llength $var(stack)]} {
  2058.     set var(stack) [lreplace $var(stack) end end $cs]
  2059.     } else {
  2060.     set var(stack) [list $cs {}]
  2061.     }
  2062. }
  2063.  
  2064. # sgml::CModelSTcpAdd --
  2065. #
  2066. #    Append a content particle to the top of the stack.
  2067. #
  2068. # Arguments:
  2069. #    state    state array
  2070. #    cp    content particle
  2071. #    rep    repetition
  2072. #
  2073. # Results:
  2074. #    state is modified: end element of state is appended.
  2075.  
  2076. proc sgml::CModelSTcpAdd {state cp rep} {
  2077.     upvar #0 $state var
  2078.  
  2079.     if {[llength $var(stack)]} {
  2080.     set top [lindex $var(stack) end]
  2081.         lappend top [list $rep $cp]
  2082.     set var(stack) [lreplace $var(stack) end end $top]
  2083.     } else {
  2084.     set var(stack) [list $rep $cp]
  2085.     }
  2086. }
  2087.  
  2088. # sgml::CModelSTopenParen --
  2089. #
  2090. #    Processes a '(' in a content model spec.
  2091. #
  2092. # Arguments:
  2093. #    state    state array
  2094. #
  2095. # Results:
  2096. #    Pushes stack in state array.
  2097.  
  2098. proc sgml::CModelSTopenParen {state args} {
  2099.     upvar #0 $state var
  2100.  
  2101.     if {[llength $args]} {
  2102.     return -code error "syntax error in specification: \"$args\""
  2103.     }
  2104.  
  2105.     lappend var(state) start
  2106.     lappend var(stack) [list {} {}]
  2107. }
  2108.  
  2109. # sgml::CModelSTcloseParen --
  2110. #
  2111. #    Processes a ')' in a content model spec.
  2112. #
  2113. # Arguments:
  2114. #    state    state array
  2115. #    rep    repetition
  2116. #    cs    choice or sequence delimiter
  2117. #
  2118. # Results:
  2119. #    Stack is popped, and former top of stack is appended to previous element.
  2120.  
  2121. proc sgml::CModelSTcloseParen {state rep cs args} {
  2122.     upvar #0 $state var
  2123.  
  2124.     if {[llength $args]} {
  2125.     return -code error "syntax error in specification: \"$args\""
  2126.     }
  2127.  
  2128.     set cp [lindex $var(stack) end]
  2129.     set var(stack) [lreplace $var(stack) end end]
  2130.     set var(state) [lreplace $var(state) end end]
  2131.     CModelSTcp $state $cp $rep $cs
  2132. }
  2133.  
  2134. # sgml::CModelMakeTransitionTable --
  2135. #
  2136. #    Given a content model's syntax tree, constructs
  2137. #    the transition table for the regular expression.
  2138. #
  2139. #    See "Compilers, Principles, Techniques, and Tools",
  2140. #    Aho, Sethi and Ullman.  Section 3.9, algorithm 3.5.
  2141. #
  2142. # Arguments:
  2143. #    state    state array variable
  2144. #    st    syntax tree
  2145. #
  2146. # Results:
  2147. #    The transition table is returned, as a key/value Tcl list.
  2148.  
  2149. proc sgml::CModelMakeTransitionTable {state st} {
  2150.     upvar #0 $state var
  2151.  
  2152.     # Construct nullable, firstpos and lastpos functions
  2153.     array set var {number 0}
  2154.     foreach {nullable firstpos lastpos} [    \
  2155.     TraverseDepth1st $state $st {
  2156.         # Evaluated for leaf nodes
  2157.         # Compute nullable(n)
  2158.         # Compute firstpos(n)
  2159.         # Compute lastpos(n)
  2160.         set nullable [nullable leaf $rep $name]
  2161.         set firstpos [list {} $var(number)]
  2162.         set lastpos [list {} $var(number)]
  2163.         set var(pos:$var(number)) $name
  2164.     } {
  2165.         # Evaluated for nonterminal nodes
  2166.         # Compute nullable, firstpos, lastpos
  2167.         set firstpos [firstpos $cs $firstpos $nullable]
  2168.         set lastpos  [lastpos  $cs $lastpos  $nullable]
  2169.         set nullable [nullable nonterm $rep $cs $nullable]
  2170.     }    \
  2171.     ] break
  2172.  
  2173.     set accepting [incr var(number)]
  2174.     set var(pos:$accepting) #
  2175.  
  2176.     # var(pos:N) maps from position to symbol.
  2177.     # Construct reverse map for convenience.
  2178.     # NB. A symbol may appear in more than one position.
  2179.     # var is about to be reset, so use different arrays.
  2180.  
  2181.     foreach {pos symbol} [array get var pos:*] {
  2182.     set pos [lindex [split $pos :] 1]
  2183.     set pos2symbol($pos) $symbol
  2184.     lappend sym2pos($symbol) $pos
  2185.     }
  2186.  
  2187.     # Construct the followpos functions
  2188.     catch {unset var}
  2189.     followpos $state $st $firstpos $lastpos
  2190.  
  2191.     # Construct transition table
  2192.     # Dstates is [union $marked $unmarked]
  2193.     set unmarked [list [lindex $firstpos 1]]
  2194.     while {[llength $unmarked]} {
  2195.     set T [lindex $unmarked 0]
  2196.     lappend marked $T
  2197.     set unmarked [lrange $unmarked 1 end]
  2198.  
  2199.     # Find which input symbols occur in T
  2200.     set symbols {}
  2201.     foreach pos $T {
  2202.         if {$pos != $accepting && [lsearch $symbols $pos2symbol($pos)] < 0} {
  2203.         lappend symbols $pos2symbol($pos)
  2204.         }
  2205.     }
  2206.     foreach a $symbols {
  2207.         set U {}
  2208.         foreach pos $sym2pos($a) {
  2209.         if {[lsearch $T $pos] >= 0} {
  2210.             # add followpos($pos)
  2211.                 if {$var($pos) == {}} {
  2212.                     lappend U $accepting
  2213.                 } else {
  2214.                     eval lappend U $var($pos)
  2215.                 }
  2216.         }
  2217.         }
  2218.         set U [makeSet $U]
  2219.         if {[llength $U] && [lsearch $marked $U] < 0 && [lsearch $unmarked $U] < 0} {
  2220.         lappend unmarked $U
  2221.         }
  2222.         set Dtran($T,$a) $U
  2223.     }
  2224.     
  2225.     }
  2226.  
  2227.     return [list [array get Dtran] [array get sym2pos] $accepting]
  2228. }
  2229.  
  2230. # sgml::followpos --
  2231. #
  2232. #    Compute the followpos function, using the already computed
  2233. #    firstpos and lastpos.
  2234. #
  2235. # Arguments:
  2236. #    state        array variable to store followpos functions
  2237. #    st        syntax tree
  2238. #    firstpos    firstpos functions for the syntax tree
  2239. #    lastpos        lastpos functions
  2240. #
  2241. # Results:
  2242. #    followpos functions for each leaf node, in name/value format
  2243.  
  2244. proc sgml::followpos {state st firstpos lastpos} {
  2245.     upvar #0 $state var
  2246.  
  2247.     switch -- [lindex [lindex $st 1] 0] {
  2248.     :seq {
  2249.         for {set i 1} {$i < [llength [lindex $st 1]]} {incr i} {
  2250.             followpos $state [lindex [lindex $st 1] $i]            \
  2251.             [lindex [lindex $firstpos 0] [expr $i - 1]]    \
  2252.             [lindex [lindex $lastpos 0] [expr $i - 1]]
  2253.             foreach pos [lindex [lindex [lindex $lastpos 0] [expr $i - 1]] 1] {
  2254.             eval lappend var($pos) [lindex [lindex [lindex $firstpos 0] $i] 1]
  2255.             set var($pos) [makeSet $var($pos)]
  2256.             }
  2257.         }
  2258.     }
  2259.     :choice {
  2260.         for {set i 1} {$i < [llength [lindex $st 1]]} {incr i} {
  2261.         followpos $state [lindex [lindex $st 1] $i]            \
  2262.             [lindex [lindex $firstpos 0] [expr $i - 1]]    \
  2263.             [lindex [lindex $lastpos 0] [expr $i - 1]]
  2264.         }
  2265.     }
  2266.     default {
  2267.         # No action at leaf nodes
  2268.     }
  2269.     }
  2270.  
  2271.     switch -- [lindex $st 0] {
  2272.     ? {
  2273.         # We having nothing to do here ! Doing the same as
  2274.         # for * effectively converts this qualifier into the other.
  2275.     }
  2276.     * {
  2277.         foreach pos [lindex $lastpos 1] {
  2278.         eval lappend var($pos) [lindex $firstpos 1]
  2279.         set var($pos) [makeSet $var($pos)]
  2280.         }
  2281.     }
  2282.     }
  2283.  
  2284. }
  2285.  
  2286. # sgml::TraverseDepth1st --
  2287. #
  2288. #    Perform depth-first traversal of a tree.
  2289. #    A new tree is constructed, with each node computed by f.
  2290. #
  2291. # Arguments:
  2292. #    state    state array variable
  2293. #    t    The tree to traverse, a Tcl list
  2294. #    leaf    Evaluated at a leaf node
  2295. #    nonTerm    Evaluated at a nonterminal node
  2296. #
  2297. # Results:
  2298. #    A new tree is returned.
  2299.  
  2300. proc sgml::TraverseDepth1st {state t leaf nonTerm} {
  2301.     upvar #0 $state var
  2302.  
  2303.     set nullable {}
  2304.     set firstpos {}
  2305.     set lastpos {}
  2306.  
  2307.     switch -- [lindex [lindex $t 1] 0] {
  2308.     :seq -
  2309.     :choice {
  2310.         set rep [lindex $t 0]
  2311.         set cs [lindex [lindex $t 1] 0]
  2312.  
  2313.         foreach child [lrange [lindex $t 1] 1 end] {
  2314.         foreach {childNullable childFirstpos childLastpos} \
  2315.             [TraverseDepth1st $state $child $leaf $nonTerm] break
  2316.         lappend nullable $childNullable
  2317.         lappend firstpos $childFirstpos
  2318.         lappend lastpos  $childLastpos
  2319.         }
  2320.  
  2321.         eval $nonTerm
  2322.     }
  2323.     default {
  2324.         incr var(number)
  2325.         set rep [lindex [lindex $t 0] 0]
  2326.         set name [lindex [lindex $t 1] 0]
  2327.         eval $leaf
  2328.     }
  2329.     }
  2330.  
  2331.     return [list $nullable $firstpos $lastpos]
  2332. }
  2333.  
  2334. # sgml::firstpos --
  2335. #
  2336. #    Computes the firstpos function for a nonterminal node.
  2337. #
  2338. # Arguments:
  2339. #    cs        node type, choice or sequence
  2340. #    firstpos    firstpos functions for the subtree
  2341. #    nullable    nullable functions for the subtree
  2342. #
  2343. # Results:
  2344. #    firstpos function for this node is returned.
  2345.  
  2346. proc sgml::firstpos {cs firstpos nullable} {
  2347.     switch -- $cs {
  2348.     :seq {
  2349.         set result [lindex [lindex $firstpos 0] 1]
  2350.         for {set i 0} {$i < [llength $nullable]} {incr i} {
  2351.             if {[lindex [lindex $nullable $i] 1]} {
  2352.                 eval lappend result [lindex [lindex $firstpos [expr $i + 1]] 1]
  2353.         } else {
  2354.             break
  2355.         }
  2356.         }
  2357.     }
  2358.     :choice {
  2359.         foreach child $firstpos {
  2360.         eval lappend result $child
  2361.         }
  2362.     }
  2363.     }
  2364.  
  2365.     return [list $firstpos [makeSet $result]]
  2366. }
  2367.  
  2368. # sgml::lastpos --
  2369. #
  2370. #    Computes the lastpos function for a nonterminal node.
  2371. #    Same as firstpos, only logic is reversed
  2372. #
  2373. # Arguments:
  2374. #    cs        node type, choice or sequence
  2375. #    lastpos        lastpos functions for the subtree
  2376. #    nullable    nullable functions forthe subtree
  2377. #
  2378. # Results:
  2379. #    lastpos function for this node is returned.
  2380.  
  2381. proc sgml::lastpos {cs lastpos nullable} {
  2382.     switch -- $cs {
  2383.     :seq {
  2384.         set result [lindex [lindex $lastpos end] 1]
  2385.         for {set i [expr [llength $nullable] - 1]} {$i >= 0} {incr i -1} {
  2386.         if {[lindex [lindex $nullable $i] 1]} {
  2387.             eval lappend result [lindex [lindex $lastpos $i] 1]
  2388.         } else {
  2389.             break
  2390.         }
  2391.         }
  2392.     }
  2393.     :choice {
  2394.         foreach child $lastpos {
  2395.         eval lappend result $child
  2396.         }
  2397.     }
  2398.     }
  2399.  
  2400.     return [list $lastpos [makeSet $result]]
  2401. }
  2402.  
  2403. # sgml::makeSet --
  2404. #
  2405. #    Turn a list into a set, ie. remove duplicates.
  2406. #
  2407. # Arguments:
  2408. #    s    a list
  2409. #
  2410. # Results:
  2411. #    A set is returned, which is a list with duplicates removed.
  2412.  
  2413. proc sgml::makeSet s {
  2414.     foreach r $s {
  2415.     if {[llength $r]} {
  2416.         set unique($r) {}
  2417.     }
  2418.     }
  2419.     return [array names unique]
  2420. }
  2421.  
  2422. # sgml::nullable --
  2423. #
  2424. #    Compute the nullable function for a node.
  2425. #
  2426. # Arguments:
  2427. #    nodeType    leaf or nonterminal
  2428. #    rep        repetition applying to this node
  2429. #    name        leaf node: symbol for this node, nonterm node: choice or seq node
  2430. #    subtree        nonterm node: nullable functions for the subtree
  2431. #
  2432. # Results:
  2433. #    Returns nullable function for this branch of the tree.
  2434.  
  2435. proc sgml::nullable {nodeType rep name {subtree {}}} {
  2436.     switch -glob -- $rep:$nodeType {
  2437.     :leaf -
  2438.     +:leaf {
  2439.         return [list {} 0]
  2440.     }
  2441.     \\*:leaf -
  2442.     \\?:leaf {
  2443.         return [list {} 1]
  2444.     }
  2445.     \\*:nonterm -
  2446.     \\?:nonterm {
  2447.         return [list $subtree 1]
  2448.     }
  2449.     :nonterm -
  2450.     +:nonterm {
  2451.         switch -- $name {
  2452.         :choice {
  2453.             set result 0
  2454.             foreach child $subtree {
  2455.             set result [expr $result || [lindex $child 1]]
  2456.             }
  2457.         }
  2458.         :seq {
  2459.             set result 1
  2460.             foreach child $subtree {
  2461.             set result [expr $result && [lindex $child 1]]
  2462.             }
  2463.         }
  2464.         }
  2465.         return [list $subtree $result]
  2466.     }
  2467.     }
  2468. }
  2469.  
  2470. # sgml::DTD:ATTLIST --
  2471. #
  2472. #    <!ATTLIST ...> defines an attribute list.
  2473. #
  2474. # Arguments:
  2475. #    opts    configuration opions
  2476. #    name    Element GI
  2477. #    attspec    unparsed attribute definitions
  2478. #
  2479. # Results:
  2480. #    Attribute list variables are modified.
  2481.  
  2482. proc sgml::DTD:ATTLIST {opts name attspec} {
  2483.     variable attlist_exp
  2484.     variable attlist_enum_exp
  2485.     variable attlist_fixed_exp
  2486.  
  2487.     array set options $opts
  2488.  
  2489.     # Parse the attribute list.  If it were regular, could just use foreach,
  2490.     # but some attributes may have values.
  2491.     regsub -all {([][$\\])} $attspec {\\\1} attspec
  2492.     regsub -all $attlist_exp $attspec "\}\nDTDAttribute {$options(-attlistdeclcommand)} $name $options(attlistdecls) {\\1} {\\2} {\\3} {} \{" attspec
  2493.     regsub -all $attlist_enum_exp $attspec "\}\nDTDAttribute {$options(-attlistdeclcommand)} $name $options(attlistdecls) {\\1} {\\2} {} {\\4} \{" attspec
  2494.     regsub -all $attlist_fixed_exp $attspec "\}\nDTDAttribute {$options(-attlistdeclcommand)} $name $options(attlistdecls) {\\1} {\\2} {\\3} {\\4} \{" attspec
  2495.  
  2496.     eval "noop \{$attspec\}"
  2497.  
  2498.     return {}
  2499. }
  2500.  
  2501. # sgml::DTDAttribute --
  2502. #
  2503. #    Parse definition of a single attribute.
  2504. #
  2505. # Arguments:
  2506. #    callback    attribute defn callback
  2507. #    name    element name
  2508. #    var    array variable
  2509. #    att    attribute name
  2510. #    type    type of this attribute
  2511. #    default    default value of the attribute
  2512. #    value    other information
  2513. #    text    other text (should be empty)
  2514. #
  2515. # Results:
  2516. #    Attribute defn added to array, unless it already exists
  2517.  
  2518. proc sgml::DTDAttribute args {
  2519.     # BUG: Some problems with parameter passing - deal with it later
  2520.     foreach {callback name var att type default value text} $args break
  2521.  
  2522.     upvar #0 $var atts
  2523.  
  2524.     if {[string length [string trim $text]]} {
  2525.     return -code error "unexpected text \"$text\" in attribute definition"
  2526.     }
  2527.  
  2528.     # What about overridden attribute defns?
  2529.     # A non-validating app may want to know about them
  2530.     # (eg. an editor)
  2531.     if {![info exists atts($name/$att)]} {
  2532.     set atts($name/$att) [list $type $default $value]
  2533.     uplevel #0 $callback [list $name $att $type $default $value]
  2534.     }
  2535.  
  2536.     return {}
  2537. }
  2538.  
  2539. # sgml::DTD:ENTITY --
  2540. #
  2541. #    <!ENTITY ...> declaration.
  2542. #
  2543. #    Callbacks:
  2544. #    -entitydeclcommand for general entity declaration
  2545. #    -unparsedentitydeclcommand for unparsed external entity declaration
  2546. #    -parameterentitydeclcommand for parameter entity declaration
  2547. #
  2548. # Arguments:
  2549. #    opts    configuration options
  2550. #    name    name of entity being defined
  2551. #    param    whether a parameter entity is being defined
  2552. #    value    unparsed replacement text
  2553. #
  2554. # Results:
  2555. #    Modifies the caller's entities array variable
  2556.  
  2557. proc sgml::DTD:ENTITY {opts name param value} {
  2558.  
  2559.     array set options $opts
  2560.  
  2561.     if {[string compare % $param]} {
  2562.     # Entity declaration - general or external
  2563.     upvar #0 $options(entities) ents
  2564.     upvar #0 $options(extentities) externals
  2565.  
  2566.     if {[info exists ents($name)] || [info exists externals($name)]} {
  2567.         eval $options(-warningcommand) entity [list "entity \"$name\" already declared"]
  2568.     } else {
  2569.         if {[catch {uplevel #0 $options(-parseentitydeclcommand) [list $value]} value]} {
  2570.         return -code error "unable to parse entity declaration due to \"$value\""
  2571.         }
  2572.         switch -glob [lindex $value 0],[lindex $value 3] {
  2573.         internal, {
  2574.             set ents($name) [EntitySubst [array get options] [lindex $value 1]]
  2575.             uplevel #0 $options(-entitydeclcommand) [list $name $ents($name)]
  2576.         }
  2577.         internal,* {
  2578.             return -code error "unexpected NDATA declaration"
  2579.         }
  2580.         external, {
  2581.             set externals($name) [lrange $value 1 2]
  2582.             uplevel #0 $options(-entitydeclcommand) [eval list $name [lrange $value 1 2]]
  2583.         }
  2584.         external,* {
  2585.             set externals($name) [lrange $value 1 3]
  2586.             uplevel #0 $options(-unparsedentitydeclcommand) [eval list $name [lrange $value 1 3]]
  2587.         }
  2588.         default {
  2589.             return -code error "internal error: unexpected parser state"
  2590.         }
  2591.         }
  2592.     }
  2593.     } else {
  2594.     # Parameter entity declaration
  2595.     upvar #0 $options(parameterentities) PEnts
  2596.     upvar #0 $options(externalparameterentities) ExtPEnts
  2597.  
  2598.     if {[info exists PEnts($name)] || [info exists ExtPEnts($name)]} {
  2599.         eval $options(-warningcommand) parameterentity [list "parameter entity \"$name\" already declared"]
  2600.     } else {
  2601.         if {[catch {uplevel #0 $options(-parseentitydeclcommand) [list $value]} value]} {
  2602.         return -code error "unable to parse parameter entity declaration due to \"$value\""
  2603.         }
  2604.         if {[string length [lindex $value 3]]} {
  2605.         return -code error "NDATA illegal in parameter entity declaration"
  2606.         }
  2607.         switch [lindex $value 0] {
  2608.         internal {
  2609.             # Substitute character references and PEs (XML: 4.5)
  2610.             set value [EntitySubst [array get options] [lindex $value 1]]
  2611.  
  2612.             set PEnts($name) $value
  2613.             uplevel #0 $options(-parameterentitydeclcommand) [list $name $value]
  2614.         }
  2615.         external -
  2616.         default {
  2617.             # Get the replacement text now.
  2618.             # Could wait until the first reference, but easier
  2619.             # to just do it now.
  2620.             package require uri
  2621.             set token [uri::geturl [uri::resolve $options(-baseurl) [lindex $value 1]]]
  2622.  
  2623.             set ExtPEnts($name) [lindex [array get $token data] 1]
  2624.             uplevel #0 $options(-parameterentitydeclcommand) [eval list $name [lrange $value 1 2]]
  2625.         }
  2626.         }
  2627.     }
  2628.     }
  2629. }
  2630.  
  2631. # sgml::EntitySubst --
  2632. #
  2633. #    Perform entity substitution on an entity replacement text.
  2634. #    This differs slightly from other substitution procedures,
  2635. #    because only parameter and character entity substitution
  2636. #    is performed, not general entities.
  2637. #    See XML Rec. section 4.5.
  2638. #
  2639. # Arguments:
  2640. #    opts    configuration options
  2641. #    value    Literal entity value
  2642. #
  2643. # Results:
  2644. #    Expanded replacement text
  2645.  
  2646. proc sgml::EntitySubst {opts value} {
  2647.     array set options $opts
  2648.  
  2649.     # Protect Tcl special characters
  2650.     regsub -all {([{}\\])} $value {\\\1} value
  2651.  
  2652.     # Find entity references
  2653.     regsub -all (&#\[0-9\]+|&#x\[0-9a-fA-F\]+|%${::sgml::Name})\; $value "\[EntitySubstValue [list $options(parameterentities)] {\\1}\]" value
  2654.  
  2655.     set result [subst $value]
  2656.  
  2657.     return $result
  2658. }
  2659.  
  2660. # sgml::EntitySubstValue --
  2661. #
  2662. #    Handle a single character or parameter entity substitution
  2663. #
  2664. # Arguments:
  2665. #    PEvar    array variable containing PE declarations
  2666. #    ref    character or parameter entity reference
  2667. #
  2668. # Results:
  2669. #    Replacement text
  2670.  
  2671. proc sgml::EntitySubstValue {PEvar ref} {
  2672.     switch -glob -- $ref {
  2673.     &#x* {
  2674.         scan [string range $ref 3 end] %x hex
  2675.         return [format %c $hex]
  2676.     }
  2677.     &#* {
  2678.         return [format %c [string range $ref 2 end]]
  2679.     }
  2680.     %* {
  2681.         upvar #0 $PEvar PEs
  2682.         set ref [string range $ref 1 end]
  2683.         if {[info exists PEs($ref)]} {
  2684.         return $PEs($ref)
  2685.         } else {
  2686.         return -code error "parameter entity \"$ref\" not declared"
  2687.         }
  2688.     }
  2689.     default {
  2690.         return -code error "internal error - unexpected entity reference"
  2691.     }
  2692.     }
  2693.     return {}
  2694. }
  2695.  
  2696. # sgml::DTD:NOTATION --
  2697. #
  2698. #    Process notation declaration
  2699. #
  2700. # Arguments:
  2701. #    opts    configuration options
  2702. #    name    notation name
  2703. #    value    unparsed notation spec
  2704.  
  2705. proc sgml::DTD:NOTATION {opts name value} {
  2706.     return {}
  2707.  
  2708.     variable notation_exp
  2709.     upvar opts state
  2710.  
  2711.     if {[regexp $notation_exp $value x scheme data] == 2} {
  2712.     } else {
  2713.     eval $state(-errorcommand) notationvalue [list "notation value \"$value\" incorrectly specified"]
  2714.     }
  2715. }
  2716.  
  2717. # sgml::ResolveEntity --
  2718. #
  2719. #    Default entity resolution routine
  2720. #
  2721. # Arguments:
  2722. #    name    name of parent parser
  2723. #    base    base URL for relative URLs
  2724. #    sysId    system identifier
  2725. #    pubId    public identifier
  2726.  
  2727. proc sgml::ResolveEntity {name base sysId pubId} {
  2728.     variable ParseEventNum
  2729.  
  2730.     package require tcllib
  2731.  
  2732.     if {[catch {uri::resolve $base $sysId} url]} {
  2733.     return -code error "unable to resolve system identifier \"$sysId\""
  2734.     }
  2735.     if {[catch {uri::geturl $url} token]} {
  2736.     return -code error "unable to retrieve external entity \"$url\" for system identifier \"$sysId\""
  2737.     }
  2738.  
  2739.     upvar #0 $token data
  2740.  
  2741.     set parser [uplevel #0 $name entityparser]
  2742.  
  2743.     $parser parse $data(data) -dtdsubset external
  2744.     #$parser free
  2745.  
  2746.     return {}
  2747. }
  2748.