XPath sorting inline if statement - xslt

I've been trying to wrap my head around using XPath and XQuery for this with the help of some previous posts to no avail. Right now I have null child nodes which should just default to ordering at the end of a sort but unfortunately, the sort does not occur at all on these null nodes. As a result I have been trying to find a way to set them to zero during the sorting section. Here is a sample below:
<xsl:for-each select="MyItems/Item">
<xsl:sort select="Order/obj/Number" order="ascending">
I want to do something similar to an inline if statement as part of the sort like in C# below:
foreach(item in MyItems.OrderBy(Order/obj/Exists != false ? Order/obj/Number : 0)
I was using these links: dynamic xpath expression and XSLT transfom with inline if statements to try and understand but I'm still not getting it. Any help is appreciated. I need the solution in XSLT.

Your situation is unclear as you say nothing about the contents of your XML or the nature of your XSLT transform. But it sounds something like you have Item elements with no Order/obj/Number elements to sort on?
I would code that something like this
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="MyItems/Item[Order/obj/Number]">
<xsl:sort select="Order/obj/Number" />
</xsl:apply-templatesh>
<xsl:apply-templates select="MyItems/Item[not(Order/obj/Number)]" />
</xsl:copy>
</xsl:template>
<xsl:template select="MyItems/Item">
<xsl:copy-of select="current()" />
</xsl:template>

Talking about "null nodes" isn't helpful. It's not a well-defined term. Show us your XML, your desired results and your actual results, and we can help you.
What should happen is that if the select expression in xsl:sort returns an empty sequence/node-set, the effective sort key is a zero-length string, so these items sort before any others (assuming ascending order).

Related

Constructing, not selecting, XSL node set variable

I wish to construct an XSL node set variable using a contained for-each loop. It is important that the constructed node set is the original (a selected) node set, not a copy.
Here is a much simplified version of my problem (which could of course be solved with a select, but that's not the point of the question). I've used the <name> node to test that the constructed node set variable is in fact in the original tree and not a copy.
XSL version 1.0, processor is msxsl.
Non-working XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="entryNodes" select="msxsl:node-set($entries)"/>
<xsl:for-each select="$entryNodes">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<name>X</name>
<entry>1</entry>
<entry>2</entry>
</root>
Wanted output:
X1X2
Actual output:
12
Of course the (or a) problem is the copy-of, but I can't work out a way around this.
There isn't a "way around it" in XSLT 1.0 - it's exactly how this is supposed to work. When you have a variable that is declared with content rather than with a select then that content is a result tree fragment consisting of newly-created nodes (even if those nodes are a copy of nodes from the original tree). If you want to refer to the original nodes attached to the original tree then you must declare the variable using select. A better question would be to detail the actual problem and ask how you could write a suitable select expression to find the nodes you want without needing to use for-each - most uses of xsl:if or xsl:choose can be replaced with suitably constructed predicates, maybe involving judicious use of xsl:key, etc.
In XSLT 2.0 it's much more flexible. There's no distinction between node sets and result tree fragments, and the content of an xsl:variable is treated as a generic "sequence constructor" which can give you new nodes if you construct or copy them:
<xsl:variable name="example" as="node()*">
<xsl:copy-of select="//entry" />
</xsl:variable>
or the original nodes if you use xsl:sequence:
<xsl:variable name="example" as="node()*">
<xsl:sequence select="//entry" />
</xsl:variable>
I wish to construct an XSL node set variable using a contained
for-each loop.
I have no idea what that means.
It is important that the constructed node set is the original (a
selected) node set, not a copy.
This part I think I understand a little better. It seems you need to replace:
<xsl:variable name="entries">
<xsl:for-each select="//entry">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
with:
<xsl:variable name="entries" select="//entry"/>
or, preferably:
<xsl:variable name="entries" select="root/entry"/>
The resulting variable is a node-set of the original entry nodes, so you can do simply:
<xsl:for-each select="$entries">
<xsl:value-of select="/root/name"/>
<xsl:value-of select="."/>
</xsl:for-each>
to get your expected result.
Of course, you could do the same thing by operating directly on the original nodes, in their original context - without requiring the variable.
In response to the comments you've made:
We obviously need a better example here, but I think I am getting a vague idea of where you want to go with this. But there are a few things you must understand first:
1.
In order to construct a variable which contains a node-set of nodes in their original context, you must use select. This does not place any limits whatsoever on what you can select. You can do your selection all at once, or in stages, or even in a loop (here I mean a real loop). You can combine the intermediate selections you have made in any way sets can be combined: union, intersection, or difference. But you must use select in all these steps, otherwise you will end up with a set of new nodes, no longer having the context they did in the source tree.
IOW, the only difference between using copy and select is that the former creates new nodes, which is precisely what you wish to avoid.
2.
xsl:for-each is not a loop. It has no hierarchy or chronology. All the nodes are processed in parallel, and there is no way to use the result of previous iteration in the current one - because no iteration is "previous" to another.
If you try to use xsl:for-each in order to add each of n processed nodes to a pre-existing node-set, you will end up with n results, each containing the pre-existing node-set joined with one of the processed nodes.
3.
I think you'll find the XPath language is quite powerful, and allows you to select the nodes you want without having to go through the complicated loops you hint at.
It might help if you showed us a problem that can't be trivially solved in XSLT 1.0. You can't solve your problem the way you are asking for: there is no equivalent of xsl:sequence in XSLT 1.0. But the problem you have shown us can be solved without such a construct. So please explain why you need what you are asking for.

Debug possible escaped-text issue in XSLT?

I have an XSL template that gives different results when used in two different contexts.
The template manifesting the defect is:
<xsl:template match="*" mode="blah">
<!-- snip irrelevant stuff -->
<xsl:if test="see">
<xsl:message>Contains a cross-ref. <xsl:value-of select="."/></xsl:message>
</xsl:if>
<xsl:apply-templates select="."/>
</xsl:template>
Given:
<el>This is a '<see cref="foo"/>' cross-referenced element.</el>
In one situation, I get the desired result:
Contains a cross-ref. This is a ' ' cross-referenced element.
(the <see/> is being dealt with as an XML element and is ultimately matched by another template.)
But in another situation, the xsl:if doesn't trigger and if I output the contents with <xsl:message><xsl:value-of select="."/>, I get:
This is a '<see cref="foo"/>' cross-referenced element.
It seems to me that in the latter improperly-behaving scenario, it's acting like it's been output-escaped. Does that make sense? Am I barking up the wrong tree? This is a typically complex XSL situation and trying to trace the call-stack is difficult; is there a particular XSLT processing command I should be looking for?

Filter and sort table based XML

I am new to xslt and have done some research and read a brief book and looked at many examples but I'm afraid I just don't get it. I've only done simple procedural coding before and I guess I'm missing something. I understand a very basic example but when I try to transform my own data I am completely lost. Boo hoo hoo. It is soooo frustrating knowing that I don't it! I really feel like a hack :(
Anyway, I generated the following XML from a table in MS Word. The table ID and Row and Column IDs of each cell are given so it is possible to know how things relate to each other.
Now I want to present the data in a pick list and basically want to:
1. Filter the data on, say [Name='p_fld_parent_ref' and Value='RM12']. The data with the matching "rows" (i.e. all the nodes with the matching RowID) is what I want.
2. I also want to sort that filtered data on the column (cell) with name [p_fld_date_received]. I included a #dateSerial attribute specifically to make the sorting easier.
In the example data I should get any "rows" of data with a parent reference of 'RM12' sorted by date received. I want to use the data in the [p_fld_quantity_available] cell.
I've wasted about 3 days on this and gotten absolutely nowhere. Normally you start to get somewhere but with xslt I've gotten nowhere. Strange.
Here is one "row" of my data (sorry, I pasted the XML but don't know how to format it for reading - can someone let me know how to display it in the right format? Thanks.):
<Root><Data><Element><Name>p_fld_ref</Name><Key>SKU1</Key><KeyType>P</KeyType><ID>1</ID><Value>SKU1</Value><Description>Ref</Description><Required>True</Required><dataType>SKUn</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>1</ColumnID></Element><Element><Name>p_fld_parent_ref</Name><Key>SKU1</Key><KeyType>F</KeyType><ID>2</ID><Value>RM12</Value><Description>Parent Ref</Description><Required>True</Required><dataType>CPLn,RMn</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>2</ColumnID></Element><Element><Name>p_fld_item_no</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>3</ID><Value>ZRMH06</Value><Description>Item Code</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>3</ColumnID></Element><Element><Name>p_fld_type</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>4</ID><Value>RM</Value><Description>Type</Description><Required>True</Required><dataType>CA or RM</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>4</ColumnID></Element><Element><Name>p_fld_serial_no</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>5</ID><Value>120201</Value><Description>Serial No.</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>5</ColumnID></Element><Element><Name>p_fld_name_1</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>6</ID><Value>Product Name 1</Value><Description>Name 1</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>6</ColumnID></Element><Element><Name>p_fld_name_2</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>7</ID><Value>Product Name 1 Lang 2</Value><Description>Name 2</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>7</ColumnID></Element><Element><Name>p_fld_location</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>8</ID><Value/><Description>Location</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>8</ColumnID></Element><Element><Name>p_fld_receipt_no</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>9</ID><Value/><Description>Receipt No.</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>9</ColumnID></Element><Element><Name>p_fld_supplier</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>10</ID><Value/><Description>Supplier</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>10</ColumnID></Element><Element><Name>p_fld_supplier_ref</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>11</ID><Value/><Description>Supplier Ref</Description><Required>True</Required><dataType>Sn</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>11</ColumnID></Element><Element><Name>p_fld_supplier_batchno</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>12</ID><Value/><Description>Supplier Batch No.</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>12</ColumnID></Element><Element><Name>p_fld_supplier_coa</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>13</ID><Value/><Description>CoA Number</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>13</ColumnID></Element><Element><Name>p_fld_box_sample</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>14</ID><Value/><Description>Box to sample</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>14</ColumnID></Element><Element><Name>p_fld_number_boxes</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>15</ID><Value/><Description>Number of Containers</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>15</ColumnID></Element><Element><Name>p_fld_quantity_total</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>16</ID><Value/><Description>Total Quantity</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>16</ColumnID></Element><Element><Name>p_fld_date_received</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>17</ID><Value dateSerial="20130217000000">17-Feb-2013</Value><Description>Date Received</Description><Required>True</Required><dataType>Date</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>17</ColumnID></Element><Element><Name>p_fld_date_retest</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>18</ID><Value dateSerial="20140217000000">17-Feb-2014</Value><Description>Re-Test Date</Description><Required>True</Required><dataType>Date</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>18</ColumnID></Element><Element><Name>p_fld_date_expire</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>19</ID><Value dateSerial="20160217000000">17-Feb-2016</Value><Description>Expiry Date</Description><Required>True</Required><dataType>Date</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>19</ColumnID></Element><Element><Name>p_fld_date_released</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>20</ID><Value dateSerial="20130217000000">17-Feb-2013</Value><Description>Release Date</Description><Required>True</Required><dataType>Date</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>20</ColumnID></Element><Element><Name>p_fld_quantity_reserved</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>21</ID><Value>20000</Value><Description>Reserved Quantity</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>21</ColumnID></Element><Element><Name>p_fld_quantity_used</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>22</ID><Value>0</Value><Description>Used Quantity</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>22</ColumnID></Element><Element><Name>p_fld_quantity_available</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>23</ID><Value>20000</Value><Description>Available Quantity</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>23</ColumnID></Element><Element><Name>p_fld_status</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>24</ID><Value/><Description>Status</Description><Required>True</Required><dataType>Q, R or X</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>24</ColumnID></Element></Root>
Assuming that each row is represented by a <Data> element (incidentally, you're missing the end tag of the <Data> element in this sample), this should do the filter/sorting part:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Root">
<Root>
<xsl:apply-templates
select="Data[Element[Name='p_fld_parent_ref']/Value='RM12']">
<xsl:sort select="Element[Name='p_fld_date_received]/Value/#dateSerial" />
</xsl:apply-templates>
</Root>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You'll need to define a template for how you want your <Data> elements presented, and probably something other than simply re-creating the Root element as I've done above. Something as simple as this might do the trick, if you just want to output text:
<xsl:template match="Data">
<xsl:text>Item no.:</xsl:text>
<xsl:value-of select="Element[Name='p_fld_item_no']/Value" />
<xsl:text> Quantity available:</xsl:text>
<xsl:value-of select="Element[Name='p_fld_quantity_available']/Value" />
<xsl:text>
</xsl:text><!-- New line -->
</xsl:template>
This isn't a complete solution, but hopefully it'll give you enough pointers to figure out the rest.

XSL 1.0 - sort a list from a different list

i'm new with XSL and have tried to look through all the examples on here but none match my problem.
i have a sort order list of movies (order from left to right)
<movies>movieF,movieC,movieG</movies>
now i want to take that sort order list and sort on top of this huge movies list of mine
<moviesList>
<movie>movieA</movie>
<movie>movieB</movie>
<movie>movieC</movie>
<movie>movieD</movie>
<movie>movieE</movie>
<movie>movieF</movie>
<movie>movieG</movie>
<movie>movieH</movie>
</moviesList>
result i want:
<moviesList>
<movie>movieF</movie>
<movie>movieC</movie>
<movie>movieG</movie>
<movie>movieA</movie>
<movie>movieB</movie>
<movie>movieD</movie>
<movie>movieE</movie>
<movie>movieH</movie>
</moviesList>
would someone please give me some guidance of how to achieve such thing. i've tried to create a variable $sortlist, and then add delimited character around and then use substring-before trick on the sort. result is my sorted list did show up on top before the rest of the movies but it's not on the right order. please help.
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="sortlist" select="';movieF;movieC;movieG;'"/>
<xsl:template match="moviesList">
<xsl:copy>
<xsl:for-each select="*[contains($sortlist, concat(';',.,';'))]">
<xsl:sort select="substring-before($sortlist,concat(';',.,';'))" />
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:copy-of select="*[not(contains($sortlist, concat(';',.,';')))]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Notice, I didn't use string-length to sort, I just used the string itself. XSLT1.0 can sort alphabetically instead of numerically, which would place 14 above 7, resulting in the order movieF, movieG, movieC.
It actually uses the portion of the list of movie names that comes before each one as the sort key; alphabetically speaking, ;movieF; comes before ;movieF;movieC;, therefore it'll place movieC above movieG when sorting.
Personally I tend to avoid using commas as a separator as they can be used in some names, such as 'Crouching Tiger, Hidden Dragon'. A semicolon's far less likely, but you could use any character you like as long as it's not one that appears in a movie name.

How to filter node list based on the contents of another node list

I'd like to use XSLT to filter a node list based on the contents of another node list. Specifically, I'd like to filter a node list such that elements with identical id attributes are eliminated from the resulting node list. Priority should be given to one of the two node lists.
The way I originally imagined implementing this was to do something like this:
<xsl:variable name="filteredList1" select="$list1[not($list2[#id_from_list1 = #id_from_list2])]"/>
The problem is that the context node changes in the predicate for $list2, so I don't have access to attribute #id_from_list1. Due to these scoping constraints, it's not clear to me how I would be able to refer to an attribute from the outer node list using nested predicates in this fashion.
To get around the issue of the context node, I've tried to create a solution involving a for-each loop, like the following:
<xsl:variable name="filteredList1">
<xsl:for-each select="$list1">
<xsl:variable name="id_from_list1" select="#id_from_list1"/>
<xsl:if test="not($list2[#id_from_list2 = $id_from_list1])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
But this doesn't work correctly. It's also not clear to me how it fails... Using the above technique, filteredList1 has a length of 1, but appears to be empty. It's strange behaviour, and anyhow, I feel there must be a more elegant approach.
I'd appreciate any guidance anyone can offer. Thanks.
Use this XPath one-liner:
$vList1[not(#id = $vList2/#id)]
As far as I am aware using $var[] syntax doesn't work. What works is: expr1/[expr2 = $var], and func1($var).
What you can do is simply embed the expression that yields $list2 in the if test:
<xsl:for-each select="$list1">
<xsl:variable name="id" select="#id_from_list1"/>
<xsl:if test="not(expr2[#id_from_list2 = $id ])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
<xsl:copy-of select="$list2"/>
Substitute expr2 with actual expression.