XML: Schema and XSLT Transformations with a Complex Source
This was completed as a project for an XML course. The source file is relatively complex with many nested child nodes and attributes of different data types. It is meant to simulate data for a student exchange program. A schema and two transformations were designed for this source. The first transformation produces a styled HTML summary of the source. The second transformation produces formatted plain text. The links above will open the output for both transforms.
Show project.xml - The source document.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <?xml-stylesheet type="text/xsl" href="project.xsl" ?> <!-- Author: Brian Manning Project: XML Project File References: project.xsd , project.xsl --> <exchanges xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="project.xsd"> <exchange type="S" exchangeId="E00001"> <name> <lastName>Moon</lastName> <firstName>Scott</firstName> <middleInitial>B</middleInitial> </name> <address> <street>390 Pickford Rd.</street> <apartment></apartment> <city>Madison</city> <state>WI</state> <zip>53703</zip> <country>United States</country> </address> <telephone>(608) 555-5555</telephone> <email>smoon@madison.edu</email> <applicationDate> <month>01</month> <day>15</day> <year>2014</year> </applicationDate> <availableDate> <month>02</month> <day>01</day> <year>2014</year> </availableDate> <birthCountry>United States</birthCountry> <dateOfBirth> <month>09</month> <day>18</day> <year>1983</year> </dateOfBirth> <age>30</age> <gender>M</gender> <schoolName>Madison Area Technical College</schoolName> <schoolAddress> <street>1701 Wright Street</street> <apartment></apartment> <city>Madison</city> <state>WI</state> <zip>53704</zip> <country>United States</country> </schoolAddress> <schoolYear>Junior</schoolYear> <motherName> <lastName>Moon</lastName> <firstName>Carol</firstName> <middleInitial>L</middleInitial> </motherName> <fatherName> <lastName>Moon</lastName> <firstName>Marvin</firstName> <middleInitial>J</middleInitial> </fatherName> <emergencyPhone>(608) 555-5555</emergencyPhone> <feePaid currency="USD">2129.86</feePaid> <essay> <![CDATA[ I'd like to study abroad so I can learn more about another culture. ]]> </essay> <languagesSpoken fluency="2">Spanish</languagesSpoken> <languagesSpoken fluency="4">English</languagesSpoken> <countryChoices preference="4">Brazil</countryChoices> <countryChoices preference="1">Italy</countryChoices> <countryChoices preference="2">New Zealand</countryChoices> <countryChoices preference="5">Ecuador</countryChoices> <countryChoices preference="3">Ireland</countryChoices> </exchange> <exchange type="T" exchangeId="E00002"> <name> <lastName>Holland</lastName> <firstName>Sam</firstName> <middleInitial>R</middleInitial> </name> <address> <street>7709 Heath Rd.</street> <apartment></apartment> <city>Madison</city> <state>WI</state> <zip>53704</zip> <country>United States</country> </address> <telephone>(608) 555-5555</telephone> <email>sholland@madison.edu</email> <applicationDate> <month>01</month> <day>29</day> <year>2014</year> </applicationDate> <availableDate> <month>02</month> <day>08</day> <year>2014</year> </availableDate> <birthCountry>United States</birthCountry> <gender>M</gender> <schoolName>Madison College</schoolName> <schoolAddress> <street>1701 Wright Street</street> <apartment></apartment> <city>Madison</city> <state>WI</state> <zip>53704</zip> <country>United States</country> </schoolAddress> <schoolYear></schoolYear> <motherName> <lastName></lastName> <firstName></firstName> <middleInitial></middleInitial> </motherName> <fatherName> <lastName></lastName> <firstName></firstName> <middleInitial></middleInitial> </fatherName> <emergencyPhone>(608) 555-5555</emergencyPhone> <feePaid currency="USD">875.86</feePaid> <essay> <![CDATA[ I'd like the opportunity to teach overseas. ]]> </essay> <languagesSpoken fluency="4">English</languagesSpoken> <languagesSpoken fluency="4">Spanish</languagesSpoken> <languagesSpoken fluency="3">Japanese</languagesSpoken> <countryChoices preference="3">Brazil</countryChoices> <countryChoices preference="4">Costa Rica</countryChoices> <countryChoices preference="2">Korea</countryChoices> <countryChoices preference="5">Mexico</countryChoices> <countryChoices preference="1">Japan</countryChoices> </exchange> <exchange type="S" exchangeId="E00003"> <name> <lastName>Patterson</lastName> <firstName>Katherine</firstName> <middleInitial></middleInitial> </name> <address> <street>429 Aldine Avenue</street> <apartment>510</apartment> <city>Chicago</city> <state>IL</state> <zip>60657</zip> <country>United States</country> </address> <telephone>(312) 555-5555</telephone> <email>kpatterson@chicago.edu</email> <applicationDate> <month>09</month> <day>04</day> <year>2013</year> </applicationDate> <availableDate> <month>02</month> <day>21</day> <year>2014</year> </availableDate> <birthCountry>United States</birthCountry> <dateOfBirth> <month>03</month> <day>10</day> <year>1990</year> </dateOfBirth> <age>23</age> <gender>F</gender> <schoolName>University of Chicago</schoolName> <schoolAddress> <street>5801 South Ellis Avenue</street> <apartment></apartment> <city>Chicago</city> <state>IL</state> <zip>60637</zip> <country>United States</country> </schoolAddress> <schoolYear>Senior</schoolYear> <motherName> <lastName>Patterson</lastName> <firstName>Ruth</firstName> <middleInitial>M</middleInitial> </motherName> <fatherName> <lastName>Patterson</lastName> <firstName>Eric</firstName> <middleInitial>R</middleInitial> </fatherName> <emergencyPhone>(773) 555-5555</emergencyPhone> <feePaid currency="USD">1889.29</feePaid> <essay> <![CDATA[ I'd like to study experience day-to-day life n a different country. ]]> </essay> <languagesSpoken fluency="3">French</languagesSpoken> <languagesSpoken fluency="4">English</languagesSpoken> <countryChoices preference="4">Italy</countryChoices> <countryChoices preference="3">Japan</countryChoices> <countryChoices preference="1">France</countryChoices> <countryChoices preference="2">India</countryChoices> <countryChoices preference="5">Argentina</countryChoices> </exchange> <exchange type="S" exchangeId="E00004"> <name> <lastName>Mason-Rivera-La Follette</lastName> <firstName>Laura</firstName> <middleInitial>MKG</middleInitial> </name> <address> <street>4811 Park Street</street> <apartment>219</apartment> <city>Stockholm</city> <province>Gotland</province> <postalCode>A49896</postalCode> <country>Sweden</country> </address> <telephone>(749) 555-5555</telephone> <email>lmkgmasonrivera@stockholm.edu</email> <applicationDate> <month>12</month> <day>04</day> <year>2013</year> </applicationDate> <availableDate> <month>01</month> <day>01</day> <year>2014</year> </availableDate> <birthCountry>Sweden</birthCountry> <dateOfBirth> <month>09</month> <day>08</day> <year>1993</year> </dateOfBirth> <age>20</age> <gender>F</gender> <schoolName>University of Stockholm</schoolName> <schoolAddress> <street>896 Campus Street</street> <apartment></apartment> <city>Stockholm</city> <province>Gotland</province> <postalCode>A49896</postalCode> <country>Sweden</country> </schoolAddress> <schoolYear>Sophmore</schoolYear> <motherName> <lastName>Mason-Rivera-La Follette</lastName> <firstName>Maria</firstName> <middleInitial></middleInitial> </motherName> <fatherName> <lastName>Mason-Rivera-La Follette</lastName> <firstName>Klaus</firstName> <middleInitial>D</middleInitial> </fatherName> <emergencyPhone>(749) 555-5555</emergencyPhone> <feePaid currency="EUR">1450.00</feePaid> <essay> <![CDATA[ I'd like to study in a country where I can improve my language skills. ]]> </essay> <languagesSpoken fluency="1">French</languagesSpoken> <languagesSpoken fluency="4">Swedish</languagesSpoken> <languagesSpoken fluency="3">English</languagesSpoken> <countryChoices preference="1">United States</countryChoices> <countryChoices preference="3">Japan</countryChoices> <countryChoices preference="4">France</countryChoices> <countryChoices preference="5">Sumatra</countryChoices> <countryChoices preference="2">Canada</countryChoices> </exchange> </exchanges>
To validate the source a schema was required. The schema is relatively detailed with a lot of emphasis on required child elements and number of occurrences. For example, address elements must have one child element named either 'zip' or 'postalCode' each with it's own specific format. There must be a minimum of 2 'languagesSpoken' elements with no upper bound. Each 'languagesSpoken' element must have a 'fluency' attribute from 1 to 4. That's just a few of the details. Take a look at the code if you'd like to see more.
Show project.xsd - The schema for project.xml.
<?xml version="1.0" encoding="UTF-8" ?> <!-- Brian Manning Project 3 Schema --> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="exchanges"> <xs:complexType> <xs:sequence> <xs:element name="exchange" type="exchangeType" minOccurs="4" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="exchangeType"> <xs:group ref="exchangeElements" /> <xs:attributeGroup ref="exchangeAttr"/> </xs:complexType> <xs:attributeGroup name="exchangeAttr"> <xs:attribute name="type" use="required"> <xs:simpleType><xs:restriction base="xs:token"> <xs:enumeration value="S"/> <xs:enumeration value="T"/> </xs:restriction></xs:simpleType> </xs:attribute> <xs:attribute name="exchangeId" type="xs:ID" use="required" /> </xs:attributeGroup> <xs:group name="exchangeElements"> <xs:sequence> <xs:element name="name" type="nameType" /> <xs:element name="address" type="addressType" /> <xs:element name="telephone" type="phoneType" /> <xs:element name="email" type="xs:string" /> <xs:element name="applicationDate" type="dateType" /> <xs:element name="availableDate" type="dateType" /> <xs:element name="birthCountry" type="alphaType" /> <xs:element name="dateOfBirth" type="dateType" minOccurs="0" /> <xs:element name="age" minOccurs="0"> <xs:simpleType><xs:restriction base="xs:positiveInteger"> <xs:minInclusive value="13"/> </xs:restriction></xs:simpleType> </xs:element> <xs:element name="gender"> <xs:simpleType><xs:restriction base="xs:token"> <xs:enumeration value="M"/> <xs:enumeration value="F"/> </xs:restriction></xs:simpleType> </xs:element> <xs:element name="schoolName" type="alphaType" /> <xs:element name="schoolAddress" type="addressType" /> <xs:element name="schoolYear" type="xs:string" minOccurs="0" /> <xs:element name="motherName" type="nameType" minOccurs="0" /> <xs:element name="fatherName" type="nameType" minOccurs="0" /> <xs:element name="emergencyPhone" type="phoneType" /> <xs:element name="feePaid"> <xs:complexType><xs:simpleContent><xs:extension base="xs:decimal"> <xs:attribute name="currency" use="required"> <xs:simpleType><xs:restriction base="xs:string"> <xs:enumeration value="USD"/> <xs:enumeration value="EUR"/> <xs:enumeration value="JPY"/> <xs:enumeration value="MXN"/> <xs:enumeration value="CNY"/> </xs:restriction></xs:simpleType> </xs:attribute> </xs:extension></xs:simpleContent></xs:complexType> </xs:element> <xs:element name="essay"> <xs:simpleType><xs:restriction base="xs:string"> <xs:maxLength value="1000"/> </xs:restriction></xs:simpleType> </xs:element> <xs:element name="languagesSpoken" minOccurs="2" maxOccurs="unbounded"> <xs:complexType><xs:simpleContent><xs:extension base="xs:string"> <xs:attribute name="fluency" use="required"> <xs:simpleType><xs:restriction base="xs:positiveInteger"> <xs:pattern value="[1-4]"/> </xs:restriction></xs:simpleType> </xs:attribute> </xs:extension></xs:simpleContent></xs:complexType> </xs:element> <xs:element name="countryChoices" minOccurs="5" maxOccurs="5"> <xs:complexType><xs:simpleContent><xs:extension base="xs:string"> <xs:attribute name="preference" use="required"> <xs:simpleType><xs:restriction base="xs:positiveInteger"> <xs:pattern value="[1-5]"/> </xs:restriction></xs:simpleType> </xs:attribute> </xs:extension></xs:simpleContent></xs:complexType> </xs:element> </xs:sequence> </xs:group> <xs:complexType name="nameType"> <xs:sequence> <xs:element name="lastName" type="alphaType" /> <xs:element name="firstName" type="alphaType" /> <xs:element name="middleInitial" type="alphaType" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:simpleType name="alphaType"> <xs:restriction base="xs:token"> <xs:pattern value="[a-zA-Z -\.]*"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="phoneType"> <xs:restriction base="xs:string"> <xs:pattern value="\(\d{3}\) \d{3}-\d{4}"/> </xs:restriction> </xs:simpleType> <xs:complexType name="addressType"> <xs:sequence> <xs:element name="street" type="xs:string" /> <xs:element name="apartment" type="xs:string" minOccurs="0" /> <xs:element name="city" type="xs:string" /> <xs:choice> <xs:element name="state" type="alphaType" /> <xs:element name="province" type="alphaType" /> </xs:choice> <xs:choice> <xs:element name="zip"> <xs:simpleType><xs:restriction base="xs:string"> <xs:pattern value="\d{5}(-\d{4})?"/> </xs:restriction></xs:simpleType> </xs:element> <xs:element name="postalCode"> <xs:simpleType><xs:restriction base="xs:string"> <xs:maxLength value="12"/> </xs:restriction></xs:simpleType> </xs:element> </xs:choice> <xs:element name="country" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:complexType name="dateType"> <xs:sequence> <xs:element name="month"> <xs:simpleType><xs:restriction base="xs:positiveInteger"> <xs:minInclusive value="1" /> <xs:maxInclusive value="12" /> </xs:restriction></xs:simpleType> </xs:element> <xs:element name="day"> <xs:simpleType><xs:restriction base="xs:positiveInteger"> <xs:minInclusive value="1" /> <xs:maxInclusive value="31" /> </xs:restriction></xs:simpleType> </xs:element> <xs:element name="year"> <xs:simpleType><xs:restriction base="xs:positiveInteger"> <xs:minInclusive value="1900" /> <xs:maxInclusive value="2100" /> </xs:restriction></xs:simpleType> </xs:element> </xs:sequence> </xs:complexType> </xs:schema>
The HTML transformation does a couple of interesting things. It sorts and displays a concatenated name for each exchange element. For each exchange it lists the country choice with a priority of 1. The type attribute for each exchange is checked and the string 'Teacher' or 'Student' is substituted based on the value. At the bottom the count() function is used to tally the total number of teachers, students, and the combined total of both. The internal CSS was required for the project specific validation tool for the course.
Show project.xsl - The HTML transformation for project.xml.
<?xml version="1.0" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" version="4.0" encoding="UTF-8" /> <!-- Author: Brian Manning Project: XML Project --> <xsl:template match="/"> <html> <head> <title>Exchange Information Project</title> <style> * {color: blue;font-family: arial;} table {border: 1px solid black;border-collapse: collapse; border-spacing: 0;width: 100%;padding: .5em;margin-bottom: .5em;} td {border-style: none;padding: .5em;width: 25%} .exchangeHeader, .exchangeHeader * {color: yellow;background-color: black;} .center {text-align:center} .redtext {color:red;} </style> </head> <body> <table> <tr class="exchangeHeader"> <td colspan="3" class="center"><strong>Exchange Information</strong></td> </tr> <tr class="exchangeHeader"> <td><strong>Exchange Type</strong></td> <td><strong>Name (last, first, middle)</strong></td> <td><strong>Country Choice</strong></td> </tr> <xsl:apply-templates select="exchanges/exchange"> <xsl:sort select="name/lastName" data-type="text" /> <xsl:sort select="name/firstName" data-type="text" /> <xsl:sort select="name/middleInitial" data-type="text" /> </xsl:apply-templates> <tr><td colspan="3"> </td></tr> <tr><td colspan="3"> </td></tr> <tr> <td colspan="2" class="redtext">The number of people applying for an exchange is:</td> <td class="redtext"><xsl:value-of select="count(exchanges/exchange)"/></td> </tr> <tr> <td colspan="2">The number of teachers applying for an exchange is:</td> <td><xsl:value-of select="count(exchanges/exchange[@type='T'])"/></td> </tr> <tr> <td colspan="2">The number of students applying for an exchange is:</td> <td><xsl:value-of select="count(exchanges/exchange[@type='S'])"/></td> </tr> </table> </body> </html> </xsl:template> <xsl:template match="exchanges/exchange"> <tr> <td><xsl:choose> <xsl:when test="@type='T'">Teacher</xsl:when> <xsl:otherwise>Student</xsl:otherwise> </xsl:choose> </td> <td><xsl:value-of select="concat( name/lastName,', ', name/firstName,', ', name/middleInitial)" /> </td> <td> <xsl:value-of select="countryChoices[@preference=1]" /> </td> </tr> </xsl:template> </xsl:stylesheet>
The text transformation had to follow a very specific format. This was meant to simulate preparation of the data for use by some other process. Most numerical values are padded with zeros to the specified length. String values are padded with spaces. You'll see a template named 'pad_value' starting on line 145. It accepts 2 arguments; the value and desired length. This template is used to pad all string values with spaces and trim them to the specified length.
Show projectText.xsl - The text transformation for project.xml.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="text" encoding="UTF-8" /> <!-- Author: Brian Manning Project: XML Project --> <xsl:variable name="spaces" select="' '"/> <xsl:template match="/"> <xsl:apply-templates select="exchanges/exchange"> </xsl:apply-templates> </xsl:template> <xsl:template match="exchanges/exchange"> <xsl:value-of select="format-number(substring(@exchangeId,2),'00000000')" /> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="name/lastName"/> <xsl:with-param name="padding" select="20"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="name/firstName"/> <xsl:with-param name="padding" select="15"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="name/middleInitial"/> <xsl:with-param name="padding" select="1"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="address/street"/> <xsl:with-param name="padding" select="25"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="address/apartment"/> <xsl:with-param name="padding" select="12"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="address/city"/> <xsl:with-param name="padding" select="35"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="address/state|address/province"/> <xsl:with-param name="padding" select="15"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="address/zip|address/postalCode"/> <xsl:with-param name="padding" select="10"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="address/country"/> <xsl:with-param name="padding" select="30"/> </xsl:call-template> <xsl:value-of select="translate(telephone,'() -','')" /> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="email"/> <xsl:with-param name="padding" select="30"/> </xsl:call-template> <xsl:value-of select="concat( format-number(applicationDate/month,'00'), format-number(applicationDate/day,'00'), format-number(applicationDate/year,'0000'))" /> <xsl:value-of select="concat( format-number(availableDate/month,'00'), format-number(availableDate/day,'00'), format-number(availableDate/year,'0000'))" /> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="translate(concat( format-number(dateOfBirth/month,'00'), format-number(dateOfBirth/day,'00'), format-number(dateOfBirth/year,'0000')) ,'NaN','')"/> <xsl:with-param name="padding" select="8"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="birthCountry"/> <xsl:with-param name="padding" select="30"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="translate(format-number(age,'00'),'NaN','000')"/> <xsl:with-param name="padding" select="2"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="gender"/> <xsl:with-param name="padding" select="1"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="schoolName"/> <xsl:with-param name="padding" select="35"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="schoolYear"/> <xsl:with-param name="padding" select="9"/> </xsl:call-template> <xsl:value-of select="translate(emergencyPhone,'() -','')" /> <xsl:value-of select="format-number(feePaid,'00000000')" /> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="feePaid/@currency"/> <xsl:with-param name="padding" select="3"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="concat( motherName/lastName,' ', motherName/firstName,' ', motherName/middleInitial )"/> <xsl:with-param name="padding" select="30"/> </xsl:call-template> <xsl:call-template name="pad_value"> <xsl:with-param name="value" select="concat( fatherName/lastName,' ', fatherName/firstName,' ', fatherName/middleInitial )"/> <xsl:with-param name="padding" select="30"/> </xsl:call-template> <xsl:text> </xsl:text> </xsl:template> <xsl:template name="pad_value"> <xsl:param name="value" /> <xsl:param name="padding" /> <xsl:value-of select="substring(concat($value,$spaces),1,$padding)"/> </xsl:template> </xsl:stylesheet>