Search code examples
xmlxsltxpathxsdinfopath

Is it possible to build an XML document from a list of XPath values and the XML XSD?


I have a list of name/value pairs that I would like to map into an XML document. My idea is this, assign each of the names an XPath like this:

Account_Number       = 4294587576-32      = /my:myFields/my:Customer/my:AccountNumber
Customer_Name        = John Smith         = /my:myFields/my:Customer/my:Name
Customer_Address     = Tampa, FL  33604   = /my:myFields/my:Customer/my:Address
Amount_Due           = 129.85             = /my:myFields/my:AmountDue/my:Amount
Days_Past_Due        = 54                 = /my:myFields/my:AmountDue/my:DaysPastDue

Now, shouldn't I be able to take this information along with a complete sample of the XML document or the XSD and build an XML document that looks something like this:

<my:myFields>
    <my:Customer>
        <my:Name>John Smith</my:Name>
        <my:AccountNumber>4294587576-32</my:AccountNumber>
        <my:Address>Tampa, FL  33604</my:Address>
    </my:Customer>
    <my:AmountDue>
        <my:DaysPastDue>54</my:DaysPastDue>
        <my:Amount>129.85</my:Amount>
    </my:AmountDue>
</my:myFields>

My question is specific to Microsoft InfoPath because I need to take the list of name/value pairs and build the XML data document for an InfoPath form. The technology that performs the translation does not have to be Microsoft. Java or C++ would be the best solution. Can this be done using an XSLT processor like Apache's Xalan?


Solution

  • Here is one suggestion, as you mention Java and XSLT I wouldn't bother with Xalan and XSLT 1.0 but instead use Saxon 9 and XSLT 2.0. Assuming you have an well-formed XML input sample and your above mapping of values to XPath expressions I would feed the mapping to an XSLT 2.0 stylesheet that generates a second stylesheet that can then be applied to the input sample. So assuming we have the file 'test2011101201.txt' as

    Account_Number       = 4294587576-32      = /my:myFields/my:Customer/my:AccountNumber
    Customer_Name        = John Smith         = /my:myFields/my:Customer/my:Name
    Customer_Address     = Tampa, FL  33604   = /my:myFields/my:Customer/my:Address
    Amount_Due           = 129.85             = /my:myFields/my:AmountDue/my:Amount
    Days_Past_Due        = 54                 = /my:myFields/my:AmountDue/my:DaysPastDue
    

    the stylesheet

    <xsl:stylesheet
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:axsl="http://www.w3.org/1999/XSL/Transforma"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:my="http://example.com/my"
      version="2.0"
      exclude-result-prefixes="xs">
    
      <xsl:param name="text-file" as="xs:string" select="'test2011101201.txt'"/>
      <xsl:variable name="lines" as="xs:string*" select="tokenize(unparsed-text($text-file), '\r?\n')[normalize-space()]"/>
    
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>
    
      <xsl:template match="/">
        <axsl:stylesheet version="2.0">
          <axsl:template match="@* | node()">
            <axsl:copy>
              <axsl:apply-templates select="@*, node()"/>
            </axsl:copy>
          </axsl:template>
          <xsl:for-each select="$lines">
            <xsl:variable name="tokens" select="tokenize(., '=')"/>
            <axsl:template match="{normalize-space($tokens[3])}">
              <axsl:copy>
                <axsl:apply-templates select="@*"/>
                <axsl:text>
                  <xsl:value-of select="replace($tokens[2], '(^\s+|\s+$)', '')"/>
                </axsl:text>
              </axsl:copy>
            </axsl:template>
          </xsl:for-each>
        </axsl:stylesheet>
      </xsl:template>
    
    </xsl:stylesheet>
    

    can be run to produce a second stylesheet

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:my="http://example.com/my"
                    version="2.0">
       <xsl:template match="@* | node()">
          <xsl:copy>
             <xsl:apply-templates select="@*, node()"/>
          </xsl:copy>
       </xsl:template>
       <xsl:template match="/my:myFields/my:Customer/my:AccountNumber">
          <xsl:copy>
             <xsl:apply-templates select="@*"/>
             <xsl:text>4294587576-32</xsl:text>
          </xsl:copy>
       </xsl:template>
       <xsl:template match="/my:myFields/my:Customer/my:Name">
          <xsl:copy>
             <xsl:apply-templates select="@*"/>
             <xsl:text>John Smith</xsl:text>
          </xsl:copy>
       </xsl:template>
       <xsl:template match="/my:myFields/my:Customer/my:Address">
          <xsl:copy>
             <xsl:apply-templates select="@*"/>
             <xsl:text>Tampa, FL  33604</xsl:text>
          </xsl:copy>
       </xsl:template>
       <xsl:template match="/my:myFields/my:AmountDue/my:Amount">
          <xsl:copy>
             <xsl:apply-templates select="@*"/>
             <xsl:text>129.85</xsl:text>
          </xsl:copy>
       </xsl:template>
       <xsl:template match="/my:myFields/my:AmountDue/my:DaysPastDue">
          <xsl:copy>
             <xsl:apply-templates select="@*"/>
             <xsl:text>54</xsl:text>
          </xsl:copy>
       </xsl:template>
    </xsl:stylesheet>
    

    which, when applied to an input sample like

    <my:myFields xmlns:my="http://example.com/my">
        <my:Customer>
            <my:Name></my:Name>
            <my:AccountNumber></my:AccountNumber>
            <my:Address></my:Address>
        </my:Customer>
        <my:AmountDue>
            <my:DaysPastDue></my:DaysPastDue>
            <my:Amount></my:Amount>
        </my:AmountDue>
    </my:myFields>
    

    outputs

    <?xml version="1.0" encoding="UTF-8"?><my:myFields xmlns:my="http://example.com/my">
        <my:Customer>
            <my:Name>John Smith</my:Name>
            <my:AccountNumber>4294587576-32</my:AccountNumber>
            <my:Address>Tampa, FL  33604</my:Address>
        </my:Customer>
        <my:AmountDue>
            <my:DaysPastDue>54</my:DaysPastDue>
            <my:Amount>129.85</my:Amount>
        </my:AmountDue>
    </my:myFields>
    

    As I said, that requires XSLT 2.0 and an XSLT 2.0 processor like Saxon 9 to parse the text file. And whoever authors that text file needs to make sure the path expressions that the stylesheet maps into XSLT match patterns are not ambiguous.