Fixed / overloading when enabling true division.
[tx] / tx.txt
1 *** TX ***
2
3 [[TableOfContents]]
4
5 = Summary =
6
7 `TX` is short for `Tuxee XML`. It's a set of Python modules to
8 generate, transform, parse, search XML (and HTML) document.
9
10 | *Module*         | *Description*                            |
11 | `tx.nodes`       | Define classes for each type of node to build XML tree |
12 | `tx.tags`        | Simplify tree creation                   |
13 | `tx.htmltree`    | Build XML tree using `htmlparser` module |
14 | `tx.xpath`       | Translate XPath expressions to Python functions |
15
16 And some modules used internally:
17
18 | *Module*         | *Description* |
19 | `tx.error`       | Exceptions used in `tx`                         |
20 | `tx.parser`      | Generic parser inspired by PyParsing            |
21 | `tx.iterators`   | Iterators to walk a XML tree in various ways    |
22 | `tx.htmlparser`  | Error tolerant HTML parser                      |
23 | `tx.xpathparser` | Translate XPath expressions to "s-expression"   |
24 | `tx.xpathfn`     | Provide XPath/XQuery functions and operators    |
25 | `tx.context`     | XPath context object                            |
26 | `tx.sequence`    | XPath sequence object                           |
27
28 Misc. modules:
29
30 | *Module*        | *Description* |
31 | `tx.misc`       | Contains some utility functions                |
32 | `tx.rxpcompat`  | Translate RXP-like tree structure to `tx` tree |
33 | `tx.xpath_misc` | ... |
34 | `tx.sequence_misc` | ... |
35 | `tx.nodes_misc` | ... |
36
37 = Arch repository =
38
39 | Archive  | `frederic@jolliton.com--2005-main` |
40 | Location | `http://arch.tuxee.net/2005`       |
41 | Version  | `tx--main--0.1`                    |
42 | ArchZoom | [Web interface http://arch.tuxee.net/archzoom.cgi/frederic@jolliton.com--2005-main/tx--main--0.1] |
43
44 = Installation =
45
46 The script `install` will install `tx` into `/opt/tuxeenet` directory,
47 and will add a link (`.pth`) from Python directory to make `tx`
48 directly reachable by `tuxeenet.tx` name.
49
50 = Overview =
51
52 *Note*: The special variable `_` is the result of the previous computation.
53
54 == Generating tree with tags ==
55
56 Importing the `tags` object as `w`:
57
58 -=-=-
59 >>> from tuxeenet.tx.tags import tags as w
60 -=-=-
61
62 then generating a tree and serializing it:
63
64 -=-=-
65 >>> w.html( w.head( w.title( 'Hello, World!' ) ) , w.body( 'bla bla bla' ) )
66 >>> _.serialize()
67 '<html><head><title>Hello, World!</title></head><body>bla bla bla</body></html>'
68 -=-=-
69
70 The `_doc_` and `_comment_` name have special meanings. The former
71 create a `Document` node, while the latter create a `Comment` node.
72
73 -=-=-
74 >>> w._doc_( w.foo( w._comment_( ' this is a comment ' ) ) , w.bar( 'quux&baz' ) ).serialize()
75 '<foo><!-- this is a comment --></foo><bar>quux&amp;baz</bar>'
76
77 >>> w.foo( w._comment_( ' a comment ' ) , 'bar' , id = 'contents' , width = '92' ).serialize()
78 '<foo id="contents" width="92"><!-- a comment -->bar</foo>'
79 -=-=-
80
81 For attribute names, double `_` are translated to `:` and single `_`
82 are translated to `-`. A `_` starting a name is dropped (useful when
83 name match a Python keyword.)
84
85 -=-=-
86 >>> w._return( 'Et voila !' , xml__lang = 'fr' , _class = 'rt2' ).serialize()
87 '<return xml:lang="fr" class="rt2">Et voila !</return>'
88 -=-=-
89
90 == Generating tree from nodes ==
91
92 The `tags` object from the `tags` module is just a convenient way for
93 building tree, but in reality it just construct `Element`,
94 `Attribute`, `Text`,.. nodes implicitly.
95
96 Here is how to generate tree directly from these objects:
97
98 -=-=-
99 >>> from tuxeenet.tx.nodes import *
100 >>> a = Attribute( 'id' , 'contents' )
101 >>> b = Attribute( 'width' , '92' )
102 >>> c = Comment( ' a comment ' )
103 >>> d = Text( 'bar' )
104 >>> e = Element( 'foo' , ( a , b ) , ( c , d ) ) # name, attributes, children
105 >>> e.serialize()
106 '<foo id="contents" width="92"><!-- a comment -->bar</foo>'
107 -=-=-
108
109 == Parsing HTML ==
110
111 -=-=-
112 >>> from urllib import urlopen
113 >>> from tuxeenet.tx.htmltree import parse
114 -=-=-
115
116 Fetching and parsing the [http://slashdot.org] page:
117
118 -=-=-
119 >>> doc = parse( urlopen( 'http://slashdot.org/' ).read() )
120 >>> doc
121 <Document with 2 children>
122 -=-=-
123
124 Examples below will use this `doc` variable. Note that you will not
125 necessary get the exact same output, since the page (the homepage of
126 Slashdot) can change of course.
127
128 == Debug tree ==
129
130 From the `doc`, we can output a verbose tree to show document
131 structure with nodes type:
132
133 -=-=-
134 >>> print doc.asDebug( maxChildren = 2 )
135 DOCUMENT[0] with 2484 nodes
136   TEXT[1] '\n'
137   ELEMENT[2] html
138     ELEMENT[3] head
139       ELEMENT[4] title
140         TEXT[5] 'Slashdot: News for nerds, stuff that matters'
141       ELEMENT[6] link
142         ATTRIBUTE[7] rel = `top`
143         ATTRIBUTE[8] title = `News for nerds, stuff that matters`
144         ATTRIBUTE[9] href = `//slashdot.org/`
145       [.. and 13 more children ..]
146     TEXT[36] '\n'
147     [.. and 2 more children ..]
148 -=-=-
149
150 == Querying with XPath ==
151
152 A large part of XPath 2.0 is available.
153
154 For example, to extract the string value of the attribute `title` of
155 an element `link` which have also an attribute `rel` of value `top`,
156 and where this element `link` is a child of element `head` itself
157 child of root element `html`:
158
159 -=-=-
160 >>> doc[ '/html/head/link[@rel="top"]/@title/string()' ]
161 >>> tuple(_) # Convert resulting sequence to a tuple
162 (u'News for nerds, stuff that matters',)
163 -=-=-
164
165 (Note that an Unicode string is returned.)
166
167 == Building RXP-like structure ==
168
169 The `pyRXP` module is a wrapper for the `RXP` XML parser. `tx` provide
170 a way to convert an existing tree to the type of structure used by
171 `pyRXP`.
172
173 This is really only useful for compatibility purpose with `RXP`
174 module.
175
176 -=-=-
177 >>> sequence = doc[ '//font[@face="verdana"]' ]
178 >>> sequence[ 0 ].asRxp()
179 ('font', {'color': '#001670', 'face': 'verdana'}, [u'\xa0', ('b', None, ['OSTG'], None)], None)
180 -=-=-
181
182 and translating back to a `tx` tree:
183
184 -=-=-
185 >>> doc = ('font', {'color': '#001670', 'face': 'verdana'}, [u'\xa0', ('b', None, ['OSTG'], None)], None)
186 >>> from tuxeenet.tx.rxpcompat import fromRxp
187 >>> fromRxp( doc )
188 <Element font with 2 attributes and 2 children>
189 >>> fromRxp( doc ).serialize()
190 '<font color="#001670" face="verdana">\xc2\xa0<b>OSTG</b></font>'
191 -=-=-
192
193 *Important note*: You may have noticed that the `\xa0` is printed as
194 `\xc2\xa0'. It's because `.serialize()` produce string with `utf-8`
195 encoding by default.
196
197 == Translating XQuery and XSLT examples to Python ==
198
199 First 2 examples show how to translate XQuery to Python, while the
200 third example show how to translate XSLT to Python.
201
202 These examples are important to show that specialized languages are
203 not necessary for processing XML document with same power as
204 XQuery, XSLT,..
205
206 === XQuery example 1 ===
207
208 Python could be used to fully replace XQuery complex operations.
209
210 Taking the following example from the XQuery spec:
211
212 -=-=-
213 let $i := <tool>wrench</tool>
214 let $o := <order> {$i} <quantity>5</quantity> </order>
215 let $odoc := document ($o)
216 let $newi := $o/tool
217 -=-=-
218
219 Which is followed by these expected results:
220
221  * `fn:root($i)` returns `$i`
222
223  * `fn:root($o/quantity)` returns `$o`
224
225  * `fn:root($odoc//quantity)` returns `$odoc`
226
227  * `fn:root($newi)` returns `$o`
228
229 Some notes:
230
231  * The XQuery version implictly copy tree, but in Python we have to
232  ask it explicitly with `clone` member function.
233
234  * We have to call `.finalize()` on tree which are not `Document`
235  because by default any node which is not `Document` is considered
236  part of another tree, and not a root of a tree by itself.
237
238  * We have to run `fnRoot` (the `fn:root` function) in the
239  `nullContext` explicitly.
240
241 First part translated in Python with `tx` modules (using `tags` module):
242
243 -=-=-
244 i = w.tool( 'wrench' )
245 o = w.order( i.clone() , w.quantity( '5' ) )
246 odoc = o.clone()
247 newi = o/'tool'  # Notice the use of the '/' operator to use XPath
248 -=-=-
249
250 Declare trees as standalone:
251
252 -=-=-
253 i.finalize()
254 o.finalize()
255 odoc.finalize()
256 -=-=-
257
258 (Note: We make a `root` function to simplify `fnRoot` usage.)
259
260 -=-=-
261 >>> from tuxeenet.tx.sequence import Sequence
262 >>> from tuxeenet.tx.context import Context
263 >>> from tuxeenet.tx.xpathfn import fnRoot
264 >>> root = lambda node : fnRoot( Context() , Sequence( node ) )
265 -=-=-
266
267 Then we can check expected results:
268
269 -=-=-
270 >>> assert root( i ) == Sequence( i )
271 >>> assert root( o/'quantity' ) == Sequence( o )
272 >>> assert root( odoc/'.//quantity' ) == Sequence( odoc ) # The '.' is important
273 >>> assert root( newi ) == Sequence( o )
274 -=-=-
275
276 === XQuery example 2 ===
277
278 An example from [http://www.perfectxml.com/XQuery.html], with the
279 `books.xml` document used below:
280
281 -=-=-
282 <bib>
283     <book year="1994">
284         <title>TCP/IP Illustrated</title>
285         <author>
286             <last>Stevens</last>
287             <first>W.</first>
288         </author>
289         <publisher>Addison-Wesley</publisher>
290         <price>65.95</price>
291     </book>
292
293     <book year="1992">
294         <title>Advanced Programming in the UNIX Environment</title>
295         <author>
296             <last>Stevens</last>
297             <first>W.</first>
298         </author>
299         <publisher>Addison-Wesley</publisher>
300         <price>65.95</price>
301     </book>
302
303     <book year="2000">
304         <title>Data on the Web</title>
305         <author>
306             <last>Abiteboul</last>
307             <first>Serge</first>
308         </author>
309         <author>
310             <last>Buneman</last>
311             <first>Peter</first>
312         </author>
313         <author>
314             <last>Suciu</last>
315             <first>Dan</first>
316         </author>
317         <publisher>Morgan Kaufmann Publishers</publisher>
318         <price>65.95</price>
319     </book>
320
321     <book year="1999">
322         <title>The Economics of Technology and Content for Digital TV</title>
323         <editor>
324             <last>Gerbarg</last>
325             <first>Darcy</first>
326             <affiliation>CITI</affiliation>
327         </editor>
328         <publisher>Kluwer Academic Publishers</publisher>
329         <price>129.95</price>
330     </book>
331 </bib>
332 -=-=-
333
334 The XQuery source code:
335
336 -=-=-
337 <listings>
338   {
339      for $p in distinct-values(doc("books.xml")//publisher)
340      order by $p
341      return
342          <result>
343          { $p }
344          {
345               for $b in doc("books.xml")/bib/book
346               where $b/publisher = $p
347               order by $b/title
348               return $b/title
349          }
350          </result>
351   }
352 </listings>
353 -=-=-
354
355 Translation to Python using `tx`, supposing `books.xml` XML tree is in
356 `doc` variable:
357
358 -=-=-
359 w.listings(
360         w.result( p , sorted( b/'title'
361                               for b in doc/'/bib/book'
362                               if b/'publisher' == p ) )
363         for p in sorted( doc/'distinct-values(//publisher)' ) )
364 -=-=-
365
366 Which construct the following document: (indentation added manually)
367
368 -=-=-
369 <listings>
370   <result>
371     <publisher>Addison-Wesley</publisher>
372     <title>Advanced Programming in the UNIX Environment</title>
373     <title>TCP/IP Illustrated</title>
374   </result>
375   <result>
376     <publisher>Kluwer Academic Publishers</publisher>
377     <title>The Economics of Technology and Content for Digital TV</title>
378   </result>
379   <result>
380     <publisher>Morgan Kaufmann Publishers</publisher>
381     <title>Data on the Web</title>
382   </result>
383 </listings>
384 -=-=-
385
386 === XSLT example 1 ===
387
388 Doing XSLT like transformation.
389
390 Taking example from [http://www.adp-gmbh.ch/xml/xslt_examples.html],
391 with document:
392
393 -=-=-
394 <?xml version="1.0" ?>
395
396 <famous-persons>
397   <persons category="medicine">
398     <person>
399       <firstname> Edward   </firstname>
400       <name>      Jenner   </name>
401     </person>
402     <person>
403       <firstname> Gertrude </firstname>
404       <name>      Elion    </name>
405     </person>
406   </persons>
407   <persons category="computer science">
408     <person>
409       <firstname> Charles  </firstname>
410       <name>      Babbage  </name>
411     </person>
412     <person>
413       <firstname> Alan     </firstname>
414       <name>      Touring  </name>
415     </person>
416     <person>
417       <firstname> Ada      </firstname>
418       <name>      Byron    </name>
419     </person>
420   </persons>
421   <persons category="astronomy">
422     <person>
423       <firstname> Tycho    </firstname>
424       <name>      Brahe    </name>
425     </person>
426     <person>
427       <firstname> Johannes </firstname>
428       <name>      Kepler   </name>
429     </person>
430     <person>
431       <firstname> Galileo  </firstname>
432       <name>      Galilei  </name>
433     </person>
434   </persons>
435 </famous-persons>
436 -=-=-
437
438 and stylesheet:
439
440 -=-=-
441 <?xml version="1.0" ?>
442 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
443
444   <xsl:template match="/">
445      <html><head><title>Sorting example</title></head><body>
446      <xsl:apply-templates select="famous-persons/persons">
447        <xsl:sort select="@category" />
448      </xsl:apply-templates>
449      </body></html>
450   </xsl:template>
451
452   <xsl:template match="persons">
453     <h2><xsl:value-of select="@category" /></h2>
454     <ul>
455        <xsl:apply-templates select="person">
456          <xsl:sort select="name"      />
457          <xsl:sort select="firstname" />
458        </xsl:apply-templates>
459     </ul>
460   </xsl:template>
461
462   <xsl:template match="person">
463         <xsl:text disable-output-escaping="yes">
464             &lt;li&gt;
465         </xsl:text>
466         <b><xsl:value-of select="name"      /></b>
467            <xsl:value-of select="firstname" />
468   </xsl:template>
469
470 </xsl:stylesheet>
471 -=-=-
472
473 could be translated to Python as follow:
474
475 -=-=-
476 import operator as op # for op.itemgetter
477 def transform( node ) :
478     if node.match( '/' ) :
479         return w.html( w.head( w.title( 'Sorting example' ) ) ,
480                        w.body( map( transform ,
481                                     sorted( node/'famous-persons/persons' ,
482                                             key = op.itemgetter( '@category' ) ) ) ) )
483     elif node.match( 'persons' ) :
484         return ( w.h2( node/'@category/string()' ) ,
485                  w.ul( map( transform ,
486                             sorted( sorted( node/'person' ,
487                                             key = op.itemgetter( 'firstname' ) ) ,
488                                     key = op.itemgetter( 'name' ) ) ) ) )
489     elif node.match( 'person' ) :
490         return w.li( w.b( node/'name/string()' ) ,
491                      node/'firstname/string()' )
492
493 result = w._doc_( transform( doc ) )
494 -=-=-
495
496 which produce:
497
498 -=-=-
499 DOCUMENT[0] with 47 nodes
500   ELEMENT[1] html
501     ELEMENT[2] head
502       ELEMENT[3] title
503         TEXT[4] 'Sorting example'
504     ELEMENT[5] body
505       ELEMENT[6] h2
506         TEXT[7] u'astronomy'
507       ELEMENT[8] ul
508         ELEMENT[9] li
509           ELEMENT[10] b
510             TEXT[11] u'      Brahe    '
511           TEXT[12] u' Tycho    '
512         ELEMENT[13] li
513           ELEMENT[14] b
514             TEXT[15] u'      Galilei  '
515           TEXT[16] u' Galileo  '
516         ELEMENT[17] li
517           ELEMENT[18] b
518             TEXT[19] u'      Kepler   '
519           TEXT[20] u' Johannes '
520       ELEMENT[21] h2
521         TEXT[22] u'computer science'
522       ELEMENT[23] ul
523         ELEMENT[24] li
524           ELEMENT[25] b
525             TEXT[26] u'      Babbage  '
526           TEXT[27] u' Charles  '
527   [...]
528 -=-=-
529
530 *Note* that we first sort by `firstname` then by `name`. Also note
531 that such example are just here to show that we can translate XSLT or
532 XQuery to Python, but that not necessary give optimized alternative.
533
534 *Note* also that for emulating XSLT we could have to add other node
535 match at end of transform such as:
536
537 -=-=-
538     [...]
539     elif node.match( '@*|text()' ) :
540         return node
541     else :
542         return map( transform , node/'node()' )
543 -=-=-
544
545 but in our example that was not necessary.
546
547 = Nodes =
548
549 An XML document is a tree of nodes.
550
551 With actual implementation, nodes are supposed to be constant. Once
552 created, they're not expected to be updated. Mainly because `Document`
553 node number all its descendants and inserting some children somewhere
554 in the document would need this numbering to be redone at some point.
555
556 FIXME: Tag the root node with some flag (or the node from which we
557 need to restart the numbering -the lowest one if several descendant
558 are updated) to let it know that it should number again its descendant
559 when needed ? In the meantime, numbers new children with unique number
560 (the parent one) ?
561
562 == Document ==
563
564 A `Document` node can contains any nodes except `Document` and `Attribute`.
565
566 Constructor: `Document( children = () , finalize = True )`
567
568 If `finalize` is `True` (default value), then `Document` numbers all
569 its descendant and mark their `root` pointers to it, hence making the
570 `Document` node the root node of the tree.
571
572 == Element ==
573
574 An `Element` node can contains any nodes except `Document`.
575
576 Constructor: `Element( name , attributes = () , children = () , finalize = False )`
577
578 == Attribute ==
579
580 An `Attribute` node is only allowed inside an `Element` node.
581
582 Constructor: `Attribute( name , value )`
583
584 == Comment ==
585
586 A `Comment` node is only allowed inside a `Document` or an `Element` node.
587
588 Constructor: `Comment( contents )`
589
590 *Restriction*: In XML, a comment cannot contains `--` nor ends with `-`. 
591
592 == Text ==
593
594 A `Text` node is only allowed inside a `Document` or an `Element` node.
595
596 Constructor: `Text( contents )`
597
598 = Tags =
599
600 The `tags` module provide the `w` object which can be used to create
601 document tree with Python syntax.
602
603 A string is automatically translated to a `Node` element.
604
605 Otherwise, a node of the type `Document`, `Element` or `Comment` is
606 create with the general syntax: `w.name( child1 , .. , attribute1 = value1 , .. )`.
607 Attributes make sense only for `Element` node type however.
608
609 It's also possible to pass a function, which take no parameters and
610 should return a correct XML tree. This function will be called at
611 serialization time.
612
613 -=-=-
614 w._doc_(
615   w.html(
616     w._comment_( ' Header ' ) ,
617     w.head(
618       w.title( 'This is a example page' ) ,
619       w.link( rel = 'stylesheet' , href = '/default-style.css' , title = 'Default style' ) ,
620       w.meta( http_equiv = 'Content-type' , content = 'text/html' , charset = 'utf-8' ) ) ,
621     w._comment_( ' Body ' ) ,
622     w.body(
623       w.h1( 'Section 1' ) ,
624       w.h2( 'Section 1.1' ) ,
625       'Bla bla bla.' ,
626       w.h2( 'Section 1.2' ) ,
627       'Bla bla bla.' ) ) )
628 -=-=-
629
630 generate a document which once serialized give:
631
632 -=-=-
633 <html>
634   <!-- Header -->
635   <head>
636     <title>This is a example page</title>
637     <link href="/default-style.css" rel="stylesheet" title="Default style"/>
638     <meta content="text/html" charset="utf-8" http-equiv="Content-type"/>
639   </head>
640   <!-- Body -->
641   <body>
642     <h1>Section 1</h1>
643     <h2>Section 1.1</h2>
644     Bla bla bla.
645     <h2>Section 1.2</h2>
646     Bla bla bla.
647   </body>
648 </html>
649 -=-=-
650
651 *Note* that the result here is split into several lines and indented
652 while in reality the result is just one line of text since no `\n`
653 (new line) characters are part of the document.
654
655 Same document presented with `debug` output:
656
657 -=-=-
658 >>> print doc.asDebug()
659 DOCUMENT[0] with 24 nodes
660   ELEMENT[1] html
661     COMMENT[2] ' Header '
662     ELEMENT[3] head
663       ELEMENT[4] title
664         TEXT[5] 'This is a example page'
665       ELEMENT[6] link
666         ATTRIBUTE[7] href = `/default-style.css`
667         ATTRIBUTE[8] rel = `stylesheet`
668         ATTRIBUTE[9] title = `Default style`
669       ELEMENT[10] meta
670         ATTRIBUTE[11] content = `text/html`
671         ATTRIBUTE[12] charset = `utf-8`
672         ATTRIBUTE[13] http-equiv = `Content-type`
673     COMMENT[14] ' Body '
674     ELEMENT[15] body
675       ELEMENT[16] h1
676         TEXT[17] 'Section 1'
677       ELEMENT[18] h2
678         TEXT[19] 'Section 1.1'
679       TEXT[20] 'Bla bla bla.'
680       ELEMENT[21] h2
681         TEXT[22] 'Section 1.2'
682       TEXT[23] 'Bla bla bla.'
683 -=-=-
684
685 Example of deferred function:
686
687 -=-=-
688 count = 0
689 def foo() :
690   global count
691   count += 1
692   return w.p( "I'm generated %d time(s)." % count )
693
694 doc = w.body( foo )
695
696 print doc.serialize()
697 print doc.serialize()
698 print doc.serialize()
699 -=-=-
700
701 produce:
702
703 -=-=-
704 <body><p>I'm generated 1 time(s).</p></body>
705 <body><p>I'm generated 2 time(s).</p></body>
706 <body><p>I'm generated 3 time(s).</p></body>
707 -=-=-
708
709 = XPath =
710
711 The module `xpath` provide a large subset of XPath 2.0.
712
713 Unsupported features are:
714
715  * `instance of`, `treat as`, `castable as`, `cast as` operators,
716
717  * `processing-instruction(..)` and `namespace(..)` tests,
718
719  * `schema-attribute(..)` and `schema-element(..)` tests,
720
721  * date support or any type except `string`, `float` and `boolean`
722  (`decimal` and `double` are considered as `float`.)
723
724 == Compilation ==
725
726 The module `xpath` contains a `compile` function which take a XPath
727 expression and return a function taking a context as argument and
728 returning a sequence as result of the evaluation.
729
730 == XPath class wrapper ==
731
732 `XPath` class is a convenient (small) wrapper around `compile` function.
733
734 An instance of the `XPath` class is created with a XPath expression.
735 To evaluate the XPath expression, use `eval` member function with a
736 optional context node.
737
738 -=-=-
739 >>> from tuxeenet.tx.xpath import XPath
740 >>> x1 = XPath( '//@href' )
741 >>> x1.eval( doc ) # return all href attribute in document 'doc'
742 -=-=-
743
744 == Shortcut syntax ==
745
746 The `Node` base class define operator `[]` and `/` to make it easy to
747 query a tree with XPath expression.
748
749 -=-=-
750 >>> doc[ '/html/head/link[@rel="top"]/@title/string()' ]
751 -=-=-
752
753 or
754
755 -=-=-
756 >>> doc / '/html/head/link[@rel="top"]/@title/string()'
757 -=-=-
758
759 are equivalent, while however the latter form could be written:
760
761 -=-=-
762 >>> doc/'html/head/link[@rel="top"]/@title/string()'
763 -=-=-
764
765 (without the initial `/` in the XPath expression) since `doc` is
766 already the root node (for this example.)
767
768 This is almost the direct translation of the following XQuery code:
769
770 -=-=-
771 $doc/html/head/link[@rel="top"]/@title/string()
772 -=-=-
773
774 == XPath prompt ==
775
776 For debugging purpose, a "XPath prompt" application is available
777 to interactively evaluate XPath expressions.
778
779 -=-=-
780 $ tx-prompt
781 XPath TX 0.1 - (c)2005  Frederic Jolliton <frederic@jolliton.com>
782
783 XPath2.0> 
784 -=-=-
785
786 Then any supported XPath expression can be entered.
787
788 There is some special command:
789
790  * `\.' followed by an URI (filename, URL,..) to load a document
791  as default context item,
792
793  * `\d` switch to default display mode,
794
795  * `\f` switch to full display mode,
796
797  * `\s` switch to short display mode,
798
799  * `\i` switch to inline display mode,
800
801  * `\l` switch the display of the location of resulting node on/off,
802
803  * `\e` followed by an XPath expression show its syntax tree,
804
805  * `\x` toggle query duration display,
806
807  * `\v` display names of variables currently defined,
808
809  * `\o` toggle query optimization on/off,
810
811  * `\c` flush query cache (useful after `\o` command to flush already
812  compiled expression),
813
814  * `$name := expression` evaluate XPath `expression` and store
815  the result into variable named `name`.
816
817 The `$current` variable is used as context item when evaluating XPath
818 expression.
819
820 Producing sequence of numbers:
821
822 -=-=-
823 XPath2.0> 1+2
824 3
825 XPath2.0> 18 div 4
826 4.5
827 XPath2.0> (12+3)*5
828 75
829 XPath2.0> 1, 2, 3
830 Sequence(1, 2, 3)
831 XPath2.0> 12, 17 to 20, 22
832 Sequence(12, 17, 18, 19, 20, 22)
833 -=-=-
834
835 Using `if` ternary operator:
836
837 -=-=-
838 XPath2.0> if (1=1) then "ok" else "failed"
839 ok
840 XPath2.0> if (1!=1) then "ok" else "failed"
841 failed
842 -=-=-
843
844 Working with XML tree:
845
846  * Fetching document
847
848  -=-=-
849 XPath2.0> $current := doc('http://slashdot.org')
850 -=-=-
851
852  * Extracting the title:
853
854  -=-=-
855 XPath2.0> /html/head/title
856 <Element title with 0 attributes and 1 children>
857 XPath2.0> \i
858 [inline]
859 XPath2.0> /html/head/title
860 <title>Slashdot: News for nerds, stuff that matters</title>
861 -=-=-
862
863  * Extracting articles title (pre-september 2005):
864
865  -=-=-
866 XPath2.0> \s
867 [short]
868 XPath2.0> //td[@align='LEFT']//font[@color]/b[text()]/string()
869 1 STRING Ask Slashdot: GSM and Asterisk Integration?
870 2 STRING Hardware: Free WiFi Trend Continues
871 3 STRING Linux: Winemaker Drinks To Linux
872 4 STRING Games: World of Warcraft Card Game Coming Soon
873 5 STRING Your Rights Online: Is Your Boss a Psychopath?
874 6 STRING Linux: Australian Linux Trademark Holds Water
875 7 STRING Science: Nanotubes Start to Show their Promise
876 -=-=-
877
878  * Extracting articles title (post-september 2005):
879
880  -=-=-
881 XPath2.0> \s
882 [short]
883 XPath2.0> //div[@class='generaltitle']/normalize-space()
884 1 STRING Tivo Institutes 1 Year Service Contracts
885 2 STRING Politics: US Senate Allows NASA To Buy Soyuz Vehicles
886 3 STRING IT: Reconnaissance In Virtual Space
887 4 STRING Your Rights Online: FBI Agents Put New Focus on Deviant Porn
888 5 STRING Ask Slashdot: Top 50 Science Fiction TV Shows
889 6 STRING Your Rights Online: Business At The Price Of Freedom
890 7 STRING Apple: Music Exec Fires Back At Apple CEO
891 8 STRING Science: Grammar Traces Language Roots
892 9 STRING Developers: RMS Previews GPL3 Terms
893 10 STRING Massachusetts Finalizes OpenDocument Standard Plan
894 11 STRING Developers: Palm Teams With Microsoft for Smart Phone
895 12 STRING Developers: Why Vista Had To Be Rebuilt From Scratch
896 13 STRING Hardware: Nabaztag the WiFi Bunny
897 14 STRING Revamping the Movie Distribution Chain
898 15 STRING Politics: Municipal Broadband Projects Spread Across U.S.
899 -=-=-
900
901  * Extracting RSS title from an external document:
902
903  -=-=-
904 XPath2.0> \s
905 [short]
906 XPath2.0> doc('http://rss.slashdot.org/Slashdot/slashdot')//item/title/string()
907 1 STRING Tivo Institutes 1 Year Service Contracts
908 2 STRING US Senate Allows NASA To Buy Soyuz Vehicles
909 3 STRING Reconnaissance In Virtual Space
910 4 STRING FBI Agents Put New Focus on Deviant Porn
911 5 STRING Top 50 Science Fiction TV Shows
912 6 STRING Business At The Price Of Freedom
913 7 STRING Music Exec Fires Back At Apple CEO
914 8 STRING Grammar Traces Language Roots
915 9 STRING RMS Previews GPL3 Terms
916 10 STRING Massachusetts Finalizes OpenDocument Standard Plan
917 -=-=-
918
919  * Extracting 1st, 3rd and 7th comment of the document:
920
921  -=-=-
922 XPath2.0> \i
923 [inline]
924 XPath2.0> \x
925 Timer On
926 XPath2.0> (//comment())[position()=(1,3,7)]
927 <!-- BEGIN: AdSolution-Tag 4.2: Global-Code -->
928 <!-- begin OSTG navbar -->
929 <!-- end ad code -->
930
931 -- 0.038567s(parse) + 0.006351s(eval) --
932 -=-=-
933
934  * Querying distinct `value` attributes of the document:
935
936  -=-=-
937 XPath2.0> \f
938 [full]
939 XPath2.0> distinct-values(//@value)
940 1 NODE ATTRIBUTE[1856] value = ``
941 2 NODE ATTRIBUTE[1866] value = `//slashdot.org/`
942 3 NODE ATTRIBUTE[1871] value = `userlogin`
943 4 NODE ATTRIBUTE[1882] value = `yes`
944 5 NODE ATTRIBUTE[1889] value = `Log in`
945 6 NODE ATTRIBUTE[1940] value = `1307`
946 7 NODE ATTRIBUTE[1945] value = `mainpage`
947 8 NODE ATTRIBUTE[1954] value = `1`
948 9 NODE ATTRIBUTE[1960] value = `2`
949 10 NODE ATTRIBUTE[1966] value = `3`
950 11 NODE ATTRIBUTE[1972] value = `4`
951 12 NODE ATTRIBUTE[1978] value = `5`
952 13 NODE ATTRIBUTE[1984] value = `6`
953 14 NODE ATTRIBUTE[1990] value = `7`
954 15 NODE ATTRIBUTE[1995] value = `Vote`
955 16 NODE ATTRIBUTE[2346] value = `freshmeat.net`
956 17 NODE ATTRIBUTE[2439] value = `Search`
957 -- 0.012589s(parse) + 0.055717s(eval) --
958 -=-=-
959
960  * Computing number of pixels covered by `img` elements
961
962  -=-=-
963 XPath2.0> \d
964 [default]
965 XPath2.0> for $img in //img return $img/@width * $img/@height
966 Sequence(19800, 4800, 2862, 4307, 4225, 4602, 5, 208, 4800, 208, 2862, \
967     208, 4307, 208, 4800, 208, 4225, 208, 4602, 208, 4125, 208, 6216, 208, \
968     5184, 208, 5070, 230, 230, 230, 230, 230, 230, 230, 5, nan, 1)
969 -=-=-
970
971  or alternatively:
972
973  -=-=-
974 XPath2.0> //img/(@height * @width)
975 Sequence(19800, 4800, 2862, 4307, 4225, 4602, 5, 208, 4800, 208, 2862, \
976     208, 4307, 208, 4800, 208, 4225, 208, 4602, 208, 4125, 208, 6216, 208, \
977     5184, 208, 5070, 230, 230, 230, 230, 230, 230, 230, 5, nan, 1)
978 -=-=-
979
980  * Looking for `rel` attribute, with location (the XPath expression
981  that could be used to identify precisely the node in the resulting
982  sequence):
983
984  -=-=-
985 XPath2.0> \f
986 [full]
987 XPath2.0> \l
988 Location on
989 XPath2.0> //@rel
990 1 NODE /html/head/link[1]/@rel
991 ATTRIBUTE[7] rel = `top`
992 2 NODE /html/head/link[2]/@rel
993 ATTRIBUTE[12] rel = `search`
994 3 NODE /html/head/link[3]/@rel
995 ATTRIBUTE[17] rel = `alternate`
996 4 NODE /html/head/link[4]/@rel
997 ATTRIBUTE[23] rel = `shortcut icon`
998 -=-=-
999
1000 Displaying parse tree of some expressions (mainly useful for debugging purpose !):
1001
1002 -=-=-
1003 XPath2.0> \e 1+2
1004 (exprlist (+ (path (integer "1"))
1005              (path (integer "2"))))
1006
1007 XPath2.0> \e foo() + bar()
1008 (exprlist (+ (path (call "foo"))
1009              (path (call "bar"))))
1010
1011 XPath2.0> \e ../foo | @bar
1012 (exprlist (union (path (parent (node))
1013                        (child (element "foo")))
1014                  (path (attribute (attribute "bar")))))
1015
1016 XPath2.0> \e /html/child::head/element(title)/string()
1017 (exprlist (path "/"
1018                 (child (element "html"))
1019                 (child (element "head"))
1020                 (child (element "title"))
1021                 (call "string")))
1022
1023 XPath2.0> \e for $att in distinct-values(//@*/name()) return ($att,count(//attribute()[name()=$att]))
1024 (exprlist (for ((att (path (call "distinct-values"
1025                                  (path "/"
1026                                        (descendant-or-self (node))
1027                                        (attribute (attribute "*"))
1028                                        (call "name"))))))
1029                (path (exprlist (path (varref "att"))
1030                                (path (call "count"
1031                                            (path "/"
1032                                                  (descendant-or-self (node))
1033                                                  (predicates (attribute (attribute))
1034                                                              (exprlist (= (path (call "name"))
1035                                                                           (path (varref "att"))))))))))))
1036 -=-=-
1037
1038 = Pattern =
1039
1040 Patterns are used for XSLT nodes matching.
1041
1042 -=-=-
1043 node.match( 'a/b' ) # Return True if node is an element `b` with a parent element `a`
1044 -=-=-
1045
1046 *Note*: Internally patterns are translated to XPath expression. Such
1047 expression return the empty sequence if pattern doesn't match the
1048 node. For example, `a/b` become something like
1049 `self::element(b)/parent::element(a)`.
1050
1051 = Parsing HTML =
1052
1053 The `htmlparser` module provide a replacement for `HTMLParser` module
1054 provided with Python. The main difference is that the `tx` module
1055 never throw an error. It is able to parse the worst HTML documents.
1056
1057 *Note*: To parse regular XML document, a parser like `rxp` could be
1058 used instead with help of `rxpcompat` module, because the `HTMLParser`
1059 is not designed for XML and is not necessary good enough for this
1060 purpose. Your mileage may vary.
1061
1062 The `htmltree` module use the `htmlparser` module and produce
1063 `Document`.
1064
1065 -=-=-
1066 >>> import sys
1067 >>> from tuxeenet.tx.htmltree import parse
1068 >>> parse( '<html><test></html>' ).asDebug( file = sys.stdout )
1069 DOCUMENT[0] with 3 nodes
1070   ELEMENT[1] html
1071     ELEMENT[2] test
1072 >>> parse( '<html>some ~< bad <p>document</b> /><p>really' ).asDebug( file = sys.stdout )
1073 DOCUMENT[0] with 7 nodes
1074   ELEMENT[1] html
1075     TEXT[2] 'some ~< bad '
1076     ELEMENT[3] p
1077       TEXT[4] 'document />'
1078     ELEMENT[5] p
1079       TEXT[6] 'really'
1080 >>> parse( '<html>some ~< bad <p>document</b> /><p>really' ).serialize()
1081 '<html>some ~&lt; bad <p>document /></p><p>really</p></html>'
1082 -=-=-