XSLT variable in predicate not working - variables

my xml looks something like this:
<units>
<unit>
<unitnum></unitnum>
<Year></Year>
<Qty></Qty>
</unit>
</units>
I create a key to capture all the possible years. Like this:
<xsl:key name="yr" match="//Year/text()" use="." />
Then I for-each loop through the keys to get unique years. My ultimate goal is to sum the quantites like this:
<xsl:for-each select="//Year/text()[generate-id()=generate-id(key('yr',.)[1])]">
<li>
<xsl:variable name="varjobyear" select="."/>
<xsl:value-of select="$varjobyear"/> - sum -
<xsl:value-of select="sum(//unit[Year=$varjobyear]/Qty)"/>
</li>
</xsl:for-each>
For some reason my output looks like this:
2010 - sum - 0
2011 - sum - 0
2012 - sum - 0
But what I want is for those 0's to be the actual sum of the quantities for each year.
My best guess is that there is something wrong with the predicate [Year=$varjobyear], but I can't figure out what it is. I have also tried [Year=string($varjobyear)] and also [Year=number($varjobyear)].
What am I doing wrong? Thanks!

You want something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kYearByVal" match="Year" use="."/>
<xsl:template match="Year[generate-id()=generate-id(key('kYearByVal',.)[1])]">
<li>
<xsl:value-of select="concat(., ' - sum - ', sum(key('kYearByVal',.)/../Qty))"/>
</li>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<units>
<unit>
<unitnum>12</unitnum>
<Year>1922</Year>
<Qty>3</Qty>
</unit>
<unit>
<unitnum>13</unitnum>
<Year>1922</Year>
<Qty>8</Qty>
</unit>
<unit>
<unitnum>14</unitnum>
<Year>1999</Year>
<Qty>5</Qty>
</unit>
<unit>
<unitnum>15</unitnum>
<Year>1999</Year>
<Qty>7</Qty>
</unit>
<unit>
<unitnum>16</unitnum>
<Year>2003</Year>
<Qty>3</Qty>
</unit>
</units>
the wanted, correct result is produced:
<li>1922 - sum - 11</li>
<li>1999 - sum - 12</li>
<li>2003 - sum - 3</li>

Related

Creating SQL INSERT statement out of XML file data

I want to generate an SQL INSERT statement from data encoded in XML files, using an XSLT.
My XML files include, for example, the following tags (describing a tombstone):
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="http://www.stoa.org/epidoc/schema/latest/tei-epidoc.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<TEI xmlns="http://www.tei-c.org/ns/1.0" xml:lang="en">
<teiHeader>
<fileDesc>
<titleStmt>
<title>Funerary inscription for Marcellus, a smith</title>
</titleStmt>
<publicationStmt>
<authority>I.Sicily</authority>
<idno type="TM">491539</idno>
</publicationStmt>
<sourceDesc>
<msDesc>
<msIdentifier>
<country>Italy</country>
<region>Sicily</region>
<settlement>Catania</settlement>
<repository role="museum" ref="http://sicily.classics.ox.ac.uk/museum/018"
>Museo Civico di Catania </repository>
<idno type="inventory">390</idno>
<altIdentifier>
<settlement/>
<repository/>
<idno type="old"/>
</altIdentifier>
</msIdentifier>
<msContents>
<textLang mainLang="la">Latin </textLang>
</msContents>
<physDesc>
<objectDesc>
<supportDesc>
<support>
<material n="marble"
ref="http://www.eagle-network.eu/voc/material/lod/48.html"
>marble </material>
<objectType n="tabula"
ref="http://www.eagle-network.eu/voc/objtyp/lod/257">tablet </objectType>
<dimensions>
<height unit="cm">29</height>
<width unit="cm">33.5</width>
<depth unit="cm">2.1</depth>
</dimensions>
</support>
</supportDesc>
</objectDesc>
</physDesc>
</msDesc>
</sourceDesc>
</fileDesc>
</teiHeader>
<!-- lots more content that I cut away here -->
</TEI>
I would like to extract information from this and insert it into an SQL table.
My desired output would look like this:
INSERT INTO tblObjects
(ObjectID, Title, TMid, Material, ObjectType, Height)
VALUES
('Funerary inscription for Marcellus, a smith', 491539, 'marble', 'tabula', 29);
My original - now solved - problem was this:
I tried setting the output method to text after some examples I found
online, but this is giving me the error message "Non-whitespace
characters are not allowed in schema elements other than 'xs:appinfo'
and 'xs:documentation'."
I changed the file format to xsl, now it's no longer complaining about the non-whitespace characters. I can now put text into the final output. The following (inspired by your answers) is the current state of my XSLT, for now I want to try to just insert the value of the title, the position of which is "TEI/teiHeader/fileDesc/titleStmt/title":
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"
encoding="UTF-8"
omit-xml-declaration="yes"
indent="no"/>
<xsl:template match="/">
<xsl:text>INSERT INTO tblObjects</xsl:text>
<xsl:text>(ObjectID, Title, TMid, Material, ObjectType, Height) VALUES (</xsl:text>
<xsl:apply-templates select="root"/>
<xsl:text>);</xsl:text>
</xsl:template>
<xsl:template match="root">
<xsl:value-of select="TEI/teiHeader/fileDesc/titleStmt/title"/>
</xsl:template>
</xsl:stylesheet>
This gives me the following output:
INSERT INTO tblObjects(ObjectID, Title, TMid, Material, ObjectType, Height) VALUES ();
Yet, as you can see, it does not insert the value of the title. I am not sure why that's not working (only trying the title for now).
Simply walk down the tree with templates and concatenate values with needed quotes and commas for SQL query's VALUES clause. Due to the many nested structure of XML, ancestor::* and descendant::* paths are used for specific node value extraction.
Note: This solution works for XML files for one teiHeader. You will need to tailor this solution or run other XSLT scripts for other types.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:doc="http://www.tei-c.org/ns/1.0">
<xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="/doc:TEI">
<xsl:text>INSERT INTO tblObjects</xsl:text>
<xsl:text>(ObjectID, Title, TMid, Material, ObjectType, Height)
VALUES
</xsl:text>
<xsl:apply-templates select="doc:teiHeader/doc:fileDesc/doc:sourceDesc/doc:msDesc/doc:physDesc"/>
</xsl:template>
<xsl:template match="doc:physDesc">
<xsl:variable name="quote">&apos;</xsl:variable>
<xsl:value-of select="concat('(',
$quote, ancestor::doc:fileDesc/doc:titleStmt/doc:title, $quote, ', ',
ancestor::doc:fileDesc/doc:publicationStmt/doc:idno[#type='TM'], ', ',
$quote, normalize-space(descendant::doc:material), $quote, ', ',
$quote, normalize-space(descendant::doc:objectType), $quote, ', ',
descendant::doc:dimensions/doc:height,
')'
)"/>
</xsl:template>
</xsl:stylesheet>
Online Demo

xslt : xml transformation, need soql query as output

Thanks for the solution.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns="urn:partner.soap.sforce.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Header>
<LimitInfoHeader>
<limitInfo>
<current>18029</current>
<limit>5000000</limit>
<type>API REQUESTS</type>
</limitInfo>
</LimitInfoHeader>
</soapenv:Header>
<soapenv:Body>
<upsertResponse>
<result>
<created>false</created>
<id xsi:nil="true"/>
<success>false</success>
</result>
<result>
<created>false</created>
<id xsi:nil="true"/>
<success>false</success>
</result>
<result>
<created>false</created>
<id xsi:nil="true"/>
<success>false</success>
</result>
</upsertResponse>
</soapenv:Body>
</soapenv:Envelope>
for the above xml as input to xslt, the output is below one
<?xml version="1.0" encoding="utf-8"?><soql>select (select Id from contacts where AccountId in (SELECT AccountId from Opportunity where id in ())), (select Id from opportunities where id in()) from account where id in (SELECT AccountId from Opportunity where id in())</soql>
if all the success tag values are false in the given input xml, the <soql> tag should not be generated. i mean output should be empty
eventually what i need is valid SOQL, so that i can query sales force, for the above given xml input the output generated is not a valid SOQL. henceforth i cannot query sales force.
so where the changes in xslt needs to be done, need your help
Thanks
Why can't you do simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="urn:partner.soap.sforce.com"
exclude-result-prefixes="ns">
<xsl:variable name="ids">
<xsl:for-each select="//ns:result[ns:success='true']">
<xsl:text>'</xsl:text>
<xsl:value-of select="ns:id" />
<xsl:text>'</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<soql>
<xsl:text>select (select Id from contacts where AccountId in (SELECT AccountId from Opportunity where id in (</xsl:text>
<xsl:value-of select="$ids"/>
<xsl:text>))), (select Id from opportunities where id in(</xsl:text>
<xsl:value-of select="$ids"/>
<xsl:text>)) from account where id in (SELECT AccountId from Opportunity where id in(</xsl:text>
<xsl:value-of select="$ids"/>
<xsl:text>))</xsl:text>
</soql>
</xsl:template>
</xsl:stylesheet>

How to sum the amounts in a xslt for same ids appearing in XML

I need to produce a text file in the output. I have to first check if a perosn has aby of the following node PreTaxAmt,MatchAmt,RothAmt, then produce a single line in output with required values and then I have to check if the perosn has LoanAmt node, if they have then I have to create another entry for that person. I was able to code till this point but now I am having a requiremnet where if the same person is repated more than once for example below laurel Alex , I need to produce just one entry for single participant id and the PreTaxAmt,MatchAmt,RothAmt amount should be summed up in the first entry in the output and in the in the loan entry row the LoanAmt should be summed up.
Here is my xml.
<Report_Data >
<Report_Entry>
<Participant_ID>03300</Participant_ID>
<Workers>
<FULL_PART_Time_Indicator>1</FULL_PART_Time_Indicator>
<Last_Name>Mary</Last_Name>
<First_Name>Mehul</First_Name>
</Workers>
<End_Date_from_Pay_Period>2016-01-03-08:00</End_Date_from_Pay_Period>
<PreTaxAmt>100.8</PreTaxAmt>
<MatchAmt>100.8</MatchAmt>
<RothAmt>0</RothAmt>
<LoanAmt>0</LoanAmt>
</Report_Entry>
<Report_Entry>
<Participant_ID>10600</Participant_ID>
<Workers>
<FULL_PART_Time_Indicator>1</FULL_PART_Time_Indicator>
<Last_Name>Laurel</Last_Name>
<First_Name>Alex</First_Name>
</Workers>
<End_Date_from_Pay_Period>2016-01-03-08:00</End_Date_from_Pay_Period>
<PreTaxAmt>61.48</PreTaxAmt>
<MatchAmt>61.47</MatchAmt>
<RothAmt>0</RothAmt>
<LoanAmt>0</LoanAmt>
</Report_Entry>
<Report_Entry>
<Participant_ID>10600</Participant_ID>
<Workers>
<FULL_PART_Time_Indicator>1</FULL_PART_Time_Indicator>
<Last_Name>Laurel</Last_Name>
<First_Name>Alex</First_Name>
<Middle_Name>S</Middle_Name>
</Workers>
<End_Date_from_Pay_Period>2016-01-03-08:00</End_Date_from_Pay_Period>
<PreTaxAmt>0</PreTaxAmt>
<MatchAmt>0</MatchAmt>
<RothAmt>0</RothAmt>
<LoanAmt>0</LoanAmt>
</Report_Entry>
<Report_Entry>
<Participant_ID>13100</Participant_ID>
<Workers>
<FULL_PART_Time_Indicator>1</FULL_PART_Time_Indicator>
<Last_Name>Smita</Last_Name>
<First_Name>Paul</First_Name>
</Workers>
<End_Date_from_Pay_Period>2016-01-03-08:00</End_Date_from_Pay_Period>
<PreTaxAmt>0</PreTaxAmt>
<MatchAmt>0</MatchAmt>
<RothAmt>0</RothAmt>
<LoanAmt>0</LoanAmt>
</Report_Entry>
Here is my xslt :
<xsl:template match="/">
<xsl:for-each select="Report_Data/Report_Entry">
<xsl:if test="(PreTaxAmt) or(MatchAmt) or (RothAmt) ">
<xsl:call-template name="DataRecords"/>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="Report_Data/Report_Entry">
<xsl:if test="LoanAmt">
<xsl:call-template name="LoanDataRecord"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="DataRecords">
<xsl:for-each-group select="." group-by="wd:Participant_ID">
<xsl:variable name="ParticpantLoan" select="current-grouping-key()"/>
!-- Transaction Code -->
<xsl:value-of select="'Contribution'"/>
<!-- Participant Number -->
<xsl:value-of select="Participant_ID,$filler),1,9"/>
<!-- FT/PT Indicator -->
<xsl:value-of select="substring(concat(Workers/FULL_PART_Time_Indicator,$filler),1,1)"/>
<!-- Contribution Amount1 -->
<xsl:value-of select="(sum(current-group()/PreTaxAmt))"/>
<!-- Contribution Amount2 -->
<xsl:value-of select="(sum(current-group()/MatchAmt))"/>
<!-- Contribution Amount3 -->
<xsl:value-of select="(sum(current-group()/RothAmt))"/>
<xsl:value-of select="$linefeed"/>
</xsl:for-each-group>
</xsl:template>
<xsl:template name="LoanDataRecord">
<xsl:for-each-group select="." group-by="Participant_ID">
<xsl:variable name="ParticpantLoan" select="current-grouping-key()"/>
<!-- Transaction Code -->
<xsl:value-of select="substring(concat('LoanData',$filler),1,3)"/>
<!-- Participant Number -->
<xsl:value-of select="substring(concat(Participant_ID,$filler),1,9)"/>
<!-- Loan Amt -->
<xsl:value-of select="(sum(current-group()/LoanAmt))"/>
<xsl:value-of select="$linefeed"/>
</xsl:for-each-group>
</xsl:template>
The output should have six lines.
Also, how can I calculate the total count of the records in my output file. Earlier I was using below code but now since there would be duplicate SSN how should I modify my existing code for counting ..
0 ] or Report_Data/Report_Entry/MatchAmt[number(.) > 0 ] or Report_Data/Report_Entry/RothAmt[number(.) > 0 ])+
count(Report_Data/Report_Entry/LoanAmt[number(.) > 0 ])

Get element value from XML datatype field

I want to retrieve element value from XML data field. Please check below code snippet for more detail:
SQL Script:
CREATE TABLE #Temp1 (ConfigXSLT XML);
INSERT INTO #Temp1 VALUES('<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:variable name="transport">
<transport protocol="FILE" direction="get" filename="file1*.csv" />
</xsl:variable>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>')
-- I want only filename element value 'file1*.csv' in select statement
-- I have tried with below query but it not works
SELECT ConfigXSLT.value('declare namespace PD="http://www.w3.org/1999/XSL/Transform"; (//PD:variable/PD:transport/PD:filename)[1]','VARCHAR(MAX)')
FROM #Temp1
DROP TABLE #Temp1
You went overboard with using PD namespace (why PD btw?), the "transport" telement is without a namespace. Also, to select attribute you use # sign before the attributes name :)
Try this:
SELECT ConfigXSLT.value('declare namespace PD="http://www.w3.org/1999/XSL/Transform"; (//PD:variable/transport/#filename)[1]','VARCHAR(MAX)')
FROM #Temp1
Result:
file1*.csv
select #xml.value('(/stylesheet/variable/transport/#filename)[1]', 'nvarchar(max)')
Maybe something like that?

For-each through rows -1 in xslt

I have a question regarding xslt:
basically i have filtered rows:
<xsl:variable name="filteredRows" select="TestRows[(contains(#*[name()='FieldName'],#Title))]" />
And now i want to do for each on them but -1 so if there are 3 rows i want to execute for each only twice ... how to do that?
<xsl:for-each select ="$filteredRows - 1">
<xsl:text> label = </xsl:text>EMPTY
<br />
</xsl:for-each>
Thanks for help ..
Not tested (you didn't provide any sample), but <xsl:for-each select ="$filteredRow[position() < last()]"> could work.