显示标签为“xslt”的博文。显示所有博文
显示标签为“xslt”的博文。显示所有博文

2012-11-13

multi mapping and split message with XSLT in SAP PI

应用场景是主数据的复制,从SAP到Salesforce,即:
SAP --RFC--> PI --SOAP-->Salesforce

在调用Salesforce的upsert时,一次最多只能包含200个对象,所以当PI接收到超过200个对象的数据时,需要把一个message分成多个messages,即1 --> n。Operation Mapping包括三个步骤:
  1. message mapping,从源数据结构映射到Salesforce的upsert结构
  2. Java mapping,从value mapping中取出有效的sessionID和serverURL,在dynamic configuration里设置相应的值
  3. XSLT mapping,生成多个SOAP request,每个request里的upsert包括最多200个objects,其中要注意output的结构和正确的namespace
在第三步要注意ouput的正确结构为
<message>
 <message1>
  <soapenv:Envelope>
   ...
  </soapenv:Envelope>
  <soapenv:Envelope>
   ...
  </soapenv:Envelope>
  </message1>
<message>

 上面的例子会被adapter正确的解释成2个messages。

如果XSLT生成的结构像下面这样:

<message>
 <message1>
  <soapenv:Envelope>
  ...
  </soapenv:Envelope>
 </message1>
  <message1>
  <soapenv:Envelope>
   ...
  </soapenv:Envelope>
  </message1>
<message>

那么在monitoring里会得到下面的错误:

<SAP:Category>XIServer</SAP:Category>
  <SAP:Code area="MAPPING">MISSING_INTERFACE</SAP:Code>
  <SAP:P1>2</SAP:P1>
  <SAP:P2 />
  <SAP:P3 />
  <SAP:P4 />
  <SAP:AdditionalText />
  <SAP:Stack>No interface specified for parameter 2</SAP:Stack>


在测试时还需注意,如果是在Repository(design time)里测试operation mapping或message mapping,source input里需要构造
<message> <message1> ...</message1><message>这样的结构,但是在directory里或RWB(runtime)里测试时,payload里都不应该出现<message> <message1> ...</message1></message>这样的结构,否则会得到类似下面的错误:
sap.aii.mappingtool.tf7.IllegalInstanceException: Cannot create target element /ns0:Messages. Values missing in queue context.
第3步的XSLT:

<?xml version="1.0" encoding="UTF-8"?><xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:urn="urn:enterprise.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://pi.cceag.de/SFDC" xmlns:ns0="http://sap.com/xi/XI/SplitAndMerge">
    <xsl:output indent="yes" method="xml"/>
    <xsl:template match="/">
        <ns0:Messages>
        <ns0:Message1>
            <xsl:for-each select="//sObjects[position() mod 200 = 1]">
                    <soapenv:Envelope>
                        <xsl:call-template name="header"/>
                        <soapenv:Body>
                            <urn:upsert>
                                <externalIDFieldName>
                                    <xsl:value-of select="//upsert/externalIDFieldName"/>
                                </externalIDFieldName>
                                <xsl:for-each select=". | following-sibling::sObjects[position() &lt; 200]">
                                    <xsl:call-template name="sObjects" />
                                </xsl:for-each>
                            </urn:upsert>
                        </soapenv:Body>
                    </soapenv:Envelope>
            </xsl:for-each>
         </ns0:Message1>       
        </ns0:Messages>
    </xsl:template>

    <xsl:template match="soapenv:Header" name="header">
        <xsl:copy-of select="/soapenv:Envelope/soapenv:Header"/>
    </xsl:template>

    <xsl:template match="sObjects" name="sObjects">
        <urn:sObjects xsi:type="Account">
        <xsl:for-each select="./*">
            <xsl:element name="{local-name()}">
                <xsl:value-of select="./text()" />
            </xsl:element>
         </xsl:for-each>
         </urn:sObjects>
    </xsl:template>
</xsl:stylesheet>

2011-01-15

XSLT 1.0处理分组

在处理一个接口映射时遇到需要分组的问题,因为发送方使用JDBC adapter从数据库抽取数据,SAP PI得到的数据结构为:
<row>
<column1/>
<column2/>
<column3/>
</row>
其中column1,column2,column3是JDBC adapter中SQL查询语句里select的表列。
这个问题通过graphic mapping的node function估计应该能解决,但没有例子和好的文档就先放弃了,改用XSLT来实现。现在读取出来的数据中有一列为INVOCIE_ID,同一个INVOICE下有多个line_item。为每一个INVOICE _ID应生成一个IDoc,所有具有相同INVOICE_ID的line_item要映射到同一个IDoc下。如果环境支持 XSLT 2.0,可以很方便的使用group-by解决分组的问题。试了一下SAP PI环境不支持group-by语句,只能使用XSLT 1.0支持的语句。一个名为Muenchian的方法颇为有名,使用了xsl:key语句和key函数,提高了XSLT处理分组的性能。一个简化的例子:
XML 源文件
<?xml version="1.0" encoding="UTF-8"?>
<MT_PTP_PURCHASEINVOICE>
    <row>
        <INVOICE_ID>12</INVOICE_ID>
        <INVOICE_CURRENCY>EUR</INVOICE_CURRENCY>
        <LINE_NUMBER>1</LINE_NUMBER>
    </row>
    <row>
        <INVOICE_ID>12</INVOICE_ID>
        <INVOICE_CURRENCY>EUR</INVOICE_CURRENCY>
        <LINE_NUMBER>2</LINE_NUMBER>
    </row>
    <row>
        <INVOICE_ID>13</INVOICE_ID>
        <INVOICE_CURRENCY>EUR</INVOICE_CURRENCY>
        <LINE_NUMBER>1</LINE_NUMBER>       
    </row>
   <row>
        <INVOICE_ID>13</INVOICE_ID>
        <INVOICE_CURRENCY>EUR</INVOICE_CURRENCY>
        <LINE_NUMBER>2</LINE_NUMBER>
    </row>
</MT_PTP_PURCHASEINVOICE>
XSLT 表单
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:key name="mykey" match="row" use="INVOICE_ID"/>
    <xsl:template match="/">
        <doc>
            <xsl:for-each select="//row[count(. | key('mykey', INVOICE_ID)[1]) = 1]">
                <xsl:sort select="INVOICE_ID"/>
                <invoice>
                    <INVOICE_ID>
                        <xsl:value-of select="INVOICE_ID"/>
                    </INVOICE_ID>
                    <items>
                        <xsl:for-each select="key('mykey', INVOICE_ID)">
                            <xsl:sort select="LINE_NUMBER"/>
                            <line>
                                <xsl:value-of select="LINE_NUMBER"/>
                            </line>
                        </xsl:for-each>
                    </items>
                </invoice>
            </xsl:for-each>
        </doc>
    </xsl:template>
</xsl:stylesheet>

2011-01-11

XSLT下填充空白字符

今天遇到一个问题需要对一些字符串进行操作,以保证字符串达到给定的长度。如果字符串长度不够则在字符串后面添加空白字符来补齐。刚开始的想法是用给定的长度(m)减去字符串的长度(n),然后在字符串后面添加(m-n)个空白字符。不过去构造这(m-n)个空白字符还要额外想办法。google了一下xslt padding,更简洁的办法是添加超过给定长度的空白字符,然后用substring去取指定长度的字符串即可。同一个问题换一个思路方便多了。

如果要常用到,定义一个template,带两个参数。
<xsl:template name="pad">
<xsl:param name="str"/>
<xsl:param name="len"/>
<xsl:variable name="spaces"><xsl:text>                           </xsl:text></xsl:variable>
<!-- <xsl:text>在这里很重要,避免空白字符被过滤掉 -->
<xsl:value-of select="substring(concat($s, $spaces), 1, $len)"/>
</xsl:template>

需要时调用这个template即可:
<xsl:variable name="var">
<xsl:call-template name="pad">
<xsl:with-param name="str" select="concat(firstname, ', ', name)" />
<xsl:with-param name="len" select="10" />
</xsl:call-template>
</xsl:variable>

更灵活一些的template,'padVar' 是要填充的字符, 'length' 是给定的长度。
在前面插入,右对齐:
<xsl:template name="prepend-pad">
<!-- recursive template to right justify and prepend-->
<!-- the value with whatever padChar is passed in   -->
<xsl:param name="padChar"> </xsl:param>
<xsl:param name="padVar"/>
<xsl:param name="length"/>
<xsl:choose>
<xsl:when test="string-length($padVar) &lt; $length">
<xsl:call-template name="prepend-pad">
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padVar" select="concat($padChar,$padVar)"/>
<xsl:with-param name="length" select="$length"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($padVar,string-length($padVar) - $length + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

在后面插入,左对齐:
<xsl:template name="append-pad">    
<!-- recursive template to left justify and append  -->
<xsl:param name="padChar"> </xsl:param>
<xsl:param name="padVar"/>
<xsl:param name="length"/>
<xsl:choose>
<xsl:when test="string-length($padVar) &lt; $length">
<xsl:call-template name="append-pad">
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padVar" select="concat($padVar,$padChar)"/>
<xsl:with-param name="length" select="$length"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($padVar,1,$length)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

2010-12-14

在XSLT中调用Java函数

在为客户实现一个sap PI的interface时,用XSLT去生成IDoc的XML文件形式,通过sap PI的IDoc adapter发送IDoc。由于客户现在用SAP替代了旧的Oracle系统,需要映射一些如Cost Centre和Profit Centre的东西,映射所需的表在SAP PI系统中。要读取数据库中的东西借助XSLT不容易实现,只能用Java。在Java代码中使用SAP JCO读取并返回了正确的值,但是在XSLT中调用JAVA函数却大费了一番周折,问题主要出在该如何声明自定义的命名空间才能使XSLT processor能找到编译好的Java class文件,以调用Java类中定义的函数。
当XSLT文件和Java class文件位于一目录下,经过测试,在Altova XML Spy下,命名空间需要声明为:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:myns="java:Test" exclude-result-prefixes="myns">
其中需要注意的是在xmlns:myns="java:Test" exclude-result-prefixes="myns"中,java不能省掉,Test为Java class的名字,否则Altova 的XSLT processo找不到声明的Java class,会返回ClassNotFound exception。
而在SAP PI 7.0环境下,声明自定义的命名空间必须去掉“java:”,否则也会出现找不到Java class的错误。
示例:
XSLT文件
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:myns="java:Test" exclude-result-prefixes="myns">
 
<xsl:template match="/">
<root>
<text><xsl:value-of select="myns:foo(‘hello’, ‘world’)" /></text>
</root>
</xsl:template>
</xsl:stylesheet>
Java class
public class Test {
     public static String foo(String str1, String str2) {
     return str1 + “ ” + str2 ;
     } 
}

2010-12-13

XPATH点滴

XPATH 1.0中一个XPATH表达式返回一个节点的集合(node set),因为是一个集合,所以不存在节点顺序的概念。而在XPATH 2.0中一个表达式返回的则是一个节点序列(sequence of nodes),顺序为document order。

/ 符号:以前基本上认为就是在轴上分隔不同的节点。更广义的理解是这是一个高阶的运算符。比如$EXP1和$EXP2 分别为两个节点集合,那么$EXP1/$EXP2表示对$EXP1中的每一个节点对$EXP2求值,$EXP1/$EXP2的值即为:对$EXP1中每个节点对$EXP2求值的集合。

对轴的理解:轴实际上是一种节点间一对多的映射,可以把轴理解为一种函数,输入为一个单个节点,输出为与该节点有某种关系的节点的集合。

(@code,"N/A")[1]:如果code属性的值不为空,那么这个表达式的值即为code属性的值,否则返回”N/A”。因为当code属性的值为空时,通过运算符逗号 “,” 创建的sequence只有一个item即”N/A”,筛选条件[1]选择sequence中第一个元素。

2010-10-27

XSLT SAP PI 7.0

这两天写xslt文件用于在SAP PI 7.0环境下的interface mapping,才发现SAP PI 7.0下的XSLT环境不完整支持xpath 2.0,如:
  • 不支持xpath 2.0下表示两个节点集合的xpath运算符intersect。Dr. Kay在很多年前就提出在xpath 1.0下的替代表达式 $ns1[count(.|$ns2) = count($ns2)]倒是派上了用场,只是效率差些。
  • 不支持XPATH 2.0下的函数current-date。XPATH 1.0里根本就没有针对date和time的函数,在XSLT下通常只能通过传递参数给stylesheet才能解决这个问题。不过这个问题倒是借助SAP PI的运行环境能解决,因为它提供一些参数,只需在stylesheet下声明一下就可以使用如:

<xsl:param name="TimeSent" />
SendTime的值的格式为:YYYY-MM-DDTHH:MM:SSZ

更多可供使用的参数见:http://help.sap.com/saphelp_nw04/helpdata/en/73/f61eea1741453eb8f794e150067930/frameset.htm

2007-07-28

cocoon环境下在XSLT中使用request parameter

在cocoon的sitemap.xmap文件中XSLT这个transformater默认的配置是不启用request paramter的,要在XSLT中使用request parameter的值只需把:
<use-request-parameters>false</use-request-parameters>

的值改为:

<use-request-parameters>true</use-request-parameters>

这样在XSLT中声明一下这个parameter就可以使用了:

<xsl:param name="parameter-name"/>