Reputation: 1029
I'm working with some very large XML files containing logging records. I have multiple XML files with varying parameters, though the <record>
, <date>
and <message>
tag are always present.
They look something like this (very simplified):
data1.xml:
<record>
<date>2018-10-01 00:00:00</date>
<message>data1</message>
<param key="Key1">Info</param>
<param key="Key2">Info</param>
<param key="Key3">Info</param>
</record>
<record>
<date>2018-10-02 00:00:00</date>
<message>data1</message>
<param key="Key1">Info</param>
<param key="Key2">Info</param>
<param key="Key3">Info</param>
</record>
data2.xml:
<record>
<date>2018-10-01 00:00:00</date>
<message>data2</message>
<param key="Key4">Info</param>
<param key="Key5">Info</param>
</record>
<record>
<date>2018-10-02 00:00:00</date>
<message>data2</message>
<param key="Key6">Info</param>
<param key="Key7">Info</param>
</record>
data3.xml:
<record>
<date>2018-10-01 00:00:00</date>
<message>data3</message>
<param key="Duration(h)">0:00:10</param>
<attribute1>Info</attribute1>
</record>
<record>
<date>2018-10-02 00:00:00</date>
<message>data3</message>
<param key="Duration(h)">0:01:30</param>
<attribute1>Info</attribute1>
</record>
I'm using XSLT to filter the records based on a number of variables, i.e. could be date & message, or message and key1, etc, etc.
Inside of the XSLT , all I need to do is filter out the desired records, and call <xsl:apply-imports>
to have a chain of other XSLT files that manage things like sorting the output, formatting and other things.
I'm currently filtering out the desired XML nodes as following:
For this example, lets assume I want the records with <message>data1</message>
and <message>data3</message>
, and edit their attributes to display them in a table nicely.
filter1.xsl:
<!-- ignores records not matched by another template, so in this case 'data2' -->
<xsl:template match="record" />
<!-- applies imports to 'wanted' data -->
<xsl:template match="record[message='data1']">
<xsl:apply-imports />
</xsl:template>
<!-- applies imports to 'wanted' data -->
<xsl:template match="record[message='data3']">
<xsl:apply-imports />
</xsl:template>
<!-- rename 'param' to match the attribute from 'data3.xml' -->
<xsl:template match="param[@key='key1']">
<xsl:copy>
<xsl:attribute>attribute1</xsl:attribute>
<xsl:value-of select="." />
</xsl:copy>
</xsl:template>
So far so good, now for my question.
Every record in 'data3.xml' marks the end of an operation, hence the 'duration' parameter. I want to duplicate every node to mark the start of this operation.
So for input:
<!-- marking end of operation -->
<record>
<date>2018-10-01 00:00:00</date>
<message>data3</message>
<param key="Duration(h)">0:00:10</param>
<attribute1>Info</attribute1>
</record>
I want output:
<!-- marking start of operation -->
<record>
<date>2018-09-30 23:59:50</date>
<message>data3</message>
<attribute1>Info</attribute1>
</record>
<!-- marking end of operation -->
<record>
<date>2018-10-01 00:00:00</date>
<message>data3</message>
<param key="Duration(h)">0:00:10</param>
<attribute1>Info</attribute1>
</record>
But I haven't found out how to duplicate an entire node and process both of them. Here's what I've tried so far:
Calling named template to create a new node based on existing nodes attributes, This did not insert any node at all:
<xsl:template match="record[message='data3']">
<xsl:call-template name="duplicateNode"></xsl:call-template>
<xsl:apply-imports />
</xsl:template>
<xsl:template name="duplicatePrintedNode">
<xsl:copy>
<record>
<date>2018-09-30 23:59:50</date>
<message>data3</message>
<attribute1>Info</attribute1>
</record>
</xsl:copy>
</xsl:template>
Copy entire node using <xsl:copy-of value"">
. This seemed to insert a node inside an attribute of the existing node I wanted to copy:
<xsl:template match="param[@key='Duration(h)']" mode="copy">
<record>
<xsl:copy>
<xsl:copy-of select="@*"/>
</xsl:copy>
</record>
</xsl:template>
So how can I duplicate a node, change the attributes and then apply templates and imports to both of them?
I've searched on SO and Google a lot but the keyword copy
seems to have multiple meanings withing the XSLT context. Any help will be appreciated.
Upvotes: 0
Views: 1087
Reputation: 1029
I've found a way to do it, I realize that the educational value of this is very slim because my scenario is so specific but I'll post it anyway. Also thanks to @michael.hor257k for trying to help.
The way I duplicated the specific node and applied imports to both is this:
filter1.xsl
<!-- ignores records not matched by another template, so in this case 'data2' -->
<xsl:template match="record" />
<!-- applies imports to 'wanted' data -->
<xsl:template match="record[message='data1']">
<xsl:apply-imports />
</xsl:template>
<!-- applies imports to 'wanted' data -->
<xsl:template match="record[message='data3']">
<xsl:call-template name="duplicateNode"></xsl:call-template>
</xsl:template>
<!-- rename 'param' to match the attribute from 'data3.xml' -->
<xsl:template match="param[@key='key1']">
<xsl:copy>
<xsl:attribute>attribute1</xsl:attribute>
<xsl:value-of select="." />
</xsl:copy>
</xsl:template>
<!-- here I edit attributes to my liking, and then simply apply imports again -->
<xsl:template name="duplicateNode">
<record>
<date>
<xsl:value-of select="./date" />
</date>
<message>data3 copy</message>
<attribute1>Info</attribute1>
</record>
<xsl:apply-imports />
</xsl:template>
Upvotes: 0
Reputation: 117140
Consider the following (much) simplified example:
XML
<input>
<record>
<date>2018-10-01 00:00:00</date>
<message>data3</message>
<param key="Duration(h)">0:00:10</param>
<attribute1>Info</attribute1>
</record>
</input>
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="/input">
<output>
<xsl:apply-templates/>
</output>
</xsl:template>
<xsl:template match="record[message='data3']">
<xsl:copy>
<date>new value</date>
<xsl:copy-of select="node()[not(self::date or self::param)]"/>
</xsl:copy>
<xsl:copy>
<xsl:copy-of select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<record>
<date>new value</date>
<message>data3</message>
<attribute1>Info</attribute1>
</record>
<record>
<date>2018-10-01 00:00:00</date>
<message>data3</message>
<param key="Duration(h)">0:00:10</param>
<attribute1>Info</attribute1>
</record>
</output>
Note: For the purpose of this demonstration, I have used xsl:copy-of
instead of xsl:apply-imports
- since we don't know what those imports are.
Upvotes: 1