home *** CD-ROM | disk | FTP | other *** search
/ H4CK3R 5 / hacker05 / 05_HACK_05.ISO / programacao / freewrap / TCLLIBsampleApp.exe / sample / tcllib / tcllib1.0 / htmlparse / htmlparse.tcl next >
Encoding:
Text File  |  2001-08-17  |  25.8 KB  |  848 lines

  1. # htmlparse.tcl --
  2. #
  3. #    This file implements a simple HTML parsing library in Tcl.
  4. #    It may take advantage of parsers coded in C in the future.
  5. #
  6. #    The functionality here is a subset of the
  7. #
  8. #        Simple HTML display library by Stephen Uhler (stephen.uhler@sun.com)
  9. #        Copyright (c) 1995 by Sun Microsystems
  10. #        Version 0.3 Fri Sep  1 10:47:17 PDT 1995
  11. #
  12. #    The main restriction is that all Tk-related code in the above
  13. #    was left out of the code here. It is expected that this code
  14. #    will go into a 'tklib' in the future.
  15. #
  16. # Copyright (c) 2001 by ActiveState Tool Corp.
  17. # See the file license.terms.
  18.  
  19. package require Tcl 8.2
  20. package require struct 1
  21. package require cmdline 1.1
  22. package provide htmlparse 0.2
  23.  
  24. namespace eval ::htmlparse {
  25.     namespace export        \
  26.         parse        \
  27.         debugCallback    \
  28.         mapEscapes        \
  29.         2tree        \
  30.         removeVisualFluff    \
  31.         removeFormDefs
  32.  
  33.     # Table of escape characters. Maps from their names to the actual
  34.     # character.
  35.  
  36.     variable escapes
  37.  
  38.     # I. Standard escapes. (ISO latin-1 esc's are in a different table)
  39.  
  40.     array set escapes {
  41.     lt <   gt >   amp &   quot \"   copy \xa9
  42.     reg \xae   ob \x7b   cb \x7d   nbsp \xa0
  43.     } ; # " make the emacs highlighting code happy.
  44.  
  45.     # II. ISO Latin-1 escape codes
  46.  
  47.     array set escapes {
  48.     nbsp \xa0 iexcl \xa1 cent \xa2 pound \xa3 curren \xa4
  49.     yen \xa5 brvbar \xa6 sect \xa7 uml \xa8 copy \xa9
  50.     ordf \xaa laquo \xab not \xac shy \xad reg \xae
  51.     hibar \xaf deg \xb0 plusmn \xb1 sup2 \xb2 sup3 \xb3
  52.     acute \xb4 micro \xb5 para \xb6 middot \xb7 cedil \xb8
  53.     sup1 \xb9 ordm \xba raquo \xbb frac14 \xbc frac12 \xbd
  54.     frac34 \xbe iquest \xbf Agrave \xc0 Aacute \xc1 Acirc \xc2
  55.     Atilde \xc3 Auml \xc4 Aring \xc5 AElig \xc6 Ccedil \xc7
  56.     Egrave \xc8 Eacute \xc9 Ecirc \xca Euml \xcb Igrave \xcc
  57.     Iacute \xcd Icirc \xce Iuml \xcf ETH \xd0 Ntilde \xd1
  58.     Ograve \xd2 Oacute \xd3 Ocirc \xd4 Otilde \xd5 Ouml \xd6
  59.     times \xd7 Oslash \xd8 Ugrave \xd9 Uacute \xda Ucirc \xdb
  60.     Uuml \xdc Yacute \xdd THORN \xde szlig \xdf agrave \xe0
  61.     aacute \xe1 acirc \xe2 atilde \xe3 auml \xe4 aring \xe5
  62.     aelig \xe6 ccedil \xe7 egrave \xe8 eacute \xe9 ecirc \xea
  63.     euml \xeb igrave \xec iacute \xed icirc \xee iuml \xef
  64.     eth \xf0 ntilde \xf1 ograve \xf2 oacute \xf3 ocirc \xf4
  65.     otilde \xf5 ouml \xf6 divide \xf7 oslash \xf8 ugrave \xf9
  66.     uacute \xfa ucirc \xfb uuml \xfc yacute \xfd thorn \xfe
  67.     yuml \xff
  68.     }
  69.  
  70.     # Internal cache for the foreach variable-lists and the
  71.     # substitution strings used to split a HTML string into
  72.     # incrementally handleable scripts. This should reduce the
  73.     # time compute this information for repeated calls with the same
  74.     # split-factor. The array is indexed by a combination of the
  75.     # numerical split factor and the length of the command prefix and
  76.     # maps this to a 2-element list containing variable- and
  77.     # subst-string.
  78.  
  79.     variable  splitdata
  80.     array set splitdata {}
  81.  
  82. }
  83.  
  84. # htmlparse::parse --
  85. #
  86. #    This command is the basic parser for HTML. It takes a HTML
  87. #    string, parses it and invokes a command prefix for every tag
  88. #    encountered. It is not necessary for the HTML to be valid for
  89. #    this parser to function. It is the responsibility of the
  90. #    command invoked for every tag to check this. Another
  91. #    responsibility of the invoked command is the handling of tag
  92. #    attributes and character entities (escaped characters). The
  93. #    parser provides the un-interpreted tag attributes to the
  94. #    invoked command to aid in the former, and the package at large
  95. #    provides a helper command, '::htmlparse::mapEscapes', to aid
  96. #    in the handling of the latter. The parser *does* ignore
  97. #    leading DOCTYPE declarations and all valid HTML comments it
  98. #    encounters.
  99. #
  100. #    All information beyond the HTML string itself is specified via
  101. #    options, these are explained below.
  102. #
  103. #    To help understanding the options some more background
  104. #    information about the parser.
  105. #
  106. #    It is capable to detect incomplete tags in the HTML string
  107. #    given to it. Under normal circumstances this will cause the
  108. #    parser to throw an error, but if the option '-incvar' is used
  109. #    to specify a global (or namespace) variable the parser will
  110. #    store the incomplete part of the input into this variable
  111. #    instead. This will aid greatly in the handling of
  112. #    incrementally arriving HTML as the parser will handle whatever
  113. #    he can and defer the handling of the incomplete part until
  114. #    more data has arrived.
  115. #
  116. #    Another feature of the parser are its two possible modes of
  117. #    operation. The normal mode is activated if the option '-queue'
  118. #    is not present on the command line invoking the parser. If it
  119. #    is present the parser will go into the incremental mode instead.
  120. #
  121. #    The main difference is that a parser in normal mode will
  122. #    immediately invoke the command prefix for each tag it
  123. #    encounters. In incremental mode however the parser will
  124. #    generate a number of scripts which invoke the command prefix
  125. #    for groups of tags in the HTML string and then store these
  126. #    scripts in the specified queue. It is then the responsibility
  127. #    of the caller of the parser to ensure the execution of the
  128. #    scripts in the queue.
  129. #
  130. #    Note: The queue objecct given to the parser has to provide the
  131. #    same interface as the queue defined in tcllib -> struct. This
  132. #    does for example mean that all queues created via that part of
  133. #    tcllib can be immediately used here. Still, the queue doesn't
  134. #    have to come from tcllib -> struct as long as the same
  135. #    interface is provided.
  136. #
  137. #    In both modes the parser will return an empty string to the
  138. #    caller.
  139. #
  140. #    To a parser in incremental mode the option '-split' can be
  141. #    given and will specify the size of the groups he creates. In
  142. #    other words, -split 5 means that each of the generated scripts
  143. #    will invoke the command prefix for 5 consecutive tags in the
  144. #    HTML string. A parser in normal mode will ignore this option
  145. #    and its value.
  146. #
  147. #    The option '-vroot' specifies a virtual root tag. A parser in
  148. #    normal mode will invoke the command prefix for it immediately
  149. #    before and after he processes the tags in the HTML, thus
  150. #    simulating that the HTML string is enclosed in a <vroot>
  151. #    </vroot> combination. In incremental mode however the parser
  152. #    is unable to provide the closing virtual root as he never
  153. #    knows when the input is complete. In this case the first
  154. #    script generated by each invocation of the parser will contain
  155. #    an invocation of the command prefix for the virtual root as
  156. #    its first command.
  157. #
  158. #    Interface to the command prefix:
  159. #
  160. #    In normal mode the parser will invoke the command prefix with
  161. #    for arguments appended. See '::htmlparse::debugCallback' for a
  162. #    description. In incremental mode however the generated scripts
  163. #    will invoke the command prefix with five arguments
  164. #    appended. The last four of these are the same which were
  165. #    mentioned above. The first however is a placeholder string
  166. #    (\win\) for a clientdata value to be supplied later during the
  167. #    actual execution of the generated scripts. This could be a tk
  168. #    window path, for example. This allows the user of this package
  169. #    to preprocess HTML strings without commiting them to a
  170. #    specific window, object, whatever during parsing. This
  171. #    connection can be made later. This also means that it is
  172. #    possible to cache preprocessed HTML. Of course, nothing
  173. #    prevents the user of the parser to replace the placeholder
  174. #    with an empty string.
  175. #
  176. # Arguments:
  177. #    args    An option/value-list followed by the string to
  178. #        parse. Available options are:
  179. #
  180. #        -cmd    The command prefix to invoke for every tag in
  181. #            the HTML string. Defaults to
  182. #            '::htmlparse::debugCallback'.
  183. #
  184. #        -vroot    The virtual root tag to add around the HTML in
  185. #            normal mode. In incremental mode it is the
  186. #            first tag in each chunk processed by the
  187. #            parser, but there will be no closing tags.
  188. #            Defaults to 'hmstart'.
  189. #
  190. #        -split    The size of the groups produced by an
  191. #            incremental mode parser. Ignored when in
  192. #            normal mode. Defaults to 10. Values <= 0 are
  193. #            not allowed.
  194. #
  195. #        -incvar    The name of the variable where to store any
  196. #            incomplete HTML into. Optional.
  197. #
  198. #        -queue
  199. #            The handle/name of the queue objecct to store
  200. #            the generated scripts into. Activates
  201. #            incremental mode. Normal mode is used if this
  202. #            option is not present.
  203. #
  204. #        After the option the command explect a single argument
  205. #        containing the HTML string to parse.
  206. #
  207. # Side Effects:
  208. #    In normal mode as of the invoked command. Else none.
  209. #
  210. # Results:
  211. #    None.
  212.  
  213. proc ::htmlparse::parse {args} {
  214.     # Convert the HTML string into a evaluable command sequence.
  215.  
  216.     variable splitdata
  217.  
  218.     # Option processing, start with the defaults, then run through the
  219.     # list of arguments.
  220.  
  221.     set cmd    ::htmlparse::debugCallback
  222.     set vroot  hmstart
  223.     set incvar ""
  224.     set split  10
  225.     set queue  ""
  226.  
  227.     while {[set err [cmdline::getopt args {cmd.arg vroot.arg incvar.arg split.arg queue.arg} opt arg]]} {
  228.     if {$err < 0} {
  229.         return -code error "::htmlparse::parse : $arg"
  230.     }
  231.     switch -exact -- $opt {
  232.         cmd    -
  233.         vroot  -
  234.         incvar -
  235.         queue  {
  236.         if {[string length $arg] == 0} {
  237.             return -code error "::htmlparse::parse : -$opt illegal argument (empty)"
  238.         }
  239.         # Each option has an variable with the same name associated with it.
  240.         # FRINK: nocheck
  241.         set $opt $arg
  242.         }
  243.         split  {
  244.         if {$arg <= 0} {
  245.             return -code error "::htmlparse::parse : -split illegal argument (<= 0)"
  246.         }
  247.         set split $arg
  248.         }
  249.         default {# Can't happen}
  250.     }
  251.     }
  252.  
  253.     if {[llength $args] > 1} {
  254.     return -code error "::htmlparse::parse : to many arguments behind the options, expected one"
  255.     }
  256.     if {[llength $args] < 1} {
  257.     return -code error "::htmlparse::parse : html string missing"
  258.     }
  259.  
  260.     set html [PrepareHtml [lindex $args 0]]
  261.  
  262.     # Handle incomplete HTML
  263.  
  264.     if {[regexp -- {[^<]*(<[^>]*)$} [lindex "\{$html\}" end] -> trailer]} {
  265.     if {$incvar == {}} {
  266.         return -code error "::htmlparse::parse : HTML is incomplete, option -incvar is missing"
  267.     }
  268.     upvar $incvar incomplete
  269.     set incomplete $trailer
  270.     set html       [string range $html 0 [expr {[string last "<" $html] - 1}]]
  271.     }
  272.  
  273.     # Convert the HTML string into a script.
  274.  
  275.     set w " \t\r\n"    ;# white space
  276.     set exp <(/?)([CClass ^$w>]+)[CClass $w]*([CClass ^>]*)>
  277.     set sub "\}\n$cmd {\\2} {\\1} {\\3} \{"
  278.     regsub -all -- $exp $html $sub html
  279.  
  280.     # The value of queue now determines wether we process the HTML by
  281.     # ourselves (queue is empty) or if we generate a list of  scripts
  282.     # each of which processes n tags, n the argument to -split.
  283.  
  284.     if {$queue == {}} {
  285.     # And evaluate it. This is the main parsing step.
  286.  
  287.     eval "$cmd {$vroot} {} {} \{$html\}"
  288.     eval "$cmd {$vroot} /  {} {}"
  289.     } else {
  290.     # queue defined, generate list of scripts doing small chunks of tags.
  291.  
  292.     set lcmd [llength $cmd]
  293.     set key  $split,$lcmd
  294.  
  295.     if {![info exists splitdata($key)]} {
  296.         for {set i 0; set group {}} {$i < $split} {incr i} {
  297.         # Use the length of the command prefix to generate
  298.         # additional variables before the main variable after
  299.         # which the placeholder will be inserted.
  300.  
  301.         for {set j 1} {$j < $lcmd} {incr j} {
  302.             append group "b${j}_$i "
  303.         }
  304.  
  305.         append group "a$i c$i d$i e$i f$i\n"
  306.         }
  307.         regsub -all -- {(a[0-9]+)}          $group    {{$\1} \\\\win\\\\} subgroup
  308.         regsub -all -- {([b-z_0-9]+[0-9]+)} $subgroup {{$\1}}             subgroup
  309.  
  310.         set splitdata($key) [list $group $subgroup]
  311.     }
  312.  
  313.     foreach {group subgroup} $splitdata($key) break ; # lassign
  314.     foreach $group "$cmd {$vroot} {} {} \{$html\}" {
  315.         $queue put [string trimright [subst $subgroup]]
  316.     }
  317.     }
  318.     return
  319. }
  320.  
  321. # htmlparse::PrepareHtml --
  322. #
  323. #    Internal helper command of '::htmlparse::parse'. Removes
  324. #    leading DOCTYPE declarations and comments, protects the
  325. #    special characters of tcl from evaluation.
  326. #
  327. # Arguments:
  328. #    html    The HTML string to prepare
  329. #
  330. # Side Effects:
  331. #    None.
  332. #
  333. # Results:
  334. #    The provided HTML string with the described modifications
  335. #    applied to it.
  336.  
  337. proc ::htmlparse::PrepareHtml {html} {
  338.     # Remove the following items from the text:
  339.     # - A leading    <!DOCTYPE...> declaration.
  340.     # - All comments    <!-- ... -->
  341.     #
  342.     # Also normalize the line endings (\r -> \n).
  343.  
  344.     set html [string map [list \r \n] $html]
  345.  
  346.     regsub -- "^.*<!DOCTYPE\[^>\]*>"       $html {}     html
  347.     regsub -all -- "-->"                $html "\001" html
  348.     regsub -all -- "<!--\[^\001\]*\001" $html {}     html
  349.  
  350.     # Protect characters special to tcl (braces, slashes) by
  351.     # converting them to their escape sequences.
  352.  
  353.     regsub -all -- \{   $html {\&ob;}  html
  354.     regsub -all -- \}   $html {\&cb;}  html
  355.     regsub -all -- \\\\ $html {\&bsl;} html
  356.  
  357.     return $html
  358. }
  359.  
  360.  
  361.  
  362. # htmlparse::debugCallback --
  363. #
  364. #    The standard callback used by the parser in
  365. #    '::htmlparse::parse' if none was specified by the user. Simply
  366. #    dumps its arguments to stdout.  This callback can be used for
  367. #    both normal and incremental mode of the calling parser. In
  368. #    other words, it accepts four or five arguments. The last four
  369. #    arguments are described below. The optional fifth argument
  370. #    contains the clientdata value given to the callback by a
  371. #    parser in incremental mode. All callbacks have to follow the
  372. #    signature of this command in the last four arguments, and
  373. #    callbacks used in incremental parsing have to follow this
  374. #    signature in the last five arguments.
  375. #
  376. # Arguments:
  377. #    tag            The name of the tag currently
  378. #                processed by the parser.
  379. #
  380. #    slash            Either empty or a slash. Allows us to
  381. #                distinguish between opening (slash is
  382. #                empty) and closing tags (slash is
  383. #                equal to a '/').
  384. #
  385. #    param            The un-interpreted list of parameters
  386. #                to the tag.
  387. #
  388. #    textBehindTheTag    The text found by the parser behind
  389. #                the tag named in 'tag'.
  390. #
  391. # Side Effects:
  392. #    None.
  393. #
  394. # Results:
  395. #    None.
  396.  
  397. proc ::htmlparse::debugCallback {args} {
  398.     # args = ?clientData? tag slash param textBehindTheTag
  399.     puts "==> $args"
  400.     return
  401. }
  402.  
  403. # htmlparse::mapEscapes --
  404. #
  405. #    Takes a HTML string, substitutes all escape sequences with
  406. #    their actual characters and returns the resulting string.
  407. #    HTML not containing escape sequences is returned unchanged.
  408. #
  409. # Arguments:
  410. #    html    The string to modify
  411. #
  412. # Side Effects:
  413. #    None.
  414. #
  415. # Results:
  416. #    The argument string with all escape sequences replaced with
  417. #    their actual characters.
  418.  
  419. proc ::htmlparse::mapEscapes {html} {
  420.     # Find HTML escape characters of the form &xxx;
  421.  
  422.     if {![regexp -- & $html]} {
  423.     # HTML not containing escape sequences is returned unchanged.
  424.     return $html
  425.     }
  426.  
  427.     regsub -all -- {([][$\\])} $html {\\\1} new
  428.     regsub -all -- {&#([0-9][0-9]?[0-9]?);?} $new {[format %c [scan \1 %d tmp;set tmp]]} new
  429.     regsub -all -- {&([a-zA-Z]+);?} $new {[DoMap \1]} new
  430.     return [subst $new]
  431. }
  432.  
  433. # htmlparse::CClass --
  434. #
  435. #    Internal helper command used by '::htmlparse::parse' while
  436. #    transforming the HTML string. Makes it easier to declare
  437. #    character classes in a ""-bounded string without traipsing
  438. #    into quoting hell.
  439. #
  440. # Arguments:
  441. #    x    A set of characters.
  442. #
  443. # Side Effects:
  444. #    None.
  445. #
  446. # Results:
  447. #    Returns a regular expression for the specified character
  448. #    class.
  449.  
  450. proc ::htmlparse::CClass {x} {
  451.     return "\[$x\]"
  452. }
  453.  
  454. # htmlparse::DoMap --
  455. #
  456. #    Internal helper command. Takes a the body of a single escape
  457. #    sequence (i.e. the string without the sourounding & and ;) and
  458. #    returns the associated actual character. Used by
  459. #    '::htmlparse::mapEscapes' to do the real work.
  460. #
  461. # Arguments:
  462. #    text    The body of the escape sequence to convert.
  463. #
  464. #    unknown    Optional. Defaults to '?'. The string to return if the
  465. #        escape sequence is not known to the command.
  466. #
  467. # Side Effects:
  468. #    None.
  469. #
  470. # Results:
  471. #    None.
  472.  
  473. proc ::htmlparse::DoMap {text {unknown ?}} {
  474.     # Convert an HTML escape sequence into a character.
  475.  
  476.     variable escapes
  477.     set result $unknown
  478.     catch {set result $escapes($text)}
  479.     return $result
  480. }
  481.  
  482. # htmlparse::2tree --
  483. #
  484. #    This command is a wrapper around '::htmlparse::parse' which
  485. #    takes a HTML string and converts it into a tree containing the
  486. #    logical structure of the parsed document. The tree object has
  487. #    to be created by the caller. It is also expected that the tree
  488. #    object provides the same interface as the tree object from
  489. #    tcllib -> struct. It doesn't have to come from that module
  490. #    though. The internal callback does some basic checking of HTML
  491. #    validity and tries to recover from the most basic errors.
  492. #
  493. # Arguments:
  494. #    html    The HTML string to parse and convert.
  495. #    tree    The name of the tree to fill.
  496. #
  497. # Side Effects:
  498. #    Creates a tree object (see tcllib -> struct)
  499. #    and modifies it.
  500. #
  501. # Results:
  502. #    The contents of 'tree'.
  503.  
  504. proc ::htmlparse::2tree {html tree} {
  505.  
  506.     # One internal datastructure is required, a stack of open
  507.     # tags. This stack is also provided by the 'struct' module of
  508.     # tcllib. As the operation of this command is synchronuous we
  509.     # don't have to take care against multiple running copies at the
  510.     # same times (Such are possible, but will be in different
  511.     # interpreters and true concurrency is possible only if they are
  512.     # in different threads too). IOW, no need for tricks to make the
  513.     # internal datastructure unique.
  514.  
  515.     catch {::htmlparse::tags destroy}
  516.  
  517.     ::struct::stack ::htmlparse::tags
  518.     ::htmlparse::tags push root
  519.     $tree set root -key type root
  520.  
  521.     parse -cmd [list ::htmlparse::2treeCallback $tree] $html
  522.  
  523.     # A bit hackish, correct the ordering of nodes for the optional
  524.     # tag types, over a larger area when was seen by the parser itself.
  525.  
  526.     $tree walk root -order post -command [list ::htmlparse::Reorder %t %n]
  527.  
  528.     ::htmlparse::tags destroy
  529.     return $tree
  530. }
  531.  
  532. # htmlparse::2treeCallback --
  533. #
  534. #    Internal helper command. A special callback to
  535. #    '::htmlparse::parse' used by '::htmlparse::2tree' which takes
  536. #    the incoming stream of tags and converts them into a tree
  537. #    representing the inner structure of the parsed HTML
  538. #    document. Recovers from simple HTML errors like missing
  539. #    opening tags, missing closing tags and overlapping tags.
  540. #
  541. # Arguments:
  542. #    tree            The name of the tree to manipulate.
  543. #    tag            See '::htmlparse::debugCallback'.
  544. #    slash            See '::htmlparse::debugCallback'.
  545. #    param            See '::htmlparse::debugCallback'.
  546. #    textBehindTheTag    See '::htmlparse::debugCallback'.
  547. #
  548. # Side Effects:
  549. #    Manipulates the tree object whose name was given as the first
  550. #    argument.
  551. #
  552. # Results:
  553. #    None.
  554.  
  555. proc ::htmlparse::2treeCallback {tree tag slash param textBehindTheTag} {
  556.     # This could be table-driven I think but for now the switches
  557.     # should work fine.
  558.  
  559.     # Normalize tag information for later comparisons. Also remove
  560.     # superfluous whitespace. Don't forget to decode the standard
  561.     # entities.
  562.  
  563.     set  tag  [string tolower $tag]
  564.     set  textBehindTheTag [string trim $textBehindTheTag]
  565.     if {$textBehindTheTag != {}} {
  566.     set text [mapEscapes $textBehindTheTag]
  567.     }
  568.  
  569.     if {"$slash" == "/"} {
  570.     # Handle closing tags. Standard operation is to pop the tag
  571.     # from the stack of open tags. We don't do this for </p> and
  572.     # </li>. As they were optional they were never pushed onto the
  573.     # stack (Well, actually they are just popped immediately after
  574.     # they were pusheed, see below).
  575.  
  576.     switch -exact -- $tag {
  577.         base - option - meta - li - p {
  578.         # Ignore, nothing to do.        
  579.         }
  580.         default {
  581.         # The moment we get a closing tag which does not match
  582.         # the tag on the stack we have two possibilities on how
  583.         # this came into existence to choose from:
  584.         #
  585.         # a) A tag is now closed but was never opened.
  586.         # b) A tag requiring an end tag was opened but the end
  587.         #    tag was omitted and we now are at a tag which was
  588.         #    opened before the one with the omitted end tag.
  589.  
  590.         # NOTE:
  591.         # Pages delivered from the amazon.uk site contain both
  592.         # cases: </a> without opening, <b> & <font> without
  593.         # closing. Another error: <a><b></a></b>, i.e. overlapping
  594.         # tags. Fortunately this can be handled by the algorithm
  595.         # below, in two cycles, one of which is case (b), followed
  596.         # by case (a). It seems as if Amazon/UK believes that visual
  597.         # markup like <b> and <font> is an option (switch-on) instead
  598.         # of a region.
  599.  
  600.         # Algorithm used here to deal with these:
  601.         # 1) Search whole stack for the matching opening tag.
  602.         #    If there is one assume case (b) and pop everything
  603.         #    until and including this opening tag.
  604.         # 2) If no matching opening tag was found assume case
  605.         #    (a) and ignore the tag.
  606.         #
  607.         # Part (1) also subsumes the normal case, i.e. the
  608.         # matching tag is at the top of the stack.
  609.  
  610.         set nodes [::htmlparse::tags peek [::htmlparse::tags size]]
  611.         # Note: First item is top of stack, last item is bottom of stack !
  612.         # (This behaviour of tcllib stacks is not documented
  613.         # -> we should update the manpage).
  614.  
  615.         #foreach n $nodes {lappend tstring [p get $n -key type]}
  616.         #puts stderr --[join $tstring]--
  617.  
  618.         set level 1
  619.         set found 0
  620.         foreach n $nodes {
  621.             set type [$tree get $n -key type]
  622.             if {0 == [string compare $tag $type]} {
  623.             # Found an earlier open tag -> (b).
  624.             set found 1
  625.             break
  626.             }
  627.             incr level
  628.         }
  629.         if {$found} {
  630.             ::htmlparse::tags pop $level
  631.             if {$level > 1} {
  632.             #foreach n $nodes {lappend tstring [$tree get $n -key type]}
  633.             #puts stderr "\tdesync at <$tag> ($tstring) => pop $level"
  634.             }
  635.         } else {
  636.             #foreach n $nodes {lappend tstring [$tree get $n -key type]}
  637.             #puts stderr "\tdesync at <$tag> ($tstring) => ignore"
  638.         }
  639.         }
  640.     }
  641.  
  642.     # If there is text behind a closing tag X it belongs to the
  643.     # parent tag of X.
  644.  
  645.     if {$textBehindTheTag != {}} {
  646.         # Attach the text behind the closing tag to the reopened
  647.         # context.
  648.  
  649.         set        pcd  [$tree insert [::htmlparse::tags peek] end]
  650.         $tree set $pcd  -key type PCDATA
  651.         $tree set $pcd  -key data $textBehindTheTag
  652.     }
  653.  
  654.     } else {
  655.     # Handle opening tags. The standard operation for most is to
  656.     # push them onto the stack and thus open a nested context.
  657.     # This does not happen for both the optional tags (p, li) and
  658.     # the ones which don't have closing tags (meta, br, option,
  659.     # input, area, img).
  660.     #
  661.     # The text coming with the tag will be added after the tag if
  662.     # it is a tag without a matching close, else it will be added
  663.     # as a node below the tag (as it is the region between the
  664.     # opening and closing tag and thus nested inside). Empty text
  665.     # is ignored under all circcumstances.
  666.  
  667.     set        node [$tree insert [::htmlparse::tags peek] end]
  668.     $tree set $node -key type $tag
  669.     $tree set $node -key data $param
  670.  
  671.     if {$textBehindTheTag != {}} {
  672.         switch -exact -- $tag {
  673.         input -    area - img - br {
  674.             set pcd  [$tree insert [::htmlparse::tags peek] end]
  675.         }
  676.         default {
  677.             set pcd  [$tree insert $node end]
  678.         }
  679.         }
  680.         $tree set $pcd  -key type PCDATA
  681.         $tree set $pcd  -key data $textBehindTheTag
  682.     }
  683.  
  684.     ::htmlparse::tags push $node
  685.  
  686.     # Special handling: <p>, <li> may have no closing tag => pop
  687.     #                 : them immediately.
  688.     #
  689.     # Special handling: <meta>, <br>, <option>, <input>, <area>,
  690.     #                 : <img>: no closing tags for these.
  691.  
  692.     switch -exact -- $tag {
  693.         hr - base - meta - li - br - option - input - area - img - p - h1 - h2 - h3 - h4 - h5 - h6 {
  694.         ::htmlparse::tags pop
  695.         }
  696.         default {}
  697.     }
  698.     }
  699. }
  700.  
  701. # htmlparse::removeVisualFluff --
  702. #
  703. #    This command walks a tree as generated by '::htmlparse::2tree'
  704. #    and removes all the nodes which represent visual tags and not
  705. #    structural ones. The purpose of the command is to make the
  706. #    tree easier to navigate without getting bogged down in visual
  707. #    information not relevant to the search.
  708. #
  709. # Arguments:
  710. #    tree    The name of the tree to cut down.
  711. #
  712. # Side Effects:
  713. #    Modifies the specified tree.
  714. #
  715. # Results:
  716. #    None.
  717.  
  718. proc ::htmlparse::removeVisualFluff {tree} {
  719.     $tree walk root \
  720.         -order post \
  721.         -command [list ::htmlparse::RemoveVisualFluff %t %n]
  722.     return
  723. }
  724.  
  725. # htmlparse::removeFormDefs --
  726. #
  727. #    Like '::htmlparse::removeVisualFluff' this command is here to
  728. #    cut down on the size of the tree as generated by
  729. #    '::htmlparse::2tree'. It removes all nodes representing forms
  730. #    and form elements.
  731. #
  732. # Arguments:
  733. #    tree    The name of the tree to cut down.
  734. #
  735. # Side Effects:
  736. #    Modifies the specified tree.
  737. #
  738. # Results:
  739. #    None.
  740.  
  741. proc ::htmlparse::removeFormDefs {tree} {
  742.     $tree walk root \
  743.         -order post \
  744.         -command {::htmlparse::RemoveFormDefs %t %n}
  745.     return
  746. }
  747.  
  748. # htmlparse::RemoveVisualFluff --
  749. #
  750. #    Internal helper command to
  751. #    '::htmlparse::removeVisualFluff'. Does the actual work.
  752. #
  753. # Arguments:
  754. #    tree    The name of the tree currently processed
  755. #    node    The name of the node to look at.
  756. #
  757. # Side Effects:
  758. #    Modifies the specified tree.
  759. #
  760. # Results:
  761. #    None.
  762.  
  763. proc ::htmlparse::RemoveVisualFluff {tree node} {
  764.     switch -exact -- [$tree get $node -key type] {
  765.     hmstart - html - font - center - div - sup - b - i {
  766.         # Removes the node, but does not affect the nodes below
  767.         # it. These are just made into chiildren of the parent of
  768.         # this node, in its place.
  769.  
  770.         $tree cut $node
  771.     }
  772.     script - option - select - meta - map - img {
  773.         # Removes this node and everything below it.
  774.         $tree delete $node
  775.     }
  776.     default {
  777.         # Ignore tag
  778.     }
  779.     }
  780. }
  781.  
  782. # htmlparse::RemoveFormDefs --
  783. #
  784. #    Internal helper command to
  785. #    '::htmlparse::removeFormDefs'. Does the actual work.
  786. #
  787. # Arguments:
  788. #    tree    The name of the tree currently processed
  789. #    node    The name of the node to look at.
  790. #
  791. # Side Effects:
  792. #    Modifies the specified tree.
  793. #
  794. # Results:
  795. #    None.
  796.  
  797. proc ::htmlparse::RemoveFormDefs {tree node} {
  798.     switch -exact -- [$tree get $node -key type] {
  799.     form {
  800.         $tree delete $node
  801.     }
  802.     default {
  803.         # Ignore tag
  804.     }
  805.     }
  806. }
  807.  
  808. # htmlparse::Reorder --
  809.  
  810. #    Internal helper command to '::htmlparse::2tree'. Moves the
  811. #    nodes between p/p, li/li and h<i> sequences below the
  812. #    paragraphs and items. IOW, corrects misconstructions for
  813. #    the optional node types.
  814. #
  815. # Arguments:
  816. #    tree    The name of the tree currently processed
  817. #    node    The name of the node to look at.
  818. #
  819. # Side Effects:
  820. #    Modifies the specified tree.
  821. #
  822. # Results:
  823. #    None.
  824.  
  825. proc ::htmlparse::Reorder {tree node} {
  826.     switch -exact -- [set tp [$tree get $node -key type]] {
  827.     h1 - h2 - h3 - h4 - h5 - h6 - p - li {
  828.         # Look for right siblings until the next node with the
  829.         # same type (or end of level) and move these below this
  830.         # node.
  831.  
  832.         while {1} {
  833.         set sibling [$tree next $node]
  834.         if {
  835.             $sibling == {} ||
  836.             (![string compare $tp [$tree get $sibling -key type]])
  837.         } {
  838.             break
  839.         }
  840.         $tree move $node end $sibling
  841.         }
  842.     }
  843.     default {
  844.         # Ignore tag
  845.     }
  846.     }
  847. }
  848.