source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/htmlparse/htmlparse.tcl

devel
Last change on this file was 8597867d, checked in by Jorge Pérez <josp.jorge@…>, 16 years ago

closes #976, se incluye tcllib1.10, base64 debe eliminarse del raiz de lib ya que esta incluido en tcllib

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@2556 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 28.0 KB
Line 
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
19package require Tcl       8.2
20package require struct::stack
21package require cmdline   1.1
22
23namespace eval ::htmlparse {
24    namespace export            \
25            parse               \
26            debugCallback       \
27            mapEscapes          \
28            2tree               \
29            removeVisualFluff   \
30            removeFormDefs
31
32    # Table of escape characters. Maps from their names to the actual
33    # character.  See http://htmlhelp.org/reference/html40/entities/
34
35    variable namedEntities
36
37    # I. Latin-1 Entities (HTML 4.01)
38    array set namedEntities {
39        nbsp \xa0 iexcl \xa1 cent \xa2 pound \xa3 curren \xa4
40        yen \xa5 brvbar \xa6 sect \xa7 uml \xa8 copy \xa9
41        ordf \xaa laquo \xab not \xac shy \xad reg \xae
42        macr \xaf deg \xb0 plusmn \xb1 sup2 \xb2 sup3 \xb3
43        acute \xb4 micro \xb5 para \xb6 middot \xb7 cedil \xb8
44        sup1 \xb9 ordm \xba raquo \xbb frac14 \xbc frac12 \xbd
45        frac34 \xbe iquest \xbf Agrave \xc0 Aacute \xc1 Acirc \xc2
46        Atilde \xc3 Auml \xc4 Aring \xc5 AElig \xc6 Ccedil \xc7
47        Egrave \xc8 Eacute \xc9 Ecirc \xca Euml \xcb Igrave \xcc
48        Iacute \xcd Icirc \xce Iuml \xcf ETH \xd0 Ntilde \xd1
49        Ograve \xd2 Oacute \xd3 Ocirc \xd4 Otilde \xd5 Ouml \xd6
50        times \xd7 Oslash \xd8 Ugrave \xd9 Uacute \xda Ucirc \xdb
51        Uuml \xdc Yacute \xdd THORN \xde szlig \xdf agrave \xe0
52        aacute \xe1 acirc \xe2 atilde \xe3 auml \xe4 aring \xe5
53        aelig \xe6 ccedil \xe7 egrave \xe8 eacute \xe9 ecirc \xea
54        euml \xeb igrave \xec iacute \xed icirc \xee iuml \xef
55        eth \xf0 ntilde \xf1 ograve \xf2 oacute \xf3 ocirc \xf4
56        otilde \xf5 ouml \xf6 divide \xf7 oslash \xf8 ugrave \xf9
57        uacute \xfa ucirc \xfb uuml \xfc yacute \xfd thorn \xfe
58        yuml \xff
59    }
60
61    # II. Entities for Symbols and Greek Letters (HTML 4.01)
62    array set namedEntities {
63        fnof \u192 Alpha \u391 Beta \u392 Gamma \u393 Delta \u394
64        Epsilon \u395 Zeta \u396 Eta \u397 Theta \u398 Iota \u399
65        Kappa \u39A Lambda \u39B Mu \u39C Nu \u39D Xi \u39E
66        Omicron \u39F Pi \u3A0 Rho \u3A1 Sigma \u3A3 Tau \u3A4
67        Upsilon \u3A5 Phi \u3A6 Chi \u3A7 Psi \u3A8 Omega \u3A9
68        alpha \u3B1 beta \u3B2 gamma \u3B3 delta \u3B4 epsilon \u3B5
69        zeta \u3B6 eta \u3B7 theta \u3B8 iota \u3B9 kappa \u3BA
70        lambda \u3BB mu \u3BC nu \u3BD xi \u3BE omicron \u3BF
71        pi \u3C0 rho \u3C1 sigmaf \u3C2 sigma \u3C3 tau \u3C4
72        upsilon \u3C5 phi \u3C6 chi \u3C7 psi \u3C8 omega \u3C9
73        thetasym \u3D1 upsih \u3D2 piv \u3D6 bull \u2022
74        hellip \u2026 prime \u2032 Prime \u2033 oline \u203E
75        frasl \u2044 weierp \u2118 image \u2111 real \u211C
76        trade \u2122 alefsym \u2135 larr \u2190 uarr \u2191
77        rarr \u2192 darr \u2193 harr \u2194 crarr \u21B5
78        lArr \u21D0 uArr \u21D1 rArr \u21D2 dArr \u21D3 hArr \u21D4
79        forall \u2200 part \u2202 exist \u2203 empty \u2205
80        nabla \u2207 isin \u2208 notin \u2209 ni \u220B prod \u220F
81        sum \u2211 minus \u2212 lowast \u2217 radic \u221A
82        prop \u221D infin \u221E ang \u2220 and \u2227 or \u2228
83        cap \u2229 cup \u222A int \u222B there4 \u2234 sim \u223C
84        cong \u2245 asymp \u2248 ne \u2260 equiv \u2261 le \u2264
85        ge \u2265 sub \u2282 sup \u2283 nsub \u2284 sube \u2286
86        supe \u2287 oplus \u2295 otimes \u2297 perp \u22A5
87        sdot \u22C5 lceil \u2308 rceil \u2309 lfloor \u230A
88        rfloor \u230B lang \u2329 rang \u232A loz \u25CA
89        spades \u2660 clubs \u2663 hearts \u2665 diams \u2666
90    }
91
92    # III. Special Entities (HTML 4.01)
93    array set namedEntities {
94        quot \x22 amp \x26 lt \x3C gt \x3E OElig \u152 oelig \u153
95        Scaron \u160 scaron \u161 Yuml \u178 circ \u2C6
96        tilde \u2DC ensp \u2002 emsp \u2003 thinsp \u2009
97        zwnj \u200C zwj \u200D lrm \u200E rlm \u200F ndash \u2013
98        mdash \u2014 lsquo \u2018 rsquo \u2019 sbquo \u201A
99        ldquo \u201C rdquo \u201D bdquo \u201E dagger \u2020
100        Dagger \u2021 permil \u2030 lsaquo \u2039 rsaquo \u203A
101        euro \u20AC
102    }
103
104    # Internal cache for the foreach variable-lists and the
105    # substitution strings used to split a HTML string into
106    # incrementally handleable scripts. This should reduce the
107    # time compute this information for repeated calls with the same
108    # split-factor. The array is indexed by a combination of the
109    # numerical split factor and the length of the command prefix and
110    # maps this to a 2-element list containing variable- and
111    # subst-string.
112
113    variable  splitdata
114    array set splitdata {}
115
116}
117
118# htmlparse::parse --
119#
120#       This command is the basic parser for HTML. It takes a HTML
121#       string, parses it and invokes a command prefix for every tag
122#       encountered. It is not necessary for the HTML to be valid for
123#       this parser to function. It is the responsibility of the
124#       command invoked for every tag to check this. Another
125#       responsibility of the invoked command is the handling of tag
126#       attributes and character entities (escaped characters). The
127#       parser provides the un-interpreted tag attributes to the
128#       invoked command to aid in the former, and the package at large
129#       provides a helper command, '::htmlparse::mapEscapes', to aid
130#       in the handling of the latter. The parser *does* ignore
131#       leading DOCTYPE declarations and all valid HTML comments it
132#       encounters.
133#
134#       All information beyond the HTML string itself is specified via
135#       options, these are explained below.
136#
137#       To help understanding the options some more background
138#       information about the parser.
139#
140#       It is capable to detect incomplete tags in the HTML string
141#       given to it. Under normal circumstances this will cause the
142#       parser to throw an error, but if the option '-incvar' is used
143#       to specify a global (or namespace) variable the parser will
144#       store the incomplete part of the input into this variable
145#       instead. This will aid greatly in the handling of
146#       incrementally arriving HTML as the parser will handle whatever
147#       he can and defer the handling of the incomplete part until
148#       more data has arrived.
149#
150#       Another feature of the parser are its two possible modes of
151#       operation. The normal mode is activated if the option '-queue'
152#       is not present on the command line invoking the parser. If it
153#       is present the parser will go into the incremental mode instead.
154#
155#       The main difference is that a parser in normal mode will
156#       immediately invoke the command prefix for each tag it
157#       encounters. In incremental mode however the parser will
158#       generate a number of scripts which invoke the command prefix
159#       for groups of tags in the HTML string and then store these
160#       scripts in the specified queue. It is then the responsibility
161#       of the caller of the parser to ensure the execution of the
162#       scripts in the queue.
163#
164#       Note: The queue objecct given to the parser has to provide the
165#       same interface as the queue defined in tcllib -> struct. This
166#       does for example mean that all queues created via that part of
167#       tcllib can be immediately used here. Still, the queue doesn't
168#       have to come from tcllib -> struct as long as the same
169#       interface is provided.
170#
171#       In both modes the parser will return an empty string to the
172#       caller.
173#
174#       To a parser in incremental mode the option '-split' can be
175#       given and will specify the size of the groups he creates. In
176#       other words, -split 5 means that each of the generated scripts
177#       will invoke the command prefix for 5 consecutive tags in the
178#       HTML string. A parser in normal mode will ignore this option
179#       and its value.
180#
181#       The option '-vroot' specifies a virtual root tag. A parser in
182#       normal mode will invoke the command prefix for it immediately
183#       before and after he processes the tags in the HTML, thus
184#       simulating that the HTML string is enclosed in a <vroot>
185#       </vroot> combination. In incremental mode however the parser
186#       is unable to provide the closing virtual root as he never
187#       knows when the input is complete. In this case the first
188#       script generated by each invocation of the parser will contain
189#       an invocation of the command prefix for the virtual root as
190#       its first command.
191#
192#       Interface to the command prefix:
193#
194#       In normal mode the parser will invoke the command prefix with
195#       for arguments appended. See '::htmlparse::debugCallback' for a
196#       description. In incremental mode however the generated scripts
197#       will invoke the command prefix with five arguments
198#       appended. The last four of these are the same which were
199#       mentioned above. The first however is a placeholder string
200#       (\win\) for a clientdata value to be supplied later during the
201#       actual execution of the generated scripts. This could be a tk
202#       window path, for example. This allows the user of this package
203#       to preprocess HTML strings without commiting them to a
204#       specific window, object, whatever during parsing. This
205#       connection can be made later. This also means that it is
206#       possible to cache preprocessed HTML. Of course, nothing
207#       prevents the user of the parser to replace the placeholder
208#       with an empty string.
209#
210# Arguments:
211#       args    An option/value-list followed by the string to
212#               parse. Available options are:
213#
214#               -cmd    The command prefix to invoke for every tag in
215#                       the HTML string. Defaults to
216#                       '::htmlparse::debugCallback'.
217#
218#               -vroot  The virtual root tag to add around the HTML in
219#                       normal mode. In incremental mode it is the
220#                       first tag in each chunk processed by the
221#                       parser, but there will be no closing tags.
222#                       Defaults to 'hmstart'.
223#
224#               -split  The size of the groups produced by an
225#                       incremental mode parser. Ignored when in
226#                       normal mode. Defaults to 10. Values <= 0 are
227#                       not allowed.
228#
229#               -incvar The name of the variable where to store any
230#                       incomplete HTML into. Optional.
231#
232#               -queue
233#                       The handle/name of the queue objecct to store
234#                       the generated scripts into. Activates
235#                       incremental mode. Normal mode is used if this
236#                       option is not present.
237#
238#               After the option the command explect a single argument
239#               containing the HTML string to parse.
240#
241# Side Effects:
242#       In normal mode as of the invoked command. Else none.
243#
244# Results:
245#       None.
246
247proc ::htmlparse::parse {args} {
248    # Convert the HTML string into a evaluable command sequence.
249
250    variable splitdata
251
252    # Option processing, start with the defaults, then run through the
253    # list of arguments.
254
255    set cmd    ::htmlparse::debugCallback
256    set vroot  hmstart
257    set incvar ""
258    set split  10
259    set queue  ""
260
261    while {[set err [cmdline::getopt args {cmd.arg vroot.arg incvar.arg split.arg queue.arg} opt arg]]} {
262        if {$err < 0} {
263            return -code error "::htmlparse::parse : $arg"
264        }
265        switch -exact -- $opt {
266            cmd    -
267            vroot  -
268            incvar -
269            queue  {
270                if {[string length $arg] == 0} {
271                    return -code error "::htmlparse::parse : -$opt illegal argument (empty)"
272                }
273                # Each option has an variable with the same name associated with it.
274                # FRINK: nocheck
275                set $opt $arg
276            }
277            split  {
278                if {$arg <= 0} {
279                    return -code error "::htmlparse::parse : -split illegal argument (<= 0)"
280                }
281                set split $arg
282            }
283            default {# Can't happen}
284        }
285    }
286
287    if {[llength $args] > 1} {
288        return -code error "::htmlparse::parse : to many arguments behind the options, expected one"
289    }
290    if {[llength $args] < 1} {
291        return -code error "::htmlparse::parse : html string missing"
292    }
293
294    set html [PrepareHtml [lindex $args 0]]
295
296    # Look for incomplete HTML from the last iteration and prepend it
297    # to the input we just got.
298
299    if {$incvar != {}} {
300        upvar $incvar incomplete
301    } else {
302        set incomplete ""
303    }
304
305    if {[catch {set new $incomplete$html}]} {set new $html}
306    set html $new
307
308    # Handle incomplete HTML (Recognize incomplete tag at end, buffer
309    # it up for the next call).
310
311    set end [lindex \{$html\} end]
312    if {[set idx [string last < $end]] > [string last > $end]} {
313
314        if {$incvar == {}} {
315            return -code error "::htmlparse::parse : HTML is incomplete, option -incvar is missing"
316        }
317
318        #  upvar $incvar incomplete -- Already done, s.a.
319        set incomplete [string range $end $idx end]
320        incr idx -1
321        set html       [string range $end 0 $idx]
322       
323    } else {
324        set incomplete ""
325    }
326
327    # Convert the HTML string into a script.
328
329    set sub "\}\n$cmd {\\1} {} {\\2} \{\}\n$cmd {\\1} {/} {} \{"
330    regsub -all -- {<([^\s>]+)\s*([^>]*)/>} $html $sub html
331
332    set sub "\}\n$cmd {\\2} {\\1} {\\3} \{"
333    regsub -all -- {<(/?)([^\s>]+)\s*([^>]*)>} $html $sub html
334
335    # The value of queue now determines wether we process the HTML by
336    # ourselves (queue is empty) or if we generate a list of  scripts
337    # each of which processes n tags, n the argument to -split.
338
339    if {$queue == {}} {
340        # And evaluate it. This is the main parsing step.
341
342        eval "$cmd {$vroot} {} {} \{$html\}"
343        eval "$cmd {$vroot} /  {} {}"
344    } else {
345        # queue defined, generate list of scripts doing small chunks of tags.
346
347        set lcmd [llength $cmd]
348        set key  $split,$lcmd
349
350        if {![info exists splitdata($key)]} {
351            for {set i 0; set group {}} {$i < $split} {incr i} {
352                # Use the length of the command prefix to generate
353                # additional variables before the main variable after
354                # which the placeholder will be inserted.
355
356                for {set j 1} {$j < $lcmd} {incr j} {
357                    append group "b${j}_$i "
358                }
359
360                append group "a$i c$i d$i e$i f$i\n"
361            }
362            regsub -all -- {(a[0-9]+)}          $group    {{$\1} \\\\win\\\\} subgroup
363            regsub -all -- {([b-z_0-9]+[0-9]+)} $subgroup {{$\1}}             subgroup
364
365            set splitdata($key) [list $group $subgroup]
366        }
367
368        foreach {group subgroup} $splitdata($key) break ; # lassign
369        foreach $group "$cmd {$vroot} {} {} \{$html\}" {
370            $queue put [string trimright [subst $subgroup]]
371        }
372    }
373    return
374}
375
376# htmlparse::PrepareHtml --
377#
378#       Internal helper command of '::htmlparse::parse'. Removes
379#       leading DOCTYPE declarations and comments, protects the
380#       special characters of tcl from evaluation.
381#
382# Arguments:
383#       html    The HTML string to prepare
384#
385# Side Effects:
386#       None.
387#
388# Results:
389#       The provided HTML string with the described modifications
390#       applied to it.
391
392proc ::htmlparse::PrepareHtml {html} {
393    # Remove the following items from the text:
394    # - A leading       <!DOCTYPE...> declaration.
395    # - All comments    <!-- ... -->
396    #
397    # Also normalize the line endings (\r -> \n).
398
399    # Tcllib SF Bug 861287 - Processing of comments.
400    # Recognize EOC by RE, instead of fixed string.
401
402    set html [string map [list \r \n] $html]
403
404    regsub -- "^.*<!DOCTYPE\[^>\]*>"    $html {}     html
405    regsub -all -- "--(\[ \t\n\]*)>"      $html "\001\\1\002" html
406
407    # Recognize borken beginnings of a comment and convert them to PCDATA.
408    regsub -all -- "<--(\[^\001\]*)\001(\[^\002\]*)\002" $html {\&lt;--\1--\2\&gt;} html
409
410    # And now recognize true comments, remove them.
411    regsub -all -- "<!--\[^\001\]*\001(\[^\002\]*)\002"  $html {}                   html
412
413    # Protect characters special to tcl (braces, slashes) by
414    # converting them to their escape sequences.
415
416    return [string map [list \
417                    "\{" "&#123;" \
418                    "\}" "&#125;" \
419                    "\\" "&#92;"] $html]
420}
421
422
423
424# htmlparse::debugCallback --
425#
426#       The standard callback used by the parser in
427#       '::htmlparse::parse' if none was specified by the user. Simply
428#       dumps its arguments to stdout.  This callback can be used for
429#       both normal and incremental mode of the calling parser. In
430#       other words, it accepts four or five arguments. The last four
431#       arguments are described below. The optional fifth argument
432#       contains the clientdata value given to the callback by a
433#       parser in incremental mode. All callbacks have to follow the
434#       signature of this command in the last four arguments, and
435#       callbacks used in incremental parsing have to follow this
436#       signature in the last five arguments.
437#
438# Arguments:
439#       tag                     The name of the tag currently
440#                               processed by the parser.
441#
442#       slash                   Either empty or a slash. Allows us to
443#                               distinguish between opening (slash is
444#                               empty) and closing tags (slash is
445#                               equal to a '/').
446#
447#       param                   The un-interpreted list of parameters
448#                               to the tag.
449#
450#       textBehindTheTag        The text found by the parser behind
451#                               the tag named in 'tag'.
452#
453# Side Effects:
454#       None.
455#
456# Results:
457#       None.
458
459proc ::htmlparse::debugCallback {args} {
460    # args = ?clientData? tag slash param textBehindTheTag
461    puts "==> $args"
462    return
463}
464
465# htmlparse::mapEscapes --
466#
467#       Takes a HTML string, substitutes all escape sequences with
468#       their actual characters and returns the resulting string.
469#       HTML not containing escape sequences or invalid escape
470#       sequences is returned unchanged.
471#
472# Arguments:
473#       html    The string to modify
474#
475# Side Effects:
476#       None.
477#
478# Results:
479#       The argument string with all escape sequences replaced with
480#       their actual characters.
481
482proc ::htmlparse::mapEscapes {html} {
483    # Find HTML escape characters of the form &xxx(;|EOW)
484
485    # Quote special Tcl chars so they pass through [subst] unharmed.
486    set new [string map [list \] \\\] \[ \\\[ \$ \\\$ \\ \\\\] $html]
487    regsub -all -- {&([[:alnum:]]{2,7})(;|\M)} $new {[DoNamedMap \1 {\2}]} new
488    regsub -all -- {&#([[:digit:]]{1,5})(;|\M)} $new {[DoDecMap \1 {\2}]} new
489    regsub -all -- {&#x([[:xdigit:]]{1,4})(;|\M)} $new {[DoHexMap \1 {\2}]} new
490    return [subst $new]
491}
492
493proc ::htmlparse::DoNamedMap {name endOf} {
494    variable namedEntities
495    if {[info exist namedEntities($name)]} {
496        return $namedEntities($name)
497    } else {
498        # Put it back..
499        return "&$name$endOf"
500    }
501}
502
503proc ::htmlparse::DoDecMap {dec endOf} {
504    scan $dec %d dec
505    if {$dec <= 0xFFFD} {
506        return [format %c $dec]
507    } else {
508        # Put it back..
509        return "&#$dec$endOf"
510    }
511}
512
513proc ::htmlparse::DoHexMap {hex endOf} {
514    scan $hex %x value
515    if {$value <= 0xFFFD} {
516        return [format %c $value]
517    } else {
518        # Put it back..
519        return "&#x$hex$endOf"
520    }
521}
522
523# htmlparse::2tree --
524#
525#       This command is a wrapper around '::htmlparse::parse' which
526#       takes a HTML string and converts it into a tree containing the
527#       logical structure of the parsed document. The tree object has
528#       to be created by the caller. It is also expected that the tree
529#       object provides the same interface as the tree object from
530#       tcllib -> struct. It doesn't have to come from that module
531#       though. The internal callback does some basic checking of HTML
532#       validity and tries to recover from the most basic errors.
533#
534# Arguments:
535#       html    The HTML string to parse and convert.
536#       tree    The name of the tree to fill.
537#
538# Side Effects:
539#       Creates a tree object (see tcllib -> struct)
540#       and modifies it.
541#
542# Results:
543#       The contents of 'tree'.
544
545proc ::htmlparse::2tree {html tree} {
546
547    # One internal datastructure is required, a stack of open
548    # tags. This stack is also provided by the 'struct' module of
549    # tcllib. As the operation of this command is synchronuous we
550    # don't have to take care against multiple running copies at the
551    # same times (Such are possible, but will be in different
552    # interpreters and true concurrency is possible only if they are
553    # in different threads too). IOW, no need for tricks to make the
554    # internal datastructure unique.
555
556    catch {::htmlparse::tags destroy}
557
558    ::struct::stack ::htmlparse::tags
559    ::htmlparse::tags push root
560    $tree set root type root
561
562    parse -cmd [list ::htmlparse::2treeCallback $tree] $html
563
564    # A bit hackish, correct the ordering of nodes for the optional
565    # tag types, over a larger area when was seen by the parser itself.
566
567    $tree walk root -order post n {
568        ::htmlparse::Reorder $tree $n
569    }
570
571    ::htmlparse::tags destroy
572    return $tree
573}
574
575# htmlparse::2treeCallback --
576#
577#       Internal helper command. A special callback to
578#       '::htmlparse::parse' used by '::htmlparse::2tree' which takes
579#       the incoming stream of tags and converts them into a tree
580#       representing the inner structure of the parsed HTML
581#       document. Recovers from simple HTML errors like missing
582#       opening tags, missing closing tags and overlapping tags.
583#
584# Arguments:
585#       tree                    The name of the tree to manipulate.
586#       tag                     See '::htmlparse::debugCallback'.
587#       slash                   See '::htmlparse::debugCallback'.
588#       param                   See '::htmlparse::debugCallback'.
589#       textBehindTheTag        See '::htmlparse::debugCallback'.
590#
591# Side Effects:
592#       Manipulates the tree object whose name was given as the first
593#       argument.
594#
595# Results:
596#       None.
597
598proc ::htmlparse::2treeCallback {tree tag slash param textBehindTheTag} {
599    # This could be table-driven I think but for now the switches
600    # should work fine.
601
602    # Normalize tag information for later comparisons. Also remove
603    # superfluous whitespace. Don't forget to decode the standard
604    # entities.
605
606    set  tag  [string tolower $tag]
607    set  textBehindTheTag [string trim $textBehindTheTag]
608    if {$textBehindTheTag != {}} {
609        set text [mapEscapes $textBehindTheTag]
610    }
611
612    if {"$slash" == "/"} {
613        # Handle closing tags. Standard operation is to pop the tag
614        # from the stack of open tags. We don't do this for </p> and
615        # </li>. As they were optional they were never pushed onto the
616        # stack (Well, actually they are just popped immediately after
617        # they were pusheed, see below).
618
619        switch -exact -- $tag {
620            base - option - meta - li - p {
621                # Ignore, nothing to do.               
622            }
623            default {
624                # The moment we get a closing tag which does not match
625                # the tag on the stack we have two possibilities on how
626                # this came into existence to choose from:
627                #
628                # a) A tag is now closed but was never opened.
629                # b) A tag requiring an end tag was opened but the end
630                #    tag was omitted and we now are at a tag which was
631                #    opened before the one with the omitted end tag.
632
633                # NOTE:
634                # Pages delivered from the amazon.uk site contain both
635                # cases: </a> without opening, <b> & <font> without
636                # closing. Another error: <a><b></a></b>, i.e. overlapping
637                # tags. Fortunately this can be handled by the algorithm
638                # below, in two cycles, one of which is case (b), followed
639                # by case (a). It seems as if Amazon/UK believes that visual
640                # markup like <b> and <font> is an option (switch-on) instead
641                # of a region.
642
643                # Algorithm used here to deal with these:
644                # 1) Search whole stack for the matching opening tag.
645                #    If there is one assume case (b) and pop everything
646                #    until and including this opening tag.
647                # 2) If no matching opening tag was found assume case
648                #    (a) and ignore the tag.
649                #
650                # Part (1) also subsumes the normal case, i.e. the
651                # matching tag is at the top of the stack.
652
653                set nodes [::htmlparse::tags peek [::htmlparse::tags size]]
654                # Note: First item is top of stack, last item is bottom of stack !
655                # (This behaviour of tcllib stacks is not documented
656                # -> we should update the manpage).
657
658                #foreach n $nodes {lappend tstring [p get $n -key type]}
659                #puts stderr --[join $tstring]--
660
661                set level 1
662                set found 0
663                foreach n $nodes {
664                    set type [$tree get $n type]
665                    if {0 == [string compare $tag $type]} {
666                        # Found an earlier open tag -> (b).
667                        set found 1
668                        break
669                    }
670                    incr level
671                }
672                if {$found} {
673                    ::htmlparse::tags pop $level
674                    if {$level > 1} {
675                        #foreach n $nodes {lappend tstring [$tree get $n type]}
676                        #puts stderr "\tdesync at <$tag> ($tstring) => pop $level"
677                    }
678                } else {
679                    #foreach n $nodes {lappend tstring [$tree get $n type]}
680                    #puts stderr "\tdesync at <$tag> ($tstring) => ignore"
681                }
682            }
683        }
684
685        # If there is text behind a closing tag X it belongs to the
686        # parent tag of X.
687
688        if {$textBehindTheTag != {}} {
689            # Attach the text behind the closing tag to the reopened
690            # context.
691
692            set        pcd  [$tree insert [::htmlparse::tags peek] end]
693            $tree set $pcd  type PCDATA
694            $tree set $pcd  data $textBehindTheTag
695        }
696
697    } else {
698        # Handle opening tags. The standard operation for most is to
699        # push them onto the stack and thus open a nested context.
700        # This does not happen for both the optional tags (p, li) and
701        # the ones which don't have closing tags (meta, br, option,
702        # input, area, img).
703        #
704        # The text coming with the tag will be added after the tag if
705        # it is a tag without a matching close, else it will be added
706        # as a node below the tag (as it is the region between the
707        # opening and closing tag and thus nested inside). Empty text
708        # is ignored under all circcumstances.
709
710        set        node [$tree insert [::htmlparse::tags peek] end]
711        $tree set $node type $tag
712        $tree set $node data $param
713
714        if {$textBehindTheTag != {}} {
715            switch -exact -- $tag {
716                input - area - img - br {
717                    set pcd  [$tree insert [::htmlparse::tags peek] end]
718                }
719                default {
720                    set pcd  [$tree insert $node end]
721                }
722            }
723            $tree set $pcd type PCDATA
724            $tree set $pcd data $textBehindTheTag
725        }
726
727        ::htmlparse::tags push $node
728
729        # Special handling: <p>, <li> may have no closing tag => pop
730        #                 : them immediately.
731        #
732        # Special handling: <meta>, <br>, <option>, <input>, <area>,
733        #                 : <img>: no closing tags for these.
734
735        switch -exact -- $tag {
736            hr - base - meta - li - br - option - input - area - img - p - h1 - h2 - h3 - h4 - h5 - h6 {
737                ::htmlparse::tags pop
738            }
739            default {}
740        }
741    }
742}
743
744# htmlparse::removeVisualFluff --
745#
746#       This command walks a tree as generated by '::htmlparse::2tree'
747#       and removes all the nodes which represent visual tags and not
748#       structural ones. The purpose of the command is to make the
749#       tree easier to navigate without getting bogged down in visual
750#       information not relevant to the search.
751#
752# Arguments:
753#       tree    The name of the tree to cut down.
754#
755# Side Effects:
756#       Modifies the specified tree.
757#
758# Results:
759#       None.
760
761proc ::htmlparse::removeVisualFluff {tree} {
762    $tree walk root -order post n {
763        ::htmlparse::RemoveVisualFluff $tree $n
764    }
765    return
766}
767
768# htmlparse::removeFormDefs --
769#
770#       Like '::htmlparse::removeVisualFluff' this command is here to
771#       cut down on the size of the tree as generated by
772#       '::htmlparse::2tree'. It removes all nodes representing forms
773#       and form elements.
774#
775# Arguments:
776#       tree    The name of the tree to cut down.
777#
778# Side Effects:
779#       Modifies the specified tree.
780#
781# Results:
782#       None.
783
784proc ::htmlparse::removeFormDefs {tree} {
785    $tree walk root -order post n {
786        ::htmlparse::RemoveFormDefs $tree $n
787    }
788    return
789}
790
791# htmlparse::RemoveVisualFluff --
792#
793#       Internal helper command to
794#       '::htmlparse::removeVisualFluff'. Does the actual work.
795#
796# Arguments:
797#       tree    The name of the tree currently processed
798#       node    The name of the node to look at.
799#
800# Side Effects:
801#       Modifies the specified tree.
802#
803# Results:
804#       None.
805
806proc ::htmlparse::RemoveVisualFluff {tree node} {
807    switch -exact -- [$tree get $node type] {
808        hmstart - html - font - center - div - sup - b - i {
809            # Removes the node, but does not affect the nodes below
810            # it. These are just made into chiildren of the parent of
811            # this node, in its place.
812
813            $tree cut $node
814        }
815        script - option - select - meta - map - img {
816            # Removes this node and everything below it.
817            $tree delete $node
818        }
819        default {
820            # Ignore tag
821        }
822    }
823}
824
825# htmlparse::RemoveFormDefs --
826#
827#       Internal helper command to
828#       '::htmlparse::removeFormDefs'. Does the actual work.
829#
830# Arguments:
831#       tree    The name of the tree currently processed
832#       node    The name of the node to look at.
833#
834# Side Effects:
835#       Modifies the specified tree.
836#
837# Results:
838#       None.
839
840proc ::htmlparse::RemoveFormDefs {tree node} {
841    switch -exact -- [$tree get $node type] {
842        form {
843            $tree delete $node
844        }
845        default {
846            # Ignore tag
847        }
848    }
849}
850
851# htmlparse::Reorder --
852
853#       Internal helper command to '::htmlparse::2tree'. Moves the
854#       nodes between p/p, li/li and h<i> sequences below the
855#       paragraphs and items. IOW, corrects misconstructions for
856#       the optional node types.
857#
858# Arguments:
859#       tree    The name of the tree currently processed
860#       node    The name of the node to look at.
861#
862# Side Effects:
863#       Modifies the specified tree.
864#
865# Results:
866#       None.
867
868proc ::htmlparse::Reorder {tree node} {
869    switch -exact -- [set tp [$tree get $node type]] {
870        h1 - h2 - h3 - h4 - h5 - h6 - p - li {
871            # Look for right siblings until the next node with a
872            # similar type (or end of level) and move these below this
873            # node.
874
875            while {1} {
876                set sibling [$tree next $node]
877                if {
878                    ($sibling == {}) ||
879                    ([lsearch -exact {h1 h2 h3 h4 h5 h6 p li} [$tree get $sibling type]] != -1)
880                } {
881                    break
882                }
883                $tree move $node end $sibling
884            }
885        }
886        default {
887            # Ignore tag
888        }
889    }
890}
891
892# ### ######### ###########################
893
894package provide htmlparse 1.1.2
Note: See TracBrowser for help on using the repository browser.