diff --git a/docs/topics/xpath-tutorial.rst b/docs/topics/xpath-tutorial.rst index 1a8e42d2d..936295384 100644 --- a/docs/topics/xpath-tutorial.rst +++ b/docs/topics/xpath-tutorial.rst @@ -1599,6 +1599,11 @@ This is what these axes select in our ASCII-tree representation: (``∪`` denoting the "union" for node-sets.) +Attribute axis +~~~~~~~~~~~~~~ + +TODO. + Node tests ---------- @@ -1656,6 +1661,39 @@ a node test can be: selected with ``self::*``) and recursively gets text content of children, children of children and so on. +Abbreviation cheatsheet +----------------------- + +.. list-table:: + :header-rows: 1 + + * - Abbreviated step + - Meaning + + * - ``*`` (asterisk) + - all **element** nodes (i.e. not text nodes, not attribute nodes). + + Remember that ``.//*`` is not the same as ``.//node()``. + + Also, there's no ``element()`` node test. + + * - ``@*`` + - ``attribute::*`` (all attribute nodes) + + * - ``//`` + - ``/descendant-or-self::node()/`` (exactly this, nothing more, nothing less) + + so ``//*`` is not the same as ``/descendant-or-self::*`` + + * - ``.`` (a single dot) + - ``self::node()``, the context node; useful for making XPaths relative, + e.g. ``.//tr`` + + * - ``..`` (2 dots) + - ``parent::node()`` + +TODO: explain why ``//*`` is not the same as ``/descendant-or-self::*`` + Predicates ---------- @@ -1689,13 +1727,11 @@ Positional predicates ~~~~~~~~~~~~~~~~~~~~~ The first use-case is selecting nodes based on their position in a -node-set. +node-set. (Node-sets order depends on the axis, but let's consider that +the order of a node in a node-set is the document order.) -Node-sets order depends on the axis, but let's consider that -the order of a node in a node-set is the document order. - -Let's say we don't want the two paragraphs in the ``
`` we looked at -earlier, only the first one: +Remember the two paragraphs in the ``
`` we looked at +earlier: .. code:: pycon @@ -1703,6 +1739,9 @@ earlier, only the first one: [(1) '

This is a paragraph.

' (2) '

Is this a link?

'] +Let's say that we are not interested in the two paragraphs but only +the first one. You would use ``[1]`` as predicate: + .. xpathdemo:: //body/div/div/p[1] @@ -1728,6 +1767,8 @@ earlier, only the first one: +.. warning:: + Positions in XPath start from 1, not 0. If you want the last node in a node-set, you can use ``last()``: @@ -1836,30 +1877,209 @@ If you want the last node in a node-set, you can use ``last()``: Position ranges ^^^^^^^^^^^^^^^ -TODO: things like ``//table/tbody/tr[position() > 2]`` +Sometimes you need more than one node in a node-set but not all of them. +For that you can use boolean expression in your predicate in conjunction +with the ``position()`` function that returns the node's position. + +Let's change our sample HTML document a bit to include a list of five items. +Say we need all but the 1st and last one. +You can use ``[position()>1 and position()1 and position() + +
+
+
    +
  1. first item
  2. +
  3. second item
  4. +
  5. third item
  6. +
  7. fourth item
  8. +
  9. fifth item
  10. +
+
+
+ Nothing to add. + Except maybe this other link. + +
+
+ + + +``//body//div//li[position()>1 and position()`` element may have rows -- ``table/tr`` -- +with or without link anchors in them. +Within each ```` row, ``td/a`` selects something on some rows, +nothing for others: + +.. xpathdemo:: //table/tr [ td/a ] + + + +
+
+ + + + + +
first row
second row with a link
third row
fourth row with another link
+
+
+ + Boolean predicates ~~~~~~~~~~~~~~~~~~ -TODO: things like ``//table[count(tr)=10]`` +We saw boolean predicates earlier with positional ranges. But you can +craft complex boolean filters based on any features of nodes; structural +information on children or parent nodes, text values, position, etc. + +A simple example could be selecting a ```` that has a specific +number of rows, say, 5. You can simply count the number of rows: + + +.. xpathdemo:: //table[ count(tr)=5 ] + + + +
+
+
+ + + +
first row
second row
third row
+
+
+ + + + + + +
first row
second row
third row
fourth row
fifth row
+
+
+ + + Special case of string value tests ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO: things like ``//table[.//img/@src="pic.png"]`` or -``//table[th="Some headers"]`` +XPath also allows comparing string values of nodes within predicates. +If you use an equality operation with a location path and a string, +each node of the location path will be converted to its string value +and then compared with the string value to match. + +This may sound more obscure than it is. Say for example that you have +two tables, with different headers. You know the string value +of the header in the table you want, "The header I want." + +:: + +
+ + + + +
The header I want
second row
third row
+
+
+ + + + + + +
Another header I do NOT want
second row
third row
fourth row
fifth row
+
+ +To select the different headers, you would use ``//table/tr/th``. +You want the ```` so you can move the ``tr/th`` part inside +a predicate and compare it with string "The header I want". + +.. xpathdemo:: //table[ tr/th = "The header I want" ] + + + +
+
+
+ + + +
The header I want
second row
third row
+ +
+ + + + + + +
Another header I do NOT want
second row
third row
fourth row
fifth row
+
+ + + + +This kind of predicates also works for attribute values, e.g. testing +links to some website:: + + //body//p [ a/@href="http://www.example.com" ] Special trick for testing multiple node names ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In a predicate you can also test the current node of the node-set. +For example if you want to test for several wanted element names. This is when ``self::`` axis can be helpful. -TODO: things like ``./descendant-or-self::*[self::ul or self::ol]`` +One example is testing different kind of lists, ordered or unordered: + +.. xpathdemo:: //body//*[self::ul or self::ol]//li + + + +
+
+
    +
  1. first ordered item
  2. +
  3. second ordered item
  4. +
  5. third ordered item
  6. +
+
+
+ +
+
+ + + +.. note:: + Here we saw that predicates can also appear in the middle of the location + path. Indeed, predicates are (a optional) part of each location step. Nested predicates ~~~~~~~~~~~~~~~~~ @@ -1885,6 +2105,30 @@ can have predicates. So it's possible to end up with nested predicates. In fact, the above is equivalent to ``//div[p/a/@href="page2.html"]`` with no nesting: + .. xpathdemo:: //div[p [a/@href="page2.html"] ] + + + + This is a title + + + +
+
+

This is a paragraph.

+

Is this a link?

+
+ Apparently. +
+
+ Nothing to add. + Except maybe this other link. + +
+
+ + + .. xpathdemo:: //div[p/a/@href="page2.html"] @@ -1909,8 +2153,6 @@ with no nesting: - - Order of predicates is important ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1929,53 +2171,55 @@ The following 2 location paths produce different results: - ``//div[@class="second"][2]``: will output one ``
`` - ``//div[2][@class="second"]``: will select **nothing** - .. xpathdemo:: //div[2][@class="second"] +See for yourself: - - - This is a title - - - -
-
-

This is a paragraph.

-

Is this a link?

-
- Apparently. -
-
- Nothing to add. - Except maybe this other link. - -
-
- - +.. xpathdemo:: //div[2][@class="second"] - .. xpathdemo:: //div[@class="second"][2] + + + This is a title + + + +
+
+

This is a paragraph.

+

Is this a link?

+
+ Apparently. +
+
+ Nothing to add. + Except maybe this other link. + +
+
+ + - - - This is a title - - - -
-
-

This is a paragraph.

-

Is this a link?

-
- Apparently. -
-
- Nothing to add. - Except maybe this other link. - -
-
- - +.. xpathdemo:: //div[@class="second"][2] + + + + This is a title + + + +
+
+

This is a paragraph.

+

Is this a link?

+
+ Apparently. +
+
+ Nothing to add. + Except maybe this other link. + +
+
+ + The second produces nothing indeed. Why is that? @@ -1994,39 +2238,33 @@ On the contrary, ``//div[@class="second"][2]`` will first produce "second"). So the subsequent ``[2]`` predicate will never match with single-node node-sets (you cannot select the 2nd element of a 1-element list) -Abbreviation cheatsheet -~~~~~~~~~~~~~~~~~~~~~~~ -.. list-table:: - :header-rows: 1 +.. warning:: + Beware of ``position()`` in chained predicates. - * - Abbreviated step - - Meaning + Much like ``[...][2]`` is different from ``[2][...]``, if you chain + positional predicates, remember that the position is relative to + the node-set processed by the previous predicate. - * - ``*`` (asterisk) - - all **element** nodes (i.e. not text nodes, not attribute nodes). + For example, we saw that ``position()>1`` would filter out the first + nodes in a node-set. Chaining ``[position()>1]`` will remove the first + node each time it's used: - Remember that ``.//*`` is not the same as ``.//node()``. - - Also, there's no ``element()`` node test. - - * - ``@*`` - - ``attribute::*`` (all attribute nodes) - - * - ``//`` - - ``/descendant-or-self::node()/`` (exactly this, nothing more, nothing less) - - so ``//*`` is not the same as ``/descendant-or-self::*`` - - * - ``.`` (a single dot) - - ``self::node()``, the context node; useful for making XPaths relative, - e.g. ``.//tr`` - - * - ``..`` (2 dots) - - ``parent::node()`` - -TODO: explain why ``//*`` is not the same as ``/descendant-or-self::*`` + .. xpathdemo:: //ol/li[position()>1][position()>1][position()>1] + + +
+
    +
  1. first item
  2. +
  3. second item
  4. +
  5. third item
  6. +
  7. fourth item
  8. +
  9. fifth item
  10. +
+
+ + String functions ----------------