Reputation: 4610
I'm new to XSLT and have managed to work with XML where the nodes are repeated for each row. I've been given some XML to import that has different elements for each patient record, for example:
<?xml version="1.0" encoding="utf-8"?>
<data>
<patient>
<link_id>123</link_id>
<diagnoses>
<diabetes_type2>
<diabetes_type2_active>True</diabetes_type2_active>
<diabetes_type2_description>diabetes mellitus</diabetes_type2_description>
<diabetes_type2_diagnosis_date>06051999</diabetes_type2_diagnosis_date>
</diabetes_type2>
</diagnoses>
</patient>
<patient>
<link_id>456</link_id>
<diagnoses>
<chd>
<chd_active>True</chd_active>
<chd_description>ischaemic heart disease</chd_description>
<chd_diagnosis_date>05071997</chd_diagnosis_date>
</chd>
<coad>
<coad_active>True</coad_active>
<coad_description>chronic obstructive airways disease</coad_description>
<coad_diagnosis_date>28011986</coad_diagnosis_date>
</coad>
<depression>
<depression_active>True</depression_active>
<depression_description>depression</depression_description>
<depression_diagnosis_date>28011986</depression_diagnosis_date>
</depression>
<myocardial_infarction>
<myocardial_infarction_active>True</myocardial_infarction_active>
<myocardial_infarction_description>myocardial infarction</myocardial_infarction_description>
<myocardial_infarction_diagnosis_date>05071997</myocardial_infarction_diagnosis_date>
</myocardial_infarction>
<osteoarthritis>
<osteoarthritis_active>True</osteoarthritis_active>
<osteoarthritis_description>osteoarthritis of the knee</osteoarthritis_description>
<osteoarthritis_diagnosis_date>28011986</osteoarthritis_diagnosis_date>
</osteoarthritis>
<stroke>
<stroke_active>True</stroke_active>
<stroke_description>cerebrovascular accident</stroke_description>
<stroke_diagnosis_date>01011996</stroke_diagnosis_date>
</stroke>
</diagnoses>
</patient>
</data>
I need to import the diagnoses values but I don't want to hard code all the hundreds of possible values that could appear. I was hoping there was a way I could dynamically reference these regardless of their element name. I would typically use something like this:
<xsl:for-each select="./data/patient/diagnoses">
<ROW MODID="" RECORDID="">
<COL>
<DATA>
<xsl:value-of select="../../link_id"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="./type"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="./description"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="./active"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="./diagnosis_date"/>
</DATA>
</COL>
</ROW>
</xsl:for-each>
but not sure how to modify this for the dynamic elements that I'm now working with.
Upvotes: 1
Views: 697
Reputation: 12729
How about .... (This is a better and simpler solution that the one accepted by the OP!)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output omit-xml-declaration="yes" indent="yes" encoding="UTF-8" />
<xsl:strip-space elements="*" />
<xsl:template match="data">
<TABLE>
<HEADER>
<COL><DATA>Link</DATA></COL>
<COL><DATA>Type</DATA></COL>
<COL><DATA>active</DATA></COL>
<COL><DATA>description</DATA></COL>
<COL><DATA>diagnosis_date</DATA></COL>
</HEADER>
<xsl:apply-templates select="patient/diagnoses/*" />
</TABLE>
</xsl:template>
<xsl:template match="diagnoses/*">
<ROW>
<COL><DATA><xsl:value-of select="../../link_id" /></DATA></COL>
<COL><DATA><xsl:value-of select="local-name()" /></DATA></COL>
<xsl:apply-templates />
</ROW>
</xsl:template>
<xsl:template match="*">
<COL><DATA><xsl:value-of select="." /></DATA></COL>
</xsl:template>
</xsl:stylesheet>
When applied to your provided input document, this transform yields output ...
<table>
<header>
<col>
<data>Link</data>
</col>
<col>
<data>Type</data>
</col>
<col>
<data>active</data>
</col>
<col>
<data>description</data>
</col>
<col>
<data>diagnosis_date</data>
</col>
</header>
<row>
<col>
<data>123</data>
</col>
<col>
<data>diabetes_type2</data>
</col>
<col>
<data>True</data>
</col>
<col>
<data>diabetes mellitus</data>
</col>
<col>
<data>06051999</data>
</col>
</row>
<row>
<col>
<data>456</data>
</col>
<col>
<data>chd</data>
</col>
<col>
<data>True</data>
</col>
<col>
<data>ischaemic heart disease</data>
</col>
<col>
<data>05071997</data>
</col>
</row>
<row>
<col>
<data>456</data>
</col>
<col>
<data>coad</data>
</col>
<col>
<data>True</data>
</col>
<col>
<data>chronic obstructive airways disease</data>
</col>
<col>
<data>28011986</data>
</col>
</row>
<row>
<col>
<data>456</data>
</col>
<col>
<data>depression</data>
</col>
<col>
<data>True</data>
</col>
<col>
<data>depression</data>
</col>
<col>
<data>28011986</data>
</col>
</row>
<row>
<col>
<data>456</data>
</col>
<col>
<data>myocardial_infarction</data>
</col>
<col>
<data>True</data>
</col>
<col>
<data>myocardial infarction</data>
</col>
<col>
<data>05071997</data>
</col>
</row>
<row>
<col>
<data>456</data>
</col>
<col>
<data>osteoarthritis</data>
</col>
<col>
<data>True</data>
</col>
<col>
<data>osteoarthritis of the knee</data>
</col>
<col>
<data>28011986</data>
</col>
</row>
<row>
<col>
<data>456</data>
</col>
<col>
<data>stroke</data>
</col>
<col>
<data>True</data>
</col>
<col>
<data>cerebrovascular accident</data>
</col>
<col>
<data>01011996</data>
</col>
</row>
</table>
In the above I have assumed that the active, description and diagnosis_date elements are fixed and in fixed order. If the input document can have a variable range of properties (like /data/patient/diagnoses/coad/coad_myproperty) or the ~_active, ~_description, ~_diagnosis_date properties are not in fixed order, then try this alternative (more dynamic) version ...
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output omit-xml-declaration="yes" indent="yes" encoding="UTF-8" />
<xsl:strip-space elements="*" />
<xsl:variable name="fields" select="
distinct-values(
/data/patient/diagnoses/*/*
[starts-with( local-name(), concat( local-name(..),'_'))]
/substring( local-name(), string-length( local-name(..))+2))"
/>
<xsl:template match="data">
<TABLE>
<HEADER>
<COL><DATA>Link</DATA></COL>
<COL><DATA>Type</DATA></COL>
<COL><DATA>active</DATA></COL>
<xsl:for-each select="$fields">
<COL><DATA><xsl:value-of select="." /></DATA></COL>
</xsl:for-each>
</HEADER>
<xsl:apply-templates select="patient/diagnoses/*" />
</TABLE>
</xsl:template>
<xsl:template match="diagnoses/*">
<ROW>
<COL><DATA><xsl:value-of select="../../link_id" /></DATA></COL>
<COL><DATA><xsl:value-of select="local-name()" /></DATA></COL>
<xsl:variable name="this" select="." as="element()" />
<xsl:variable name="disease" select="local-name()" />
<xsl:for-each select="for $f in $fields return concat($disease,'_',$f)">
<COL><DATA>
<xsl:value-of select="$this/*[local-name()=current()]" />
</DATA></COL>
</xsl:for-each>
</ROW>
</xsl:template>
</xsl:stylesheet>
Upvotes: 0
Reputation: 116992
If the diagnoses values are always given in a known order, you could do simply:
<xsl:template match="/data">
<xsl:for-each select="patient/diagnoses/*">
<ROW MODID="" RECORDID="">
<COL><DATA><xsl:value-of select="../../link_id"/></DATA></COL>
<COL><DATA><xsl:value-of select="name()"/></DATA></COL>
<xsl:for-each select="*">
<COL><DATA><xsl:value-of select="."/></DATA></COL>
</xsl:for-each>
</ROW>
</xsl:for-each>
</xsl:template>
Although I suspect you'd want to convert the date to your own date format, so perhaps:
<xsl:template match="/data">
<xsl:for-each select="patient/diagnoses/*">
<ROW MODID="" RECORDID="">
<COL><DATA><xsl:value-of select="../../link_id"/></DATA></COL>
<COL><DATA><xsl:value-of select="name()"/></DATA></COL>
<COL><DATA><xsl:value-of select="*[1]"/></DATA></COL>
<COL><DATA><xsl:value-of select="*[2]"/></DATA></COL>
<xsl:variable name="date" select="*[3]"/>
<COL><DATA>
<xsl:value-of select="substring($date, 1, 2)"/>
<xsl:text>/</xsl:text>
<xsl:value-of select="substring($date, 3, 2)"/>
<xsl:text>/</xsl:text>
<xsl:value-of select="substring($date, 5, 4)"/>
</DATA></COL>
</ROW>
</xsl:for-each>
</xsl:template>
assuming you want d/m/y
.
Upvotes: 0
Reputation: 89285
You can use *
to reference element of any name, and use name()
or local-name()
functions to get element name dynamically, for example :
<?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="/">
<TABLE>
<xsl:for-each select="./data/patient/diagnoses/*">
<xsl:variable name="diagnose" select="name()"/>
<ROW MODID="" RECORDID="">
<COL>
<DATA>
<xsl:value-of select="../../link_id"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="./*[name()=concat($diagnose, '_description')]"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="./*[name()=concat($diagnose, '_active')]"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="./*[name()=concat($diagnose, '_diagnosis_date')]"/>
</DATA>
</COL>
</ROW>
</xsl:for-each>
</TABLE>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1