从XML节点java生成/获取xpath

我对建议/ 代码/解释感兴趣, 而不是实际的实现

  • 我想要通过XML文件,所有的节点
  • 检查节点是否存在属性

如果节点没有属性,则get/generate String with value of its xpath
如果节点确实有属性,则遍历槽属性列表,并为每个属性(包括节点)创建xpath。

建议的意思? 希望你会提供一些有用的英特尔

编辑:

这样做的原因是..我在jmeter写自动化测试,所以对于每个请求我需要验证请求实际上做了它的工作,所以我断言结果通过获取节点值与xpath(额外信息 – 无关)

当这个请求很小的时候,它不是手工创建断言的问题,但是对于更大的请求来说,它真的很痛苦..(额外信息 – 无关紧要)

BOUNTY:

我正在寻找java方法

目标

我的目标是从这个EXML文件实现以下功能:

 <root> <elemA>one</elemA> <elemA attribute1='first' attribute2='second'>two</elemA> <elemB>three</elemB> <elemA>four</elemA> <elemC> <elemB>five</elemB> </elemC> </root> 

产生以下内容:

 //root[1]/elemA[1]='one' //root[1]/elemA[2]='two' //root[1]/elemA[2][@attribute1='first'] //root[1]/elemA[2][@attribute2='second'] //root[1]/elemB[1]='three' //root[1]/elemA[3]='four' //root[1]/elemC[1]/elemB[1]='five' 

解释:

  • 如果节点值/文本不为空/零,则为断言目的获取xpath,add ='nodevalue'
  • 如果节点有属性也为他们创建断言

BOUNTY更新:

我发现这个例子,它不会产生正确的结果,但我看起来像这样:

http://www.coderanch.com/how-to/java/SAXCreateXPath

更新

@ c0mrade已经更新了他的问题。 这是一个解决方案:

这个XSLT转换

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:variable name="vApos">'</xsl:variable> <xsl:template match="*[@* or not(*)] "> <xsl:if test="not(*)"> <xsl:apply-templates select="ancestor-or-self::*" mode="path"/> <xsl:value-of select="concat('=',$vApos,.,$vApos)"/> <xsl:text>&#xA;</xsl:text> </xsl:if> <xsl:apply-templates select="@*|*"/> </xsl:template> <xsl:template match="*" mode="path"> <xsl:value-of select="concat('/',name())"/> <xsl:variable name="vnumPrecSiblings" select= "count(preceding-sibling::*[name()=name(current())])"/> <xsl:if test="$vnumPrecSiblings"> <xsl:value-of select="concat('[', $vnumPrecSiblings +1, ']')"/> </xsl:if> </xsl:template> <xsl:template match="@*"> <xsl:apply-templates select="../ancestor-or-self::*" mode="path"/> <xsl:value-of select="concat('[@',name(), '=',$vApos,.,$vApos,']')"/> <xsl:text>&#xA;</xsl:text> </xsl:template> </xsl:stylesheet> 

当应用于提供的XML文档时

 <root> <elemA>one</elemA> <elemA attribute1='first' attribute2='second'>two</elemA> <elemB>three</elemB> <elemA>four</elemA> <elemC> <elemB>five</elemB> </elemC> </root> 

产生正确的想要的,正确的结果

 /root/elemA='one' /root/elemA[2]='two' /root/elemA[2][@attribute1='first'] /root/elemA[2][@attribute2='second'] /root/elemB='three' /root/elemA[3]='four' /root/elemC/elemB='five' 

当通过@ c0mrade应用到新提供的文档时

 <root> <elemX serial="kefw90234kf2esda9231"> <id>89734</id> </elemX> </root> 

再次产生正确的结果

 /root/elemX='89734' /root/elemX[@serial='kefw90234kf2esda9231'] 

说明

  • 只有没有子元素或具有属性的元素才会被匹配和处理。

  • 对于任何这样的元素,如果它没有子元素,则其所有祖先或自身元素都将以特定模式(称为'path' 。 然后输出"='theValue'"部分,然后输出一个NL字符。

  • 然后处理匹配元素的所有属性

  • 最后,模板应用于所有的子元素

  • 'path'模式下处理一个元素很简单 :输出一个字符和元素的名字。 然后,如果有同名的兄弟姐妹,则输出“[numPrecSiblings + 1]”部分。

  • 属性的处理很简单 :首先,所有ancestor-or-self::元素都以'path'模式处理,然后输出[attrName = attrValue]部分,然后输出一个NL字符。

请注意

  • 名称空间中的名称显示时没有任何问题,并且处于其初始可读形式。

  • 为了便于阅读,从不显示[1]的索引。


以下是我的初步答案(可以忽略)

这是一个纯粹的XSLT 1.0解决方案

下面是一个示例xml文档和一个样式表,它采用节点集参数并为每个成员节点生成一个有效的XPath表达式。

样式表(buildPath.xsl):


 <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:msxsl="urn:schemas-microsoft-com:xslt" > <xsl:output method="text"/> <xsl:variable name="theParmNodes" select="//namespace::*[local-name() = 'myNamespace']"/> <xsl:template match="/"> <xsl:variable name="theResult"> <xsl:for-each select="$theParmNodes"> <xsl:variable name="theNode" select="."/> <xsl:for-each select="$theNode | $theNode/ancestor-or-self::node()[..]"> <xsl:element name="slash">/</xsl:element> <xsl:choose> <xsl:when test="self::*"> <xsl:element name="nodeName"> <xsl:value-of select="name()"/> <xsl:variable name="thisPosition" select="count(preceding-sibling::*[name(current()) = name()])"/> <xsl:variable name="numFollowing" select="count(following-sibling::*[name(current()) = name()])"/> <xsl:if test="$thisPosition + $numFollowing > 0"> <xsl:value-of select="concat('[', $thisPosition + 1, ']')"/> </xsl:if> </xsl:element> </xsl:when> <xsl:otherwise> <!-- This node is not an element --> <xsl:choose> <xsl:when test="count(. | ../@*) = count(../@*)"> <!-- Attribute --> <xsl:element name="nodeName"> <xsl:value-of select="concat('@',name())"/> </xsl:element> </xsl:when> <xsl:when test="self::text()"> <!-- Text --> <xsl:element name="nodeName"> <xsl:value-of select="'text()'"/> <xsl:variable name="thisPosition" select="count(preceding-sibling::text())"/> <xsl:variable name="numFollowing" select="count(following-sibling::text())"/> <xsl:if test="$thisPosition + $numFollowing > 0"> <xsl:value-of select="concat('[', $thisPosition + 1, ']')"/> </xsl:if> </xsl:element> </xsl:when> <xsl:when test="self::processing-instruction()"> <!-- Processing Instruction --> <xsl:element name="nodeName"> <xsl:value-of select="'processing-instruction()'"/> <xsl:variable name="thisPosition" select="count(preceding-sibling::processing-instruction())"/> <xsl:variable name="numFollowing" select="count(following-sibling::processing-instruction())"/> <xsl:if test="$thisPosition + $numFollowing > 0"> <xsl:value-of select="concat('[', $thisPosition + 1, ']')"/> </xsl:if> </xsl:element> </xsl:when> <xsl:when test="self::comment()"> <!-- Comment --> <xsl:element name="nodeName"> <xsl:value-of select="'comment()'"/> <xsl:variable name="thisPosition" select="count(preceding-sibling::comment())"/> <xsl:variable name="numFollowing" select="count(following-sibling::comment())"/> <xsl:if test="$thisPosition + $numFollowing > 0"> <xsl:value-of select="concat('[', $thisPosition + 1, ']')"/> </xsl:if> </xsl:element> </xsl:when> <!-- Namespace: --> <xsl:when test="count(. | ../namespace::*) = count(../namespace::*)"> <xsl:variable name="apos">'</xsl:variable> <xsl:element name="nodeName"> <xsl:value-of select="concat('namespace::*', '[local-name() = ', $apos, local-name(), $apos, ']')"/> </xsl:element> </xsl:when> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:for-each> <xsl:text>&#xA;</xsl:text> </xsl:for-each> </xsl:variable> <xsl:value-of select="msxsl:node-set($theResult)"/> </xsl:template> </xsl:stylesheet> 

xml源码(buildPath.xml):


 <!-- top level Comment --> <root> <nodeA>textA</nodeA> <nodeA id="nodeA-2"> <?myProc ?> xxxxxxxx <nodeB/> <nodeB xmlns:myNamespace="myTestNamespace"> <!-- Comment within /root/nodeA[2]/nodeB[2] --> <nodeC/> <!-- 2nd Comment within /root/nodeA[2]/nodeB[2] --> </nodeB> yyyyyyy <nodeB/> <?myProc2 ?> </nodeA> </root> <!-- top level Comment --> 

结果

 /root/nodeA[2]/nodeB[2]/namespace::*[local-name() = 'myNamespace'] /root/nodeA[2]/nodeB[2]/nodeC/namespace::*[local-name() = 'myNamespace'] 

以下是SAX如何做到这一点:

 import java.util.HashMap; import java.util.Map; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; public class FragmentContentHandler extends DefaultHandler { private String xPath = "/"; private XMLReader xmlReader; private FragmentContentHandler parent; private StringBuilder characters = new StringBuilder(); private Map<String, Integer> elementNameCount = new HashMap<String, Integer>(); public FragmentContentHandler(XMLReader xmlReader) { this.xmlReader = xmlReader; } private FragmentContentHandler(String xPath, XMLReader xmlReader, FragmentContentHandler parent) { this(xmlReader); this.xPath = xPath; this.parent = parent; } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { Integer count = elementNameCount.get(qName); if(null == count) { count = 1; } else { count++; } elementNameCount.put(qName, count); String childXPath = xPath + "/" + qName + "[" + count + "]"; int attsLength = atts.getLength(); for(int x=0; x<attsLength; x++) { System.out.println(childXPath + "[@" + atts.getQName(x) + "='" + atts.getValue(x) + ']'); } FragmentContentHandler child = new FragmentContentHandler(childXPath, xmlReader, this); xmlReader.setContentHandler(child); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { String value = characters.toString().trim(); if(value.length() > 0) { System.out.println(xPath + "='" + characters.toString() + "'"); } xmlReader.setContentHandler(parent); } @Override public void characters(char[] ch, int start, int length) throws SAXException { characters.append(ch, start, length); } } 

它可以用以下方法测试:

 import java.io.FileInputStream; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; public class Demo { public static void main(String[] args) throws Exception { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); xr.setContentHandler(new FragmentContentHandler(xr)); xr.parse(new InputSource(new FileInputStream("input.xml"))); } } 

这将产生所需的输出:

 //root[1]/elemA[1]='one' //root[1]/elemA[2][@attribute1='first] //root[1]/elemA[2][@attribute2='second] //root[1]/elemA[2]='two' //root[1]/elemB[1]='three' //root[1]/elemA[3]='four' //root[1]/elemC[1]/elemB[1]='five' 

使用jOOX (一个jQuery的API端口到Java,免责声明 – 我为图书馆后面的公司工作),你几乎可以在一个声明中实现你想要的:

 // I'm assuming this: import static org.joox.JOOX.$; // And then... List<String> coolList = $(document).xpath("//*[not(*)]").map( context -> $(context).xpath() + "='" + $(context).text() + "'" ); 

如果文档是您的示例文档:

 <root> <elemA>one</elemA> <elemA attribute1='first' attribute2='second'>two</elemA> <elemB>three</elemB> <elemA>four</elemA> <elemC> <elemB>five</elemB> </elemC> </root> 

这会产生

 /root[1]/elemA[1]='one' /root[1]/elemA[2]='two' /root[1]/elemB[1]='three' /root[1]/elemA[3]='four' /root[1]/elemC[1]/elemB[1]='five' 

“几乎”,我的意思是说,jOOX不(还)支持匹配/映射属性。 因此,你的属性不会产生任何输出。 不过,这将在不久的将来实施。

  1. 使用w3c.dom
  2. 递归下去
  3. 对于每个节点来说,有一个简单的方法来获取它的xpath:或者通过在#2存储数组/列表,或者通过递归直到parent为空的函数,然后反转所遇到的节点的数组/列表。

类似的东西。

UPD:并且连接最后的列表为了得到最后的xpath。 不要认为属性会成为问题。

 private static void buildEntryList( List<String> entries, String parentXPath, Element parent ) { NamedNodeMap attrs = parent.getAttributes(); for( int i = 0; i < attrs.getLength(); i++ ) { Attr attr = (Attr)attrs.item( i ); //TODO: escape attr value entries.add( parentXPath+"[@"+attr.getName()+"='"+attr.getValue()+"']"); } HashMap<String, Integer> nameMap = new HashMap<String, Integer>(); NodeList children = parent.getChildNodes(); for( int i = 0; i < children.getLength(); i++ ) { Node child = children.item( i ); if( child instanceof Text ) { //TODO: escape child value entries.add( parentXPath+"='"+((Text)child).getData()+"'" ); } else if( child instanceof Element ) { String childName = child.getNodeName(); Integer nameCount = nameMap.get( childName ); nameCount = nameCount == null ? 1 : nameCount + 1; nameMap.put( child.getNodeName(), nameCount ); buildEntryList( entries, parentXPath+"/"+childName+"["+nameCount+"]", (Element)child); } } } public static List<String> getEntryList( Document doc ) { ArrayList<String> entries = new ArrayList<String>(); Element root = doc.getDocumentElement(); buildEntryList(entries, "/"+root.getNodeName()+"[1]", root ); return entries; } 

此代码与两个假设一起工作:您没有使用名称空间,也没有混合的内容元素。 命名空间的限制并不是一个严重的问题,但它会让你的XPath表达式*:<name>[namespace-uri()='<nsuri>'][<index>]阅读,因为每个元素都会像*:<name>[namespace-uri()='<nsuri>'][<index>] ,否则很容易实现。 另一方面,混合内容会使xpath的使用非常繁琐,因为您必须能够单独处理元素中的第二个,第三个等文本节点。

我曾经做过类似的任务。 使用的主要思想是可以在xpath中使用元素的索引。 例如在下面的xml中

 <root> <el /> <something /> <el /> </root> 

xpath到第二个<el/>将是/root[1]/el[2] (xpath索引是从1开始的)。 这读取为“采取第一个根,然后采取所有元素与名称 el的第二个 ”。 所以元素不会影响元素el索引。 所以你可以在理论上为xml中的每个特定元素创建一个xpath。 在实践中,我已经完成了这个步骤,在树上行走,记住关于元素及其索引的信息。
创建xpath引用元素的特定属性,然后只是添加“/ @ attrName”元素的xpath。

我写了一个方法来返回Practical XML库中元素的绝对路径。 为了让你了解它是如何工作的,下面是一个单元测试的提取表单:

 assertEquals("/root/wargle[2]/zargle", DomUtil.getAbsolutePath(child3a)); 

所以,你可以遍历文档,应用你的测试,并使用它返回XPath。 或者,可能更好的是,您可以使用来自同一个库的基于XPath的断言 。

上周我做了同样的事情来处理我的xml到solr兼容格式。

既然你想要一个伪代码:这是我做到的。

//你可以跳过对父母和孩子的引用。

1_初始化自定义节点对象:NodeObjectVO {String nodeName,String path,List attr,NodeObjectVO parent,List child}

2_创建一个空的列表

3_创建xml的dom表示并迭代节点。 对于每个节点,获取相应的信息。 像节点名称,属性名称和值的所有信息都应该可以从dom对象中随时获得。 (您需要检查DOM节点类型,代码应忽略处理指令和纯文本节点。)

//代码膨胀警告。 4_唯一棘手的部分是获取路径。 我创建了一个迭代实用程序方法来从NodeElement获取xpath字符串。 (while(node.Parent!= null){path + = node.parent.nodeName}。

(您也可以通过维护一个全局路径变量来实现这一点,该路径变量跟踪每次迭代的父路径。)

5_在setAttributes(List)的setter方法中,我将追加对象的路径与所有可用的属性。 (一个具有所有可用属性的路径,而不是每个可能的属性组合的路径列表,您可能需要采取其他方法)。

6_将NodeObjectVO添加到列表中。

7_现在我们有一个自定义节点对象的平面(非层次结构)列表,它具有我需要的所有信息。

(注意:就像我提到的,我维护父子关系,你应该跳过这个部分,有代码膨胀的可能性,特别是getparentpath。对于小的XML这不是一个问题,但这是一个大的XML的关注) 。