Filter xml in .Net with param - vb.net

I am trying to filter an xml file in a .Net application I am developing. Some sample xml below, obviously not the proper xml, but near enough :-)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Row A1="1" A2="AMS">
<Name>Ashley</Name>
<Team>Team B</Team>
<Date>3/25/2012</Date>
<Value>511681.15</Value>
</Row>
<Row A1="2" A2="AMS">
<Name>Kylie</Name>
<Team>Team A</Team>
<Date>9/28/2010</Date>
<Value>408438.47</Value>
</Row>
<Row A1="3" A2="AMS">
<Name>Gianna</Name>
<Team>Team B</Team>
<Date>40004</Date>
<Value>109709.22</Value>
</Row>
<Row A1="4" A2="AMS">
<Name>Chase</Name>
<Team>Team F</Team>
<Date>40152</Date>
<Value>279018.79</Value>
</Row>
The stylesheet has a param that is set by XsltArgumentList in the application. The param is passed into the stylesheet, but does not filter the xml. I have tried using the ms node-set and exsl node-set but only get the top level root returned. Stylesheet below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes" encoding="UTF-8" />
<xsl:decimal-format name="NN" NaN="0" />
<xsl:param name="Filter" />
<xsl:template match="/">
<Root>
<xsl:for-each select="exsl:node-set($Filter)">
<Row>
<xsl:attribute name="A1">
<xsl:value-of select="#A1" />
</xsl:attribute>
<xsl:attribute name="A2">
<xsl:value-of select="#A2" />
</xsl:attribute>
<Team>
<xsl:value-of select="Team"/>
</Team>
</Row>
</xsl:for-each>
</Root>
</xsl:template>
The filter i am trying to pass could be using any of the combination of the xml elements. The filter is am currently attempting is below
<xsl:param name="Filter" select="//Row[Team='Team A']" />
But this is just returns
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Row A1="" A2="">
<Team></Team>
</Row>
</Root>
Any help or pointers would be appreciated!
Thanks

This will not woke like you tried it, because you can't have a xpath expression in an XLST parameter or variable. You can use variable or parameter in please of const values.
Therefore you can use something like
<xsl:param name="teamFilter" select="'Team A'" />
...
<xsl:for-each select="//Row[Team='$teamFilter']">
Or if you have to filter for different nodes you may try:
<xsl:param name="filterValue" select="'Team A'" />
<xsl:param name="filterNode" select="'Team'" />
...
<xsl:for-each select="//Row[*[name()= $filterNode and . =$filterValue]]">
Other alternatives would be:
Patch the XSLT before loading it
Use xpath iterator with XPathExpression from .NET.

Related

Advanced muenchian grouping: group by items in child collection

I'm familiar with simple muenchian grouping in XSL, but I've encountered a problem, which I honestly don't even know, how to approach it.
So I've got an XML:
<whiskies>
<whisky name="ABC" percent="" region="" type="">
<tastingNotesCollection/>
<bottles>
<bottle price="" size="" level="0" date=""/>
<bottle price="" size="" level="70" date=""/>
<bottle price="" size="" level="100" date=""/>
</bottles>
<comments/>
</whisky>
<whisky name="DEF" percent="" region="" type="">
<tastingNotesCollection/>
<bottles>
<bottle price="" size="" level="0" date=""/>
<bottle price="" size="" level="100" date=""/>
</bottles>
<comments/>
</whisky>
<whisky name="GHI" percent="" region="" type="">
<tastingNotesCollection/>
<bottles>
<bottle price="" size="" level="30" date=""/>
</bottles>
<comments/>
</whisky>
<whiskies>
And the goal is to group the whiskies by levels of a bottle:
So level="0" is considered empty.
Anything from level="1" to level="99" is considered open.
And level="100" is considered unopened.
So the transformed result (will be done in HTML) should look like this:
<h1>Empty</h1>
<ul>
<li>ABC</li>
<li>DEF</li>
</ul>
<h1>Open</h1>
<ul>
<li>ABC</li>
<li>GHI</li>
</ul>
<h1>Unopened</h1>
<ul>
<li>ABC</li>
<li>DEF</li>
</ul>
As you can see, the same whisky can show up in multiple groups, depending on how much bottles there are and how full those bottles are.
The second problem is, that the "Open" group doesn't have an exact value and can be aynthing from 1 to 99.
So yeah, don't really know if this can be solved at all or how to even start on this. Any tips appreciated.
I don't think you want to use Muenchian grouping for this. You would need to define a key that enumerates all values in the range from 1 to 99 - and I believe that would take away any advantage that using a key would otherwise bring.
Since your result has either exactly or at most 3 groups (your question is ambiguous in this respect), you could do simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/whiskies">
<output>
<group status="empty">
<xsl:for-each select="whisky[bottles/bottle/#level=0]">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="open">
<xsl:for-each select="whisky[bottles/bottle[#level>0 and #level < 100]]">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="unopened">
<xsl:for-each select="whisky[bottles/bottle/#level=100]">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
</output>
</xsl:template>
</xsl:stylesheet>
to get (after fixing the input to be a well-formed XML!):
Result
<?xml version="1.0" encoding="utf-8"?>
<output>
<group status="empty">
<name>ABC</name>
<name>DEF</name>
</group>
<group status="open">
<name>ABC</name>
<name>GHI</name>
</group>
<group status="unopened">
<name>ABC</name>
<name>DEF</name>
</group>
</output>
Make your own adjustment for HTML output.
Added:
Here is an alternative approach using keys (though still not Muenchian grouping) which might be more performant:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="w" match="whisky" use="bottles/bottle/#level" />
<xsl:key name="w1" match="whisky" use="boolean(bottles/bottle[#level!=0 and #level!=100])" />
<xsl:template match="/whiskies">
<output>
<group status="empty">
<xsl:for-each select="key('w', 0)">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="open">
<xsl:for-each select="key('w1', true())">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="unopened">
<xsl:for-each select="key('w', 100)">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
</output>
</xsl:template>
</xsl:stylesheet>
Or even:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="bottle" match="bottle" use="ceiling(#level div 99)" />
<xsl:template match="/whiskies">
<output>
<group status="empty">
<xsl:for-each select="key('bottle', 0)/../..">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="open">
<xsl:for-each select="key('bottle', 1)/../..">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
<group status="unopened">
<xsl:for-each select="key('bottle', 2)/../..">
<name>
<xsl:value-of select="#name"/>
</name>
</xsl:for-each>
</group>
</output>
</xsl:template>
</xsl:stylesheet>
If you did want to use xsl:key, you could create 3 of them with the filter criteria:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:key name="Empty" match="whisky[bottles/bottle/#level=0]" use="#name"/>
<xsl:key name="Open" match="whisky[bottles/bottle/#level[. > 1 and . < 99]]" use="#name"/>
<xsl:key name="Unopened" match="whisky[bottles/bottle/#level=100]" use="#name"/>
<xsl:template match="/">
<xsl:variable name="whiskies" select="/whiskies/whisky/#name"/>
<xsl:for-each select="document('')/xsl:stylesheet/xsl:key/#name">
<xsl:variable name="status" select="."/>
<h1><xsl:value-of select="$status"/></h1>
<ul>
<xsl:for-each select="$whiskies[key($status, .)]">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT - First Instance of Empty Node edit

I am trying to edit the first instance of an empty node(Status) in the following xml :
<?xml version='1.0' encoding='windows-1252'?>
<root>
<row>
<flowname>1</flowname>
<path>#[payload]</path>
<id>3</id>
<setMessage>4</setMessage>
<MockOne>5</MockOne>
<MockTwo>6</MockTwo>
<MockThree>7</MockThree>
<Assert></Assert>
<status>12</status>
</row>
<row>
<flowname>2</flowname>
<path>4</path>
<id>5</id>
<setMessage>6</setMessage>
<MockOne>7</MockOne>
<MockTwo>8</MockTwo>
<MockThree></MockThree>
<Assert></Assert>
<status></status>
</row>
<row>
<flowname>3</flowname>
<path>5</path>
<id>6</id>
<setMessage>7</setMessage>
<MockOne>8</MockOne>
<MockTwo>9</MockTwo>
<MockThree></MockThree>
<Assert>3</Assert>
<status></status>
</row>
</root>
What I want to achieve is for the xslt to find the first instance of the tag which is empty and edit it to say 123. I tried using the following XSLT, but it seems to be replacing every Empty Status tag and I need it only to do the first instance. Kindly suggest on what has to be changed
The XSLT as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row/status[1][not(text())][1]">
<status>123</status>
</xsl:template>
</xsl:stylesheet>
The output right now is as follows( Every empty Status tag is replaced instead of the first instance of an empty one)
<root>
<row>
<flowname>1</flowname>
<path>#[payload]</path>
<id>3</id>
<setmessage>4</setmessage>
<mockone>5</mockone>
<mocktwo>6</mocktwo>
<mockthree>7</mockthree>
<assert></assert>
<status>12</status>
</row>
<row>
<flowname>2</flowname>
<path>4</path>
<id>5</id>
<setmessage>6</setmessage>
<mockone>7</mockone>
<mocktwo>8</mocktwo>
<mockthree></mockthree>
<assert></assert>
<status>123</status>
</row>
<row>
<flowname>3</flowname>
<path>5</path>
<id>6</id>
<setmessage>7</setmessage>
<mockone>8</mockone>
<mocktwo>9</mocktwo>
<mockthree></mockthree>
<assert>3</assert>
<status>123</status>
</row>
</root>
Your Xpath expression is saying to update all row/status which is empty.
You need to change like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row[not(preceding-sibling::row[not(normalize-space(status))])]/status[1][not(text())][1]">
<status>123</status>
</xsl:template>
</xsl:stylesheet>

xslt 3.0 xsl:evaluate example

For the following xml document:
<company>
<employee>
<name>John Andrews</name>
<age>23</age>
<salary>4000</salary>
<division>Accounting</division>
</employee>
</company>
I have the xsl like
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of select="company/employee/name"/>
<xsl:variable name="test">
<xsl:text>company/employee/name</xsl:text>
</xsl:variable>
<xsl:evaluate xpath="$test"/>
</xsl:template>
</xsl:stylesheet>
How can I use $test variable in xsl:evaluate in order to get the same result as in the:
<xsl:value-of select="company/employee/name"/>
Is it possible?
You need to set the context item with e.g.
<xsl:evaluate xpath="$test" context-item="."/>

Based on the object corresponding values should be taken using xslt

INPUT XML
<root>
<file1>
<commodity>
<units>1</units>
<obj>mango</obj>
</commodity>
<commodity>
<units>5</units>
<obj>guava</obj>
</commodity>
</file1>
<file2>
<category>
<object>guava</object>
<type>CAT1</type>
<colour>green</colour>
</category>
<category>
<object>mango</object>
<type>CAT2</type>
<colour>yellow</colour>
</category>
</file2>
</root>
I need to compare the values of obj in file1 and object in file2 under root, if same I need to take their corresponding units, type and colour and produce the following output using xslt.
OUTPUT XML
<output>
<com>
<name>guava</name>
<num>5</num>
<category>CAT1</category>
<col>green</col>
</com>
<com>
<name>mango</name>
<num>1</num>
<category>CAT2</category>
<col>yellow</col>
</com>
</output>
I tried the below XSLT but the response is not as expected. Its not looping properly. Could you please tell me where I am going wrong.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:key name="object-search" match="root/file1/commodity" use="obj" />
<xsl:template match="/">
<output>
<xsl:for-each select="key('object-search', //category/object)">
<com>
<name>
<xsl:value-of select="obj" />
</name>
<num>
<xsl:value-of select="units" />
</num>
<category>
<xsl:value-of
select="//root/file2/category/type" />
</category>
<col>
<xsl:value-of
select="//root/file2/category/colour" />
</col>
</com>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Try it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="cat" match="category" use="object" />
<xsl:template match="/root">
<output>
<xsl:for-each select="file1/commodity">
<com>
<name>
<xsl:value-of select="obj" />
</name>
<num>
<xsl:value-of select="units" />
</num>
<xsl:variable name="cat" select="key('cat', obj)" />
<category>
<xsl:value-of select="$cat/type" />
</category>
<col>
<xsl:value-of select="$cat/colour" />
</col>
</com>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Note that the result is slightly different from what you posted:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<com>
<name>mango</name>
<num>1</num>
<category>CAT2</category>
<col>yellow</col>
</com>
<com>
<name>guava</name>
<num>5</num>
<category>CAT1</category>
<col>green</col>
</com>
</output>
Alternatively, you could do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="com" match="commodity" use="obj" />
<xsl:template match="/root">
<output>
<xsl:for-each select="file2/category">
<xsl:variable name="com" select="key('com', object)" />
<com>
<name>
<xsl:value-of select="$com/obj" />
</name>
<num>
<xsl:value-of select="$com/units" />
</num>
<category>
<xsl:value-of select="type" />
</category>
<col>
<xsl:value-of select="colour" />
</col>
</com>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
and get:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<com>
<name>guava</name>
<num>5</num>
<category>CAT1</category>
<col>green</col>
</com>
<com>
<name>mango</name>
<num>1</num>
<category>CAT2</category>
<col>yellow</col>
</com>
</output>

How to group a specific number of blocks in an xml based on number of tags

I have a big xml like below and I would like to group a specific number of tags under one block; The expected input and output below will make my question clearer. Any help is greatly appreciated
The input file is
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Root>
<ListABC>
<ABC name="name1" class="class1" age="age1" />
<ABC name="name2" class="class2" age="age2" />
<ABC name="name3" class="class3" age="age3" />
<ABC name="name4" class="class4" age="age4" />
<ABC name="name5" class="class5" age="age5" />
</ListABC>
<ListABC>
<EOF tag1="1" tag2="2" tag3="3"/>
</ListABC>
</Root>
I need to create a tag ListABC after every 2 ABC elements and at the same time, the last ListABC which contains EOF element should not be impacted at all. This is how I need the output
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<Root>
<ListABC>
<ABC name="name1" class="class1" age="age1" />
<ABC name="name2" class="class2" age="age2" />
</ListABC>
<ListABC>
<ABC name="name3" class="class3" age="age3" />
<ABC name="name4" class="class4" age="age4" />
</ListABC>
<ListABC>
<ABC name="name5" class="class5" age="age5" />
</ListABC>
<ListABC>
<EOF tag1="1" tag2="2" tag3="3"/>
</ListABC>
</Root>
Thanks much!
How about:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Root">
<xsl:copy>
<xsl:for-each select="ListABC[not (EOF)]/ABC[position() mod 2 = 1]">
<ListABC>
<xsl:copy-of select=". | following-sibling::ABC[1]"/>
</ListABC>
</xsl:for-each>
<xsl:copy-of select="ListABC[EOF]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>