Teun van der Wijst
Teun van der Wijst

Reputation: 1029

XSLT copy node and alter attributes

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

Answers (2)

Teun van der Wijst
Teun van der Wijst

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

michael.hor257k
michael.hor257k

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

Related Questions