Cómo determinar el espacio de nombres de un atributo dado dentro de NodeChild

I'm trying to parse some Android XML using XmlSlurper. For a given child node, I want to detect whether or not an attribute with a particular namespace has been specified.

For example, in the following XML I would like to know whether the EditText node has had any attributes from the 'b' namespace declared:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:b="http://x.y.z.com">

    <EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        b:enabled="true" />

</LinearLayout>

I start by calling:

 def rootNode = new XmlSlurper().parseText(text)

to get a handle on the root GPathResult. As I iterate through the children, I am given an instance of groovy.util.slurpersupport.NodeChild. On this class I can inspect the attributes by calling attributes() y en el caso de EditText above, this will return the following map: [layout_width: "fill_parent", layout_height: "wrap_content", enabled: "true"].

This is all well and good. However, there doesn't seem to be a way to query the namespace of a given attribute. Am I missing something here?

preguntado el 01 de febrero de 12 a las 22:02

Node has a private variable attributeNamespaceMap. If you could access this then you'd be there. -

2 Respuestas

Puede usar el XmlParser más bien que XmlSlurper y haz esto:

def xml = '''<LinearLayout
            |    xmlns:android="http://schemas.android.com/apk/res/android"
            |    xmlns:b="http://x.y.z.com">
            |
            |  <EditText
            |      android:layout_width="fill_parent"
            |      android:layout_height="wrap_content"
            |      b:enabled="true" />
            |</LinearLayout>'''.stripMargin()

def root = new XmlParser().parseText( xml )

root.EditText*.attributes()*.each { k, v ->
  println "$k.localPart $k.prefix($k.namespaceURI) = $v"
}

Que imprime

layout_width android(http://schemas.android.com/apk/res/android) = fill_parent
layout_height android(http://schemas.android.com/apk/res/android) = wrap_content
enabled b(http://x.y.z.com) = true

Editar

To use XmlSlurper, you first need to access the namespaceTagHints property from the root node using reflection:

def rootNode = new XmlSlurper().parseText(xml)

def xmlClass = rootNode.getClass()
def gpathClass = xmlClass.getSuperclass()
def namespaceTagHintsField = gpathClass.getDeclaredField("namespaceTagHints")
namespaceTagHintsField.setAccessible(true)

def namespaceDeclarations = namespaceTagHintsField.get(rootNode)

namespaceTagHints es propiedad de GPathResult, que es una superclase de NodeChild.

You can then cross-reference this map to access the namespace prefix and print out the same result as above:

rootNode.EditText.nodeIterator().each { groovy.util.slurpersupport.Node n ->
  n.@attributeNamespaces.each { name, ns ->
    def prefix = namespaceDeclarations.find {key, value -> value == ns}.key
    println "$name $prefix($ns) = ${n.attributes()"$name"}"
  }
}

Respondido 12 Feb 12, 18:02

Useful answer, thanks, but for performance reasons I want to use XmlSlurper in my case. - Robert Taylor

You can access the namespace prefix by accessing the namespaceTagHints in GPathResult. I found that solution here: stackoverflow.com/questions/7385303/…. I've updated your answer accordingly. - Robert Taylor

Although this solution seems harder than it ought to be, I'm marking this as accepted. I still feel NodeChild ought to expose an attributeNamespaces() method, as it does for attributes(). But there you go :) - Robert Taylor

@Robert maybe post it as a suggestion on the groovy jira with the example problem you've got and the current best solution? You never know, it might make it to 1.8.7 :-) you can even do pull requests now groovy is on github - tim_yates

The only solution I have found so far involves falling back on reflection.

As Damo pointed out above, NodeChild contiene una Node property and inside Node es la attributeNamespaces map I need to get a hold of.

La Node class does not expose this property (as it does with attributes()), and it only seems to be used inside the build() method. Darn.

Having retrieved the node, I therefore call:

def attributeNamespacesField = node.getClass().getDeclaredField("attributeNamespaces")
attributeNamespacesField.setAccessible(true)
def attributeNamespacesMap = attributeNamespacesField.get(node)

It works, although it doesn't feel so Maravilloso.

Respondido 03 Feb 12, 21:02

Added an edit to my answer showing a groovier way of accessing attributeNamespaces, but I still don't think it's 100% the solution you wanted... - tim_yates

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.