xpath based on attribute and value - c#

I need to process same xml, below is partial of it:
<response>
<dat id="1">
<b>
<i>1</i>
</b>
<ds>
<d>
<t>2012-12-21</t>
what xpath I need to use to select the element "d", which has a parent-parent element "dat" with attribute id="1" and has a child element "t" with value of 2012-12-21?
It is in C#, the application grabs this xml from internet and loads it into XmlDocument. Currently I am doing multiple steps: find note "dat", then find "t" under "dat", then go to parent node "d". I am hoping to have xpath to replace the above multiple steps.
I've tried this xpath: //dat[#id="1"]/ds/d[t="2012-12-21"], but return null node
Thanks.
EDIT:
See below for Dimitre Novatchev's solution.
btw: //dat[#id="1"]/ds/d[t="2012-12-21"] worked as well, not sure what happened when I did the test.

Use:
//dat[#id = 1]/*/d[t[. = '2012-12-21']]
This selects all d elements that have a t child, whose string value is the string '2012-12-21',
and whose grand-parent is a dat that has an id attribute with numeric value of 1.
In case you want to select just one (say the first) of all such elements, use:
(//dat[#id = 1]/*/d[t[. = '2012-12-21']])[1]
XSLT - based verification:
<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="/">
<xsl:copy-of select="//dat[#id = 1]/*/d[t[. = '2012-12-21']]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the (finished) provided XML document:
<response>
<dat id="1">
<b>
<i>1</i>
</b>
<ds>
<d>
<t>2012-12-21</t>
</d>
</ds>
</dat>
</response>
the above XPath expression is evaluated and the result of this evaluation (the selected node(s) ) is copied to the output:
<d>
<t>2012-12-21</t>
</d>

Related

Create result tree fragment variable

I am using an xsl stylesheet to output an xsl:fo document with an SVG chart embedded.
I am having trouble taking an array from the input, and splitting it into several smaller arrays, stored in variables so that the SVG template can be applied to the different arrays to generate 3 different charts. The input looks like this (please note the custom ns):
<root xmlns="http://xml.mynamespace.com">
<data>
<list>
<item>
<id>1</id>
<title>Foo</title>
<score>10</score>
</item>
<item>
<id>2</id>
<title>Bar</title>
<score>6</score>
</item>
<item>
<id>3</id>
<title>Baz</title>
<score>16</score>
</item>
<item>
<id>4</id>
<title>Fizz</title>
<score>14</score>
</item>
<item>
<id>5</id>
<title>Buzz</title>
<score>7</score>
</item>
</list>
</data>
</root>
These value can be split into 3 distinct groups. I am trying to split the array list into 3 separate variables to that a template can be applied to turn them into an SVG chart. The SVG transform is known to work for the array as above, so I think the problem is the way I am trying to create the variables. I have tried a few different ways, but I have had the most success (if you can call it that) using xsl:copy-of as so (again, please be aware of the ns):
<xslt:stylesheet xmlns:m="http://xml.mynamespace.com" version="1.0">
<xsl:variable name="group1">
<xsl:element name="m:list">
<xsl:copy-of select="/m:root/m:data/m:list/m:item[id <= 3]"/>
</xsl:element>
</xsl:variable>
</xslt:stylesheet>
and then later the variable is used like so:
<xsl:apply-templates select="msxsl:node-set($group1)/m:list" />
The reason I am putting them in variables is because the template that creates the SVG expects input in the format of <list> with one or more child item elements. The SVG transform template is as so:
<xsl:template match="m:list">
<xsl:variable name="canvasHeight" select="28 * count(m:item)"/>
<svg height="{$canvasHeight}">
<xsl:for-each select="m:item">
<!-- Draw bar here -->
</xsl:for-each>
</svg>
</xsl:template>
The output when I try to transform the variable to SVG as above indicates that the list element is created correctly (because the template matches and the SVG element is output) but the item elements aren't copied because the for-each doesn't seem to have executed and the outputted height is 0.
Am I incorrectly creating the variable group1? Or is there an easier way to do this that doesn't require splitting the initial list into separate variables?
Well with /m:root/m:data/m:list/m:item[Id <= 3] you simply have the wrong case (Id versus id) and the wrong namespace (none versus m:id) in the predicate.

JSON to XML using XSLT

I met a requirement in which I need to get transfer JSON data into various XML document on the basis of XSLT.
In fact, same json data goes to different systems and they have their own object structure (properties nesting level etc) to store it.
I use XslCompiledTransform() in C# to transform Xml into Json; And now looking if there is any efficient way of transforming JSON into XML using XSLT ?
I don't think this will work. JSON is not XML based, so you can't apply XSLT transformations on it. XML to JSON would work, but not JSON to XML
Edit. I was wrong, check this: https://github.com/bramstein/xsltjson and this: How to convert json to xml using xslt
XSLT is to change one xml document to another xml document, however, json is not even a xml type document..
you can write a simple application to transfer the format
Setting aside the fact that XSLT is definitely not the right tool for that job, here's a pseudo approach to how I'd do it if I ever had to:
Create an extension function in C# that does the real job, i.e., getting a JSON string as an argument, returning a generic XPathNodeIterator XML chunk.
Process that result normally with XSLT to return the final custom converted format.
The XSLT would then look something like this (assuming XSLT 1.0 since you're in C#):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:util="urn:JsonUtility.Converter"
>
<!-- Supplied from environment -->
<xsl:param name="json" />
<xsl:template match="/">
<xsl:variable name="xml" select="util:JSON2XML($json)" />
<!-- Start processing the returned XML -->
<xsl:apply-templates select="$xml/json" />
</xsl:template>
<xsl:template match="key">
<!-- output -->
</xsl:template>
<xsl:template match="array">
<!-- output -->
</xsl:template>
<!-- etc. -->
</xsl:stylesheet>
(Alternatively, if you create the final format in the C# extension, you could just do a <xsl:copy-of select="$xml" /> in the root template.)

String.Format("Your query {0} with {1} Placeholders", theQuery, results.Count) Equivalent in XSLT

Is there an equivalent function to string format in XSLT?
I'm working on a multi-lingual site in umbraco. I'm not aware of what languages will be needed, butbeing as they are, one language could order the words differently e.g.
English "Your query 'Duncan' matched 5 results." could translate word for word to
"5 results matched 'Duncan' query".
For this reason having a item for "Your query", "matched" and "results" in my umbraco translation isn't feasible. If I was to make this a user control for C# I would have the translator to provide a dictionary item like "Your query '{0}' matched {1} results".
Is there an equivalent function to
string format in XSLT?
This is a close analog in XSLT:
The dictionary entry has the following format:
<t>Your query "<query/>" matched <nResults/> results</t>
The transformation (corresponding to string.format()) is very simple:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pQuery" select="'XPath and XSLT'"/>
<xsl:param name="pNumResults" select="3"/>
<xsl:template match="query">
<xsl:value-of select="$pQuery"/>
</xsl:template>
<xsl:template match="nResults">
<xsl:value-of select="$pNumResults"/>
</xsl:template>
</xsl:stylesheet>
and it produces the wanted, correct result:
Your query "XPath and XSLT" matched 3 results
You could extend your XSLT with a custom function:
http://our.umbraco.org/wiki/reference/xslt/extend-your-xslt-with-custom-functions.

C# consolidate namespace references in xml

I have xml formatted by the atom formatter.
The atom formatter seems to specify namespaces inline multiple times.
Is there any way to easily consolidate these.
The example below shows namespaces specified three times for each property.
This is horrible.
I would like prefixes at the top of the document and no namespaces in the document (just prefixes). Is there a writer or formatter option to achieve this?
<property p3:name="firstname" xmlns:p3="http://a9.com/-/opensearch/extensions/property/1.0/" xmlns="http://a9.com/-/opensearch/extensions/property/1.0/">Drikie</property>
Thanks
Craig.
The atom formatter seems to specify
namespaces inline multiple times.
Is there any way to easily consolidate
these. The example below shows
namespaces specified three times for
each property. This is horrible.
The easiest way to produce this more compact format is to apply the following XSLT transformation on your XML document:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()[not(self::*)]|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="descendant::*/namespace::*"/>
<xsl:copy-of select="namespace::*"/>
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
For example, when applied on the following XML document (based on your question):
<t xmlns="http://a9.com/-/opensearch/extensions/property/1.0/">
<property p3:name="firstname"
xmlns:p3="http://a9.com/-/opensearch/extensions/property/1.0/"
xmlns="http://a9.com/-/opensearch/extensions/property/1.0/"
>Drikie</property>
</t>
the wanted result is produced:
<t
xmlns="http://a9.com/-/opensearch/extensions/property/1.0/"
xmlns:p3="http://a9.com/-/opensearch/extensions/property/1.0/">
<property p3:name="firstname">Drikie</property>
</t>
Do note:
A namespace declaration cannot be promoted further above an element that has a declaration that binds the same prefix to another namespace.
Promoting a namespace declaration to an ancestor element may increase the size of the parsed XML document, because all namespace nodes are propagated down to all descendent nodes, some of which may not need at all that namespace.

How determine the right xml to write out

<?xml version="1.0" encoding="UTF-8"?>
<idmef:IDMEF-Message version="1.0" xmlns:idmef="http://iana.org/idmef">
<idmef:Alert messageid="abc123456789">
<idmef:Analyzer analyzerid="bc-corr-01">
<idmef:Node category="dns">
<idmef:name>correlator01.example.com</idmef:name>
</idmef:Node>
</idmef:Analyzer>
<idmef:CreateTime ntpstamp="0xbc72423b.0x00000000">2000-03-09T15:31:07Z
</idmef:CreateTime>
<idmef:Source ident="a1">
<idmef:Node ident="a1-1">
<idmef:Address ident="a1-2" category="ipv4-addr">
<idmef:address>192.0.2.200</idmef:address>
</idmef:Address>
</idmef:Node>
</idmef:Source>
<idmef:Target ident="a2">
<idmef:Node ident="a2-1" category="dns">
<idmef:name>www.example.com</idmef:name>
<idmef:Address ident="a2-2" category="ipv4-addr">
<idmef:address>192.0.2.50</idmef:address>
</idmef:Address>
</idmef:Node>
<idmef:Service ident="a2-3">
<idmef:portlist>5
</idmef:portlist>
</idmef:Service>
</idmef:Target>
<idmef:Classification text="Login Authentication">
<idmef:Reference origin="vendor-specific">
<idmef:name>portscan</idmef:name>
<idmef:url>http://www.vendor.com/portscan</idmef:url>
</idmef:Reference>
</idmef:Classification>
<idmef:Assessment>
<idmef:Impact severity ="high" completion ="failed" type ="file" >
</idmef:Impact>
</idmef:Assessment>
</idmef:Alert>
</idmef:IDMEF-Message>
I'm working with a xml messaging system, where a message packet is read from a queue, and applied against a rule with a pattern in it. If the pattern matches, the rule fires and some elements, node etc of the xml are read and stored. The definition of what to be read from the message is defined using Xpath expression. For example, the following xpath takes the severity attribute and store it.
name.set(".//idmef:Classification/idmef:Assesment/idmef:Impact/#severity","high");
So, I would take that xpath, compile it, and read the serverity attribute and store for latter use.
When I go to create the new XML message using the stored value, there may be a case that the completion and type attribute are mandatory.
So question is, how do I check if those attributes need to be written out. I know that schema is involved somehow, but how do you do it. More to the point, if the user selects only the severity attribute, how would I go about, adding in the rest of the structure, like Classification, Message and other elements, when have additional xpath lookups, for example down at
Bob.
The commenters are correct - you need to first fix your XML to make it well formed.
However, If I understand your problem correctly, you need write out some XML, adding or changing some attributes.
If this is what you need I would try using an XSL transform to add the attributes.
Here is a modified version of the identity transform that should be close to what you need.
if you need some conditional logic then surround the attribute tags with xsl:if
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:idmef="http://iana.org/idmef" xpath-default-namespace="http://iana.org/idmef">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Impact">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:attribute name="severity">high</xsl:attribute>
<xsl:attribute name="completion">failed</xsl:attribute>
<xsl:attribute name="type">file</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You could:
Open original XML (A)
Create a new XML document (B)
Run your xpath against (A)
Add matching results to (B)
Save (B)
This makes any sense?
I found an answer here on stackoverflow, and here it is. Create XML Nodes from XPath I know it is as far away from how I described it above, but at the time I was designing it, I
didn't have a scobie how it would work.

Categories