I need to modifies the XML, I have a parent tag parts which contain child tag part and Child tag sub child tags, They are Item,
brand, Manufacturer, Model, Cost. And it have a Brands tag and it contain Tag name called Brand. I need to change the brand attribute
(identifier) value. If the Brand child tag Contain (textcontent) text (Geforce_GT) is equal to the text in part tag : brand child tag : text
(Geforce_GT). Then Id attribute value of brand tag in part tag should assign to the Identifeir attribute in Brand tag child of Brands Parent tag.
<?xml version="1.0" ?>
<!DOCTYPE PARTS SYSTEM "parts.dtd">
<?xml-stylesheet type="text/css" href="xmlpartsstyle.css" ?>
<PARTS>
<TITLE>Computer Parts</TITLE>
<PART>
<ITEM id="CP1809_E1">Motherboard</ITEM>
<MANUFACTURER>ASUS</MANUFACTURER>
<MODEL>P3B-F</MODEL>
<COST>123.00</COST>
</PART>
<PART>
<ITEM id="CP1809_E2">Video Card</ITEM>
<BRAND id="CP1809_B1">Geforce_GT</BRAND>
<MANUFACTURER>ATI</MANUFACTURER>
<MODEL>All-in-Wonder Pro</MODEL>
<BRAND id="CP1809_B2">730_64-BIT</BRAND>
<COST>160.00</COST>
</PART>
<PART>
<ITEM id="CP1809_E3">Sound Card</ITEM>
<MANUFACTURER>Creative Labs</MANUFACTURER>
<MODEL>Sound Blaster Live</MODEL>
<COST>80.00</COST>
</PART>
<PART>
<ITEM id="CP1809_E3">inch Monitor</ITEM>
<MANUFACTURER>LG Electronics</MANUFACTURER>
<MODEL>995E</MODEL>
<COST>290.00</COST>
</PART>
<BRANDS>
<BRAND identifier="CP1809_E2">
<TEXTCONTENT>Geforce_GT</TEXTCONTENT>
</BRAND>
<BRAND identifier="CP1809_E2">
<TEXTOVERVIEW>730_64-BIT</TEXTOVERVIEW>
</BRAND>
<BRAND identifier="B1809_E3">
<TEXT>Empty</TEXT>
</BRAND>
<BRAND identifier="B1809_E4">
<TEXT>Empty</TEXT>
</BRAND>
</BRANDS>
</PARTS>
I need to make modifications such as:
<BRANDS>
<BRAND identifier = "CP1809_B1">
<TEXTCONTENT>Geforce_GT<TEXTCONTENT>
</BRAND>
<BRAND identifier = "CP1809_B2">
<TEXTCONTENT>730_64-BIT<TEXTCONTENT>
</BRAND>
<!-- ... -->
<BRANDS>
XSLT can do that (see example at http://xsltransform.net/ej9EGda):
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="desc" match="PART/BRAND" use="."/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="BRANDS/BRAND[key('desc', TEXTCONTENT)]/#identifier">
<xsl:attribute name="{name()}">
<xsl:value-of select="key('desc', ../TEXTCONTENT)/#id"/>
</xsl:attribute>
</xsl:template>
</xsl:transform>
On the Java platform you have a choice of XSLT processors like Saxon 9 for XSLT 2.0 or Xalan or Saxon 6 for XSLT 1.0 and the built-in version of Xalan in the JRE.
Related
How can I sort the XML nodes according to the tag and append in
the new XML using DOM parser or can it be done using DOM parser. We've
used DOM parser extensively for appending nodes into a new file but I am not able to sort the nodes.
Any help would be highly appreciated.
Input.xml
<rss version="2.0">
<Configs>
<Value>defaultValue</Value>
<Config name="test1">
<title>Title 1</title>
<author>Author1</author>
<value>5600</value>
<order>02</order>
</Config>
<Config name="test2">
<title>Title 2</title>
<author>Author2</author>
<Value>6100</Value>
<order>01</order>
</Config>
</Configs>
<Ratings>
<body>
<Items name="ac_object1">
<something1>something1</something1>
<value>someValue1</value>
<order>02</order>
</Items>
<Items name="op_object2">
<something1>something2</something1>
<value>someValue2</value>
<order>03</order>
</Items>
<Items name="vt_object3">
<something1>something3</something1>
<value>someValue3</value>
<order>01</order>
</Items>
</body>
</Ratings>
</rss>
Expected Output.xml
<rss version="2.0">
<Configs>
<Value>defaultValue</Value>
<Config name="test2">
<title>Title 2</title>
<author>Author2</author>
<Value>6100</Value>
<order>01</order>
</Config>
<Config name="test1">
<title>Title 1</title>
<author>Author1</author>
<value>5600</value>
<order>02</order>
</Config>
</Configs>
<Ratings>
<body>
<Items name="vt_object3">
<something1>something3</something1>
<value>someValue3</value>
<order>01</order>
</Items>
<Items name="ac_object1">
<something1>something1</something1>
<value>someValue1</value>
<order>02</order>
</Items>
<Items name="op_object2">
<something1>something2</something1>
<value>someValue2</value>
<order>03</order>
</Items>
</body>
</Ratings>
</rss>
You really don't want to do this using low-level DOM interfaces. Here's how to do it in XSLT 3.0 (which you can call from Java after installing Saxon-HE):
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform> version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[*/order]">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="number(order)"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:transform>
With a few extra lines of code you could also do it using XSLT 1.0, which comes bundled with the JDK.
How it works:
The xsl:mode declaration says that the default action for elements is to copy the element and then process its children
xsl:strip-space says ignore whitespace in the input
xsl:output says add indentation in the output
The xsl:template rule says that when processing an element that has order elements among its grandchildren, copy the start and end tag, and process the children in sorted order of the numeric value of their order child element.
So I am trying to pull results from two different XML files using XSLT in order to show a Restaurant Review. I have Restaurant details in allRestaurants.xml and have all of the reviews for these restaurants in allReviews.xml. I have currently stored a tag against each restaurant, and the reviews are each associated with a specific restaurant as well, so carry the same tag. I need to build a page that takes the restaurant with ID 1 and beneath is show the reviews for that restaurant. The reviews are stored with the exactly the same 1 as per below. Please help.
allRestaurants.xml
<restaurants>
<restaurant>
<restaurant_id>1</restaurant_id>
<name>The Jackaroo</name>
<street_address>107-109 Darlinghurst Road</street_address>
<postcode>2011</postcode>
<city>Sydney</city>
<state>NSW</state>
<country>Australia</country>
<email>info#jackaroo.com.au</email>
<telephone>93322244</telephone>
<stars>3</stars>
</restaurant>
<restaurant>
<restaurant_id>2</restaurant_id>
<name>Four Seasons restaurant Sydney</name>
<street_address>199 George Street</street_address>
<postcode>2000</postcode>
<city>Sydney</city>
<state>NSW</state>
<country>Australia</country>
<email>info#sydneyfourseasons.com.au</email>
<telephone>92503100</telephone>
<stars>5</stars>
</restaurant>
</restaurants>
allReviews.xml
<reviews>
<review id="1">
<restaurant_id>1</restaurant_id>
<author_id>1</author_id>
<headline>Clean Bare-Bones Hostel</headline>
<details>
Example text here
</details>
<rating>3</rating>
<date>1388782853</date>
</review>
<review id="2">
<restaurant_id>1</restaurant_id>
<author_id>3</author_id>
<headline>Wouldn't Recommend</headline>
<details>
Example text here
</details>
<rating>2</rating>
<date>1368748800</date>
</review>
<review id="3">
<restaurant_id>2</restaurant_id>
<author_id>2</author_id>
<headline>Overall I Enjoyed</headline>
<details>
Example text here
</details>
<rating>4</rating>
<date>1378788850</date>
</review>
</reviews>
I thought maybe merging them into one XML file like so would do the trick, but even then, I'm not sure where to start:
oneHotel.xml
<?xml-stylesheet type="text/xsl" href="oneHotel.xsl"?>
<list>
<entry name="allHotels.xml" />
<entry name="reviews.xml" />
</list>
This is as far as I got in the XSLT doc, and am drawing a massive blank. I don't even know where to start:
oneHotel.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:copy-of name="restaurant" select="document('allRestaurants.xml')
/restaurants/restaurant[restaurant_id=1]"/>
<xsl:copy-of name="reviews" select="document('allReviews.xml')
/reviews/review[restaurant_id=1]"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="document('allRestaurants.xml')
/restaurants/restaurant[restaurant_id=1]"/>
<h2><xsl:value-of select="name"/></h2>
</xsl:choose>
<h2><xsl:value-of select="$restaurant/name"/></h2>
</xsl:template>
</xsl:stylesheet>
Try this as your starting point:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:param name="path-to-reviews" select="'allReviews.xml'"/>
<xsl:key name="review-by-restaurant-id" match="review" use="restaurant_id" />
<xsl:template match="/restaurants">
<html>
<body>
<h1>Restaurant Reviews</h1>
<xsl:apply-templates select="restaurant"/>
</body>
</html>
</xsl:template>
<xsl:template match="restaurant">
<h2>
<xsl:value-of select="name"/>
</h2>
<xsl:variable name="id" select="restaurant_id" />
<!-- switch context to lookup document in order to use key -->
<xsl:for-each select="document($path-to-reviews)">
<xsl:for-each select="key('review-by-restaurant-id', $id)">
<h3>
<xsl:value-of select="headline"/>
</h3>
<p>
<xsl:value-of select="details"/>
</p>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This assumes that you are instructing your XSLT processor to process the allRestaurants.xml document and passing the path to the allReviews.xml document as a parameter.
You didn't tell us what you want your final result to look like, so I just made up a very basic page.
I have a pair of XML Files with following structure :
* the data contained here is random
<root_tag>
<packages>
<package>
<name>class_name1</name>
<classes>3</classes>
<functions>21</functions>
< ncss>285</ncss>
<javadocs>20</javadocs>
<javadoc_lines>111</javadoc_lines>
<single_comment_lines>11</single_comment_lines>
<multi_comment_lines>222</multi_comment_lines>
</package>
</packages>
<objects>
<object>
<name>object1</name>
<ncss>255</ncss>
<functions>17</functions>
<classes>2</classes>
<javadocs>20</javadocs>
</object>
</objects<
<functions>
<function>
<name>function1</name>
<ncss>242</ncss>
<ccn>63</ccn>
<javadocs>1</javadocs>
</function>
</functions>
</root_tag>
A package has following data items within it :
name classes functions ncss javadocs javadoc_lines single_comment_lines multi_comment_lines
An object has following data items associated with it :
name functions ncss javadocs classes
A Function has following data items :
name ncss ccn javadocs
Suppose that my 2nd xml file contains some different values for function1. How do I merge these xml files into a third file and assign a unique id to each name element so the output is as follows :
File Id Name Classes Functions NCSS JavaDocs JavaDocLines SingleCommentLines
File1 func1 somefun Null Null 10 20 30 40
File2 func1 somefun Null Null 11 23 40 50
And is there any way to do this through a java program ?
With XSLT you could achieve it with something like this :
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<!-- You need to store the other inputs in xsl:variable to access it inside the same
stylesheet -->
<xsl:variable name="file2" select="document('file2.xml')"/>
<xsl:output method="xml"/>
<xsl:template match="root_tag">
<!--Open here your personnal layout stuffs,
like opening tables <table:table> and things like that (column labels...)-->
<table>
<labels/>
<xsl:apply-templates select=".//function | $file2//function">
<!-- All the work is done by the xsl:sort which can specify the order you
want to process you elements. -->
<xsl:sort select="name"/>
</xsl:apply-templates>
<!-- Close here your layout stuffs -->
</table>
</xsl:template>
<xsl:template match="function">
<!-- Open here your layout inline stuffs-->
<line>
<!-- Here you may prefer to apply the templates
in specific order in case of 'melted' input,
do this by calling templates in queue, like <xsl:apply-templates
select="name"/> <xsl:apply-templates select="ncss"/>...-->
<xsl:apply-templates select="*"/>
<!-- Close here your layout inline stuffs -->
</line>
</xsl:template>
<!-- This template may apply to anything but he's applied only on function childs
during the process -->
<xsl:template match="*">
<!-- Open here the cell stuffs (<table:table-cell>) -->
<cell>
<xsl:value-of select="."/>
<!-- Close here the cell stuffs-->
</cell>
</xsl:template>
I used some dummy elements for the "layout stuffs" (like table, line and cell).
Hope this may help.
What would be the best way to populate (or generate) an XML template-file from a mapping of XPath expressions?
The requirements are that we will need to start with a template (since this might contain information not otherwise captured in the XPath expressions).
For example, a starting template might be:
<s11:Envelope xmlns:s11='http://schemas.xmlsoap.org/soap/envelope/'>
<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'>
<article xmlns:ns1='http://predic8.com/material/1/'>
<name>?XXX?</name>
<description>?XXX?</description>
<price xmlns:ns1='http://predic8.com/common/1/'>
<amount>?999.99?</amount>
<currency xmlns:ns1='http://predic8.com/common/1/'>???</currency>
</price>
<id xmlns:ns1='http://predic8.com/material/1/'>???</id>
</article>
</ns1:create>
</s11:Body>
</s11:Envelope>
Then we are supplied, something like:
expression: /create/article[1]/id => 1
expression: /create/article[1]/description => bar
expression: /create/article[1]/name[1] => foo
expression: /create/article[1]/price[1]/amount => 00.00
expression: /create/article[1]/price[1]/currency => USD
expression: /create/article[2]/id => 2
expression: /create/article[2]/description => some name
expression: /create/article[2]/name[1] => some description
expression: /create/article[2]/price[1]/amount => 00.01
expression: /create/article[2]/price[1]/currency => USD
We should then generate:
<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'>
<article xmlns:ns1='http://predic8.com/material/1/'>
<name xmlns:ns1='http://predic8.com/material/1/'>foo</name>
<description>bar</description>
<price xmlns:ns1='http://predic8.com/common/1/'>
<amount>00.00</amount>
<currency xmlns:ns1='http://predic8.com/common/1/'>USD</currency>
</price>
<id xmlns:ns1='http://predic8.com/material/1/'>1</id>
</article>
<article xmlns:ns1='http://predic8.com/material/2/'>
<name>some name</name>
<description>some description</description>
<price xmlns:ns1='http://predic8.com/common/2/'>
<amount>00.01</amount>
<currency xmlns:ns1='http://predic8.com/common/2/'>USD</currency>
</price>
<id xmlns:ns1='http://predic8.com/material/2/'>2</id>
</article>
</ns1:create>
I am implemented in Java, although I would prefer an XSLT-based solution if one is possible.
PS: This question is the reverse of another question I recently asked.
This transformation creates from the "expressions" an XML document that has the structure of the wanted result -- it remains to transform this result into the final result:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vPop" as="element()*">
<item path="/create/article[1]/id">1</item>
<item path="/create/article[1]/description">bar</item>
<item path="/create/article[1]/name[1]">foo</item>
<item path="/create/article[1]/price[1]/amount">00.00</item>
<item path="/create/article[1]/price[1]/currency">USD</item>
<item path="/create/article[1]/price[2]/amount">11.11</item>
<item path="/create/article[1]/price[2]/currency">AUD</item>
<item path="/create/article[2]/id">2</item>
<item path="/create/article[2]/description">some name</item>
<item path="/create/article[2]/name[1]">some description</item>
<item path="/create/article[2]/price[1]/amount">00.01</item>
<item path="/create/article[2]/price[1]/currency">USD</item>
</xsl:variable>
<xsl:template match="/">
<xsl:sequence select="my:subTree($vPop/#path/concat(.,'/',string(..)))"/>
</xsl:template>
<xsl:function name="my:subTree" as="node()*">
<xsl:param name="pPaths" as="xs:string*"/>
<xsl:for-each-group select="$pPaths"
group-adjacent=
"substring-before(substring-after(concat(., '/'), '/'), '/')">
<xsl:if test="current-grouping-key()">
<xsl:choose>
<xsl:when test=
"substring-after(current-group()[1], current-grouping-key())">
<xsl:element name=
"{substring-before(concat(current-grouping-key(), '['), '[')}">
<xsl:sequence select=
"my:subTree(for $s in current-group()
return
concat('/',substring-after(substring($s, 2),'/'))
)
"/>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="current-grouping-key()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the result is:
<create>
<article>
<id>1</id>
<description>bar</description>
<name>foo</name>
<price>
<amount>00.00</amount>
<currency>USD</currency>
</price>
<price>
<amount>11.11</amount>
<currency>AUD</currency>
</price>
</article>
<article>
<id>2</id>
<description>some name</description>
<name>some description</name>
<price>
<amount>00.01</amount>
<currency>USD</currency>
</price>
</article>
</create>
Note:
You need to transform the "expressions" you are given into the format used in this transformation -- this is easy and straightforward.
In the final transformation you need to copy every node "as-is" (using the identity rule), with the exception that the top node should be generated in the "http://predic8.com/wsdl/material/ArticleService/1/" namespace. Note that the other namespaces present in the "template" are not used and can be safely ommitted.
This solution requires you to re-organise your XPATH input information slightly, and to allow a 2-step transformation. The first transformation will write the stylesheet, which will be executed in the second transformation - Thus the client is required to do two invocations of the XSLT engine. Let us know if this is a problem.
Step One
Please re-organise your XPATH information into an XML document like so. It should not be difficult to do, and even an XSLT script could be written to do the job.
<paths>
<rule>
<match>article[1]/id[1]</match>
<namespaces>
<namespace prefix="ns1">http://predic8.com/wsdl/material/ArticleService/1/</namespace>
<!-- The namespace node declares a namespace that is used in the match expression.
There can be many of these. It is not required to define the s11: namespace,
nor the ns1 namespace. -->
</namespaces>
<replacement>1</replacement>
</rule>
<rule>
<match>article[1]/description[1]</match>
<namespaces/>
<replacement>bar</replacement>
</rule>
... etc ...
</paths>
Solution constraints
In the above rules document we are constrained so that:
The match is implicitly prefixed 'expression: /create/'. Don't put that explicitly.
All matches must begin like article[n] where n is some ordinal number.
We can't have zero rules.
Any prefixes that you use in the match, other than s11="http://schemas.xmlsoap.org/soap/envelope/" and ns1="http://predic8.com/wsdl/material/ArticleService/1/". (Note: I don't think it is valid for namespaces to end in '/' - but not sure about that), are defined in the namespaces node.
The above is the input document to the step one transformation. Apply this document to this style-sheet ...
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:step2="http://www.w3.org/1999/XSL/Transform-step2"
xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://predic8.com/wsdl/material/ArticleService/1/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes='xsl'>
<xsl:output method="xml" indent="yes" encoding="UTF-8" />
<xsl:namespace-alias stylesheet-prefix="step2" result-prefix="xsl"/>
<xsl:template match="/">
<step2:stylesheet version="2.0">
<step2:output method="xml" indent="yes" encoding="UTF-8" />
<step2:variable name="replicated-template" as="element()*">
<step2:apply-templates select="/" mode="replication" />
</step2:variable>
<step2:template match="#*|node()" mode="replication">
<step2:copy>
<step2:apply-templates select="#*|node()" mode="replication" />
</step2:copy>
</step2:template>
<step2:template match="/s11:Envelope/s11:Body/ns1:create/article" mode="replication">
<step2:variable name="replicant" select="." />
<step2:for-each select="for $i in 1 to
{max(for $m in /paths/rule/match return
xs:integer(substring-before(substring-after($m,'article['),']')))}
return $i">
<step2:for-each select="$replicant">
<step2:copy>
<step2:apply-templates select="#*|node()" mode="replication" />
</step2:copy>
</step2:for-each>
</step2:for-each>
</step2:template>
<step2:template match="#*|node()">
<step2:copy>
<step2:apply-templates select="#*|node()"/>
</step2:copy>
</step2:template>
<step2:template match="/">
<step2:apply-templates select="$replicated-template" />
</step2:template>
<xsl:apply-templates select="paths/rule" />
</step2:stylesheet>
</xsl:template>
<xsl:template match="rule">
<step2:template match="s11:Envelope/s11:Body/ns1:create/{match}">
<xsl:for-each select="namespaces/namespace">
<xsl:namespace name="{#prefix}" select="." />
</xsl:for-each>
<step2:copy>
<step2:apply-templates select="#*"/>
<step2:value-of select="'{replacement}'"/>
<step2:apply-templates select="*"/>
</step2:copy>
</step2:template>
</xsl:template>
</xsl:stylesheet>
Step Two
Apply your soap envelope file, as an input document, to the style-sheet which was output from step one. The result is the original soap document, altered as required. This is a sample of a step two style-sheet, with just the first rule (/create/article[1]/id => 1) being considered for the sake of simplicity of illustration.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/"
version="2.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template xmlns:ns1="http://predic8.com/wsdl/material/ArticleService/1/"
match="/s11:Envelope/s11:Body/ns1:create[1]/article[1]/id[1]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:value-of select="'1'"/>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
More solution constraints
The template document must contain at least one /s11:Envelope/s11:Body/ns1:create/article . Only the article node is replicated (deeply) as required by rules. Other than than it can be any structure.
The template document cannot contain nested levels of the s11:Envelope/s11:Body/ns1:create node.
Explanation
You will notice that your XPATH expressions are not far removed from a match condition of template. Therefore it is not too difficult to write a stylesheet which re-expresses your XPATH and replacement values as template rules. When writing a style-sheet writing style-sheet the xsl:namespace-alias enables us to disambiguate "xsl:" as an instruction and "xsl:" as intended output. When XSLT 3.0 comes along, we are quiet likely to be able to reduce this algorithm into one step, as it will allow dynamic XPATH evaluation, which is really the nub of your problem. But for the moment we must be content with a 2-step process.
The second style-sheet is a two-phase transformation. The first stage replicates the template from the article level, as many times as needed by the rules. The second phase parses this replicated template, and applies the dynamic rules substituting text values as indicated by the XPATHs.
UPDATE
My original post was wrong. Thanks to Dimitre for pointing out the error. Please find updated solution above.
After-thought
If a two-step solultion is too complicated, and you are running on a wintel platform, you may consider purchasing the commercial version of Saxon. I believe that the commercial version has a dynamic XPATH evaluation function. I can't give you such a solution because I don't have the commercial version. I imagine a solution using an evaluate() function would be a lot simpler. XSLT is just a hobby for me. But if you are using XSLT for business purposes, the price is quiet reasonable.
In my case, I have:
<booklist>
<book id="1">
</book>
<book id="2">
</book>
<book id="3">
</book>
......
</booklist>
How can i just return:
<booklist>
<book id="1">
</book>
</booklist>
if I use /booklist/book[#id=1], I can only get
<book id="1">
</book>
But I also need the document element.
Thanks
Rather than selecting the element that you do want, try excluding the elements that you don't want.
If you are just using XPATH, this will select all of the elements except for the book elements who's #id is not equal to 1 (i.e. <booklist><book id="1" /></booklist>).
//*[not(self::book[#id!='1'])]
If you want an XSLT solution, this stylesheet has an empty template that matches all of the <book> elements that do not have #id="1", which prevents them from being copied into the output.
Everything else (document node <booklist> and <book id="1">) will match the identity template, which copies forward.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!--Empty template to prevent book elements
that do not have #id="1" from being
copied into the output -->
<xsl:template match="book[#id!='1']" />
<!--identity template to copy all nodes and attributes to output -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
How can i just return:
< booklist >
< book id=1 >
< /book >
< /booklist >
XPath is a query language. Evaluating an XPath expression cannot change the structure of the XML document.
This is why the answer is: No, with XPath this is not possible!
Whenever you want to transform an XML document (which is exactly the case here), the probably best solution is to use XSLT -- a language which was designed especially for processing and transforming tree-structured data.
Here is a very simple XSLT solution:
<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:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="book[not(#id=1)]"/>
</xsl:stylesheet>
When this transformation is applied to the provided XML file, the wanted, correct result is produced:
<booklist>
<book id="1"/>
</booklist>
When you try to select a sub-element, only this will be returned.