Using XSLT to transform XML to C++ initializer list - c++

I've been trying to use XSLT to transform an XML into C++ code. I have fields that represent class member variables, with information like their type. For example:
<members>
<member name="Attribute1" type="int" length="32" />
<member name="Attribute2" type="string" length="64" />
<member name="Attribute3" type="char array" length="10" />
<member name="Attribute4" type="unsigned int" length="32" />
</members>
However, I really can't seem to come up with any way to write a proper initialisation list for constructors. My main problem comes from the C++ syntax:
ClassName:ClassName()
: Attribute1(value),
Attribute2(value),
Attribute4(value)
{
memset(Attribute3, 0, typeof(Attribute));
}
The part that is giving me trouble is the fact that the last member initialized is not followed by a comma. This is should be an easy problem, but XSLT's functional + declarative nature is giving me hell. Here is what I have considered so far:
1) Create a printInitialisationList template which has a "firstMemberPrinted" param. If this param is false, we print a comma and a newline first. If it is true (meaning this is the first member being printed), we don't print the comma. The problem with this, however, is that since variables are immutable in XSLT, I can't actually create a variable to pass in and change the value once the I have printed a member.
2) I thought about solving #1 by simply checking if a "printed" variable had already been created, and if not, to pass True into firstMemberPrinted and then create the variable. However, since XSLT is a declarative language, the idea of checking for an undeclared variable doesn't actually make sense.
3) Checking if "position() = last" doesn't help, since the last member could very well just be an array, which needs to be initialized inside the braces.
Note that I can't change the format of the XML file :(
I would appreciate all and any help! Thanks!

Related

xquery/xslt for selecting a value based on a specific value

I am stuck on an xquery for below request. I want to select the address based on the type value.
i.e. if type is "StreetAddress" then pick freeFormat "Maguire"
<associatedAddress>
<address xmlns="http://services.oracle.com/v1.0/Common">
<type xmlns="http://services.oracle.com/v1.0/Common">StreetAddress</type>
<freeFormat xmlns="http://services.oracle.com/v1.0/Common">Maguire</freeFormat>
</address>
<address>
<type xmlns="http://services.oracle.com/v1.0/Common">CityAddress</type>
<freeFormat xmlns="http://services.oracle.com/v1.0/Common">SanFransisco</freeFormat>
</address>
</associatedAddress>
I tried in couple of ways:
if associatedAddress/address/type="StreetAddress"
then.., But this one gives me only the first address
I also tried to use a for loop and then use if case inside it but even that gave me just the first address
Please let me know any other options. Thanks
The XPath for fetching value Maguire using the value in <type> is
<xsl:value-of select="associatedAddress/address[type='StreetAddress']/freeFormat" />
You need to take care of the namespace associated with the different elements in your input XML when fetching the value. If the namespace is not properly handled in the XSL, the value will not be extracted.
Since you asked for xQuery given your input xml, this:
declare namespace common="http://services.oracle.com/v1.0/Common";
for $n in associatedAddress//common:type[. = "StreetAddress"]
return
$n/../common:freeFormat
produces the desired result
<freeFormat xmlns="http://services.oracle.com/v1.0/Common">Maguire</freeFormat>
The trick is that in your input xml associatedAddress is not in the same namespace as the other elements, so you need to adjust your query to take this into account.
You mention an if clause, but its is not clear what the desired output is from your question. You can use the below as a template to modify results.
declare namespace common="http://services.oracle.com/v1.0/Common";
for $n in associatedAddress//common:type
return
switch($n)
case "StreetAddress" return $n/../common:freeFormat
case "CityAddress" return "something else"
default
return ()

How to use contains() with a set of strings in XSLT

I have the following XML snippet:
<figure customer="ABC DEF">
<image customer="ABC"/>
<image customer="XYZ"/>
</figure>
I'd like to check if the figure element's customer attribute contains the customer attributes of the image elements.
<xsl:if test="contains(#customer, image/#customer)">
...
</xsl:if>
I get an error saying:
a sequence of more than one item is not allowed as the second argument of contains
It's important to note that I cannot tell the values of the customer attributes in advance, thus using xsl:choose is not an option here.
Is it possible to solve this without using xsl:for-each?
In XSLT 2.0 you can use:
test="image/#customer/contains(../../#customer, .) = true()"
and you will get a true() result if any of them are true. Actually, that leads me to suggest:
test="some $cust in image/#customer satisfies contains(#customer, $cust)"
but that won't address the situation where the customer string is a subset of another customer string.
Therefore, perhaps this is best:
test="tokenize(#customer,'\s+') = image/#customer"
... as that will do a string-by-string comparison and give you true() if any of the tokenized values of the figure attribute is equal to one of the image attributes.

For-each loop in JDeveloper doesn't map the response at all

I'm developing a BPEL service using JDeveloper 11.1.1.5.0.
The API's response contains some recurring fields and I'm trying to use a for-each loop to map them with the final web-service's response parameters.
The API's response structure is like this :
<Data>
<Item>
<F6181_SubsWalletCounter>-1</F6181_SubsWalletCounter>
<FBalanceExpDate>2013-08-13T00:00:00</FBalanceExpDate>
<FResetWalletCounterValue>0</FResetWalletCounterValue>
<FRecurringPeriod>0</FRecurringPeriod>
<FRecurringRefreshDate>1899-12-30T00:00:00</FRecurringRefreshDate>
<FRecurringRefreshDay>0</FRecurringRefreshDay>
<F6150_AccountProfileId>18</F6150_AccountProfileId>
<FLimit>0</FLimit>
<F8345_PaymentDebt>0</F8345_PaymentDebt>
<F9217_MinBalance>0</F9217_MinBalance>
<F9218_MaxPaymentDebt>-1</F9218_MaxPaymentDebt>
</Item>
<Item>
<F6181_SubsWalletCounter>-1</F6181_SubsWalletCounter>
<FBalanceExpDate>2013-08-13T00:00:00</FBalanceExpDate>
<FResetWalletCounterValue>0</FResetWalletCounterValue>
<FRecurringPeriod>0</FRecurringPeriod>
<FRecurringRefreshDate>1899-12-30T00:00:00</FRecurringRefreshDate>
<FRecurringRefreshDay>0</FRecurringRefreshDay>
<F6150_AccountProfileId>18</F6150_AccountProfileId>
<FLimit>0</FLimit>
<F8345_PaymentDebt>0</F8345_PaymentDebt>
<F9217_MinBalance>0</F9217_MinBalance>
<F9218_MaxPaymentDebt>-1</F9218_MaxPaymentDebt>
</Item>
</Data>
The <Item> element and its sub-elements could repeat multiple times maintaining the exact structure within itself. Out of these sub elements, few are to be mapped with the final response parameters.
The xsl code is :
<ns2:responseBody>
<ns2:balanceInfo>
<ns2:balance>
<xsl:for-each select=
"/tns:Subscriber_WalletInfo_GetResponse/Data/Item">
<ns2:wallet>
<xsl:value-of select="F6091_WalletTypeName"/>
</ns2:wallet>
</xsl:for-each>
<ns2:expirationDate>
<xsl:value-of select="FBalanceExpDate"/>
</ns2:expirationDate>
<ns2:balanceAmount>
<xsl:value-of select="F9261_Balance"/>
</ns2:balanceAmount>
<ns2:unit>
<xsl:value-of select="F8341_CurrencyName"/>
</ns2:unit>
</ns2:balance>
</ns2:balanceInfo>
<ns3:Error>
<ns3:description>
<xsl:value-of select="/tns:Subscriber_WalletInfo_GetResponse/ErrorDescription"/>
</ns3:description>
</ns3:Error>
</ns2:responseBody>
But the response doesn't get mapped with this xsl. I'm not sure if I've made any syntactical error or if any namespace is missing.
Any kind of guidance would be great!
I'm not sure if I've made any syntactical error or if any namespace is missing.
Right both times, I think.
First, the syntax errors.
If the XML introduced with the words "The xsl code is" is in fact the entire stylesheet, then your first syntactic error is that your stylesheet is not namespace-well-formed: you have not declared any of the namespace prefixs ns2, ns3, xsl.
Your second syntactic error is at the XSLT level: the namespace prefix tns is also not declared.
Your third syntactic error is that the outermost element does not include any xsl:version attribute.
When I supply dummy namespace declarations for the unbound prefixes (and http://www.w3.org/1999/XSL/Transform for the prefix xsl) and supply an xsl:version="1.0" attribute on the outermost element, then the stylesheet becomes a legal XSLT 1.0 stylesheet, using the "literal result element as stylesheet" option.
So much for the syntax errors. When I run the repaired stylesheet on the input you supply, I get output. Not the output you want, but it's progress:
<?xml version="1.0"?>
<ns2:responseBody xmlns:ns2="http://example.com/ns2"
xmlns:ns3="http://example.com/ns3"
xmlns:tns="http://example.com/tns">
<ns2:balanceInfo>
<ns2:balance>
<ns2:expirationDate/>
<ns2:balanceAmount/>
<ns2:unit/>
</ns2:balance>
</ns2:balanceInfo>
<ns3:Error>
<ns3:description/>
</ns3:Error>
</ns2:responseBody>
Now the semantic errors, which include but are not limited to a namespace error.
You're not getting any information about your input here, because the select expressions in your stylesheet (specifically /tns:Subscriber_WalletInfo_GetResponse/Data/Item and /tns:Subscriber_WalletInfo_GetResponse/ErrorDescription) aren't matching anything. Both of these path expressions say, roughly:
Start at the root of the document (that's the /).
The outermost element will be named Subscriber_WalletInfo_GetResponse, in the namespace bound to prefix tns.
Within that element, the second select expression searches for a child named ErrorDescription which is not namespace-qualified. The first expression searches for a child (or: the set of all children) named Data, and then within the Data element(s), the set of all children named Item, and evaluates the contents of the for-each instruction once for each of them. The elements Item and Data are both expected to be namespace-unqualified.
In the data you show, by contrast, the outermost element is not named Subscriber_WalletInfo_GetResponse and is not namespace-qualified. Naturally, neither select expression doesn't match anything: they both fail at step 2.

Idiom for templating similar to other templating engines like Velocity?

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

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.