1

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.

3
  • What version of XSLT are you using? Commented Jul 30, 2015 at 7:04
  • You didn't state the version, so I have assume XSLT version 2. Commented Jul 30, 2015 at 8:50
  • @SeanB.Durkin The output schema is almost certainly FileMaker, so that means XSLT 1.0 (Xalan) only. Commented Jul 30, 2015 at 8:54

3 Answers 3

1

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>

Xsltransform.net Demo

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks that works great so far. I just realised I would also like to retrieve the value of the 'diagnose' variable to insert into another field (e.g. "diabetes_type2"). Do you know the syntax for referencing that, e.g. <xsl:value-of select="$diagnose"/>
0

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.

Comments

0

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>

Alternative version

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.