Technical Blog Post
Abstract
How to create a PDF in Sterling B2B Integrator
Body
Although Sterling B2B Integrator can create reports in PDF format, this code is not exposed to the user and currently no Service or Adapter within SBI creates PDF output. Be that as it may, creating PDF output is nevertheless possible using Apache's fop. The solution outlined here is by no means the only method by which one could accomplish this. Nonetheless, herewith is one approach to creating PDFs in Sterling B2B Integrator.
The first step was to download Apache's fop 2.2 classes (as of this writing, these can be found at https://xmlgraphics.apache.org/fop/download.html.) I chose the binary version, which did not require any compilation - I merely un-gzipped and un-tarred the downloaded file. The resulting fop-2.2 directory resided in the home directory of the administrative user for the SBI environment.. I was now ready to create the Sterling B2B Integrator components needed to create the PDF. The input file in this case was a flat file:
10ACMEPROD 850 102055720160125 TECHSUPP
20STTECHNICAL SUPPORT, INC. WEST T20020159 1234 FRANKLIN AVENUE HOLLYWOOD FL33019 USA
301210 1VPEA 3USDPE 505.00
40MONOCULAR MICROSCOPE
404X,10X,40X,100X (OIL)
40ACHROMATIC (DIN) 20W HALOGEN
301222 2VPPK 2USDPE 19.75
40LOW FORM GRADUATED GLASS BEAKER
40250 ML
4012 PER PACK
50 2 1561.20
Since fop handles XML easily, I first created a map to translate the flat file to XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Inbound>
<CustomerIDCode>TECHSUPP</CustomerIDCode>
<OrderNum>1020557</OrderNum>
<OrderDate>20170501</OrderDate>
<Address>
<AddressCode>BILL TO</AddressCode>
<AddressName>TECHNICAL SUPPORT, INC. WEST</AddressName>
<AddressStreet>7025 FRANKLIN AVENUE</AddressStreet>
<AddressCity>HOLLYWOOD</AddressCity>
<AddressState>FL</AddressState>
<AddressPostCode>33019</AddressPostCode>
<AddressCountry>USA</AddressCountry>
</Address>
<Item>
<ItemLineNum>1</ItemLineNum>
<ItemCode>1210</ItemCode>
<ItemCurrency>US Dollar</ItemCurrency>
<ItemSoldBy>Each</ItemSoldBy>
<ItemQuantity>3</ItemQuantity>
<ItemPriceCode>Price Per Each</ItemPriceCode>
<ItemPrice>505</ItemPrice>
<Description>MONOCULAR MICROSCOPE</Description>
<Description>4X,10X,40X,100X (OIL)</Description>
<Description>ACHROMATIC (DIN) 20W HALOGEN</Description>
</Item>
<Item>
<ItemLineNum>2</ItemLineNum>
<ItemCode>1222</ItemCode>
<ItemCurrency>US Dollar</ItemCurrency>
<ItemSoldBy>Package</ItemSoldBy>
<ItemQuantity>2</ItemQuantity>
<ItemPriceCode>Price Per Each</ItemPriceCode>
<ItemPrice>19.75</ItemPrice>
<Description>LOW FORM GRADUATED GLASS BEAKER</Description>
<Description>250 ML</Description>
<Description>12 PER PACK</Description>
</Item>
<Totals>
<TotalLineItems>2</TotalLineItems>
<TotalOrderAmount>1554.50</TotalOrderAmount>
</Totals>
</Inbound>
Now it was time to create the XSL with formatting commands to transform the XML into a wonderful Portable Document Format (PDF) file suitable for framing. Not knowing much about formatting XML, I muddled through until I had the following stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="simple" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm">
<fo:region-body margin-top="3cm"/>
<fo:region-before extent="3cm"/>
<fo:region-after extent="1.5cm"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<fo:flow flow-name="xsl-region-body">
<xsl:apply-templates/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="Inbound">
<xsl:text>Invoice</xsl:text>
<fo:block>
<xsl:apply-templates select="CustomerIDCode"/>
<xsl:apply-templates select="Address/AddressCode"/>
<xsl:for-each select="Item">
<fo:block font-size="12pt" font-family="sans-serif" line-height="15pt" background-color="rgb(240,255,255)" text-align="left" white-space-collapse="false" space-after.optimum="12pt">
<fo:block>
<xsl:text>PO Line Number: </xsl:text>
<xsl:value-of select="./ItemLineNum"/>
<xsl:text> Item Code: </xsl:text>
<xsl:value-of select="./ItemCode"/>
<xsl:text> Quantity Ordered: </xsl:text>
<xsl:value-of select="./ItemQuantity"/>
</fo:block>
<fo:block>
<xsl:text>Price: $</xsl:text>
<fo:inline background-color="red" color="white">
<xsl:value-of select="./ItemPrice"/>
</fo:inline>
</fo:block>
<fo:block>
<xsl:text>Unit: </xsl:text>
<xsl:value-of select="./ItemSoldBy"/>
</fo:block>
<fo:block>
<fo:inline white-space-collapse="false">
<xsl:text> Item Description: </xsl:text>
</fo:inline>
</fo:block>
<xsl:for-each select="./Description">
<fo:block>
<xsl:value-of select="."/>
</fo:block>
</xsl:for-each>
<fo:block>
<xsl:text>Total This Item: $</xsl:text>
<fo:inline background-color="red" color="white">
<xsl:value-of select="number(./ItemQuantity * ./ItemPrice)"/>
</fo:inline>
</fo:block>
</fo:block>
</xsl:for-each>
</fo:block>
<xsl:apply-templates select="Totals"/>
</xsl:template>
<xsl:template match="CustomerIDCode">
<fo:block font-size="18pt" font-family="sans-serif" line-height="24pt" background-color="rgb(70,130,180)" color="white" text-align="center" padding-top="3pt" space-after.optimum="12pt">
<fo:block>
<xsl:text>Customer Identification Code: </xsl:text>
<xsl:value-of select="."/>
</fo:block>
<fo:block>
<xsl:text>Invoice for Order Number: </xsl:text>
<xsl:value-of select="following-sibling::OrderNum"/>
</fo:block>
<fo:block>
<xsl:text>Invoice Date: </xsl:text>
<xsl:value-of select="following-sibling::OrderDate"/>
</fo:block>
</fo:block>
</xsl:template>
<xsl:template match="AddressCode">
<fo:block font-size="12pt" font-family="sans-serif" line-height="15pt" background-color="rgb(240,255,240)" color="black" text-align="left" padding-top="3pt" white-space-collapse="false" space-after.optimum="12pt">
<fo:block>
<xsl:value-of select="."/>
<xsl:text>:</xsl:text>
</fo:block>
<fo:block>
<xsl:value-of select="following-sibling::AddressName"/>
</fo:block>
<fo:block>
<xsl:value-of select="following-sibling::AddressStreet"/>
</fo:block>
<fo:block>
<xsl:value-of select="following-sibling::AddressCity"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="following-sibling::AddressState"/>
<xsl:text> </xsl:text>
<xsl:value-of select="following-sibling::AddressPostCode"/>
</fo:block>
<fo:block>
<xsl:value-of select="following-sibling::AddressCountry"/>
</fo:block>
</fo:block>
</xsl:template>
<xsl:template match="Totals">
<fo:block font-size="14pt" font-family="sans-serif" line-height="16pt" space-before.optimum="3pt" space-after.optimum="3pt" background-color="rgb(240,255,240)" color="black" text-align="left" padding-top="3pt" white-space-collapse="false">
<fo:block>
<xsl:text>Total Line Items: </xsl:text>
<xsl:value-of select="./TotalLineItems"/>
</fo:block>
<fo:block>
<xsl:text>Total Amount Due: $</xsl:text>
<fo:inline background-color="red" color="white">
<xsl:value-of select="./TotalOrderAmount"/>
</fo:inline>
</fo:block>
<fo:block>
<xsl:text>Terms: 30 Days Net</xsl:text>
</fo:block>
<xsl:if test="ceiling(./TotalOrderAmount/text()) > 500">
<fo:block>
<xsl:text>We're running a special this month on orders of $500 or more!</xsl:text>
</fo:block>
<fo:block>
<xsl:text>Pay within 15 days and receive a rebate of 2% off your order!</xsl:text>
</fo:block>
<fo:block>
<xsl:text>On today's order, you qualify for a savings of </xsl:text>
<xsl:value-of select="number(./TotalOrderAmount * .02)"/>
<xsl:text>!</xsl:text>
</fo:block>
</xsl:if>
</fo:block>
</xsl:template>
<xsl:template match="OrderNum | OrderDate"/>
<xsl:template match="AddressName | AddressStreet | AddressCity | AddressState | AddressCountry"/>
<xsl:template match="ItemLineNum | ItemCode | ItemCurrency | ItemSoldBy | ItemQuantity | ItemPriceCode | ItemPrice | Description"/>
<xsl:template match="TotalLineItems | TotalOrderAmount"/>
</xsl:stylesheet>
It is probably not the most elegant stylesheet in the world, but it gave me something to use in my test. Now I needed a business process:
<process name="create_PDF">
<sequence>
<operation name="Text to XML">
<participant name="Translation"/>
<output message="TranslationTypeInputMessage">
<assign to="map_name">App_to_XML</assign>
<assign to="validate_input">NO</assign>
<assign to="validate_output">NO</assign>
<assign to="." from="*"></assign>
</output>
<input message="inmsg">
<assign to="." from="*"></assign>
</input>
</operation>
<operation name="Command Line Adapter">
<participant name="create_PDF_CLA"/>
<output message="CmdLineInputMessage">
<assign to="." from="*"/>
</output>
<input message="inmsg">
<assign to="." from="*"/>
</input>
</operation>
<assign to="PrimaryDocument" from="/ProcessData/CLA2/document/@SCIObjectID"/>
<operation name="FSA">
<participant name="save_PDF_FSA"/>
<output message="Xout">
<assign to="Action">FS_EXTRACT</assign>
<assign to="." from="*"/>
</output>
<input message="Xin">
<assign to="." from="*"/>
</input>
</operation>
<operation name="Release Service">
<participant name="ReleaseService"/>
<output message="ReleaseServiceTypeInputMessage">
<assign to="TARGET">/ProcessData/CLA2</assign>
<assign to="." from="*"/>
</output>
<input message="inmsg">
<assign to="." from="*"/>
</input>
</operation>
</sequence>
</process>
The Command Line Adapter merits a little more scrutiny. It is configured as follows:
create_PDF_CLA
Service Settings
Service Type Command Line Adapter
Description create a PDF file using external fop
System Name create_PDF_CLA
Group Name None
Remote Name localhost
Remote Port 30052
Command Line /home/admin/fop-2.2/fop/fop -xml $Input -xsl /home/admin/pdf_fo.xsl $Output
Working Directory None provided
Turn on debugging messages? Yes
Wait on the process to complete before continuing? Yes
Does this service start a Business Process? No
Does the command line process require an input file? Yes
Input Filename None provided
Delete input file after process completes? No
Use the output generated by the command line process? Yes
Output Filename ourPDF.pdf
Delete output file after process completes? No
To fill out the picture, here is the File System Adapter's configuration:
save_PDF_FSA
Service Settings
Service Type File System Adapter
Description Save the PDF to the File System
System Name save_PDF_FSA
Environment node1
Group Name None
Collection folder NONE
Filename filter None provided
Collect files from sub folders within and including the collection folder? No
Use the absolute file path name for the document name? No
Start a business process once files are collected? No
Extraction folder /opt/si/install/nonedioutbound/extract
Unobscure File Contents? No
Filenaming convention Assign a specific name
Filename ourPDF_%^.pdf
The moment of truth had come; it was time to test the map, stylesheet and business process. I kicked off the BP using the XML created by the translation map. The result was the colorful PDF that you see here:
This is admittedly a rather bare bones example, but if you find it useful. you can customize it to suit your business needs.
UID
ibm11120497