Differentiate between XML breadthFirst() traversal and * navigation (minor tweaks: closes #517)
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/3b77da35 Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/3b77da35 Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/3b77da35 Branch: refs/heads/master Commit: 3b77da35512d157c67e8d3e9185779ab4603d051 Parents: 2e3cd26 Author: paulk <[email protected]> Authored: Sat Apr 22 16:04:09 2017 +1000 Committer: paulk <[email protected]> Committed: Sat Apr 22 16:04:09 2017 +1000 ---------------------------------------------------------------------- .../groovy-xml/src/spec/doc/xml-userguide.adoc | 51 ++++++++++++++------ .../spec/test/UserGuideXmlSlurperTest.groovy | 23 +++++++-- 2 files changed, 54 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/groovy/blob/3b77da35/subprojects/groovy-xml/src/spec/doc/xml-userguide.adoc ---------------------------------------------------------------------- diff --git a/subprojects/groovy-xml/src/spec/doc/xml-userguide.adoc b/subprojects/groovy-xml/src/spec/doc/xml-userguide.adoc index c7fb1d7..84a9cce 100644 --- a/subprojects/groovy-xml/src/spec/doc/xml-userguide.adoc +++ b/subprojects/groovy-xml/src/spec/doc/xml-userguide.adoc @@ -219,30 +219,32 @@ the Both of them are equally valid. -=== Speed things up with * and ** navigation +=== Flexible navigation with children (\*), depthFirst (**) and breadthFirst -If you ever have used XPath you may have used expressions like +If you ever have used XPath, you may have used expressions like: -* `//` : Look everywhere * `/following-sibling::othernode` : Look for a node "othernode" in the same level +* `//` : Look everywhere -More or less we have their counterparts in `GPath` with the shortcuts `*` and `**`. +More or less we have their counterparts in GPath with the shortcuts `\*` (aka `children()`) and `**` (aka `depthFirst()`). The first example shows a simple use of `*`, which only iterates over the direct children of the node. [source,groovy] .Using * ---- -include::{rootProjectDir}/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy[tags=testBreadthFirst1,indent=0] +include::{rootProjectDir}/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy[tags=testChildren,indent=0] ---- -This test searches for any node at the same level of the "books" node -first. This operation roughly corresponds to the `breadthFirst()` method, except that it only stops at *one level* instead of continuing to the inner levels. +This test searches for any child nodes of the "books" node matching the given +condition. In a bit more detail, the expression says: _Look for any node with +a tag name equal to 'book' having an id with a value of '2' directly under +the 'books' node_. -The expression says *_Look for any node with a tag name -equals 'book' having an id with a value of '2' directly under the 'books' node_*. +This operation roughly corresponds to the `breadthFirst()` method, except that +it only stops at *one level* instead of continuing to the inner levels. -But what if we would like to look for a given value +What if we would like to look for a given value without having to know exactly where it is. Let's say that the only thing we know is the id of the author "Lewis Carroll" . How are we going to be able to find that book? Using `**` is the solution: @@ -253,20 +255,37 @@ we going to be able to find that book? Using `**` is the solution: include::{rootProjectDir}/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy[tags=testDepthFirst1,indent=0] ---- -`**` is the same as looking something *everywhere in the -tree from this point down*. In this case we've used the method -`find(Closure cl)` to find just the first occurrence. `**` corresponds to the `depthFirst()` method. +`**` is the same as looking for something _everywhere in the +tree from this point down_. In this case, we've used the method +`find(Closure cl)` to find just the first occurrence. -What if we want to collect all book's titles? +What if we want to collect all book's titles? That's easy, just use `findAll`: [source,groovy] -.depthFirst() ---- include::{rootProjectDir}/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy[tags=testDepthFirst2,indent=0] ---- +In the last two examples, `**` is used as a shortcut for the `depthFirst()` +method. It goes as far down the tree as it can while navigating down the +tree from a given node. The `breadthFirst()` method finishes off all nodes +on a given level before traversing down to the next level. + +The following example shows the difference between these two methods: + +[source,groovy] +.depthFirst() vs .breadthFirst +---- +include::{rootProjectDir}/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy[tags=testDepthVsBreadth,indent=0] +---- + +In this example, we search for any nodes with an id attribute with value 2 or 3. +There are both `book` and `author` nodes that match that criteria. The different +traversal orders will find the same nodes in each case but in different orders +corresponding to how the tree was traversed. + It is worth mentioning again that there are some useful methods -converting a node's value to an integer, float... etc. Those methods +converting a node's value to an integer, float, etc. Those methods could be convenient when doing comparisons like this: [source,groovy] http://git-wip-us.apache.org/repos/asf/groovy/blob/3b77da35/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy ---------------------------------------------------------------------- diff --git a/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy b/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy index 9d5901f..13e7252 100644 --- a/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy +++ b/subprojects/groovy-xml/src/spec/test/UserGuideXmlSlurperTest.groovy @@ -94,23 +94,25 @@ class UserGuideXmlSlurperTest extends GroovyTestCase { // end::testGettingAnAttributeText[] } - void testBreadthFirst1() { - // tag::testBreadthFirst1[] + void testChildren() { + // tag::testChildren[] def response = new XmlSlurper().parseText(books) + // .'*' could be replaced by .children() def catcherInTheRye = response.value.books.'*'.find { node-> - /* node.@id == 2 could be expressed as node['@id'] == 2 */ + // node.@id == 2 could be expressed as node['@id'] == 2 node.name() == 'book' && node.@id == '2' } assert catcherInTheRye.title.text() == 'Catcher in the Rye' - // end::testBreadthFirst1[] + // end::testChildren[] } void testDepthFirst1() { // tag::testDepthFirst1[] def response = new XmlSlurper().parseText(books) + // .'**' could be replaced by .depthFirst() def bookId = response.'**'.find { book-> book.author.text() == 'Lewis Carroll' }.@id @@ -129,6 +131,19 @@ class UserGuideXmlSlurperTest extends GroovyTestCase { // end::testDepthFirst2[] } + void testDepthVsBreadth() { + // tag::testDepthVsBreadth[] + def response = new XmlSlurper().parseText(books) + def nodeName = { node -> node.name() } + def withId2or3 = { node -> node.@id in [2, 3] } + + assert ['book', 'author', 'book', 'author'] == + response.value.books.depthFirst().findAll(withId2or3).collect(nodeName) + assert ['book', 'book', 'author', 'author'] == + response.value.books.breadthFirst().findAll(withId2or3).collect(nodeName) + // end::testDepthVsBreadth[] + } + void testHelpers() { // tag::testHelpers[] def response = new XmlSlurper().parseText(books)
