web-dev-qa-db-ja.com

XMLノードからxpathを生成/取得java

アドバイスに興味があります/擬似コード コード/説明 実際の実装ではなく

  • 私はXMLドキュメント、そのすべてのノードを行きたいです
  • ノードの属性の存在を確認します

ノードに属性がない場合、get/generate String with value of its xpath
ノードに属性がある場合、属性リストで繰り返し処理を行い、ノードを含む各属性のxpathを作成します。

アドバイスの言葉?便利な情報を提供してください

編集:

これを行う理由は.. jmeterで自動化されたテストを書いているため、すべてのリクエストに対して、リクエストが実際にジョブを実行したことを確認する必要があるため、xpathでノード値を取得することで結果をアサートしています(追加情報-無関係)

リクエストが小さい場合、手動でアサートを作成するのは問題ではありませんが、大きいリクエストの場合、..(追加情報-無関係)で本当に苦痛になります。

バウンティ:

Javaアプローチを探しています

目標

私の目標は、この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 /ゼロでない場合、xpathを取得し、アサーション目的でadd = 'nodevalue'を取得します
  • ノードに属性がある場合、それらもアサートします

バウンティ更新:

私はこの例を見つけましたが、正しい結果を生成しませんが、私はこのようなものを見ています:

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

36
ant

更新

@ 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ドキュメントと、ノードセットパラメーターを受け取り、すべてのメンバーノードに対して1つの有効な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']
41

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'
14
bdoughan

jOOX (a jquery API Javaへの移植、免責条項-私はライブラリの背後にある会社で働いています)を使用すると、1つのステートメントで目的をほぼ達成できます。

// 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が(まだ)属性のマッチング/マッピングをサポートしていないことを意味します。したがって、属性は出力を生成しません。ただし、これは近い将来に実装される予定です。

12
Lukas Eder
_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;
}
_

このコードは、2つの仮定で機能します。名前空間を使用しておらず、コンテンツ要素が混在していないことです。名前空間の制限は深刻なものではありませんが、すべての要素が*:<name>[namespace-uri()='<nsuri>'][<index>]のようなものになるため、XPath式を読みにくくしますが、それ以外の場合は実装が簡単です。一方、混合コンテンツでは、要素内の2番目、3番目などのテキストノードを個別にアドレス指定できるようにする必要があるため、xpathの使用は非常に面倒です。

3
biziclop
  1. w3c.domを使用
  2. 再帰的に下がる
  3. 各ノードに対して、xpathを取得する簡単な方法があります:#2の間に配列/リストとして保存するか、親がnullになるまで再帰的に上がってから、検出されたノードの配列/リストを逆にする関数を使用します。

そんな感じ。

UPD:最終的なxpathを取得するために最終リストを連結します。属性が問題になるとは思わないでください。

2
Osw

Xmlをsolr準拠の形式に処理するために、先週まったく同じことをしました。

あなたが擬似コードを望んでいたので:これは私がそれを達成した方法です。

//親と子への参照をスキップできます。

1_カスタムノードオブジェクトの初期化:NodeObjectVO {String nodeName、String path、List attr、NodeObjectVO parent、List child}

2_空のリストを作成する

3_ xmlのdom表現を作成し、ノードを反復処理します。各ノードについて、対応する情報を取得します。 Node name、attribute name and value)のようなすべての情報は、domオブジェクトからすぐに利用できるはずです(dom NodeTypeを確認する必要があり、コードは処理命令とプレーンテキストノードを無視する必要があります)。

//コードブロート警告。 4_唯一のトリッキーな部分はgetパスです。 NodeElementからxpath文字列を取得するための反復ユーティリティメソッドを作成しました。 (While(node.Parent!= null){path + = node.parent.nodeName}。

(グローバルパス変数を維持することでこれを達成することもできます。これは、各反復の親パスを追跡します。)

5_ setAttributes(リスト)のセッターメソッドで、オブジェクトのパスに利用可能なすべての属性を追加します。 (使用可能なすべての属性を持つ1つのパス。属性の可能な組み合わせごとのパスのリストではありません。別の方法を実行することもできます。)

6_ NodeObjectVOをリストに追加します。

7_これで、カスタムのNodeオブジェクト、必要な情報がすべて揃ったオブジェクト)のフラットな(階層ではない)リストができました。

(注:前述したように、親子関係は維持します。おそらくその部分はスキップする必要があります。特にgetparentpath中にコードが肥大化する可能性があります。小さなxmlではこれは問題ではありませんが、これは大きなxmlの懸念です。

1

同様のタスクを1回実行しました。使用された主なアイデアは、xpathで要素のインデックスを使用できるということでした。たとえば、次のXML

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

2番目の<el/>へのxpathは/root[1]/el[2]になります(xpathインデックスは1から始まります)。これは、「最初のルートを取得してから、名前を持つすべての要素の2番目のルート el」を取得します。したがって、要素somethingは要素elのインデックス付けに影響しません。したがって、理論的には、xml内の特定の要素ごとにxpathを作成できます。実際には、ツリーを再帰的に歩いて、途中で要素とそのインデックスに関する情報を記憶することでこれを達成しました。
要素の特定の属性を参照するxpathを作成すると、要素のxpathに「/ @ attrName」が追加されるだけでした。

1
alpha-mouse

Practical XML ライブラリの要素の絶対パスを返すメソッドを作成しました。それがどのように機能するかのアイデアを与えるために、ここに nit tests のいずれかの抜粋フォームがあります:

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

そのため、ドキュメントを再帰処理し、テストを適用し、これを使用してXPathを返すことができます。または、おそらくより良いのは、同じライブラリから XPathベースのアサーション を使用できることです。

1
kdgregory