Генерация / получение xpath из XML-узла Java
Я заинтересован в совете /псевдокоде код / объяснение, а не фактическая реализация.
- Я хотел бы пройти через документ XML, все его узлы
- Проверьте узел на наличие атрибута
Случай, если узел не имеет атрибута, 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'
Объяснил:
- Если значение / текст узла не равно нулю / нулю, получите xpath, add = 'nodevalue' для цели утверждения
- Если у узла есть атрибуты, создайте для них также assert
ОБНОВЛЕНИЕ BOUNTY:
Я нашел этот пример, он не дает правильных результатов, но я выгляжу примерно так:
8 ответов
Обновление:
@ 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'
Режим прост: A/
символ и имя элемента выводятся. Затем, если есть предшествующие братья и сестры с тем же именем, выводится часть "[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>
</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']
Вот как это можно сделать с помощью 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 (порт API jquery для 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 (пока) не поддерживает сопоставление / сопоставление атрибутов. Следовательно, ваши атрибуты не будут производить никакого вывода. Это будет реализовано в ближайшем будущем.
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 очень утомительным, так как вам придется иметь возможность индивидуально обращаться ко второму, третьему и так далее текстовому узлу в элементе.
- использовать w3c.dom
- идти рекурсивно вниз
- для каждого узла есть простой способ получить его xpath: либо путем сохранения его в виде массива / списка в то время как #2, либо с помощью функции, которая рекурсивно поднимается до нуля, а затем инвертирует массив / список встреченных узлов.
что-то вроде того.
UPD: и объединить окончательный список, чтобы получить окончательный xpath. не думаю, что атрибуты будут проблемой.
Я написал метод для возврата абсолютного пути к элементу в практической XML- библиотеке. Чтобы дать вам представление о том, как это работает, вот выдержка из одного из модульных тестов:
assertEquals("/root/wargle[2]/zargle",
DomUtil.getAbsolutePath(child3a));
Таким образом, вы можете просмотреть документ, применить свои тесты и использовать его для возврата XPath. Или, что, вероятно, лучше, вы можете использовать утверждения на основе XPath из той же библиотеки.
Однажды я проделал похожую задачу. Основная идея заключалась в том, что вы можете использовать индексы элемента в xpath. Например, в следующем XML
<root>
<el />
<something />
<el />
</root>
путь ко второму <el/>
будет /root[1]/el[2]
(индексы xpath основаны на 1). Это читается как "взять первый корень, затем взять второй из всех элементов с именем el". Так стихия something
не влияет на индексацию элементов el
, Таким образом, теоретически вы можете создать xpath для каждого конкретного элемента в xml. На практике я добился этого, обходя дерево рекурсивно и запоминая информацию об элементах и их индексах по пути.
Создание xpath, ссылающегося на определенный атрибут элемента, просто добавляло '/@attrName' к xpath элемента.
На прошлой неделе я сделал то же самое для обработки моего xml в solr совместимом формате.
Так как вы хотели псевдокод: вот как я это сделал.
// Вы можете пропустить ссылку на parent и child.
1_ Инициализировать объект пользовательского узла: NodeObjectVO {String nodeName, String path, List attr, NodeObjectVO parent, List child}
2_ Создать пустой список
3_ Создайте dom-представление xml и выполните итерацию по узлу. Для каждого узла получите соответствующую информацию. Вся информация, такая как имя узла, имена атрибутов и значение, должна быть легко доступна из объекта dom. (Вы должны проверить dom NodeType, код должен игнорировать инструкцию обработки и текстовые узлы.)
// Code Bloat предупреждение. 4_ Единственная сложная часть - получить путь. Я создал метод итеративной утилиты для получения строки xpath из NodeElement. (Пока (node.Parent!= Null) {путь + = node.parent.nodeName}.
(Этого также можно добиться, поддерживая глобальную переменную пути, которая отслеживает родительский путь для каждой итерации.)
5_ В методе установки setAttributes (List) я добавлю путь к объекту со всеми доступными атрибутами. (один путь со всеми доступными атрибутами. Не список путей с каждой возможной комбинацией атрибутов. Возможно, вы захотите сделать другой путь.)
6_ Добавьте NodeObjectVO в список.
7_ Теперь у нас есть плоский (не иерархический) список пользовательских объектов узлов, в которых есть вся необходимая информация.
(Примечание. Как я уже говорил, я поддерживаю отношения родитель-потомок, возможно, вам следует пропустить эту часть. Существует вероятность вздутия кода, особенно при использовании getparentpath. Для небольших XML это не было проблемой, но это касается больших XML).,