首页 文章

从XML节点java生成/获取xpath

提问于
浏览
34

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

  • 我想通过xml文档,它的所有节点

  • 检查节点是否存在属性

如果节点没有属性,则为 get/generate String with value of its xpath
如果节点确实具有属性,则迭代通过属性列表并为包括该节点的每个属性创建xpath .

忠告?希望你能提供一些有用的英特尔

EDIT:

这样做的原因是..我正在用jmeter编写自动化测试,所以对于我需要验证请求实际完成其工作的每个请求,所以我通过使用xpath获取节点值来声明结果 . (额外信息 - 不相关)

当请求很小时,手动创建断言并不是问题,但对于较大的请求,它真的很痛苦...(额外信息 - 不相关)

BOUNTY :

我正在寻找Java方法

Goal

我的目标是从这个ex 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[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'

解释:

  • 如果节点值/文本不为null / 0,则获取xpath,add = 'nodevalue'以进行断言

  • 如果节点具有属性,则也为它们创建断言

BOUNTY UPDATE :

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

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

8 回答

  • 41

    Update

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

    This XSLT transformation

    <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>
    

    when applied on the provided XML document

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

    produces exactly the wanted, correct result

    /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'
    

    When applied to the newly-provided document by @c0mrade

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

    again the correct result is produced

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

    Explanation

    • Only elements that have no children elements, or have attributes are matched 并已处理完毕 .

    • For any such element, if it doesn't have children-elements all of its ancestor-or self elements are processed 在特定模式下,名为 'path' . 然后输出 "='theValue'" 部分,然后输出NL字符 .

    • All attributes of the matched element are then processed .

    • Then finally, templates are applied to all children-elements .

    • Processing an element in the 'path' mode is simple :输出 / 字符和元素名称 . 然后,如果有前面的兄弟姐妹具有相同的名称,则输出“[numPrecSiblings 1]”部分 .

    • Processing of attributes is simple :首先在 'path' 模式下处理其父元素的所有 ancestor-or-self:: 元素,然后输出[attrName = attrValue]部分,后跟NL字符 .

    Do note

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

    • 为了提高可读性,永远不会显示 [1] 的索引 .


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

    Here is a pure XSLT 1.0 solution

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

    stylesheet (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 source (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 -->
    

    Result

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

    以下是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'
    
  • 1

    使用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
    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>] ,但是否则它必须能够单独地解决第二个问题 . ,第三个等元素中的文本节点 .

  • 2
    • 使用w3c.dom

    • 递归下去

    • 对于每个节点都有简单的方法来获取它的xpath:或者通过将它作为数组/列表存储在#2中,或者通过递归递增直到parent为空的函数,然后反转遇到的节点的数组/列表 .

    类似的东西 .

    UPD:并连接最终列表以获得最终的xpath . 不要认为属性会成为问题 .

  • 12

    我做过一次类似的任务 . 使用的主要思想是您可以在xpath中使用元素的索引 . 例如,在以下xml中

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

    到第二个 <el/> 的xpath将是 /root[1]/el[2] (xpath索引是从1开始的) . 这读作“取第一个根,然后取 second one from all elements with the name el” . 因此元素 something 不会影响元素 el 的索引 . 因此,理论上您可以为xml中的每个特定元素创建一个xpath . 在实践中,我通过循序渐进地走树并记住有关元素及其元素的信息来实现这一目标沿途的索引 .
    创建引用元素特定属性的xpath然后只是将'/@attrName'添加到元素的xpath .

  • 3

    我编写了一个方法来返回Practical XML库中元素的绝对路径 . 为了让您了解它是如何工作的,这里是unit tests之一的摘录:

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

    因此,您可以通过文档递归,应用测试,并使用它来返回XPath . 或者,更好的是,您可以使用同一个库中的XPath-based assertions .

  • 1

    上周我做了完全相同的事情来处理我的xml到solr兼容格式 .

    因为你想要一个伪代码:这就是我完成它的方式 .

    //您可以跳过对父级和子级的引用 .

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

    2_创建一个空列表

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

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

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

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

    6_将NodeObjectVO添加到列表中 .

    7_现在我们有一个自定义节点对象的平面(非层级)列表,它包含我需要的所有信息 .

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

相关问题