Showing posts with label XSLT. Show all posts
Showing posts with label XSLT. Show all posts

Monday, January 20, 2014

How to remove trailing zeros when XSLT can't do it

We ran into a situation where we needed to search the document number in a database. To avoid misleads by leading zeros, we've always converted that string to numeric using number().
number($value)


But suddenly, a huge number appeared... and XSLT was representing it in scientific notation.

What other ways can leading zeros desappear? In XSLT 2.0 we can say "remove all zeros at beginning"
replace( $value, '^0*', '' )


In XSLT 1.0 it takes more. We needed a recursive template to take down 0's one by one.

<xsl:template name="remove-leading-zeros">
<xsl:template name="remove-leading-zeros">
</xsl:template></xsl:template>
<xsl:template name="remove-leading-zeros">
<xsl:param name="value"></xsl:param>
</xsl:template><xsl:template name="remove-leading-zeros">
</xsl:template>
<xsl:template name="remove-leading-zeros">
<xsl:choose>
    <xsl:when test="starts-with($value,'0')">
      <xsl:call-template name="remove-leading-zeros">
        <xsl:with-param name="value">select="substring-after($value,'0')"/></xsl:with-param></xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$value">
</xsl:value-of>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="remove-leading-zeros">
</xsl:template>

<xsl:param name="value"><xsl:choose><xsl:when test="starts-with($value,'0')"><xsl:call-template name="remove-leading-zeros"><xsl:with-param br="" name="value"> </xsl:with-param></xsl:call-template> </xsl:when></xsl:choose></xsl:param><xsl:otherwise><xsl:value-of select="$value"></xsl:value-of></xsl:otherwise>
There is a smaller solution. It's not that easy to understand, but it works.Instead of replace we could use
translate($value, '0', '')
, but this would simply remove the 0's everywhere. Good, but not what we planned.

Use it like this to find the first non 0.
substring(translate($value, '0', ''), 1, 1)


We can't do a substring-after or we'd lose that first non-zero, but we can see what is before it.
substring-before($value, substring(translate($value, '0', ''), 1, 1))


Then, get what is after the leading 0's.
substring-after($var, substring-before($value, substring(translate($value, '0', ''), 1, 1)))


Not that bad.

Friday, May 17, 2013

Replace a schema in a map: the easy way

We had a little problem on the first day in production of an EDI project, when our partner says they have to send their orders in EFACT_D96A_ORDERS_EAN008, instead of the EFACT_D93A_ORDERS_EAN007 we tested for months. Not a big stress. Those schemas are similar, so the change was easy.


To redo a map like this one and properly test it in the same day, was impossible. But the task was simplified in the fastest way. No XSLT knowledge needed, just basic XML and logic.

Supposing the change is in the input

1. Add the new schema or reference to the project.

2. Open the btm as xml.

3. Find and replace all references
3.1. Source Tree Root Node and Location

<SrcTree RootNode_Name="EFACT_D93A_ORDERS_EAN007">
<Reference Location="*.EFACT_D93A_ORDERS_EAN007" />
</SrcTree> 
3.2. Some on the Link elements (LinkFrom attribute).

<Link LinkID="9" LinkFrom="10" LinkTo="/*[local-name()='<Schema>']/*[local-name()='ORDERS01']/*[local-name()='E1EDK03']/*[local-name()='DATAHEADERREC']/*[local-name()='DOCNUM']" Label="" />
        <Link LinkID="10" LinkFrom="/*[local-name()='<Schema>']/*[local-name()='EFACT_D93A_ORDERS_EAN007']/*[local-name()='DTM']/*[local-name()='C507']/*[local-name()='C50702']" LinkTo="9" Label="" />
        <Link LinkID="11" LinkFrom="/*[local-name()='<Schema>']/*[local-name()='EFACT_D93A_ORDERS_EAN007']/*[local-name()='DTM']" LinkTo="11" Label="" />

4. Close the xml view and open the same .btm with the regular view.

4.1. If no Map Loading Errors popped up, it’s done.
4.2. Otherwise, keep following the procedure.

5. Copy all the errors (no Ctrl+A here, just the old mouse dragging).

(1) Link from Source Tree Node with XPath: '/*[local-name()='']/*[local-name()='EFACT_D96A_ORDERS_EAN008']/*[local-name()='LINLoop1']/*[local-name()='QTY_3']/*[local-name()='C186_3']/*[local-name()='C18601']' to Equal functoid (with FunctoidID: 30).
...

6. Close the errors screen but DO NOT save the map.

7. Locate the missing field and take note of the correct path
In this case there was no EFACT_D96A_ORDERS_EAN008/LINLoop1/QTY_3/C186_3/C18601 , but instead, EFACT_D96A_ORDERS_EAN008/LINLoop1/QTY_2/C186_2/C18601

8. Close the map (don’t save!) and open again with the xml view.

9. Replace those fields. [local -name()='QTY_3']/*[local-name()='C186_3'] to [local-name()='QTY_2']/*[local-name()='C186_2']. In case of doubt, use the FunctoidId or any additional info from the errors screen to locate the Link Id.

10. Repeat from step 4 until all the errors are gone.

Changes in Destination schema are in TrgTree instead of SrcTree, and LinkTo instead of LinkFrom.

Please note that this is only an advanced starting point. If the schema was replaced, probably it was because the previous one no longer supported the information needed. It’s very likely new fields are filled and some others have changed, including the ones used within the map. A new batch of tests will show it very quickly.


Extra: Two orchestrations sharing a receive port

If they enter by the same port, use the Message property to receive only one of them.
If they arrive by Direct Binding –independently of the port, from the MessageBox - use the Filter Expression.


Be careful with:

  • Typename
    Don’t give the same name to both. Not only it is unauthorized, but also is the name by which the orchestrations will be known after deployment and so should be different for whoever is managing.
  • Ports types
    The Orchestration 2 will try to create a port type that already exists. Delete the Operation on every port and configure to use the type from Orchestration 1. If the input message is different (source schema changed) the receive port will have a different type. The same goes for the output message (when the target schema changed) at the send port.
  • Tuesday, March 27, 2012

    Copy name and value with XSLT

    I run across a situation where I had to map a huge list of sibling nodes. I was required to send tuples of name (in upper case) and value. If they were few, I would use a Table Looping like always, but this time it was a lot of fields and I was feeling lazy. Here is the solution.

    <xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
    <xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />


                <xsl:for-each select="//Data/item/*">
                      <xsl:element name="ns0:GenericDataContainerEntry">
                            <xsl:element name="ns0:GenericDataContainerEntryIndex">
                                  <xsl:attribute name="value">
                                        <xsl:value-of select="./text()" />
                                  xsl:attribute>
                                  <xsl:attribute name="name">
                                        <xsl:value-of select="translate(name(.),$smallcase,$uppercase)" />
                                  xsl:attribute>
                            xsl:element>
                      xsl:element>
                xsl:for-each>
          xsl:for-each>
    xsl:element>


    Do to all nodes

    <xsl:for-each select="//Data/item/*">

    Copy value

    <xsl:value-of select="./text()" />

    Copy name

    <xsl:value-of select="name(.)" />


    Copy name in upper case in XSLT 1.0 (found it here)

    <xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
    <xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
    <xsl:value-of select="translate(name(.),$smallcase,$uppercase)" />
    
    

    Tuesday, April 20, 2010

    XSLT in Any situation

    XSLT: The Basics


    Here are some basics XSLT commands that will be useful for the next step.

    Setting a variable


    Variables can only be set once. For loops consider recursive functions (a.k.a. templates).
    MyVariable=5
    <xsl:variable name="MyVariable">5</xsl:variable>


    Reading a variable


    <xsl:value-of select="@MyVariable" />

    Read element


    Read an element called Element from a node Node2 inside a node Node1
    <xsl:value-of select="/Root/Node1/Node2/Element" />
    Remember to close the tag

    Setting a variable with the value of an element


    <xsl:variable name="ValueOfElement">
    <xsl:value-of select="/Root/Node1/Node2/Element" />
    </xsl:variable>


    Read attribute


    Read an attribute called Attribute from a node Node4 inside a node Node3
    <xsl:value-of select="/Root/Node3/Node4/@Attribute" />

    Setting a variable with the value of an attribute


    <xsl:variable name="ValueOfAttribute">
    <xsl:value-of select="/Root/Node3/Node4/@Attribute" />
    </xsl:variable>



    If condition


    Remember that XSLT is used to construct a XML document so the return of a value is not mandatory. The easiest way to think of it is like a print command, not like a function.
    if(MyVariable>10) print MyVariable (> is a reserved char so we use &gt; instead)
    <xsl:if test="@MyVariable &gt; 10">
    <xsl:value-of select="@MyVariable" />
    </xsl:if>


    When the comparition is not against variables or numbers (string comparition for instance) the method is very similar, just delimit the string with ' '.
    if(MyVariable="test") print "this is a test"
    <xsl:if test="@MyVariable='10'">
    this is a test
    </xsl:if>


    If, Elseif, Else


    Does not exist an equivalent approach. We use a kind of switch instead.

    if(MyVariable=10) print 1;
    else if (MyVariable=10) print 10;
    else print 100;


    <xsl:choose>
    <xsl:when test="@MyVariable &lt; 10">1</xsl:when> <!-- if --!>
    <xsl:when test="@MyVariable = 10">10</xsl:when> <!-- else if --!>
    <xsl:otherwise>100</xsl:otherwise> <!-- else --!>
    </xsl:choose>




    <Any> and <AnyAttribute>



    A schema can be created without knowing the name of fields to fill. The menu Add Element has the option <Any>, and the same applies for Add Attribute (<AnyAttribute>). Like this, you can add virtually any node or attribute even after compilation. Be sure to define 0 at minimum occurrencies or it will fail while you don't have values to fill.


    How to Map to <Any>


    Create a Script Functoid of type "Inline XSLT". On the code area write the new tag and the desired value.

    <NewField>
    42
    </NewField>


    <NewField>
    <xsl:value-of select="@MyVariable" />
    </NewField>


    <NewField>
    <xsl:choose>
    <xsl:when test="@MyVariable='X'">1</xsl:when>
    <xsl:otherwise>0</xsl:otherwise>
    </xsl:choose>
    </NewField>


    To give a namespace instead of writing directly the element, write the tag element:
    <xsl:element name="ns2:ElementName">

    and so on...

    How to Map to <AnyAttribute>


    Create a Script Functoid of type "Inline XSLT". On the code area write the new tag and the desired value between xsl:attribute tags.


    <xsl:attribute name="NewAttribute">
    <xsl:value-of select="$MyVariable" />
    </xsl:attribute>

    or any other case.

    The Scripting Fuctoid is not linked to source schema, but be careful on linking Scripting Functoid to the destination schema. Point to the desired parent node.