Table-like output that wraps above the 2nd column if long - xslt

So current use case I am trying to address is as follows.
I have a two column table and under certain circumstances (When the first is long enough) it needs to overlap the second column before breaking onto a second line, the 2nd column entry is then right aligned and on the second line
| Column A | Column B |
-----------------------
|Case one | Case one|
-----------------------
|Case two where the |
|line breaks| Case two|
<xml>
<block>
<case>
<caseonewords/>
<caseone/>
</case>
</block>
<block>
<case>
<casetwolongwords/>
<casetwo/>
</case>
</block>
</xml>
The issue I am having is how I should go about formatting this to get the desired outcome. Have tried a number of things but have come up short, potentially was thinking floats but I haven't used them before very much. I'm not attached to using tables if there is another way to achieve this.

You could do the text with fo:leader and fo:inline-container, but I'm not sure how you'd do the borders of the 'table cells' without some of the borders doubling up:
<fo:block>Case one<fo:leader leader-length.minimum="0"
leader-length.optimum="100%" keep-with-previous.within-line="always"
keep-with-next.within-line="always" /><fo:inline-container
width="50%"><fo:block text-align="right">Case
one</fo:block></fo:inline-container></fo:block>
<fo:block>Case two where the line breaks<fo:leader
leader-length.minimum="0" leader-length.optimum="100%"
keep-with-previous.within-line="always"
keep-with-next.within-line="always" /><fo:inline-container
width="50%"><fo:block text-align="right">Case
two</fo:block></fo:inline-container></fo:block>

Related

XSLT: do a comparison based on a list

I've got a list, with paired values:
aircon, aircon_no - these two values are a pair
ABS, ABS_no
heatedseats, heatedseats_no
etc.
So a pair is two values that start with the same substring. These values are stored in the #type attribute.
I've got about 50 pairs in my data.
I want to process an XML which looks like this:
<object type="aircon" id="1">
..
</object>
<object type="ABS" id="2">
..
</object>
<object type="heatedseats" id="3">
..
</object>
<object type="ABS" id="4">
..
</object>
<object type="ABS_no" id="5">
..
</object>
This is a short excerpt, the data contains hundreds of "object" nodes.
The operation depends on the type attribute of the "object" node, and the type attr. of the preceding "object" node. I could find those like this:
<if test="#type='ABS_no' and preceding-sibling::object/#type='ABS'"> ...
My list of paired values is quite long, so I get a long list of these if statements.
Is there a way to avoid creating a long list of if statements, and instead have a more general statement that works for each of my pairs:
<if test="#type='-second value of a pair-' and preceding-sibling::object[1]/#type='-first value of that same pair-'">
and get the whole list of pairs into a single statement?
The goal is to find node ABS_no if it is preceded by node ABS (so #id='5' in my example list), and do this for each pair of values.
The XSLT is passthrough, except for one template that matches the nodes. If these pass the test, an extra subnode is added to the node.
I use XSLT2, but XSLT3 is an option if it helps.
a pair is two values that start with the same letter.
...
The goal is to find node B2 if it is preceded by node B1
I am not sure that constitutes a well-defined problem (what if there is a sequence B1, B2, B3?), but I believe this will do what you asked for:
<xsl:for-each select="object[position() > 1 and starts-with(#type, substring(preceding-sibling::object[1]/#type, 1, 1))]">
<!-- do something -->
</xsl:for-each>
--- added in responses to your edit ---
the _ is the separator between the common substring of a pair and the unique part of the values
In such case you could select (or match):
object[substring-before(#type, '_') = preceding-sibling::object[1]/#type]
But if you can use XSLT 2.0, grouping might be a better alternative.
You could use <xsl:for-each-group select="object" group-by="replace(#type, '_.*$', '')" and then each pair would be a group.

XSLT: Return the value of a specific child if it is present in one of the parents' in ancestor tree

I have an XML with the structure similar to below.
<root>
<randomElement>..</randomElement>
<EFFECT>..</EFFECT>
<parent2>
<randomElement>..</randomElement>
<EFFECT>..</EFFECT>
<parent>
<randomElement>..</randomElement>
<EFFECT>..</EFFECT>
<randomElement>..</randomElement>
<ITEM>..</ITEM>
</parent>
</parent2>
</root>
Note: there can be any number of <randomElement>s at the places
where it's specified.
So, right now, my pointer is at the <ITEM> tag. I need to return the value inside of the <EFFECT> tag, but, here's the catch.
If it's present, I must return the value of the <EFFECT> tag which is inside <parent> tag. If it's not present there, I must return value of <EFFECT> tag which is inside the <parent2> tag. Again, if it is not present there too, I need to finally return the value of the <EFFECT> tag which is inside <root>. The <EFFECT> inside the <root> will always be present and there can be any number of parents for the <ITEM> element.
Sorry if it's confusing.
To go to any <ITEM>, this is sufficient.
//ITEM
Now, <EFFECT> is a sibling to <ITEM>, i.e. it's on the same level. Another way of thinking about siblings is that they are children of the same parent.
In fact, all <EFFECT> elements in question are children of some ancestor of <ITEM>. This means we can move upwards along the ancestor:: axis and grab all those ancestor elements in one step:
//ITEM/ancestor::*
This will give us <parent>, <parent2> and <root>, in this order.
And from those we only need to take one step down to grab all <EFFECT> elements:
//ITEM/ancestor::*/EFFECT
This will give us three EFFECT elements, this time again in document order (only the ancestor:: type of axis works inside out).
We are interested in the last one of those, because this will be closest to the <ITEM> we started from. The last() function will help here:
(//ITEM/ancestor::*/EFFECT)[last()]
The parentheses around the path are necessary, because otherwise the condition ([last()]) is tested on every <EFFECT> individually, and each one of them is the last one of its kind for its parent, giving us three matches. The parentheses make it so that first a node-set of three <EFFECT> elements is constructed and then the last one is taken, giving us only one match.
When you already currently are at the <ITEM> element, the relative version of the path selects the last effect for that particular item:
(ancestor::*/EFFECT)[last()]

pre-processing script to switch product codes

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).

XSLT Select all nodes containing a specific substring

I'm trying to write an XPath that will select certain nodes that contain a specific word.
In this case the word is, "Lockwood". The correct answer is 3. Both of these paths give me 3.
count(//*[contains(./*,'Lockwood')])
count(BusinessLetter/*[contains(../*,'Lockwood')])
But when I try to output the text of each specific node
//*[contains(./*,'Lockwood')][1]
//*[contains(./*,'Lockwood')][2]
//*[contains(./*,'Lockwood')][3]
Node 1 ends up containing all the text and nodes 2 and 3 are blank.
Can some one please tell me what's happening or what I'm doing wrong.
Thanks.
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="XPathFunctions.xsl"?>
<BusinessLetter>
<Head>
<SendDate>November 29, 2005</SendDate>
<Recipient>
<Name Title="Mr.">
<FirstName>Joshua</FirstName>
<LastName>Lockwood</LastName>
</Name>
<Company>Lockwood & Lockwood</Company>
<Address>
<Street>291 Broadway Ave.</Street>
<City>New York</City>
<State>NY</State>
<Zip>10007</Zip>
<Country>United States</Country>
</Address>
</Recipient>
</Head>
<Body>
<List>
<Heading>Along with this letter, I have enclosed the following items:</Heading>
<ListItem>two original, execution copies of the Webucator Master Services Agreement</ListItem>
<ListItem>two original, execution copies of the Webucator Premier Support for Developers Services Description between Lockwood & Lockwood and Webucator, Inc.</ListItem>
</List>
<Para>Please sign and return all four original, execution copies to me at your earliest convenience. Upon receipt of the executed copies, we will immediately return a fully executed, original copy of both agreements to you.</Para>
<Para>Please send all four original, execution copies to my attention as follows:
<Person>
<Name>
<FirstName>Bill</FirstName>
<LastName>Smith</LastName>
</Name>
<Address>
<Company>Webucator, Inc.</Company>
<Street>4933 Jamesville Rd.</Street>
<City>Jamesville</City>
<State>NY</State>
<Zip>13078</Zip>
<Country>USA</Country>
</Address>
</Person>
</Para>
<Para>If you have any questions, feel free to call me at <Phone>800-555-1000 x123</Phone> or e-mail me at <Email>bsmith#webucator.com</Email>.</Para>
</Body>
<Foot>
<Closing>
<Name>
<FirstName>Bill</FirstName>
<LastName>Smith</LastName>
</Name>
<JobTitle>VP of Operations</JobTitle>
</Closing>
</Foot>
</BusinessLetter>
But when I try to output the text of
each specific node
//*[contains(./*,'Lockwood')][1]
//*[contains(./*,'Lockwood')][2]
//*[contains(./*,'Lockwood')][3]
Node 1 ends up containing all the text
and nodes 2 and 3 are blank
This is a FAQ.
//SomeExpression[1]
is not the equivalent to
(//someExpression)[1]
The former selects all //SomeExpression nodes that are the first child of their parent.
The latter selects the first (in document order) of all //SomeExpression nodes in the whole document.
How does this apply to your problem?
//*[contains(./*,'Lockwood')][1]
This selects all elements that have at least one child whose string value contains 'Lockwood' and that are the first such child of their parent. All three elements that have a text node containing the string 'Lockwood' are the first such child of their parents, so the result is that three elements are selected.
//*[contains(./*,'Lockwood')][2]
There is no element that has a child with string value containing the string 'Lockwood' and is the second such child of its parent. No nodes are selected.
//*[contains(./*,'Lockwood')][3]
There is no element that has a child with string value containing the string 'Lockwood' and is the third such child of its parent. No nodes are selected.
Solution:
Use:
(//*[contains(./*,'Lockwood')])[1]
(//*[contains(./*,'Lockwood')])[2]
(//*[contains(./*,'Lockwood')])[3]
Each of these selects exactly the Nth element (N = {1,2,3}) selected by //*[contains(./*,'Lockwood')], correspondingly: BusinesLetter, Recipient and Body.
Remember:
The [] operator has higher priority (precedence) than the // abbreviation.

libxml2 XPATH - Selecting subset of data from XML

I am fairly new to XML dev.. I had a few questions regarding XML parsing with XPATH and libxml.
I have an XML structured as :
<resultset>
<result count=1>
<row>
<name> He-Man! </name>
<home> Greyskull </home>
<row>
</result>
<result count=2>
<row>
<name> Spider-Man</name>
<home> Some downtown apartment </home>
<row>
<row>
<name> Disco-Man!</name>
<home> The 70's dance floor </home>
<row>
</result>
<resultset>
I need to pick out the names from this XML , but where the count is 2 , i need it only from the first record. I ran through a few tutorials, but i am unable to come up with an XPATH query which would serve this purpose.
/name will select all name elements.
/result[#count > 1 ]/row[1]/name | /result[#count =1 ]/row/name
Is this possible to be done with XPATH ? Is this better to be done via XPATH or by walking the XML tree?
Can some one point me to some complex searches through out XML's ?
Edit : The actual scenario requires select a subset of the XML row , which are nested at 2 levels at times. This sounds like i need to OR '|' many paths to select the nodes i require... I am not sure if that would be efficient as opposed to walking a tree... The above is typed to replicate the problem :)
Thanks!
Try this XPath -
/resultset/result[#count=2]/row/name
This will give a list of all nodes falling under this XPath. From this just take the first element (as you needed only the first record).
I'd probably keep my xpath simpler and just extract both cases, then loop over both node sets.
If you do need to go down the single xpath route, you should try out your xpath expressions in something that lets you enter them live, rather than having to recompile C/C++ code. You should be able to do that by loading your XML into firefox and using firebug - for example typing $x('//name') in the firebug console gives three nodes.
NOTE however that your XML is invalid... You have a bunch of "<row>"s that should be "</row>" and the same for "<resultset>" and your counts need to be
<result count="1">
i.e. with quote marks around the value.