我对建议/ 伪代码代码/解释感兴趣,而不是实际实现。
如果节点没有属性get/generate String with value of its xpath
,则为例
如果节点确实具有属性,则通过属性列表迭代并为包括该节点在内的每个属性创建xpath。
建议?希望你能提供一些有用的英特尔
修改
这样做的原因是......我在jmeter中编写自动化测试,因此对于每个请求,我需要验证请求是否真正完成了它的工作,因此我通过使用xpath获取节点值来声明结果。 (额外信息 - 不相关)
当请求很小时,手动创建断言没有问题,但对于较大的请求,它真的很痛......(额外信息 - 不相关)
BOUNTY:
我正在寻找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'
解释:
BOUNTY UPDATE:
我发现这个例子,它没有产生正确的结果,但我看起来像这样:
答案 0 :(得分:41)
<强>更新强>:
@ 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>
</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>
</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文档和一个样式表,它接受一个node-set参数并为每个成员节点生成一个有效的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>
</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 -->
<强>结果强>:
/root/nodeA[2]/nodeB[2]/namespace::*[local-name() = 'myNamespace']
/root/nodeA[2]/nodeB[2]/nodeC/namespace::*[local-name() =
'myNamespace']
答案 1 :(得分:14)
以下是如何使用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'
答案 2 :(得分:12)
使用jOOX(Java的jquery API端口,免责声明 - 我为图书馆后面的公司工作),您几乎可以在一个声明中实现您想要的目标:
// 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(尚未)支持匹配/映射属性。因此,您的属性不会产生任何输出。不过,这将在不久的将来实施。
答案 3 :(得分:3)
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>]
,但是它很容易实现。另一方面,混合内容会使xpath的使用非常繁琐,因为您必须能够单独地处理元素中的第二个,第三个等文本节点。
答案 4 :(得分:2)
类似的东西。
UPD: 并连接最终列表以获得最终的xpath。 不要认为属性会成为问题。
答案 5 :(得分:1)
我曾经做过类似的任务。使用的主要思想是您可以在xpath中使用元素的索引。例如,在以下xml
中<root>
<el />
<something />
<el />
</root>
第二个<el/>
的xpath将为/root[1]/el[2]
(xpath索引从1开始)。这读作“取第一根,然后从名称为 el的所有元素中取第二根”。因此,元素something
不会影响元素el
的索引。因此,理论上您可以为xml中的每个特定元素创建一个xpath。实际上,我已经完成了这项工作,一步一步地走树,并记住有关元素及其索引的信息。
创建引用元素特定属性的xpath然后只是将'/ @ attrName'添加到元素的xpath。
答案 6 :(得分:1)
我编写了一个方法来返回Practical XML库中元素的绝对路径。为了让您了解它是如何工作的,这里是unit tests:
之一的摘录assertEquals("/root/wargle[2]/zargle",
DomUtil.getAbsolutePath(child3a));
因此,您可以通过文档递归,应用测试,并使用它来返回XPath。或者,可能更好的是,您可以使用同一个库中的XPath-based assertions。
答案 7 :(得分: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){path + = node.parent.nodeName}。
(您也可以通过维护一个全局路径变量来实现这一点,该变量跟踪每次迭代的父路径。)
5_在setAttributes(List)的setter方法中,我将附加具有所有可用属性的对象路径。 (包含所有可用属性的一个路径。不是每个可能的属性组合的路径列表。您可能想要采取其他方式。)
6_将NodeObjectVO添加到列表中。
7_现在我们有一个自定义节点对象的扁平(非分层)列表,它包含我需要的所有信息。
(注意:就像我提到的,我保持父子关系,你应该跳过那部分。有可能代码膨胀,特别是在getparentpath时。对于小xml这不是问题,但这是一个关注点大xml)。