my XML tag contains many items that should be threated as three different groups, with similar (but different) sorting rules.
This is what I want to get:
<items>
<!-- Header - for-each sorting -->
<item name="something1_A"/>
<item name="something2_B"/>
<item name="something3_C"/>
<!-- Body - for-each-group sorting -->
<item name="something4_D"/>
<item name="something4_E"/>
<item name="something5_D"/>
<item name="something5_E"/>
<!-- Footer - for-each sorting -->
<item name="something6_F"/>
<item name="something6_G"/>
<item name="something6_H"/>
</items>
Initially, items order is random.
The first sort should create those three different parts: put everything that is header on the top, everything that is footer on the bottom, and keep everything else where it is. I can determine if something should go in the header, in the body or in the footer looking at its ending (the value after the last underscore).
The second sort should work differently on each of those parts (per-element sorting for header and footer, per-group sorting for body).
I know how I can sort the header, the body and the footer (thanks to this answer), but not how to move them and sort them with different algorithms.
Assuming you have a template that matches items then it's just a case of separating the item elements into three groups, which you say you can do via the endings:
<xsl:variable name="headerItems" select="item[
some $suf in ('_A', '_B', '_C') satisfies ends-with(#name, $suf)]" />
<xsl:variable name="footerItems" select="item[
some $suf in ('_F', '_G', '_H') satisfies ends-with(#name, $suf)]" />
<xsl:variable name="bodyItems"
select="item except ($headerItems | $footerItems)" />
and then handling the three groups in sequence however you need to.
Related
I am using boost (version 1.70.0) property tree. If I have this XML (no empty lines):
<Root>
<SomeOtherElement>..</SomeOtherElement>
<Collection>
<Item Attr1=".." attr2="" />
<Item Attr1=".." attr2="" />
</Collection>
</Root>
and I extract a node, insert to another (empty) tree:
auto node = pt.get_child("Root.Collection");
ptree new_pt{};
new_pt.put_child("Collection", node);
std::ostringstream os;
write_xml(os, new_pt);
auto xml = os.str();
I will get the output with empty lines, something like this:
<Collection>
<Item Attr1=".." attr2="" />
<Item Attr1=".." attr2="" />
</Collection>
I have tried different things. I can fix it by iterating over Item elements and adding one by one. Then it works, no extra lines. However, if Item element itself has a child element(s), then again, it will add a bunch of empty lines.
I think it's duplicated with this one, or it's just a bug in the property tree.
https://stackoverflow.com/a/6614372/1292791
To read with the trim flag will fix the problem:
pt::read_xml(filename, tree, boost::property_tree::xml_parser::trim_whitespace);
To write with pretty format:
pt::write_xml(os, tree, boost::property_tree::xml_writer_make_settings<std::string>(' ', 1));
xsl
<xsl:variable name="varName>
<xsl:value-of select="/can/be/a/long/path/down/xml/item[#id=1] />
</xsl:variable>
xml
<xml>
<item id="1" text="Yes">
<item id="2" text="No">
</xml>
use
I was thinking I could use like this:
<xsl:when test="$varName/#text = 'Yes'">
blah
</xsl:when>
but blank space is generated in place of variable. Is this even possible, have a node as a variable and use elsewhere?
<xsl:variable name="varName">
<xsl:value-of select="/can/be/a/long/path/down/xml/item[#id=1]" />
</xsl:variable>
This is one of the most common XSLT errors I see. Usually what people intended is:
<xsl:variable name="varName" select="/can/be/a/long/path/down/xml/item[#id=1]"/>
And most of the time, the code works just fine, except that it's a lot slower than it needs to be. But sometimes the fact that the two constructs are quite different beneath the covers comes back to bite you.
To understand the difference, xsl:variable with a select attribute binds the variable to whatever the select expression evaluates to, which in this case is a set of zero or more item elements. By contrast, xsl:variable with nested instructions creates a document node (XSLT 2.0) or result tree fragment (XSLT 1.0) whose content is a COPY of whatever those instructions produce. In this case, because the content is an xsl:value-of instruction, the variable contains a copy of the string-value of the selected node.
And of course, the string value of the selected node doesn't have any attributes, so test="$varname/#text = 'x'" will always return false.
I have a couple of nodes that contrain track data and I want to concatenate them all in one string that becomes a new variable $tracks.
<xsl:template match="tracks">
<xsl:variable name="trackArtist">
<xsl:apply-templates select="/artists/item/#artist" />
</xsl:variable>
<xsl:value-of select="item/concat(#unit, '|', #track, '|null|', #text, '|', $trackArtist, '|null|null|')" />
</xsl:template>
So this works, it concatenates them all. But I had one space at every end of a node, and I tried a lot of things normalize-space, string-join etc. But then I found out the extra space is just generated by each node that's processed.
<tracks>
<item text="SILENCE" track="1" unit="1"/>
<item text="HAPPINESS" track="2" unit="1"/>
<item text="DREAM" track="3" unit="1"/>
</tracks>
Result (notice the space behind '|null|null|':
1|1|null|SILENCE|FOURPLAY|null|null| 1|2|null|HAPPINESS|FOURPLAY|null|null| 1|3|null|DREAM|FOURPLAY|null|null|
How can I concatenate the data from a node PLUS all the other nodes?
Okay, figured it out with a little help from a colleague.
Since it's a nodelist that you're editing, it will apply the standard built-in way of handling, thus adding a space after each node. So by adding a string-join() you can manipulate this. This also fixes the issue of having a trailing (unnecessary) pipe.
<xsl:value-of select="string-join(item/concat(#unit, '|', #track, '|null|', #text, '|', $trackArtist, '|null|null'), '|')" />
I have a snippet of code I've inherited and I'm trying to get it to work on multiples of the match pattern and set a tag from looking up a value from a table using another tag. What happens is that, for every item, the same lookup is performed and not the relative one for the node. I can't work out the syntax to work thru all entries and substitute the correct one. It's got to be simple it's just that I am simpler :)
My source xml contains this (within an outer /oomsdoc document node not shown):
<item>
<lineqty> 1</lineqty>
<linesku>BNLP5008 </linesku>
<linecustprod>xxxxxxxxxxxxxxx</linecustprod>
<linedesc>London Pride (Bot500mlx8) </linedesc>
</item>
<item>
<lineqty> 1</lineqty>
<linesku>BNBL5008 </linesku>
<linecustprod>xxxxxxxxxxxxxxx</linecustprod>
<linedesc>Bengal Lancer (Bot500mlx8) </linedesc>
</item>
I want to substitute the xxxxxxxxxxxxxxx in each linecustprod tag with the material from the lookup table using the value of the linesku tag.
This is my lookup table:
<Materials>
<product sku='BNLP5008 ' material='LONDON PRIDE'/>
<product sku='BNBL5008 ' material='BENGAL LANCER'/>
</Materials>
and this is my xslt code.
<xsl:variable name="SkuList" select="document('d:\test\transforms\catalogue.xml')/Materials"/>
<xsl:template match="/oomsdoc/item/linecustprod">
<xsl:variable name="MySku" select="/oomsdoc/item/linesku"/>
<linecustprod>
<xsl:value-of select="$SkuList/product[#sku=$MySku]/#material"/>
</linecustprod>
</xsl:template>
I'm guessing some kind of xsl foreach would work but just can't find a usable example to crib :)
Your guidance again would be appreciated at this point in my frustration :)
Thanks,
Brian.
Changing the variable definition to
<xsl:variable name="MySku" select="../linesku"/>
should be sufficient, this will pull out the linesku that is a sibling to the linecustprod you're currently looking at. As currently defined the variable will contain a node set of all the linesku elements in the document, so the value-of will give you the first entry from $SkuList that matches any entry in the main input file.
In addition to Ian Roberts' answer, please change
<xsl:variable name="SkuList" select="document('d:\test\transforms\catalogue.xml')/Materials"/>
to
<xsl:variable name="SkuList" select="document('/d:\test\transforms\catalogue.xml')/Materials"/>
for some reason, the first throws an error (malformed URL).
I'm working with a very generic XML structure, where everything is an item (well everything relevant to this question anyway).
Based on knowing the item element I'm currently on and the item element that is the parent of the node I'm looking for, I need to find an item. I have a working xpath, but it's fairly resource intensive and I'm looking for something more elegant and cheaper.
The item key=a node is the parent of the element I'm looking for (though it's not actually a child of the document root)
XML:
<root>
<item key="a">
<item key="b">
<item key="c">
<item key="d"/>
</item>
</item>
<item key="e">
<item key="f">
<item key="g"/>
</item>
</item>
</item>
</root>
The actual XML is much deeper and with far more branching.
So for instance, if I'm on the item with key=g, e or f I need to return the item with key=e. If I'm on the item with key b,c or d I need to return the item with key=b.
I'm using this xpath, which is working, but going up and then back down the ancestor-descendant axis seems a far longer trip than I need.
current()
/ancestor-or-self::item[#key='a']
/item[descendant-or-self::* = current()]
Is there a simpler way of doing this, bearing in mind that I only know 1) the node I'm on and 2) the key attribute of the parent of the node I'm looking for?
Just for detail's sake: The XML is Sitecore generated, I'm not actually using the current() function, I'm using the sc_currentitem parameter to set the start node I need to begin processing at.
Thanks in advance.
Use:
ancestor-or-self::*[parent::item[#key='a']]