Generowanie / pobieranie xpath z XML node java
Interesują mnie porady/pseudokod kod/Wyjaśnienie zamiast rzeczywistej implementacji.
- chciałbym przejść przez dokument xml, wszystkie jego węzły
- Sprawdź węzeł pod kątem istnienia atrybutów
Case if node doesn ' t have attribute, get/generate String with value of its xpath
Przypadek, jeśli węzeł ma atrybuty, iteracja listy atrybutów koryta i tworzenie xpath dla każdego atrybutu, w tym również węzła.
EDIT:
Powodem tego jest .. Piszę testy automatyczne w jmeter, więc dla każdego żądania muszę zweryfikować, że żądanie rzeczywiście wykonało swoje zadanie, więc sprawdzam wyniki, pobierając wartości węzłów za pomocą xpath.(DODATKOWE info-nieistotne)Gdy żądanie jest małe, nie ma problemu z ręcznym tworzeniem twierdzeń, ale dla większych jest to naprawdę ból w .. (DODATKOWE info-nieistotne)
BOUNTY:
Szukam podejście java
Cel
Moim celem jest osiągnięcie następujących wyników z tego pliku 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>
Produkować:
//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'
Wyjaśnione:
- jeśli wartość/tekst węzła nie jest null / zero, get xpath, add = 'nodevalue' dla celu twierdzenia
- jeśli węzeł ma atrybuty, Utwórz assert dla nich
BOUNTY UPDATE:
Znalazłem ten przykład, nie daje poprawnych wyników, ale szukam coś takiego:
8 answers
Update :
@C0mrade zaktualizował swoje pytanie. Oto rozwiązanie tego problemu:Ta transformacja 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>
W przypadku zastosowania na dostarczonym dokumencie XML :
<root>
<elemA>one</elemA>
<elemA attribute1='first' attribute2='second'>two</elemA>
<elemB>three</elemB>
<elemA>four</elemA>
<elemC>
<elemB>five</elemB>
</elemC>
</root>
Daje dokładnie pożądany, poprawny wynik :
/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'
W przypadku zastosowania do nowo dostarczonego dokumentu przez @c0mrade :
<root>
<elemX serial="kefw90234kf2esda9231">
<id>89734</id>
</elemX>
</root>
Ponownie prawidłowy wynik to wyprodukowano:
/root/elemX='89734'
/root/elemX[@serial='kefw90234kf2esda9231']
Wyjaśnienie :
Tylko elementy, które nie mają elementów potomnych lub mają atrybuty, są dopasowywane i przetwarzane.
Dla każdego takiego elementu, jeśli nie ma dzieci-elementy wszystkie jego przodka-lub elementy self są przetwarzane w określonym trybie, o nazwie
'path'
. Następnie część"='theValue'"
jest wyjściowa, a następnie znak NL.Wszystkie atrybuty dopasowanego elementu są następnie przetwarzane .
Następnie szablony są stosowane do wszystkich elementów potomnych .
Przetwarzanie elementu w trybie
'path'
jest proste : wyprowadzany jest znak/
oraz Nazwa elementu. Następnie, jeśli istnieją poprzednie rodzeństwo o tej samej nazwie, wypisywana jest część "[numPrecSiblings+1]".Przetwarzanie atrybutów jest proste : najpierw wszystkie
ancestor-or-self::
elementy jego rodzica są przetwarzane w'path'
mode, wtedy zostanie wyprowadzona część [attrName=attrValue], po której następuje znak NL.
Uwaga :
Nazwy znajdujące się w przestrzeni nazw są wyświetlane bez problemu i w ich początkowej czytelnej formie.
Aby ułatwić czytelność, indeks
[1]
nigdy nie jest wyświetlany.
Poniżej moja początkowa odpowiedź (może być zignorowana)
Oto czyste rozwiązanie XSLT 1.0 :
Poniżej znajduje się przykładowy dokument xml i arkusz stylów, który pobiera parametr zestawu węzłów i tworzy jedno poprawne wyrażenie XPath dla każdego węzła członkowskiego.
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>
Źródło 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 -->
Wynik :
/root/nodeA[2]/nodeB[2]/namespace::*[local-name() = 'myNamespace']
/root/nodeA[2]/nodeB[2]/nodeC/namespace::*[local-name() =
'myNamespace']
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 13:54:32
Oto jak można to zrobić z SAXEM:
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);
}
}
Można go przetestować za pomocą:
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")));
}
}
Spowoduje to uzyskanie pożądanego wyniku:
//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'
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 15:04:47
Z jOOX (a jQuery API port do Javy, disclaimer - pracuję dla firmy stojącej za biblioteką), możesz prawie osiągnąć to, co chcesz w jednym oświadczeniu:
// I'm assuming this:
import static org.joox.JOOX.$;
// And then...
List<String> coolList = $(document).xpath("//*[not(*)]").map(
context -> $(context).xpath() + "='" + $(context).text() + "'"
);
Jeśli dokument jest Twoim przykładowym dokumentem:
<root>
<elemA>one</elemA>
<elemA attribute1='first' attribute2='second'>two</elemA>
<elemB>three</elemB>
<elemA>four</elemA>
<elemC>
<elemB>five</elemB>
</elemC>
</root>
To da
/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'
Przez "prawie" mam na myśli, że jOOX nie obsługuje (jeszcze) dopasowywania / mapowania atrybutów. W związku z tym twoje atrybuty nie będą generować żadnych danych wyjściowych. Zostanie to jednak wdrożone w niedalekiej przyszłości.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-01 18:44:59
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;
}
Ten kod działa z dwoma założeniami: nie używasz przestrzeni nazw i nie ma mieszanych elementów zawartości. Ograniczenie przestrzeni nazw nie jest poważne, ale sprawiłoby, że wyrażenie XPath byłoby trudniejsze do odczytania, ponieważ każdy element byłby czymś w rodzaju *:<name>[namespace-uri()='<nsuri>'][<index>]
, ale poza tym jest łatwe do zaimplementowania. Z drugiej strony mieszana zawartość sprawiłaby, że korzystanie z xpath byłoby bardzo żmudne, ponieważ musiałbyś być w stanie indywidualnie zająć się drugim, trzecim węzłem tekstu w elemencie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-23 17:20:39
- Użyj w3c. dom
- go recursively down
- dla każdego węzła jest łatwy sposób na uzyskanie ścieżki xpath: albo przechowując ją jako array / list while #2, albo poprzez funkcję, która idzie rekurencyjnie w górę, aż rodzic jest null, a następnie odwraca tablicę / listę napotkanych węzłów.
UPD: i połącz ostateczną listę, aby uzyskać ostateczną ścieżkę xpath. nie myśl, że atrybuty będą problemem.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 11:55:31
Kiedyś wykonałem podobne zadanie. Główną ideą było to, że można używać indeksów elementu w xpath. Na przykład w następującym xml
<root>
<el />
<something />
<el />
</root>
Xpath do drugiej {[1] } będzie /root[1]/el[2]
(indeksy xpath są oparte na 1). To brzmi jak "weź pierwszy root, a następnie weź drugi ze wszystkich elementów o nazwie el". Tak więc element something
nie wpływa na indeksowanie elementów el
. Możesz więc teoretycznie utworzyć ścieżkę xpath dla każdego konkretnego elementu w Twoim xml. W praktyce mam osiąga się to poprzez chodzenie po drzewie i zapamiętywanie informacji o elementach i ich indeksach po drodze.
Tworzenie xpath odwołujące się do określonego atrybutu elementu było po prostu dodawaniem '/ @attrName ' do xpath elementu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 12:49:26
Napisałem metodę zwracającą bezwzględną ścieżkę elementu w praktycznej bibliotece XML . Aby dać ci wyobrażenie, jak to działa, oto wyciąg z jednego z testów jednostkowych :
assertEquals("/root/wargle[2]/zargle",
DomUtil.getAbsolutePath(child3a));
Możesz więc przeszukiwać dokument, zastosować testy i użyć tego do zwrócenia ścieżki XPath. Albo, co jest prawdopodobnie lepsze, to to, że możesz użyć XPath-based assertions z tej samej biblioteki.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-20 12:53:49
Zrobiłem dokładnie to samo w zeszłym tygodniu, przetwarzając mój xml do formatu zgodnego z solr.
Skoro chciałeś pseudo kodu: tak to osiągnąłem.
/ / możesz pominąć odniesienie do rodzica i dziecka.
1_ Inicjalizacja niestandardowego obiektu węzła: NodeObjectVO {String nodeName, String path, List attr, nodeobjectvo parent, List child}
2_ Utwórz pustą listę
3_ tworzy reprezentację dom XML i iterację przez węzeł. Dla każdego węzła, get odpowiednie informacje. Wszystkie informacje takie jak nazwa węzła,nazwy atrybutów i wartość powinny być łatwo dostępne z obiektu dom. ( Musisz sprawdzić NodeType dom, kod powinien ignorować instrukcje przetwarzania i węzły zwykłego tekstu.)
/ / Code Bloat warning. 4_ jedyną trudną częścią jest get path. Stworzyłem iteracyjną metodę użytkową, aby uzyskać łańcuch XPath z NodeElement. (While (węzeł.Rodzicu != null) { ścieżka+ = węzeł.rodzic.nodeName}.
(można to również osiągnąć poprzez utrzymanie zmiennej globalnej path, która śledzi ścieżkę nadrzędną dla każdej iteracji.)
5_ w metodzie setter setAttributes (List) dodam ścieżkę obiektu ze wszystkimi dostępnymi atrybutami. (jedna ścieżka ze wszystkimi dostępnymi atrybutami. Nie Lista ścieżek z każdą możliwą kombinacją atrybutów. Może chcesz zrobić coś innego. )
6_ Dodaj NodeObjectVO do listy.
7_ teraz mamy płaską (nie hierarchialną) listę własnych obiektów węzłów, które mam wszystkie potrzebne informacje.
(Uwaga: Jak wspomniałem, utrzymuję relacje z rodzicami, powinieneś prawdopodobnie pominąć tę część. Istnieje możliwość wzdęcia kodu, szczególnie podczas getparentpath. Dla małych xml to nie był problem, ale jest to problem dla dużych xml).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-23 15:50:32