1

I have several information entries that I want to separate with a comma. However, each entry could be empty, and if the first-appearing entry is empty, then comma should not appear. For example:

if we have four XSLT parameters: name, phone number, address, occupation

and we have

  • Name: John
  • Phone number: 111-111-1111
  • Address: Imaginary street
  • Occupation: Baker

Then final string should be:

John, 111-111-1111, Imaginary street, Baker

if the name and phone number parameters were empty or null, then final string should be:

Imaginary street, Baker

if only phone number null or empty, then final string should be:

John, Imaginary street, Baker

In a language like C#, I would write the code like this:

foreach (EntryObject entry in entryList)
{
    if (firstEntry == true && entry.Type != EntryType.Age && entry.Type != EntryType.Sex)
    {
        finalString += entry.ValueString;
        firstEntry = false;
    }
    else if (firstEntry == false && entry.Type != EntryType.Age && entry.Type != EntryType.Sex)
    { 
        finalString += ", " + entry.ValueString;
    }
}
return finalString;

However, I heard that variables in XSLT are immutable. How should I approach this problem in XSLT?

Edit: The xml entry would look something like this:

<AddressBook>
    <PersonalInfo>
        <Age>33</Age>
        <Sex>Male</Sex>
        <Name>John</Name>
        <PhoneNumber></PhoneNumber>
        <Address>Imaginary Street</Address>
        <Occupation>Baker</Occupation>
    </PersonalInfo>
</AddressBook>

Note that certain entries could be empty, and I will only use name, phonenumber, address and occupation. Age and Sex should be ignored.

7
  • What does your XML input look like? Commented Nov 26, 2012 at 21:28
  • Is this XSLT 1.0 or XSLT 2.0? Commented Nov 26, 2012 at 22:22
  • @MatthewGreen I edited my original post to include an example. Commented Nov 26, 2012 at 22:53
  • @MichaelKay I am using XSLT 1.1. (I'm new to XSLT, but I am assuming this from the entry <xsl:stylesheet version="1.1" ...> Commented Nov 26, 2012 at 22:55
  • 1
    @J.L, there's no such thing as XSLT 1.1; it refers to a working draft that was abandoned in 2001. Unfortunately one edition of my book made extensive reference to it and was published just about when it was abandoned, so readers of the book have a habit of using "1.1" not realizing. So the stylesheet might be a 1.0 stylesheet or a 2.0 stylesheet. The two versions are VERY different. Commented Nov 27, 2012 at 15:28

2 Answers 2

2

Use an XPath condition that matches only the non-empy elements (string-length(.)>0 or simply string(.)), and then use the position() function to check if an element is the first or not. Input XML:

<root>
  <item>
    <name>John</name>
    <phoneNumber>111-111-1111</phoneNumber>
    <address>Imaginary street</address>
    <occupation>Baker</occupation>
  </item>
  <item>
    <name>Jane</name>
    <address>Another street</address>
    <occupation>Decorator</occupation>
  </item>
  <item>
    <address>Unknown</address>
  </item>
</root>

XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="item">
    <textItem>
      <xsl:for-each select="*[string(.)]">
        <xsl:if test="position()>1">
          <xsl:text>,</xsl:text>
        </xsl:if>
        <xsl:value-of select="."/>
      </xsl:for-each>
    </textItem>
  </xsl:template>

  <xsl:template match="text()">
  </xsl:template>

  <xsl:template match="root">
    <root>
      <xsl:apply-templates/>
    </root>
  </xsl:template>

</xsl:stylesheet>

where the first template is the one actually doing the work, <xsl:template match="text()"> removes the text contained in unmatched elements (by default XSLT processor would copy such text to the output) and <xsl:template match="root"> generates the root element of the output document.

Result:

<root>
  <textItem>John,111-111-1111,Imaginary street,Baker</textItem>
  <textItem>Jane,Another street,Decorator</textItem>
  <textItem>Unknown</textItem>
</root>

If you are interested in only some of the fields you just select them using the union operator (|) - e.g. if you want only phone, address and occupation in the example above modify the XSLT to be:

  <xsl:template match="item">
    <textItem>
      <xsl:for-each select="(phoneNumber|address|occupation)[string(.)]">
        <xsl:if test="position()>1">
          <xsl:text>,</xsl:text>
        </xsl:if>
        <xsl:value-of select="."/>
      </xsl:for-each>
    </textItem>
  </xsl:template>

  <xsl:template match="text()">
  </xsl:template>

  <xsl:template match="root">
    <root>
      <xsl:apply-templates/>
    </root>
  </xsl:template>

</xsl:stylesheet>
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for your answer, but after inspecting the xml given to me, I realized that there are other elements that I don't use under that element - for example under <item> there would be age, sex, name, phone number, address and occupation and I will only use name, phone number, address and occupation - so this strategy of checking position()>1 wouldn't work. Unfortunately I have no control over the given xml.
what are <xsl:template match="text()"> and <xsl:template match="root"> for?
Also, can I use this code under a fo:block? ex) <fo:block ...> <xsl:template match="item">...... </xsl:template> </fo:block ...>
See additional explanations in the answer concerning <xsl:template match="text()"> and <xsl:template match="root">. I don't understand the question about <fo:block> - I suggest you create a new question for that.
2

Here is a generic solution that accepts the list of "black-listed" element names as a parameter:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pIgnoreThese" select="' Age Sex '"/>

 <xsl:template match="PersonalInfo">
  <xsl:apply-templates select=
   "*[normalize-space()
    and
      not(contains($pIgnoreThese, concat(' ', name(), ' ')))
     ]"/>
   <xsl:text>&#xA;</xsl:text>
 </xsl:template>

 <xsl:template match="PersonalInfo/*">
  <xsl:if test="position() >1">, </xsl:if>
  <xsl:value-of select="."/>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<AddressBook>
        <PersonalInfo>
            <Age>33</Age>
            <Sex>Male</Sex>
            <Name>John</Name>
            <PhoneNumber>111-111-1111</PhoneNumber>
            <Address>Imaginary Street</Address>
            <Occupation>Baker</Occupation>
        </PersonalInfo>
        <PersonalInfo>
            <Age>33</Age>
            <Sex>Male</Sex>
            <Name></Name>
            <PhoneNumber></PhoneNumber>
            <Address>Imaginary Street</Address>
            <Occupation>Baker</Occupation>
        </PersonalInfo>
        <PersonalInfo>
            <Age>33</Age>
            <Sex>Male</Sex>
            <Name>John</Name>
            <PhoneNumber></PhoneNumber>
            <Address>Imaginary Street</Address>
            <Occupation>Baker</Occupation>
        </PersonalInfo>
</AddressBook>

the wanted, correct result is produced:

John, 111-111-1111, Imaginary Street, Baker
Imaginary Street, Baker
John, Imaginary Street, Baker

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.