XSLT 1.0 to concatenate elements - xslt-1.0

I have a XML which has complex element called ExternalRequestIDs,
My requirement is to concatenate the ExternalRequestID values coming at run time.
If the input has 5 ExternalRequestID values then the 5 values needs to be concatenated.
The XSL i have created perform only static translation, am trying to accomplish this logic in xslt 1.0, i am new to xslt Please help
Source XML -
<?xml version="1.0" encoding="UTF-8" ?>
<RetrieveMIProcessRequest xmlns="http://xmlns.mycompany.com/RetrieveMI">
<ExternalRequestIDs>
<ExternalRequestID>ID1</ExternalRequestID>
</ExternalRequestIDs>
<ExternalRequestIDs>
<ExternalRequestID>ID2</ExternalRequestID>
</ExternalRequestIDs>
<ExternalRequestIDs>
<ExternalRequestID>ID3</ExternalRequestID>
</ExternalRequestIDs>
<SourceSystem>SourceSystemName</SourceSystem>
</RetrieveMIProcessRequest>
Transformation Created to Concatenate ExternalRequestID values -
<xsl:template match="/">
<ns0:RetrieveMIProcessRequest>
<xsl:for-each select="/ns0:RetrieveMIProcessRequest/ns0:ExternalRequestIDs">
<xsl:variable name="ExtId"
select="concat("'",/ns0:RetrieveMIProcessRequest/ns0:ExternalRequestIDs[1]/ns0:ExternalRequestID,"','",/ns0:RetrieveMIProcessRequest/ns0:ExternalRequestIDs[2]/ns0:ExternalRequestID"'")"/>
<ns0:ExternalRequestIDs>
<ns0:ExternalRequestID>
<xsl:value-of select="$ExtId"/>
</ns0:ExternalRequestID>
</ns0:ExternalRequestIDs>
</xsl:for-each>
<ns0:SourceSystem>
<xsl:value-of select="/ns0:RetrieveMIProcessRequest/ns0:SourceSystem"/>
</ns0:SourceSystem>
</ns0:RetrieveMIProcessRequest>
</xsl:template>
Output after transformation -
<?xml version = '1.0' encoding = 'UTF-8'?>
<ns0:RetrieveMIProcessRequest xmlns:ns0="http://xmlns.mycompany.com/RetrieveMI">
<ns0:ExternalRequestIDs>
<ns0:ExternalRequestID>'ID1','ID2'</ns0:ExternalRequestID>
</ns0:ExternalRequestIDs>
<ns0:ExternalRequestIDs>
<ns0:ExternalRequestID>'ID1','ID2'</ns0:ExternalRequestID>
</ns0:ExternalRequestIDs>
<ns0:ExternalRequestIDs>
<ns0:ExternalRequestID>'ID1','ID2'</ns0:ExternalRequestID>
</ns0:ExternalRequestIDs>
<ns0:SourceSystem>SourceSystemName</ns0:SourceSystem>
</ns0:RetrieveMIProcessRequest>
Expected Output -
<?xml version = '1.0' encoding = 'UTF-8'?>
<ns0:RetrieveMIProcessRequest xmlns:ns0="http://xmlns.mycompany.com/RetrieveMI">
<ns0:ExternalRequestIDs>
<ns0:ExternalRequestID>'ID1','ID2','ID3'</ns0:ExternalRequestID>
</ns0:ExternalRequestIDs>
<ns0:SourceSystem>SourceSystemName</ns0:SourceSystem>
</ns0:RetrieveMIProcessRequest>
This needs to be achieved at run based on the ExternalRequestID values coming in the input request.
If the input has 5 ExternalRequestID values then the values needs to be concatenated

OK, this template should fit your needs :
<xsl:template match="/">
<ns0:RetrieveMIProcessRequest>
<ns0:ExternalRequestIDs>
<ns0:ExternalRequestID>
<!-- Loop on all ExternalRequestID and concatenate the textual values -->
<xsl:for-each select="/ns0:RetrieveMIProcessRequest/ns0:ExternalRequestIDs/ns0:ExternalRequestID">
<xsl:text>'</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
<xsl:if test="following::ns0:ExternalRequestID">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
</ns0:ExternalRequestID>
</ns0:ExternalRequestIDs>
<ns0:SourceSystem>
<xsl:value-of select="/ns0:RetrieveMIProcessRequest/ns0:SourceSystem"/>
</ns0:SourceSystem>
</ns0:RetrieveMIProcessRequest>
</xsl:template>
The output I get is:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:RetrieveMIProcessRequest xmlns:ns0="http://xmlns.mycompany.com/RetrieveMI">
<ns0:ExternalRequestIDs>
<ns0:ExternalRequestID>'ID1','ID2','ID3'</ns0:ExternalRequestID>
</ns0:ExternalRequestIDs>
<ns0:SourceSystem>SourceSystemName</ns0:SourceSystem>
</ns0:RetrieveMIProcessRequest>

Related

Can someone explain this 'Get all Numeric Elements' operator Predicate Filter?

Trying to get elements with a value that is numeric, including zero.
sample xml
<?xml version="1.0" encoding="utf-8"?>
<PRODUCTS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PRODUCT>
<REFERENCE>10</REFERENCE>
<ATTRIBUTES />
<TITLE>fdg</TITLE>
<ACTIVE>1</ACTIVE>
<DELETE>0</DELETE>
<STOCK>10</STOCK>
<WEIGHT>0.00</WEIGHT>
<MODEL>f</MODEL>
<EAN />
<MPN />
<ISBN />
<UPC />
<PRICE>10.000</PRICE>
<SALE_PRICE>10.000</SALE_PRICE>
<RRP_PRICE>0.000</RRP_PRICE>
<COST_PRICE>0.000</COST_PRICE>
<VAT_RATE>0.00</VAT_RATE>
</PRODUCT>
</PRODUCTS>
Expected Output
<?xml version="1.0" encoding="utf-8"?>
<Values>
<REFERENCE>10</REFERENCE>
<ACTIVE>1</ACTIVE>
<DELETE>0</DELETE>
<STOCK>10</STOCK>
<WEIGHT>0.00</WEIGHT>
<PRICE>10.000</PRICE>
<SALE_PRICE>10.000</SALE_PRICE>
<RRP_PRICE>0.000</RRP_PRICE>
<COST_PRICE>0.000</COST_PRICE>
<VAT_RATE>0.00</VAT_RATE>
</Values>
'all numeric' [number()] returns 0 as single predicate expression
<xsl:template match="/">
<Value>
<xsl:for-each select="//*[number()]">
<xsl:element name="{local-name()}">
<xsl:value-of select="."/>
</xsl:element>
Excluding elements without descendants produces expected (non-zero) numbers
<xsl:for-each select="//*[number() and not(descendant::*)]">
Get all 0 using *[number() or format-number(text(),0)='0']
non-zero numbers
<xsl:for-each select="//*[number() and not(descendant::*)]">
all numbers
<xsl:for-each select="//*[number() or format-number(text(),0)='0']">
Adding an additional and or or seems to produce the expected.
Why does select="*[number()]" output 0 results, until an and/or is added?
The rules for evaluating the expression in a predicate are as follows:
If the expression evaluates to a number $n, then the result is true if $n is equal to the context position; IOW, it is as if you have written [position() = $n];
Otherwise the expression is evaluated as a boolean.
To demonstrate, consider the following simplified example:
XML
<items>
<item>0</item>
<item>x</item>
<item>3</item>
<item>y</item>
<item>4</item>
</items>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/items">
<xsl:copy>
<xsl:copy-of select="item[number()]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>3</item>
</items>
Here only the item whose value, when converted to a number, matches the item's position was copied.
If instead you do:
<xsl:copy-of select="item[number() or false()]"/>
then the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>3</item>
<item>4</item>
</items>
because now the expression is evaluated as a boolean and any number other than 0 will be evaluated as true.
In order to copy all items with a "numeric" value, you need to do:
<xsl:copy-of select="item[number() = number()]"/>
to get:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>0</item>
<item>3</item>
<item>4</item>
</items>
This excludes the items whose value cannot be converted to a number - because NaN is not equal to anything, including itself.
When you evaluate number() in a predicate, it is behaving as if you had specified the short-hand for the position. i.e. for <REFERENCE>10</REFERENCE> it was as if you had tested //*[10] or the longer form //*[position()=10] and that element is at position 1 and not 10, so it didn't select any elements.
Another way to evaluate a boolean expression and test if they are numeric would be to use:
[number() eq number()]

CONVERT DATE INTO SQL format USING XSLT function

Input Date format : 2019-07-09-10.56.24.147431
Desired Date Format: 09-JUL-19
I need to do the above in XSLT (version 1.0)
I tried the below :
<xsl:value-of select="concat (substring (xp20:format-dateTime(substring(/ns0:outbound/ns0:cancel_wrk/ns0:hdr_data/ns0:ts_ext, 1,10),'[DD]-[MNn,3-3]-[YYYY]'), 1,6 ), substring (year-from-date (date(substring(/ns0:outbound/ns0:cancel_wrk/ns0:hdr_data/ns0:ts_ext, 1,10))), 3 ) )"/>
<tns:xtrnlSysDttm>
<xsl:value-of select="concat (substring (xp20:format-dateTime(substring(/ns0:outbound/ns0:cancel_wrk/ns0:hdr_data/ns0:ts_ext, 1,10),'[DD]-[MNn,3-3]-[YYYY]'), 1,6 ), substring (year-from-date (date(substring(/ns0:outbound/ns0:cancel_wrk/ns0:hdr_data/ns0:ts_ext, 1,10))), 3 ) )"/>
</tns:xtrnlSysDttm>
Error message while inserting into SQL database with the result of the above XSLT:
BINDING.JCA-12563
Exception occurred when binding was invoked.
Exception occurred during invocation of JCA binding: "JCA Binding execute of Reference operation 'insert' failed due to: DBWriteInteractionSpec Execute Failed Exception.
insert failed. Descriptor name: [GBE_INT_CSS_PQ_PQFFEFO.PqffefoProxy].
Caused by Exception [EclipseLink-3001] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.ConversionException
Exception Description: The object [25-Aug-19], of class [class java.lang.String], could not be converted to [class java.sql.Timestamp].
Internal Exception: BINDING.JCA-11635
Could Not Convert Date Exception.
Unable to convert a string value in the xml to a java.sql.Date.
Even though databases accept strings representing dates in a variety of formats, the adapter only accepts strings representing dates in xml ISO date format.
The input value must be in the iso 8601 date format YYYY-MM-DD.
I assume Input Data
<root>
<input_date>2019-07-09-10.56.24.147431</input_date>
</root>
for XSL:-
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="1.0">
<xsl:template match="/">
<xsl:call-template name="MMM-DD-YYY">
<xsl:with-param name="date" select="'input_date'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="MMM-DD-YYY">
<xsl:param name="date"/>
<xsl:variable name="year" select="substring(substring-before(root/input_date, '-'), 3,2)"/>
<xsl:variable name="day" select="substring-before(substring-after(substring-after(root/input_date, '-'), '-'), '-')"/>
<xsl:variable name="month">
<xsl:call-template name="month-abbr">
<xsl:with-param name="month" select="substring-before(substring-after(root/input_date, '-'), '-')"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($day,'-',$month,'-',$year)"/>
</xsl:template>
<xsl:template name="month-abbr">
<xsl:param name="month"/>
<xsl:choose>
<xsl:when test="$month = '01'">JAN</xsl:when>
<xsl:when test="$month = '02'">FEB</xsl:when>
<xsl:when test="$month = '03'">MAR</xsl:when>
<xsl:when test="$month = '04'">APR</xsl:when>
<xsl:when test="$month = '05'">MAY</xsl:when>
<xsl:when test="$month = '06'">JUN</xsl:when>
<xsl:when test="$month = '07'">JUL</xsl:when>
<xsl:when test="$month = '08'">AUG</xsl:when>
<xsl:when test="$month = '09'">SEP</xsl:when>
<xsl:when test="$month = '10'">OCT</xsl:when>
<xsl:when test="$month = '11'">NOV</xsl:when>
<xsl:when test="$month = '12'">DEC</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Get Output
09-JUL-19

XSLT error when adding accumulator logic to Web-service Request

I am creating an integration to load supplier invoices with multiple lines to workday after getting the details from an input file. Everything seems to be working as expected till i add accumulator logic in web service request in XSLT to first check if the cost center exists in workday. If it does then i populate that else i need to populate a default value. I have not built the default logic if cost center does not exist in the map yet.
I have created a custom report get all active cost center and i merge this list with my input file.
Sample data after i merge my custom report with input file -
<?xml version="1.0" encoding="utf-8"?>
<Header>
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT1007a_CR_PRC_FEDEX_Supplier_Invoices_Cost_Center">
<wd:Report_Entry>
<wd:CostCenter1>40720</wd:CostCenter1>
<wd:CostCenter2>40720</wd:CostCenter2>
</wd:Report_Entry>
</wd:Report_Data>
<root>
<row>
<col1>199779087</col1>
<col2>652233142</col2>
<col3>20190416</col3>
<col4>O</col4>
<col5>D</col5>
<col6>000000000002256</col6>
<col7>00002</col7>
<col8>019153223</col8>
<col9>US</col9>
<col10> </col10>
<col11> </col11>
<col12>774894489995</col12>
<col13>2</col13>
<col14>20190406</col14>
<col15>05</col15>
<col16>06</col16>
<col17>40720/PHL </col17>
<col18>1</col18>
<col19>000000000001128</col19>
<col20>USD</col20>
<col21>050</col21>
<col22>000000000002394</col22>
<col23>+</col23>
<col24>185</col24>
<col25>000000000001345</col25>
<col26>-</col26>
<col27>010</col27>
<col28>000000000000079</col28>
<col29>+</col29>
<col30> </col30>
<col31>000000000000000</col31>
<col32> </col32>
<col33> </col33>
<col34>000000000000000</col34>
<col35> </col35>
<col36> </col36>
<col37>000000000000000</col37>
<col38> </col38>
<col39> </col39>
<col40>000000000000000</col40>
<col41> </col41>
<col42>000000000</col42>
<col43>200 </col43>
<col44>00001</col44>
<col45>0000000</col45>
<col46>0000000</col46>
<col47>L</col47>
<col48>Test Test </col48>
<col49>Test </col49>
<col50>40720/PHL </col50>
<col51>Test </col51>
<col52> </col52>
<col53>Test </col53>
<col54>PA</col54>
<col55>19103 </col55>
<col56>US</col56>
<col57>02</col57>
<col58>Irshad Khan </col58>
<col59>FINRA </col59>
<col60>9513 Key West Avenue </col60>
<col61> </col61>
<col62>ROCKVILLE </col62>
<col63>MD</col63>
<col64>20850 </col64>
<col65>US</col65>
<col66>20190408</col66>
<col67>0836</col67>
<col68> </col68>
<col69>00000000</col69>
<col70>0000</col70>
<col71>A.WANG </col71>
<col72>A1</col72>
<col73>000000000000000</col73>
<col74>USD</col74>
<col75> </col75>
<col76> </col76>
<col77> </col77>
<col78> </col78>
<col79> </col79>
<col80> </col80>
<col81>00000000</col81>
<col82> </col82>
<col83> </col83>
<col84>000000001000000000</col84>
<col85>USD</col85>
<col86>40720/PHL </col86>
<col87> </col87>
</row>
</root>
</Header>
Below is my working XSLT i.e before adding accumulator logic -
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:wd="urn:com.workday/bsvc"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wd1="urn:com.workday.report/INT1007a_CR_PRC_FEDEX_Supplier_Invoices_Cost_Center"
exclude-result-prefixes="wd xsl xsd"
version="3.0">
<xsl:output method="xml"/>
<xsl:param name="fpCompanyRefID"/>
<xsl:param name="fpSupplierRefID"/>
<xsl:param name="fpPaymentTermsRefID"/>
<xsl:param name="fpSpendCategoryRefID"/>
<xsl:param name="fpUOMRefID"/>
<xsl:param name="fpWebServiceVersion"/>
<xsl:param name="fpEventID"/>
<xsl:template match="/Header">
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<env:Body>
<!--Group by Invoice Number to process all lines for an invoice-->
<xsl:for-each-group select="root/row" group-by="col2">
<wd:Submit_Supplier_Invoice_Request xmlns:wd="urn:com.workday/bsvc" wd:Add_Only="true">
<xsl:attribute name="wd:version" select="$fpWebServiceVersion"/>
<wd:Business_Process_Parameters>
<!--When set to "true",all required approvals will be automatically marked as approved.-->
<!--<wd:Auto_Complete>true</wd:Auto_Complete> -->
<wd:Comment_Data>
<wd:Comment>
<xsl:value-of select="concat('Integration Event WID : ',$fpEventID)"/>
</wd:Comment>
</wd:Comment_Data>
</wd:Business_Process_Parameters>
<wd:Supplier_Invoice_Data>
<!--Need to create Supplier Invoice in draft status. Therefore did not set this element.-->
<!--<wd:Submit>true</wd:Submit>-->
<wd:Company_Reference>
<wd:ID wd:type="Company_Reference_ID">
<xsl:value-of select="$fpCompanyRefID"/>
</wd:ID>
</wd:Company_Reference>
<wd:Supplier_Reference>
<wd:ID wd:type="Supplier_ID">
<xsl:value-of select="$fpSupplierRefID"/>
</wd:ID>
</wd:Supplier_Reference>
<wd:Invoice_Date><xsl:value-of select="format-date(col3,'[Y0001]-[M01]-[D01]')"/></wd:Invoice_Date>
<wd:Invoice_Date>
<xsl:value-of select="concat(substring(col3, 1, 4),'-',substring(col3,5, 2),'-',substring(col3, 7, 2))"/>
</wd:Invoice_Date>
<wd:Invoice_Received_Date>
<!-- <xsl:value-of select="format-date(current-date(),'[Y0001]-[M01]-[D01]')"/> -->
</wd:Invoice_Received_Date>
<wd:Control_Amount_Total>
<xsl:value-of select="col6 * 0.01"/>
</wd:Control_Amount_Total>
<!--<wd:Withholding_Tax_Amount><xsl:value-of select="col2"/></wd:Withholding_Tax_Amount>-->
<wd:Suppliers_Invoice_Number>
<xsl:value-of select="col2"/>
</wd:Suppliers_Invoice_Number>
<wd:Memo><xsl:value-of select="col8"/></wd:Memo>
<wd:Payment_Terms_Reference>
<wd:ID wd:type="Payment_Terms_ID">
<xsl:value-of select="$fpPaymentTermsRefID"/>
</wd:ID>
</wd:Payment_Terms_Reference>
<wd:Gross_Invoice_Amount>
<xsl:value-of select="col6 * 0.01"/>
</wd:Gross_Invoice_Amount>
<!-- Process all the lines for the group. Grouping is done at the header level by Invoice Number-->
<xsl:for-each select="current-group()">
<wd:Invoice_Line_Replacement_Data>
<wd:Item_Description>
<xsl:value-of select="col12"/>
</wd:Item_Description>
<wd:Spend_Category_Reference>
<wd:ID wd:type="Spend_Category_ID">
<xsl:value-of select="$fpSpendCategoryRefID"/>
</wd:ID>
</wd:Spend_Category_Reference>
<wd:Worktags_Reference>
<wd:ID wd:type="Cost_Center_Reference_ID">
55075
</wd:ID>
</wd:Worktags_Reference>
<wd:Worktags_Reference>
<wd:ID wd:type="Project_ID">3333</wd:ID>
</wd:Worktags_Reference>
<wd:Worktags_Reference>
<wd:ID wd:type="Location_ID">LOC_KWB</wd:ID>
</wd:Worktags_Reference>
<wd:Quantity>1</wd:Quantity>
<wd:Unit_Cost>
<xsl:value-of select="col19 * 0.01"/>
</wd:Unit_Cost>
<wd:Unit_of_Measure_Reference>
<wd:ID wd:type="UN_CEFACT_Common_Code_ID">
<xsl:value-of select="$fpUOMRefID"/>
</wd:ID>
</wd:Unit_of_Measure_Reference>
<wd:Extended_Amount>
<xsl:value-of select="col19 * 0.01"/>
</wd:Extended_Amount>
<wd:Memo><xsl:value-of select="concat('Sender Name: ',col48,' / Receiver Name: ', col58)"/></wd:Memo>
</wd:Invoice_Line_Replacement_Data>
</xsl:for-each>
</wd:Supplier_Invoice_Data>
</wd:Submit_Supplier_Invoice_Request>
</xsl:for-each-group>
</env:Body>
</env:Envelope>
</xsl:template>
</xsl:stylesheet>
But the moment I add below accumulator logic to above XSLT, Studio error out
I am adding below code after -
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="CostCenterLookup CurrentLookupValue"/>
<xsl:output method="xml"/>
<xsl:accumulator name="CurrentLookupValue" as="xs:string" initial-value="''" streamable="yes">
<xsl:accumulator-rule match="wd:CostCenter1/text()" select="."/>
</xsl:accumulator>
<xsl:accumulator name="CostCenterLookup" as="map(xs:string,xs:string)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="wd:CostCenter2/text()" select="map:put($value, accumulator-before('CurrentLookupValue'), string(.))"/>
</xsl:accumulator>
I am adding below code for getting value for Cost_Center_Reference_ID -
<xsl:value-of select="accumulator-before('CostCenterLookup')( normalize-space( substring(col17,1,5) ) )"/>
Error that I am getting is not very helpful to debug the exact issue -
Submit Supplier Invoice Request Failed for null. Error during invocation: null
Would really appreciate some help on this.
I would guess the error is simply caused by trying to use streaming (as you declare that on the xsl:mode) and xsl:for-each-group select="root/row" group-by="col2". With streaming you can't access child elements of the population elements (i.e. the row elements) in the group-by expression as that would require processing (consuming) the contents of each row. So you would need to use xsl:for-each-group select="root/row!copy-of()" group-by="col2". Otherwise the processor will reject the stylesheet code as not being streamable. You might want to try to use Saxon EE from the command line to verify, it for sure will give better diagnostics.

XSLT Increment reset upon change in element

XML:
<?xml version="1.0" encoding="utf-8"?>
<Projects>
<Project GUID="K9">
<Name>Range</Name>
<WBSs>
<WBS GUID="2">
<Name>Site</Name>
<ParentWBS/>
<ProjectGUID>K9</ProjectGUID>
<ObjectId>99040</ObjectId>
<Activities/>
<WBSs>
<WBS GUID="1">
<Name>West</Name>
<ParentWBS>2</ParentWBS>
<ProjectGUID>K9</ProjectGUID>
<ObjectId>99046</ObjectId>
<Activities>
<Activity GUID="A100">
<Name>Task</Name>
<ProjectGUID>K17</ProjectGUID>
<WBSCode>1</WBSCode>
</Activity>
</Activities>
</WBS>
<WBS GUID="2">
<Name>South</Name>
<ParentWBS>2</ParentWBS>
<ProjectGUID>K9</ProjectGUID>
<ObjectId>99047</ObjectId>
<Activities>
<Activity GUID="A200">
<Name>Task 2</Name>
</Activity>
</Activities>
</WBS>
<WBS GUID="3">
<Name>North</Name>
<ParentWBS>2</ParentWBS>
<ProjectGUID>K9</ProjectGUID>
<ObjectId>99048</ObjectId>
<Activities/>
</WBS>
</WBSs>
</WBS>
</WBSs>
<Activities/>
</Project>
</Projects>
This is my current output:
<OBJECTID>99046</OBJECTID >
<ACTIVITY>0010</ACTIVITY>
<OBJECTID>99047</OBJECTID >
<ACTIVITY>0020</ACTIVITY>
Desired output:
<OBJECTID>99046</OBJECTID >
<ACTIVITY>0010</ACTIVITY>
<OBJECTID>99047</OBJECTID >
<ACTIVITY>0010</ACTIVITY>
When I have existing Activity and existing OBJECTID, the first 2 WHEN clauses work fine. The problem arises when there is no existing
ObjectID or ObjectID changes in the 3rd WHEN. What I need is to start at ‘0010’ and increment by 10 until $ObjectID changes, then start at ‘0010’ again and so on. My background is procedural programming so still getting used to functional way of doing things and the restriction of not changing variables.
Thanks in advance.
Here's the relevant XSLT code:
<ACTIVITY>
<!-- count($ExistingActivity) = 1 -> ObjectId & Activity Id exist, increment $LastObjectIdActivity/ACTIVITY
count($ExistingObjectId) = 1 -> ObjectId exists, increment $LastObjectId/ACTIVITY
count($ExistingObjectId) = 0 -> $ObjectId start at 0010 and keep incrementing until ObjectId change.
-->
<xsl:variable name="incr" select="10"/>
<xsl:choose>
<xsl:when test="count($ExistingActivity) = 1"> <!-- Exact match of ObjectId & Activity -->
<!-- Get last existing ObjectId activity -->
<xsl:variable name="temp" select="$LastObjectIdActivity/ACTIVITY"/>
<xsl:variable name="Oper" select="format-number($temp, '0000')"/>
<xsl:value-of select="$Oper"/>
</xsl:when>
<xsl:when test="count($ExistingObjectId) = 1"> <!-- Activity not found, But ObjectId exists-->
<!-- Get last existing ObjectId activity -->
<xsl:variable name="temp" select="$LastObjectId/ACTIVITY + $incr"/>
<xsl:variable name="Oper" select="format-number($temp, '0000')"/>
<xsl:value-of select="$Oper"/>
</xsl:when>
<xsl:when test="count($ExistingObjectId) = 0"> <!-- No Existing ObjectId -->
<xsl:variable name="pos" select="(position() * 10)"/>
<xsl:variable name="NextOper" select="format-number($pos, '0000')"/> <!-- Start Activity at 0010 reset on new ObjectId-->
<xsl:value-of select="$NextOper"/>
<!-- $NextOper should be incremented by 10 -->
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</ACTIVITY>
I think what you want is to assign a value to each of the activities, so that's what you want to match
<xsl:template match="//Activity">
<OBJECTID>
<xsl:value-of select="../../ObjectId" />
</OBJECTID>
<ACTIVITY>
<xsl:value-of select="format-number(10 * (count(preceding-sibling::*) +1), '0000') "/>
</ACTIVITY>
</xsl:template>
That would give you the OBJECTID/ACTIVITY pair for every activity in the input. The count expression evaluates the number of preceding sibling activities, ie the 1-based index.

XSLT Outer Bracket Node Inside Inner Bracket

I have the following XML:
<Root>
<PersonSettings>
<Type Drinks="1">A</Type>
<Type Drinks="2">B</Type>
<Type Drinks="3">C</Type>
<LowestAge>20</LowestAge>
<MaxAge>49</MaxAge>
</PersonSettings>
<PersonSettings>
<Type Drinks="5">A</Type>
<Type Drinks="8">B</Type>
<Type Drinks="1">C</Type>
<LowestAge>50</LowestAge>
<MaxAge>90</MaxAge>
</PersonSettings>
<Person Alive="Yes">
<Type>A</Type>
<Age>23</Age>
</Person>
<Person Alive="Yes">
<Type>B</Type>
<Age>50</Age>
</Person>
<Person Alive="Yes">
<Type>C</Type>
<Age>51</Age>
</Person>
<Person Alive="Yes">
<Type>A</Type>
<Age>70</Age>
</Person>
</Root>
And the following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="no"/>
<xsl:template match="/">
<PersonsOneDrink>
<xsl:value-of select="(count(/Root/Person[#Alive = 'Yes' and /Root/PersonSettings[**(Person/Age)** >= LowestAge and **(Person/Age)** <= MaxAge]/Type[. = **(Person/Type)** and #Drinks = '1']))"/>
</PersonsOneDrink>
</xsl:template>
</xsl:stylesheet>
What I want to do in this example is count the number of people with one drink, that are alive. I have different settings depending on the Type of Person and the Age of such person. For example Type A has one drink if between 20 and 49 years of age, while type C has one drink if between 50 to 90 years. The problem I find is that I cannot reference the outer node when going inside a second set of brackets. Is there any way to create a variable on the fly?
P.S: The parts with * * () * * are where I would like to reference the outer node.
I don't think this can be done with a single count() expression, applied from the context of the root.
The problem here is that you need to refer to the values of Person within the predicate comparing them to the values of the prospective PersonSettings. For this, you need to use the current() function - but in order to have the current() function refer to Person, you must first make Person the context node.
Here is a suggested approach that writes a character for each Person that passes the test into a variable. This eliminates the need to convert the variable into a node-set before counting it:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Root">
<xsl:variable name="v1">
<xsl:for-each select="Person">
<xsl:if test="#Alive = 'Yes' and /Root/PersonSettings[LowestAge <= current()/Age and MaxAge >= current()/Age]/Type[. = current()/Type]/#Drinks = 1">
<xsl:text>Y</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<PersonsOneDrink>
<xsl:value-of select="string-length($v1)"/>
</PersonsOneDrink>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<PersonsOneDrink>2</PersonsOneDrink>
A more general solution - suitable if you want to perform several queries on the supplied data - would start by rewriting the input and assigning each Person the corresponding amount of drinks .
Here we will construct a variable that contains:
<Person>
<Type>A</Type>
<Age>23</Age>
<Drinks>1</Drinks>
</Person>
<Person>
<Type>B</Type>
<Age>50</Age>
<Drinks>8</Drinks>
</Person>
<Person>
<Type>C</Type>
<Age>51</Age>
<Drinks>1</Drinks>
</Person>
<Person>
<Type>A</Type>
<Age>70</Age>
<Drinks>5</Drinks>
</Person>
Then, once the variable is converted into a node-set, it is trivial to get the number of persons with any amount of drinks:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Root">
<!-- first pass -->
<xsl:variable name="persons-rtf">
<xsl:for-each select="Person">
<xsl:copy>
<xsl:copy-of select="*"/>
<Drinks>
<xsl:value-of select="/Root/PersonSettings[LowestAge <= current()/Age and MaxAge >= current()/Age]/Type[. = current()/Type]/#Drinks"/>
</Drinks>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="persons" select="exsl:node-set($persons-rtf)/Person"/>
<!-- output -->
<results>
<PersonsOneDrink>
<xsl:value-of select="count($persons[Drinks = 1])"/>
</PersonsOneDrink>
</results>
</xsl:template>
</xsl:stylesheet>