Idiom for templating similar to other templating engines like Velocity? - templates

I have an XSLT(2.0) file; which takes an input XML data file and creates DDL/SQL Statements.
It works just fine. But it is a bit difficult to maintain, as it contains a lot of formatting information in 'concat' statements like this:
<xsl:value-of select="concat('CREATE USER ',$username,' IDENTIFIED BY ',$password,';',$nl)"/>
What I would prefer to do would be to encode my SQL Statements in a manner like this instead:
<some-enclosing-elements>[...]CREATE USER <username/>, identified by <password/>; [literally a newline here][...]</some-enclosing-elements>
I would perhaps keep this format above in the XML data file itself in a 'lookup' table at the top of the either the XSLT or the data document iself (I can't work out which yet).
Is there a standard idiom that would allow this kind of templating ?
Any ideas ?
By the way; the data document contains many different users to create of course

The AVT approach is just a little bit too devious for my taste. I tend to rely on the implicit concatenation done (in 2.0) by xsl:value-of:
<xsl:value-of select="'CREATE USER', $username, 'identified by', $password"/>
Another approach which I have used in applications where this kind of text templating is significant is to essentially write my own templating engine within XSLT; have a "message file" containing message templates in the form
<message nr="1">CREATE USER <p:user/> IDENTIFIED BY <p:password/></message>
and then write template rules to expand the messages by substituting the parameters.

#xiaoyi is right, showing the main alternative to using concat(). However that's even more notation-heavy than the concat(), since you have to keep repeating <xsl:value-of select="..." />.
A nice alternative would be to use attribute value templates (AVTs):
[...]CREATE USER {username}, identified by {password};
[...]
But ATVs are only available for (certain) attributes, not for text nodes (directly). How do you use them for this purpose?
One way in XSLT 2.0 would be to use an AVT to create a new literal result element with an attribute; specify the value of that attribute using an AVT; and then select the value of the new attribute:
<xsl:variable name="query">
<dummy val="[...]CREATE USER {username}, identified by {password};
[...]" />
</xsl:variable>
<xsl:value-of select="$query//#val" />
Yes that's some significant overhead per formatted string, but there's very little overhead per field within the string. You could do several strings together like this:
<xsl:variable name="queries">
<q val="[...]CREATE USER {username}, identified by {password};
[...]" />
<q val="[...]CREATE TABLE {tablename}, blah blah;
[...]" />
</xsl:variable>
<xsl:value-of select="$queries/q[1]/#val" />
<xsl:value-of select="$queries/q[2]/#val" />
You could use position indices as above, or use an id attribute to identify each string.
I have not seen this method advocated elsewhere, so I'd be curious to hear what others think about it.
Never mind, except...
Given the simpler approach shown by Michael Kay's answer, I don't think there's any point in doing it this way. I guess that explains why others haven't advocated this method. :-)
The only situation I can think of where this approach might still be of use is if you can't use XSLT 2.0, but you do have access to the nodeset() extension function (e.g. in IE or .NET environment). In that case you would need to wrap nodeset( ) around $queries wherever you used it in an XPath expression before /.

Related

XSL-FO | FO: Tags are being removed

I'm currently using XSL for a work project and I'm facing an issue.
I'm trying to read values for a database that look like this:
<fo:block font-weight='bold>hello</fo:block>
and it seems that XSL is stripping the <fo:block> element because it gives me text only ( I only see Hello, not in bold, and it doesn't behave like a block element ). I feel like, somehow, that XSL interprets the value read from the DB as a string, and strip of the <fo> tags, leaving my with text only.
Any idea what could be done in order that my styling get preserved?
( Obviously this example have been simplified, the text to be displayed is longer than that )
EDIT : Self answered for future references
Based on the comments you want to change <xsl:value-of select='/fulfill-list/ticket-list/list-item/eventTicketConte‌​nt/xmlTicketContent/‌​ticketdescription'/> to <xsl:copy-of select='/fulfill-list/ticket-list/list-item/eventTicketConte‌​nt/xmlTicketContent/‌​ticketdescription'/> (or perhaps <xsl:copy-of select='/fulfill-list/ticket-list/list-item/eventTicketConte‌​nt/xmlTicketContent/‌​ticketdescription/node()'/>).
As first i wanted to thanks you all to have taken the time to answer me. I'm very very very glad to see SO community is such strong.
I have solved my problem this way :
<xsl:for-each select="/fulfill-list/ticket-list/list-item/eventTicketContent/xmlTicketContent/ticketdescription/node()">
<xsl:copy-of select="child::node()" />
</xsl:for-each>
I don't really know what happened beneath the hood and why the <fo> tags were removed, but they were. Looping through all of them and using <xsl:copy-of> did the trick.
Once again, a big thanks to y'all !

Refer to specific cell in xslt import/export filter for Calc

I am using xslt filter for importing/exporting data from Calc worksheet. Is it possible to refer to a specific cell address ? For example, if we want to export data from cell B2, how do we refer to this cell address in export xslt ?
Without knowing much about Openoffice or their xslt filter function, I can tell you that you're probably going to need a fairly simple XPath to reference a specific Cell's data - I doubt it would be as simple as calling getCell('B2') unless they have provided you with some custom xslt functions (I'm assuming they've put you in a raw XSLT environment).
Anyway, I think this question may be more about XSLT and xpath, than it is about openoffice. With that in mind, I'm going to fashion my own sample xml and examples and hopefully that will be enough to get you started.
For an input xml that looks something like this:
<ooo_calc_export>
<ooo_sheet num="1" name="sheet1">
<ooo_row num="2">
<fisrtCell>Oh</firstCell>
<secondCell>Hai</secondCell>
<thirdCell>There</thirdCell>
</ooo_row>
<ooo_row num="3">
<fisrtCell>Oh</firstCell>
<secondCell>Hello</secondCell>
<thirdCell>Back!</thirdCell>
</ooo_row>
</ooo_sheet>
</ooo_calc_export>
An absolute XPath to access cell B2's data would look like this ooo_calc_export/ooo_sheet/ooo_row[#num='2']/secondCell/text()
But the above is an absolute path and in XSLT, we would often author relative xpaths as we are in the midst of processing a document. Imagine you're in a template which matches on the ooo_calc_export node and you wanted to store Cell B2's data in a variable for later use. Consider this example:
<xsl:template match="/ooo_calc_export">
<!-- a relative xpath does not being with a forward slash -->
<xsl:variable name="B2" select="ooo_sheet/ooo_row[#num='2']/secondCell/text()" />
</xsl:template>
Now lets imagine you wanted a template to match on the cell B2 node itself:
<xsl:template match="ooo_row[#num='2']/secondCell">
<!-- a relative xpath does not being with a forward slash -->
<xsl:variable name="B2_text" select="text()" />
</xsl:template>
This is a good tutorial on XSLT to get you started. Also, the W3 Schools references on XPath and XSLT aren't the worst.

XSLT to call secondary XML based on first XML element/attribute

love the stuff - newbie Æthelred here
I have a XSLT 1.0 file pulling in a secondary XML (to a variable) to build a table
<xsl:variable name="table_values" select="document('./table_variants/external_table.xml')/xml/channel_1"/>
I then get the values i need from the variable, eg:
<xsl:value-of select="$table_values/monkey/tennis/#medals"/>
<xsl:value-of select="$table_values/monkey/tennis/#bananas"/>
What i want to do is have the first XML trigger/steer where to look for the table data.
I hoped i could, within the triggered XML, state the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/>
but apparently i cannot create a xpath on the fly like that
Please - What can i do?
What i want to do is have the first XML trigger/steer where to look
for the table data. I hoped i could, within the triggered XML, state
the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/> but
apparently i cannot create a xpath on the fly like that
Please - What can i do?
This can easily be done just extending the code that you already have.
Change this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')/xml/channel_1"/>
to this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')
/xml/*[name() = $channelName"/>
Needless to say, the variable (or global, external param) $channelName should have a value that is the (string) name of the element you want to use in the last location step of the XPath expression.

Assigning parameter value to the xsl: for each

Can anybody who has worked with XSLT help me on this?
I am using XSL version 1.0.
I have declared a parameter in XSL file like:
<xsl:param name="HDISageHelpPath"/>
Now I am assigning the value to this parameter from an asp page . The value which I assign is "document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help". Now I want to assign this parameter to the <xsl for each> like
<xsl:for-each select="msxsl:node-set($HDISageHelpPath)" > (This does not work)
But it does not work. I checked the parameter value by debugging it as below
<debug tree="$HDISageHelpPath">
<xsl:copy-of select="$HDISageHelpPath"/>
</debug>
I'm able to print the value and it seems correct. In fact when I assign the static path ("document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help") by hard-coding it, it works
<xsl:for-each select="document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help"> (This works)
Can anyone please let me know why assigning the parameter to xsl:for-each does not work?
Note: I have referred the site "http://www.dpawson.co.uk/xsl/sect2/N1553.html"
You can't easily evaluate dynamic strings as XPath expressions in XSLT 1.0. They must be hard-coded, normally.
There's EXSLT's dyn:evaluate(), but I doubt you can use that with the MXSML processor.
As an alternative approach, you could either try passing the file path only:
<xsl:param name="HDISageHelpFilePath"/>
<!-- ... -->
<xsl:for-each select="document($HDISageHelpFilePath)/HelpFiles/Help">
</xsl:for-each>
or making placeholder, replacing it with search-and-replace before you load the actual XSL code into the processor (as a string). This is a bit messy and error-prone, but it could give you the possibility to use an actual dynamic XPath expression.
<xsl:for-each select="%HELP_FILE_XPATH%">
</xsl:for-each>
Load the file as text, replace %HELP_FILE_XPATH% with your actual XPath, feed it to the processor. If it loads, you are fine, if it doesn't, your input XPath was malformed.

XSL: Combining grouping and call-template

I've read with interest the techniques available on the web to extract a unique list of items from a XML file containing duplicates using XSL.
These range into 2 categories:
1) The Muenchian method (example: http://www.jenitennison.com/xslt/grouping/)
2) Or the previous-sibling look-up
These both rely on an XPath expression to select the data to group by.
However, in the XML file that I'm trying to work out, the data is not present "natively" in the XML file. I am using a xsl:template to compute some aggregated data from my elements. And I would like to group based on the aggregated data.
For example I have:
<filmsreview>
<record><data name='movie'>Star Wars</data><data name='ratings'>John:Good, Mary:Good</data></record>
<record><data name='movie'>Indiana Jones</data><data name='ratings'>John:Good, Mary:Bad, Helen:Average</data></record>
<record><data name='movie'>Titanic</data><data name='ratings'>John:Bad, Helen:Good</data></record>
</filmsreview>
I know that the structuration of data is not perfect and that by creating sub-elements I could do something easier, but I cannot change the data source easily, so let's take this as a challenge.
And I would like to build a recap where I have John's distinct ratings:
John's ratings:
Good
Bad
I have a xsl:template that can take a record element and return John's rating for this record:
Example:
<xsl:template name="get_rating">
<xsl:param name="reviewer" />
<!-- I use some string manipulation, and xsl:value-of to return the review for John-->
</xsl:template>
I can just call it under a xsl:for-each to get the exhaustive list of John's review. But I cannot combine this call with any of the methods to get unique values.
Do I have to use an intermediary XSL to convert my XML file to a more structured way? Or can I do in a single step?
Many thanks
Gerard
Hmm... This should be possible using xslt variables and the nodeset method, perhaps something like this:
<xsl:variable name="_johnsRatings">
<xsl:apply-templates select="/" mode="johnsRatings" />
</xsl:variable>
<xsl:variable name="johnsRatings" select="msxsl:node-set($_johnsRatings)" />
<xsl:template match="/" mode="johnsRatings">
<Ratings>
<xsl:for-each select="/filmsReview/record/data[#name='ratings']">
<Rating><xsl:call-template name="get_rating" /></Rating>
</xsl:for-each>
</Ratings>
</xsl:template>
At this point, it should be possible to query the $johnsRatings variable using standard XPath queries, and you can use either of the two methods you mentioned above to retrieve unique values from it...
Hope that helps
EDIT:
I don't know what XSLT engine you are using, I assumed you have access to the msxsl:node-set() function. However, most XSLT processors have similar methods, so you might have to search around for an equivalent method in your processor