I have an XML file like this:
<table chgFlag="i" id="II325">
<row chgFlag="i" idVal="1">
<fld id="II325A">
<datVl chgFlag="i">1</datVl>
</fld>
<fld id="II325B">
<datVl chgFlag="i">2001-12-01</datVl>
</fld>
<fld id="II325C">
<datVl chgFlag="i">2006-04-30</datVl>
</fld>
<fld id="II325D">
<datVl chgFlag="i">01</datVl>
</fld>
</row>
<row chgFlag="i" idVal="2">
<fld id="II325A">
<datVl chgFlag="i">2</datVl>
</fld>
<fld id="II325B">
<datVl chgFlag="i">2006-05-01</datVl>
</fld>
<fld id="II325C">
<datVl chgFlag="i">2031-11-30</datVl>
</fld>
<fld id="II325D">
<datVl chgFlag="i">01</datVl>
</fld>
</row>
</table>
If I just put it into read_xml I get something like this:
chgFlag idVal fld
0 i 1 NaN
1 i 2 NaN
It take sthe attributes in each row as columns. I don't want that, I want value of id in fld as the column and the text inside datVl as the value.
Something like this.
I manage to get the result I wanted using this code:
data_dict = xmltodict.parse(ET.tostring(table))
table_list = []
if type(data_dict["table"]["row"]) == list:
for row in data_dict["table"]["row"]:
row_dict = {}
for field in row["fld"]:
row_dict[field["#id"]] = field["datVl"]["#text"]
table_list.append(row_dict)
df = pd.DataFrame(table_list)
I was wondering if there is more a general solution, perhaps setting some parameter inside read_xml?
I might need to scale my current solution, that is why I am asking.
As mentioned in the comments to this similar question:
read_xml does not parse beyond its immediate descendants.
But you can use the approach mentioned in the above answer: transforming the XML with XSLT to a palatable format. In your case you can use the stylesheet parameter of read_xml with this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/table">
<Root><xsl:apply-templates /></Root>
</xsl:template>
<xsl:template match="row">
<Item>
<xsl:element name="II325A"><xsl:value-of select="fld[#id='II325A']/datVl" /></xsl:element>
<xsl:element name="II325B"><xsl:value-of select="fld[#id='II325B']/datVl" /></xsl:element>
<xsl:element name="II325C"><xsl:value-of select="fld[#id='II325C']/datVl" /></xsl:element>
<xsl:element name="II325D"><xsl:value-of select="fld[#id='II325D']/datVl" /></xsl:element>
</Item>
</xsl:template>
</xsl:stylesheet>
or, a more general approach for the second template iterating over all of the children of row:
<xsl:template match="row">
<Item>
<xsl:for-each select="fld">
<xsl:element name="{#id}"><xsl:value-of select="datVl" /></xsl:element>
</xsl:for-each>
</Item>
</xsl:template>
which creates, in both versions, the following intermediate XML
<Root>
<Item><II325A>1</II325A><II325B>2001-12-01</II325B><II325C>2006-04-30</II325C><II325D>1</II325D></Item>
<Item><II325A>2</II325A><II325B>2006-05-01</II325B><II325C>2031-11-30</II325C><II325D>1</II325D></Item>
</Root>
and can be used with the following python code:
dataFolder = './'
df_bulk = pd.read_xml(
dataFolder+'input.xml',
stylesheet='transform.xslt',
xpath='/Root/Item',
)
print(df_bulk.head(10))
The output is:
II325A II325B II325C II325D
0 1 2001-12-01 2006-04-30 1
1 2 2006-05-01 2031-11-30 1
one option is to use the xpath parameter, and pass the specified sections of the xml :
(pd
.read_xml(data, xpath = ".//fld")
.assign(counter = lambda df: df.groupby('id').cumcount())
.pivot('counter', 'id', 'datVl')
.rename_axis(index = None, columns = None)
)
II325A II325B II325C II325D
0 1 2001-12-01 2006-04-30 01
1 2 2006-05-01 2031-11-30 01
Related
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()]
I need to check, if one “Item” has several nodes called "ItemDeliveryInformation" and if so, if the node "PackingSlipId" has the value "ohne Lieferschein".
e.g. one node "ItemDeliveryInformation"with “PackingSLipId = ohne Lieferschein” is ok, but if there are two ore more nodes "ItemDeliveryInformation" at “Item” it´s also ok, but if one of these several nodes has the value “ohne Lieferschein” at “PackingSlipId” the XSLT has to remove the whole node "ItemDeliveryInformation" where the “PackingSlipId = ohne Lieferschein”.
I started like that:
<xsl:template match="Item[count(./ItemDeliveryInformation < 1)] and Item/ItemDeliveryInformation[./PackingSlipId = 'ohne Lieferschein']">
<xsl:choose>
<xsl:when test="Item[count(./ItemDeliveryInformation < 1)] and Item/ItemDeliveryInformation[./PackingSlipId = 'ohne Lieferschein']"></xsl:when>
<xsl:otherwise> <xsl:apply-templates select="#* | node()"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
wrong example:
<?xml version="1.0" encoding="utf-8"?>
<SALESINVOICE>
<Interchange>
<Test>No</Test>
</Interchange>
<HeaderInformation>
<OrigInvoiceNumber>1</OrigInvoiceNumber>
</HeaderInformation>
<LineInformation>
<Item>
<LineNum>1000</LineNum>
<GTIN>1234</GTIN>
<ItemDeliveryInformation>
<LineNumDeliveryNote>1000</LineNumDeliveryNote>
<PackingSlipId_Created>2022-01-19T06:26:24</PackingSlipId_Created>
<PackingSlipId>LS-0062820</PackingSlipId>
<DeliveryDate>2022-01-19</DeliveryDate>
<DeliveredQuantity>3.00</DeliveredQuantity>
<ShipFromAddressInformation/>
</ItemDeliveryInformation>
<ItemDeliveryInformation>
<LineNumDeliveryNote>1000</LineNumDeliveryNote>
<PackingSlipId>ohne Lieferschein</PackingSlipId>
<DeliveryDate>2022-01-25</DeliveryDate>
<DeliveredQuantity>3.00</DeliveredQuantity>
<ShipFromAddressInformation/>
</ItemDeliveryInformation>
<ItemDeliveryInformation>
<LineNumDeliveryNote>1000</LineNumDeliveryNote>
<PackingSlipId_Created>2022-01-19T06:26:24</PackingSlipId_Created>
<PackingSlipId>LS-0062822</PackingSlipId>
<DeliveryDate>2022-01-19</DeliveryDate>
<DeliveredQuantity>3.00</DeliveredQuantity>
<ShipFromAddressInformation/>
</ItemDeliveryInformation>
</Item>
<Item>
<LineNum>1002</LineNum>
<GTIN>4657</GTIN>
<ItemDeliveryInformation>
<LineNumDeliveryNote>1002</LineNumDeliveryNote>
<PackingSlipId_Created>2022-01-25</PackingSlipId_Created>
<PackingSlipId>ohne Lieferschein</PackingSlipId>
<DeliveryDate>2022-01-25</DeliveryDate>
<DeliveredQuantity>1.00</DeliveredQuantity>
<ShipFromAddressInformation/>
</ItemDeliveryInformation>
</Item>
</LineInformation>
</SALESINVOICE>
correct example:
<?xml version="1.0" encoding="UTF-8"?>
<SALESINVOICE>
<Interchange>
<Test>No</Test>
</Interchange>
<HeaderInformation>
<OrigInvoiceNumber>1</OrigInvoiceNumber>
</HeaderInformation>
<LineInformation>
<Item>
<LineNum>1000</LineNum>
<GTIN>1234</GTIN>
<ItemDeliveryInformation>
<LineNumDeliveryNote>1000</LineNumDeliveryNote>
<PackingSlipId_Created>2022-01-19T06:26:24</PackingSlipId_Created>
<PackingSlipId>LS-0062820</PackingSlipId>
<DeliveryDate>2022-01-19</DeliveryDate>
<DeliveredQuantity>3.00</DeliveredQuantity>
</ItemDeliveryInformation>
<ItemDeliveryInformation>
<LineNumDeliveryNote>1000</LineNumDeliveryNote>
<PackingSlipId_Created>2022-01-19T06:26:24</PackingSlipId_Created>
<PackingSlipId>LS-0062822</PackingSlipId>
<DeliveryDate>2022-01-19</DeliveryDate>
<DeliveredQuantity>3.00</DeliveredQuantity>
</ItemDeliveryInformation>
</Item>
<Item>
<LineNum>1002</LineNum>
<GTIN>4657</GTIN>
<ItemDeliveryInformation>
<LineNumDeliveryNote>1002</LineNumDeliveryNote>
<PackingSlipId_Created>2022-01-25</PackingSlipId_Created>
<PackingSlipId>ohne Lieferschein</PackingSlipId>
<DeliveryDate>2022-01-25</DeliveryDate>
<DeliveredQuantity>1.00</DeliveredQuantity>
</ItemDeliveryInformation>
</Item>
</LineInformation>
</SALESINVOICE>
thanks for your help
Item[…] and Item/ItemDeliveryInformation[…] will never match because no node is both an Item and an ItemDeliveryInformation. Don't replicate the condition in match, that doesn't make sense.
You want to match on ItemDeliveryInformation and remove it if it both has the ohne Lieferschein and sibling nodes. Since remove is the absence of copying, invert the condition when copying:
<xsl:template match="Item/ItemDeliveryInformation">
<xsl:if test="count(preceding-sibling::ItemDeliveryInformation|following-sibling::ItemDeliveryInformation)=0 or PackingSlipId/text() != 'ohne Lieferschein'">
<xsl:copy><xsl:apply-templates select="#* | node()"/></xsl:copy>
</xsl:if>
</xsl:template>
This assumes you have a generic copying template.
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.
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>
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>