I have XML like this:
<assessment>
<variables>
<variable>
<attributes>
<variable_name value="FRED"/>
</attributes>
</variable>
</variables>
<variables>
<variable>
<attributes>
<variable_name value="MORTIMER"/>
</attributes>
</variable>
</variables>
<variables>
<variable>
<attributes>
<variable_name value="FRED"/>
</attributes>
</variable>
</variables>
</assessment>
I know that with this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html"/>
<xsl:template match="assessment">
<xsl:for-each select=".//variables/variable/attributes/variable_name">
<xsl:value-of select="#value"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I can output the following:
FRED
MORTIMER
FRED
But what I really want to output is this:
FRED: 2
MORTIMER: 1
That is, I want to list the distinct elements and how many times each occurs. Note that I want the elements to appear in the order of their first appearance (which might rule out some solutions that use sorting).
How do I do this?
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="variable_name/#value"
use="."/>
<xsl:template match="/">
<xsl:for-each select="
/*/*/variable/attributes/variable_name/#value
[generate-id()
=
generate-id(key('kValueByVal', .)[1])
]
">
<xsl:value-of select=
"concat(., ' ', count(key('kValueByVal', .)), '
')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document, produces the wanted, correct result:
FRED 2
MORTIMER 1
Related
Is it possible to group nodes and merge with a specified key? Some of my xml files have information about one person in two child nodes and I would like to find if nodes are about the same person and then marge it into one node
Input xml looks like this:
<db>
<next>
<name>John</name>
<surname>Smith</surname>
<number>304888</number>
<details>
<city>Westfield</city>
<zip-code>07090</zip-code>
<address>23 Victoria Street</address>
<phone>123456789</phone>
<fax/>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>304888</number>
<details>
<city>Westfield</city>
<zip-code>07090</zip-code>
<address>23 Victoria Street</address>
<phone>223344123</phone>
<fax>993456789</fax>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>113190</number>
<details>
<city>Richmond</city>
<zip-code>3121</zip-code>
<address>18 Seasame Street</address>
<phone>123456222</phone>
<fax/>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>113190</number>
<details>
<city>Richmond</city>
<zip-code>3133</zip-code>
<address>23 Baker Street</address>
<phone>113344123</phone>
<fax>133456789</fax>
</details>
</next>
</db>
'number' is an ID of the person and there can be more people with the same name and surename but with different 'number' value. Also, there can be the same person more than once (with the same value of 'name', 'surename' and 'number') but with different details. If the combination of 'city', 'zip-code' and 'address' are the same but 'phone' or/and 'fax' are differend then I would like to marge it.
And I want it to look like this:
<db>
<next>
<name>John</name>
<surname>Smith</surname>
<number>304888</number>
<details>
<city>Westfield</city>
<zip-code>07090</zip-code>
<address>23 Victoria Street</address>
<phone>123456789</phone>
<phone>223344123</phone>
<fax>993456789</fax>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>113190</number>
<details>
<city>Richmond</city>
<zip-code>3121</zip-code>
<address>18 Seasame Street</address>
<phone>123456222</phone>
<fax/>
</details>
</next>
<next>
<name>John</name>
<surname>Smith</surname>
<number>113190</number>
<details>
<city>Richmond</city>
<zip-code>3133</zip-code>
<address>23 Baker Street</address>
<phone>113344123</phone>
<fax>133456789</fax>
</details>
</next>
</db>
I have tried muenchian grouping but I don't know how to do it with phone and fax nodes.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="index" match="next" use="number" />
<xsl:key name="details-key" match="details" use="city,'_',zip-code,'_',address" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="db">
<xsl:copy>
<xsl:for-each select="//next[generate-id() = generate-id(key('index', number)[1])]">
<xsl:element name="name">
<xsl:value-of select="name"/>
</xsl:element>
<xsl:element name="surname">
<xsl:value-of select="surname"/>
</xsl:element>
<xsl:element name="number">
<xsl:value-of select="number"/>
</xsl:element>
<next>
<xsl:for-each select="//details[generate-id() = generate-id(key('details-key', concat(city,'_',zip-code,'_',address))[1])]">
<xsl:element name="city">
<xsl:value-of select="city"/>
</xsl:element>
<xsl:element name="zip-code">
<xsl:value-of select="zip-code"/>
</xsl:element>
<xsl:element name="address">
<xsl:value-of select="address"/>
</xsl:element>
</xsl:for-each>
</next>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
First thing, your stylesheet will produce an error because the expression you used in:
<xsl:key name="details-key" match="details" use="city,'_',zip-code,'_',address" />
is not a valid expression in XPath 1.0.
If you want to group the details elements by the combined values of city, zip-code and address, you should define your key as:
<xsl:key name="details-key" match="details" use="concat(city, '_', zip-code, '_', address)" />
Now, if I go by your expected output instead of by your description, you actually want to create a group for each unique combination of number and address. So something along the lines of:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="k1" match="next" use="concat(number, '_', details/city,'_', details/zip-code,'_', details/address)" />
<xsl:template match="db">
<xsl:copy>
<xsl:for-each select="next[generate-id() = generate-id(key('k1', concat(number, '_', details/city,'_', details/zip-code,'_', details/address))[1])]">
<next>
<xsl:copy-of select="name | surname | number"/>
<details>
<xsl:copy-of select="city | zip-code | address"/>
<xsl:variable name="group-details" select="key('k1', concat(number, '_', details/city,'_', details/zip-code,'_', details/address))/details" />
<xsl:copy-of select="$group-details/phone[text()] | $group-details/fax[text()]"/>
</details>
</next>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I have a group of dates and I'd like to create partitions with a criterion such as "exactly 7 days apart" For example this is my source xml:
<root>
<entry date="2019-05-12" />
<entry date="2019-05-19" />
<entry date="2019-05-26" />
<entry date="2019-06-16" />
<entry date="2019-06-23" />
</root>
The result should be like this:
<root>
<group>
<val>12.5.</val>
<val>19.5.</val>
<val>26.5.</val>
</group>
<group>
<val>16.6.</val>
<val>23.6.</val>
</group>
</root>
since the first three and the last two dates are all on a Sunday without a gap.
What I have so far is this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sd="urn:someprefix"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
>
<xsl:output indent="yes"/>
<xsl:template match="root">
<root>
<xsl:copy-of select="sd:partition(distinct-values(for $i in entry/#date return $i cast as xs:date))"/>
</root>
</xsl:template>
<xsl:function name="sd:partition">
<xsl:param name="dates" as="xs:date*"/>
<xsl:for-each-group select="$dates" group-adjacent="format-date(., '[F]')">
<group>
<xsl:for-each select="current-group()">
<val>
<xsl:value-of select="format-date(.,'[D].[M].')"/>
</val>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
Which only generates one group.
How can I ask for the previous element to be 7 days apart? I know of duration (xs:dayTimeDuration('P1D')), but I don't know how to compare it to a previous value.
I use Saxon 9.8 HE.
I think you can also do it using group-adjacent:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="entry/#date/xs:date(.)"
group-adjacent=". - (position() - 1) * xs:dayTimeDuration('P7D')">
<group>
<xsl:apply-templates select="current-group()"/>
</group>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match=".[. instance of xs:date]">
<val>{format-date(.,'[D].[M].')}</val>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ncdD7mM
To do your grouping, you really need to know the difference in days with the previous element, then you can group starting with dates where the difference is not 7 days. So, you can declare a variable where you build up some new XML with the dates and differences, and then use that to group.
Try this function in your XSLT instead.
<xsl:function name="sd:partition">
<xsl:param name="dates" as="xs:date*"/>
<xsl:variable name="datesWithDiff" as="element()*">
<xsl:for-each select="$dates">
<xsl:variable name="pos" select="position()" />
<date diff="{(. - $dates[$pos - 1]) div xs:dayTimeDuration('P1D')}">
<xsl:value-of select="." />
</date>
</xsl:for-each>
</xsl:variable>
<xsl:for-each-group select="$datesWithDiff" group-starting-with="date[#diff = '' or xs:int(#diff) gt 7]">
<group>
<xsl:for-each select="current-group()">
<val>
<xsl:value-of select="format-date(.,'[D].[M].')"/>
</val>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:function>
I am trying to obtain a list of all the elements with values that aren't in the (Line 1, Line2), and then insert them into the tags similar to the test.
Right now I can retrieve all the elements, but I'm having trouble restricting this to just my desired values. And then I'm unsure how to match and do a for each on elements outside my match criteria. Any advice would be greatly appreciated!
Given the Following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>test</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
Current XSL for getting elements
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:for-each select="node()[text() != '']">
<xsl:value-of select="local-name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
My WIP xml for inserting the result xml tags is below. I'm unsure how to insert the results of the above xsl into this,
<xsl:template match="Element">
<xsl:copy-of select="."/>
<Element>Value1</Element>
</xsl:template>
And ultimate desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>Identifier</Element>
<Element>Gender</Element>
<Element>Title</Element>
<Element>Name</Element>
<Element>AddressType</Element>
<Element>Line1</Element>
<Element>Suburb</Element>
<Element>State</Element>
<Element>PostCode</Element>
<Element>Country</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
I would change the current template to use mode attribute, so it is only used in specific cases, rather than matching all elements. You should also change it to output elements, not text, like so:
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
Then you can call it like this....
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
Try this XSLT. Note the use of the identity template to copy all other existing elements unchanged
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Below is my requirement. Can we do this using XSLT? I want to convert value of AttributeName as tag under policy and corresponding AttributeValue as value.
Input :
<Policy>
<Attributes>
<AttributeName>is_policy_loan</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
<Attributes>
<AttributeName>is_policy_owners</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
<Attributes>
<AttributeName>is_policy_twoyears</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
</Policy>
Output :
<Policy>
<is_policy_loan>Yes</is_policy_loan>
<is_policy_owners>Yes</is_policy_owners>
<is_policy_twoyears>Yes</is_policy_twoyears>
</Policy>
The following xsl file will do the job:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- create the <AttributeName>AttributeValue</..> nodes -->
<xsl:template match="//Attributes">
<xsl:variable name="name" select="AttributeName" />
<xsl:element name="{$name}">
<xsl:value-of select="AttributeValue" />
</xsl:element>
</xsl:template>
<!-- wrap nodes in a `Policy` node -->
<xsl:template match="/">
<Policy>
<xsl:apply-templates/>
</Policy>
</xsl:template>
</xsl:stylesheet>
The way i would do,
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="yes" />
<xsl:template match="Policy">
<xsl:element name="Policy">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Attributes">
<xsl:variable name="name" select="AttributeName" />
<xsl:element name="{$name}">
<xsl:value-of select="AttributeValue" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
output will be,
<Policy>
<is_policy_loan>Yes</is_policy_loan>
<is_policy_owners>Yes</is_policy_owners>
<is_policy_twoyears>Yes</is_policy_twoyears>
</Policy>
I have the following XML:
<assessment>
<section>
<item>
<attributes>
<variables>
<variable>
<variable_name value="MORTIMER"/>
</variable>
</variables>
</attributes>
</item>
<item>
<attributes>
<variables>
<variable>
<variable_name value="FRED"/>
</variable>
</variables>
</attributes>
</item>
<item>
<attributes>
<variables>
<variable>
<variable_name value="MORTIMER"/>
</variable>
</variables>
</attributes>
</item>
</section>
</assessment>
I have the following XSLT to process that XML:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each select="
.//item//variables//variable_name/#value
">
<xsl:value-of select=
"concat(., ' ', count(key('kValueByVal', .)), '
')"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It outputs the following, which is almost what I want:
MORTIMER 2
FRED 1
MORTIMER 2
It lists each of the variable_names and how many times each occurs. The only problem is that it gives this count once for each time the variable_name occurs instead of only once.
This is what I want it to output:
MORTIMER 2
FRED 1
How do I modify the XSLT code to give me that? Note that we're using XSLT 1.0.
The following solution, which seems like it should work, outputs nothing:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each select=".//item//variables//variable_name/#value[generate-id()
=
generate-id(key('kValueByVal',.)[1])]">
<xsl:value-of select=
"concat(., ' ', count(key('kValueByVal', .)), '
')"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You really need to understand how the Muenchian grouping works, otherwise you'd be asking variations of the same questions forever.
Do read Jeni Tennison's tutorial.
Here is a solution for your latest question:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kVarNameByVal" match="variable_name"
use="#value"/>
<xsl:template match=
"variable_name[generate-id()
=
generate-id(key('kVarNameByVal', #value)[1])
]
">
<xsl:value-of select=
"concat(#value, ' ', count(key('kVarNameByVal', #value)), '
')"/>
<br/>
</xsl:template>
</xsl:stylesheet>
When this transformation is performed on the provided XML document, the wanted result is produced:
MORTIMER 2
FRED 1
Seeing as you're using XSLT 2.0, I'd use the built-in grouping features. (For earlier versions, you'd probably want to look into Muenchian grouping.)
<?xml version="1.0" ?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each-group select=".//item//variables//variable_name" group-by="#value">
<xsl:value-of select="concat(current-grouping-key(), ' ', count(current-group()), '
')"/>
<br/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
This will do it in XSLT 1.0.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each select="
//item//variables//variable_name[not(#value=ancestor::item/preceding-sibling::item//variables//variable_name/#value)]
">
<xsl:value-of select=
"concat(#value, ' ', count(key('kValueByVal', #value)), '
')"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The output I get is
MORTIMER 2
<br />FRED 1
<br />
Note that it assumes a bit more about the document structure (the ancestor::item bit), but you should be able to take it from there.
You get what you are asking for:
<xsl:for-each select=".//item//variables//variable_name/#value">
Wich means: for each one of these attributes
When grouping, you must say: for each one of these one of a kind
And, how do you know wich are one of a kind? With Muenchian method:
<xsl:for-each select=".//item//variables//variable_name/#value[generate-id()
=
generate-id(key('kValueByVal',.)[1])]">
That means: the ones been the first with that key.
EDIT: Also, avoid // when you know input schema.
EDIT: Now I can see that you change the key... So, for you new key, who is first of a kind? Yes! variable_name element:
<xsl:for-each select=".//item//variables//variable_name[generate-id()
=
generate-id(key('kValueByVal',#value)[1])]">
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kValueByVal" match="item//variables//variable_name"
use="#value"/>
<xsl:template match="assessment">
<xsl:for-each
select="//item//variables//variable_name[
generate-id() =
generate-id(key('kValueByVal', #value)[1])]">
<xsl:value-of select=
"concat(./#value, ' ', count(key('kValueByVal', ./#value)), '
')"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>