I have this XML file and I want to create an XSL file to convert it to Excel. Each row should represent a logo. The columns will be the key attributes like color, id, description plus any other key for other logos.
<Top>
<logo>
<field key="id">172-32-1176</field>
<field key="color">Blue</field>
<field key="description"><p>Short Description</p></field>
<field key="startdate">408 496-7223</field>
</logo>
<logo>
<field key="id">111-111-111</field>
<field key="color">Red</field>
</logo>
<!-- ... -->
</Top>
The XSL file is something like this:
<xsl:stylesheet
version="1.0"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
>
<xsl:template match="/">
<Workbook
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40"
>
<xsl:apply-templates/>
</Workbook>
</xsl:template>
<xsl:template match="/*">
<Worksheet>
<xsl:attribute name="ss:Name">
<xsl:value-of> select="local-name(/*)"/>
</xsl:attribute>
<Table x:FullColumns="1" x:FullRows="1">
<Row>
<xsl:for-each select="*/*">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="#key"/>
</Data>
</Cell>
</xsl:for-each>
</Row>
<xsl:apply-templates/>
</Table>
</Worksheet>
</xsl:template>
<xsl:template match="/*/*">
<Row>
<xsl:apply-templates/>
</Row>
</xsl:template>
<xsl:template match="/*/*/*">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="."/>
</Data>
</Cell>
<!-- <xsl:apply-templates/> -->
</xsl:template>
</xsl:stylesheet>
But data are not correctly placed under the columns and column names are repeating. How can this be done?
The columns could be in any order and also column stardate should be empty for second row in excel. Similarly for more .
You were very close. Try to be more specific when it comes to template matching - don't say template match"/*/*/*" when you can say template match="field".
Other than that, this is your approach, only slightly modified:
<xsl:stylesheet
version="1.0"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="urn:my-scripts"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
>
<xsl:output method="xml" encoding="utf-8" indent="yes" />
<xsl:template match="/">
<Workbook
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40"
>
<xsl:apply-templates select="Top" />
</Workbook>
</xsl:template>
<xsl:template match="Top">
<Worksheet ss:Name="{local-name()}">
<Table x:FullColumns="1" x:FullRows="1">
<Row>
<!-- header row, made from the first logo -->
<xsl:apply-templates select="logo[1]/field/#key" />
</Row>
<xsl:apply-templates select="logo" />
</Table>
</Worksheet>
</xsl:template>
<!-- a <logo> will turn into a <Row> -->
<xsl:template match="logo">
<Row>
<xsl:apply-templates select="field" />
</Row>
</xsl:template>
<!-- convenience: <field> and #key both turn into a <Cell> -->
<xsl:template match="field | field/#key">
<Cell>
<Data ss:Type="String">
<xsl:value-of select="."/>
</Data>
</Cell>
</xsl:template>
</xsl:stylesheet>
Your "repeating column names" problem roots in this expression:
<xsl:for-each select="*/*">
In your context, this selects any third level element in the document (literally all <field> nodes in all <logo>s), and makes a header row out of them. I replaced it with
<xsl:apply-templates select="logo[1]/field/#key" />
which makes a header row out of the first <logo> only.
If a certain column order is required (other than document order) or not all <field> nodes are in the same order for all <logo>s, things get more complex. Tell me if you need that.
Related
I need help with my XSLT. Here's my XML structure.
<root>
<row>
<component>mainfield_1</component>
<type>Field</type>
<where_used>
<component>subfield_2</component>
<type>Field</type>
</where_used>
<where_used>
<component>report_1</component>
<type>Report</type>
</where_used>
</row>
<row>
<component>subfield_2</component>
<type>Field</type>
<where_used>
<component>report_2</component>
<type>report</type>
</where_used>
</row>
<row>
<component>mainfield_3</component>
<type>Field</type>
</row>
</root>
I would like it to be transformed into the following:
<root>
<row>
<component>mainfield_1</component>
<type>Field</type>
</row>
<row>
<component>subfield_2</component>
<type>Field</type>
</row>
<row>
<component>report_1</component>
<type>Report</type>
</row>
<row>
<component>report_2</component>
<type>report</type>
</row>
</root>
Basically, I am trying to get all the distinct dependencies of component mainfield_1. Here's my sample code but it is not enough to find any matching parent that has the same component name as the children.
<xsl:template match="root">
<root>
<xsl:apply-templates select="row[component='mainfield_1']"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<component>
<xsl:value-of select="component"/>
</component>
<type>
<xsl:value-of select="type" />
</type>
</row>
<xsl:apply-templates select="where_used"/>
</xsl:template>
<xsl:template match="where_used">
<row>
<component>
<xsl:value-of select="component"/>
</component>
<type>
<xsl:value-of select="type" />
</type>
</row>
</xsl:template>
If I run the above, I will not be able to get this.
<row>
<component>report_2</component>
<type>report</type>
</row>
Please help.
Consider using a key to look up the row items by component
<xsl:key name="rows" match="row" use="component" />
Then you can have a template for where_used nodes that refer to a separate row, allowing you to select that row instead
<xsl:template match="where_used[key('rows', component)]">
<xsl:apply-templates select="key('rows', component)" />
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="rows" match="row" use="component" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<root>
<xsl:apply-templates select="row[component='mainfield_1']"/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:apply-templates select="* except where_used" />
</row>
<xsl:apply-templates select="where_used"/>
</xsl:template>
<xsl:template match="where_used">
<row>
<xsl:apply-templates />
</row>
</xsl:template>
<xsl:template match="where_used[key('rows', component)]">
<xsl:apply-templates select="key('rows', component)" />
</xsl:template>
</xsl:stylesheet>
Note I have used the identity template too, to avoid having to explicitly copy existing nodes that don't need to be changed.
I'm new to XSL and am seeking a way to solve some problem. I have xml something like:
<Table>
<Row Id="1">
<Field1>"P_907"</Field1>
<Field2>"5912"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
</Row>
<Row Id="2">
<Field1>"2.1.1.M5"</Field1>
</Row>
<Row Id="3">
<Field1>"3.1.1.M5"</Field1>
</Row>
<Row Id="4">
<Field1>"P_908"</Field1>
<Field2>"5913"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
</Row>
<Row Id="5">
<Field1>"3.11.M2"</Field1>
</Row>
</Table>
Where Row Id=1 and Row Id=4 are headers of invoices and remaining rows are lines of invoices. Every invoice header has its ID in field1 but there is no invoice ID in invoice lines. I know that when there is no field3 in row, it means that row is invoice line. In other case it is invoice header. Every rows before header row belong to previous header row. How create xml with proper invoice hierarchy using xslt?
Output xml could be like:
<Invoice>
<Field1>"P_907"</Field1>
<Field2>"5912"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
<Row>
<Field1>"2.1.1.M5"</Field1>
</Row>
<Row>
<Field1>"3.1.1.M5"</Field1>
</Row>
</Invoice>
<Invoice>
<Field1>"P_908"</Field1>
<Field2>"5913"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
<Row>
<Field1>"3.11.M2"</Field1>
</Row>
</Invoice>
I would do this using keys as following:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="Rows" match="Row[not(Field3)]" use="generate-id(preceding-sibling::Row[Field3][1])"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row[Field3]">
<Invoice>
<xsl:apply-templates select="node()"/>
<xsl:apply-templates select="key('Rows', generate-id())" mode="followingRows"/>
</Invoice>
</xsl:template>
<xsl:template match="Row" mode="followingRows">
<xsl:copy><xsl:apply-templates select="node()"/></xsl:copy>
</xsl:template>
<xsl:template match="Row"/>
</xsl:stylesheet>
One solution is the following XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Table">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Row[Field3]">
<xsl:variable name="invoice-count" select="count(preceding-sibling::Row[Field3]) + 1"/>
<Invoice>
<xsl:apply-templates/>
<xsl:apply-templates select="following-sibling::Row[not(Field3)
and not(count(preceding-sibling::Row[Field3]) > $invoice-count)]" mode="copy"/>
</Invoice>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row" mode="copy">
<xsl:copy>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row"/>
</xsl:transform>
when applied to your input XML produces the output
<Invoice>
<Field1>"P_907"</Field1>
<Field2>"5912"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
<Row>
<Field1>"2.1.1.M5"</Field1>
</Row>
<Row>
<Field1>"3.1.1.M5"</Field1>
</Row>
</Invoice>
<Invoice>
<Field1>"P_908"</Field1>
<Field2>"5913"</Field2>
<Field3>"2013/05/31"</Field3>
<Field4>"2013/05/31"</Field4>
<Row>
<Field1>"3.11.M2"</Field1>
</Row>
</Invoice>
One template matches all Row elements that contain a Field3:
<xsl:template match="Row[Field3]">
This template writes an <Invoice> node and copies the content of this Row by applying templates. Then all following silbing Row elements that have no Field3 and not more preceding sibling Row elements with Field3 than the current Row are copied by applying the template mode="copy".
This template copies the content of the Row but not the attributes, so the id of the Row will be removed from the output.
To avoid writing the Row elements twice, the empty template
<xsl:template match="Row"/> matches all Row nodes that are already handled by applying templates in the template that matches the Row elements with Field3.
I need to make a dynamic table with 3 columns zones
Is better with an example:
I don't know if you are gonna understand this, but the "----" is only to format the table in the post
|--Number--|--Doc--|--Status---------|--Number--|--Doc--|--Status---------|--Number--|--Doc--|--Status--|
|-----11------|1111- |-- _____________---------|--22------- |2222 --|______________----------|---- 33----- |3333- | _______________|
|-----44----- |4444- |-- _____________ -------- |------------|----------|----------|---------|--------------|---------|------------|
My XML:
<Details>
<Detail>
<Number>11</Number>
<Doc>1111</Doc>
</Detail>
<Detail>
<Number>22</Number>
<Doc>2222</Doc>
</Detail>
<Detail>
<Number>33</Number>
<Doc>3333</Doc>
</Detail>
<Detail>
<Number>44</Number>
<Doc>4444</v>
</Detail>
</Details>
I tried to do like following post but I couldn't.
XSLT Generate Dynamic Rows and Columns for Apache FOP
Here's one way with recursion, I did not add "FO" namespaces but you should be able to get there using this. You could also add a test to fill empty cells if you are inclined.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="Details">
<table>
<xsl:call-template name="rows">
<xsl:with-param name="Details" select="*"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="rows">
<xsl:param name="Details"/>
<row>
<xsl:apply-templates select='$Details[position() < 4]/*'/>
</row>
<xsl:if test="$Details[4]">
<xsl:call-template name="rows">
<xsl:with-param name="Details" select="$Details[position() > 3]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="Number | Doc">
<cell>
<xsl:value-of select="."/>
</cell>
</xsl:template>
</xsl:stylesheet>
The output is this using your XML above (I added a few more Detail elements to make sure all was working):
<?xml version="1.0" encoding="utf-8"?>
<table>
<row>
<cell>11</cell>
<cell>1111</cell>
<cell>22</cell>
<cell>2222</cell>
<cell>33</cell>
<cell>3333</cell>
</row>
<row>
<cell>44</cell>
<cell>4444</cell>
<cell>5</cell>
<cell>55</cell>
<cell>6</cell>
<cell>66</cell>
</row>
<row>
<cell>7</cell>
<cell>777</cell>
</row>
</table>
I know that there are several other question/answers similar to this, but I haven't read one that addresses my confusion over this transform. I need to move an XML document from this format:
<root>
<row>
<t0>1</t0>
<title>Main Title</title>
</row>
<row>
<t0>2</t0>
<title>Secondary Title</title>
<note>Note</note>
</row>
<row>
<t0>3</t0>
<title>Tertiary Title</title>
</row>
<row>
<t0>3</t0>
<title>Another Title</title>
</row>
<row>
<t0>2</t0>
<title>A Second Secondary Title</title>
<note>Note</note>
</row>
<row>
<t0>3</t0>
<title>Third Level Title</title>
</row>
<row>
<t0>3</t0>
<title>Title at Level Three</title>
</row>
</root>
to this format:
<root>
<header>List</header>
<t01>
<title>Main Title</title>
<t02>
<title>Secondary Title</title>
<note>Note</note>
<t03>
<title>Tertiary Title</title>
</t03>
<t03>
<title>Another Title</title>
</t03>
</t02>
<t02>
<title>A Second Secondary Title</title>
<note>Note</note>
<t03>
<title>Third Level Title</title>
</t03>
<t03>
<title>Title at Level Three</title>
</t03>
</t02>
</t01>
</root>
I'm using XSLT 2.0 and I'm getting hung up on applying for-each-group recursively. Thanks for your time & trouble.
With XSLT 2.0 I would strongly suggest to use the for-each-grouping together with a recursive function or template; below is a sample using a function:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:output indent="yes"/>
<xsl:function name="mf:group" as="element()*">
<xsl:param name="elements" as="element(row)*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$elements" group-starting-with="row[t0 = $level]">
<xsl:element name="t{format-number($level, '00')}">
<xsl:copy-of select="* except t0"/>
<xsl:sequence select="mf:group(current-group() except ., $level + 1)"/>
</xsl:element>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="root">
<xsl:copy>
<header>List</header>
<xsl:sequence select="mf:group(row, 1)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Please give this a try:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root">
<root>
<header>List</header>
<xsl:apply-templates select="row[t0 = 1]" />
</root>
</xsl:template>
<xsl:template match="row">
<xsl:variable name="level" select="t0" />
<xsl:element name="{concat('t0', $level)}">
<xsl:apply-templates select="title | note" />
<xsl:variable name="nextPeer"
select="following-sibling::row[t0 <= $level]" />
<xsl:variable name="children"
select="following-sibling::row[t0 = $level + 1][not($nextPeer)
or (count(preceding-sibling::row) < count($nextPeer[1]/preceding-sibling::row))]" />
<xsl:apply-templates select="$children" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Even with all the good tips on this site, I still have some trouble with my xslt. I'm pretty new to it. I have this source file:
<?xml version="1.0" encoding="utf-8"?>
<file>
<id>1</id>
<row type="A">
<name>ABC</name>
</row>
<row type="B">
<name>BCA</name>
</row>
<row type="A">
<name>CBA</name>
</row>
</file>
and I want to add an element and sort the rows on type, to get this result
<file>
<id>1</id>
<details>
<row type="A">
<name>ABC</name>
</row>
<row type="A">
<name>CBA</name>
</row>
<row type="B">
<name>BCA</name>
</row>
</details>
</file>
I'm able to sort the rows using this:
<xsl:template match="file">
<xsl:copy>
<xsl:apply-templates select="#*/row"/>
<xsl:apply-templates>
<xsl:sort select="#type" data-type="text"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
and I'm able to move the rows using this
<xsl:template match="file">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates select="*[not(name(.)='row')]" />
<details>
<xsl:apply-templates select="row" />
</details>
</xsl:copy>
</xsl:template>
but I'm not able to produce the correct answer when I try to combine them. Hopefully I understand more of XSLT when I see how things are combined. Since I'm creating a new element <details>, I think the sorting has to be done before the creation of the new <details> element. I have to use xslt 1.0.
Something like this seems to work:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="file">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="row[1]/preceding-sibling::*" />
<details>
<xsl:for-each select="row">
<xsl:sort select="#type" data-type="text"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</details>
<xsl:copy-of select="row[last()]/following-sibling::*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here is the result I got:
<?xml version="1.0" encoding="utf-8"?>
<file>
<id>1</id>
<details>
<row type="A">
<name>ABC</name>
</row>
<row type="A">
<name>CBA</name>
</row>
<row type="B">
<name>BCA</name>
</row>
</details>
</file>