2

Expanding on a question I asked earlier about how to iterate over a collection of nodes in scala.xml.Node found here

I wanted to take this 1 step further and ask how I could look up to a previous child inside a recursive function to get a value once I hit a specific situation

For example (the markup is here)

<html> <head class="foo"> <title>Welcome</title> </head> <body> <div> <p>Foo</p> </div> </body> </html> 

With my current implementation (thanks to @knut-arne-vedaa)

def processNode(node: Node) { if (node.isInstanceOf[Text]) { if (node.text.contains("Welcome")) { //then inside here I want to go up to the prev element (head) and pull the class } } node.child foreach processNode } 

I want to add another conditional to get the text "foo" from inside the class section

Any idea what I could add inside this if statement to pull this value directly? Also how can I return the String value from this fx? a break w/ a simple return line or ?

3 Answers 3

4

Note that the parent of the Text node in your example is actually the <title> node.

Since Nodes don't have references to their parents, one way to access them is just to pass them with you when walking down the tree, like this:

def processNode(node: Node, parent: Option[Node]) { if (node.isInstanceOf[Text]) { if (node.text.contains("Welcome")) { println("Node: " + node) println("Parent: " + parent.getOrElse("[toplevel]")) } } node.child foreach { n: Node => processNode(n, Some(node)) } } processNode(xml, None) 

Of course, this becomes unwieldy when you want to climb back up the tree to an arbitrary level. One approach to this is to wrap your nodes in a SuperNode that has an optional parent reference.

case class SuperNode(current: Node, parent: Option[SuperNode] = None) 

For convenience, make an implicit function to convert nodes to SuperNodes in the default case of no parent.

implicit def nodeMakeSuper(n: Node) = SuperNode(n) 

Now you can navigate up the tree an arbitrary number of times, like this:

def processNode(node: SuperNode) { node.current match { case n @ Text(t) if t.contains("Welcome") => { println("Node: " + n) node.parent match { case Some(p) => { println("Parent: " + p.current) p.parent match { case Some(gp) => println("GrandParent: " + gp.current) case None => println("No grandparent!") } } case None => println("No parent!") } } case _ => // these aren't the droids you're looking for } node.current.child foreach { child: Node => processNode(SuperNode(child, Some(node))) } } 
Sign up to request clarification or add additional context in comments.

Comments

2

If you keep going in this direction, you should use an XML Zipper. I think Scalaz has one (it has one, but I'm not sure if you can use it with XML).

Comments

1

If you want to do something with the value only if it is inside a node with a certain attribute, you can do it the other way around: first find all nodes with the given attribute, then do the relevant processing on them.

You can find all nodes with a "class" attribute of "foo" like this:

xml \\ "_" filter { _.attribute("class") == Some(Text("foo")) } 

EDIT: You use it like this:

val markup = <html>...etc val filtered = markup \\ "_" filter { _.attribute("class") == Some(Text("foo")) } filtered map processNode 

If you want to return some value from the processing you need to do it differently, but your question is not clear in regards to that.

1 Comment

How could I use this w/ the above processNode method or would this be a method all its own? Also how could I print out the filtered list? When I do the filter (as you have it above) and do a println(xml) I still get the same markup returned (appearing that no filter was applied)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.