Implement SSCC Label Printing using Apache FOP, Barcode4j and XML

Written by James McDonald

September 24, 2021

Note: Note to anyone reading this that knows XSLT/XML. How do I get the below XSL to loop twice so it creates two label pages for each node of this for-each loop <xsl:for-each select=”DETAIL/SHIPMENT/STORE/TARE”> WITHOUT duplicating the entire tree of XSLT? Comment below if you can share the secret. (I just don’t know how to implement a recursive template)

MessageXchange provide EDI services for a number of Australian Supermarkets

If you are doing a budget implementation you can use their FormXchange product which will help you implement with a super market by giving you off the shelf PO, POA, ASN & INV electronic documents.

The MessageXchange interface is a little like an email web client you have an inbox and an outbox and the ability to print SSCC Pallet Logistics labels

Along with the ability to send and receive EDI documents they also provide for each document type the ability to export the document in XML format.

For example if you don’t like the format of their SSCC Label print you can use the XML ASN document to create your own

Using Apache FOP and Barcode4J to Format Your Own Labels

Here is an example I have been working on

This is a 105 x 148mm (A6) label

The stripped down XML of the ASN which the SSCC Logistics label is printed from I have removed everything I’m not referencing in the label

<MXCASN>
    <HEADER>
        <FROMBUSS>The Toggen Partnership</FROMBUSS>
    </HEADER>
    <DETAIL>
        <SHIPMENT>
            <STORE>
                <STORECODE>TGN123</STORECODE>
                <TARE>
                    <PACK>
                        <SSCC>00999999999000000245</SSCC>
                        <LINGRP>
                            <LIN>
                                <EAN>19999999999762</EAN>
                            </LIN>
                            <IMD disable-output-escaping="yes">TOGGEN DUNE HARVESTER OIL</IMD>
                            <QTY>
                                <TOTALSHIPQUANTITY>48</TOTALSHIPQUANTITY>
                            </QTY>
                            <ASNSTATUS />
                            <DTM>
                                <BESTBEFOREDATE>20220802</BESTBEFOREDATE>
                            </DTM>
                            <RFF>
                                <BATCHNO>212301</BATCHNO>
                            </RFF>
                        </LINGRP>
                    </PACK>
                </TARE>
                <TARE>
                    <PACK>
                        <SSCC>00999999999000000252</SSCC>
                        <LINGRP>
                            <LIN>
                                <EAN>19999999993678</EAN>
                            </LIN>
                            <IMD disable-output-escaping="yes">TOGGEN GOLD STANDARD 750ML XXX</IMD>
                            <QTY>
                                <TOTALSHIPQUANTITY>128</TOTALSHIPQUANTITY>
                            </QTY>
                            <ASNSTATUS />
                            <DTM>
                                <BESTBEFOREDATE>20220802</BESTBEFOREDATE>
                            </DTM>
                            <RFF>
                                <BATCHNO>212301</BATCHNO>
                            </RFF>
                        </LINGRP>
                    </PACK>
                </TARE>
                <TARE>
                    <PACK>
                        <SSCC>00999999999000000269</SSCC>
                        <LINGRP>
                            <LIN>
                                <EAN>19999999992763</EAN>
                            </LIN>
                            <IMD disable-output-escaping="yes">TOGGEN GOLDEN PEEL OIL 750ML</IMD>
                            <QTY>
                                <TOTALSHIPQUANTITY>145</TOTALSHIPQUANTITY>
                            </QTY>
                            <ASNSTATUS />
                            <DTM>
                                <BESTBEFOREDATE>20220802</BESTBEFOREDATE>
                            </DTM>
                            <RFF>
                                <BATCHNO>212301</BATCHNO>
                            </RFF>
                        </LINGRP>
                    </PACK>
                </TARE>
            </STORE>
        </SHIPMENT>
    </DETAIL>
    <SUMMARY>
    </SUMMARY>
</MXCASN>

Here is the XSLT used to transform the ASN XML into a xml-fo for use by Apache FOP and Barcode4j to create a PDF label

Some features of this XSL
  • Barcode4J has the ability to recaculate a check digit so the sample below strips the check-digit and re-adds it.
  • Also an example of embedding a FNC1 in a variable length barcode field
  • Using two variables FONT_SIZE and FONT_SIZE_LABELS to set the human-readable barcode data and the label sizes.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" exclude-result-prefixes="fo">
	<xsl:output method="xml" version="1.0" omit-xml-declaration="no" indent="yes" />
	<xsl:param name="versionParam" select="'1.0'" />
	<!-- ========================= -->
	<!-- root element: projectteam -->
	<!-- ========================= -->
	<xsl:template match="MXCASN">
		<xsl:variable name="STORECODE" select="DETAIL/SHIPMENT/STORE/STORECODE" />
		<xsl:variable name="COMPANY" select="HEADER/FROMBUSS" />
		<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
			<fo:layout-master-set>
				<fo:simple-page-master master-name="first" page-width="105mm" page-height="148mm" margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
					<fo:region-body writing-mode="lr-tb" />
					<!-- <fo:region-after extent="0mm" /> -->
				</fo:simple-page-master>
			</fo:layout-master-set>
			<fo:page-sequence master-reference="first">
				<fo:flow flow-name="xsl-region-body" font-size="12pt">
					<xsl:for-each select="DETAIL/SHIPMENT/STORE/TARE">
						<xsl:variable name="FONT_SIZE" select="'26pt'" />
						<xsl:variable name="FONT_SIZE_LABELS" select="'11pt'" />
						<xsl:variable name="SSCC" select="PACK/SSCC" />
						<xsl:variable name="EAN" select="PACK/LINGRP/LIN/EAN" />
						<xsl:variable name="BATCH" select="PACK/LINGRP/RFF/BATCHNO" />
						<!-- 20220802 -->
						<xsl:variable name="FULL_DATE" select="PACK/LINGRP/DTM/BESTBEFOREDATE" />
						<xsl:variable name="QTY" select="PACK/LINGRP/QTY/TOTALSHIPQUANTITY" />
						<xsl:variable name="YEAR" select="substring($FULL_DATE, 3,2 )" />
						<xsl:variable name="MONTH" select="substring($FULL_DATE, 5,2)" />
						<xsl:variable name="DAY" select="substring($FULL_DATE, 7,2 )" />
						<fo:block-container>
							<fo:block text-align="center" font-family="Helvetica" font-size="18pt" margin-top="2pt">
								<xsl:value-of select="$COMPANY" />
							</fo:block>
							<fo:block text-align="left" margin-left="3mm" font-family="Helvetica" font-size="10pt">
								<xsl:attribute name="font-size">
									<xsl:value-of select="$FONT_SIZE_LABELS" />
								</xsl:attribute>
								SSCC
							</fo:block>
							<fo:block text-align="left" margin-left="3mm" font-family="Helvetica" font-size="24pt">
								<xsl:attribute name="font-size">
									<xsl:value-of select="$FONT_SIZE" />
								</xsl:attribute>
								<xsl:value-of select="substring($SSCC, 3)" />
							</fo:block>
							<fo:block text-align="left" margin-left="3mm" font-family="Helvetica" font-size="14pt" space-after="8pt">
								<xsl:value-of select="PACK/LINGRP/IMD" />
							</fo:block>
							<fo:table width="10cm" table-layout="fixed" margin-left="1.5mm">
								<fo:table-column column-width="6.7cm" />
								<fo:table-column column-width="3.2cm" />
								<fo:table-body font-family="sans-serif" font-weight="normal" font-size="10pt">
									<fo:table-row>
										<fo:table-cell>
											<fo:block text-align="start">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE_LABELS" />
												</xsl:attribute>
												ITEM NO
											</fo:block>
										</fo:table-cell>
										<fo:table-cell>
											<fo:block text-align="end">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE_LABELS" />
												</xsl:attribute>
												QUANTITY
											</fo:block>
										</fo:table-cell>
									</fo:table-row>
									<fo:table-row>
										<fo:table-cell>
											<fo:block text-align="start" font-size="26pt">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE" />
												</xsl:attribute>
												<xsl:value-of select="$EAN" />
											</fo:block>
										</fo:table-cell>
										<fo:table-cell>
											<fo:block text-align="end" font-size="10pt">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE" />
												</xsl:attribute>
												<xsl:value-of select="$QTY" />
											</fo:block>
										</fo:table-cell>
									</fo:table-row>
								</fo:table-body>
							</fo:table>
							<fo:table width="10cm" table-layout="fixed" margin-left="1.5mm">
								<fo:table-column column-width="5cm" />
								<fo:table-column column-width="3.0cm" />
								<fo:table-column column-width="2cm" />
								<fo:table-body font-family="sans-serif" font-weight="normal" font-size="10pt">
									<fo:table-row>
										<fo:table-cell>
											<xsl:attribute name="font-size">
												<xsl:value-of select="$FONT_SIZE_LABELS" />
											</xsl:attribute>
											<fo:block text-align="start">BEST BEFORE (ddmmyy)</fo:block>
										</fo:table-cell>
										<fo:table-cell>
											<fo:block text-align="center">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE_LABELS" />
												</xsl:attribute>
												BATCH
											</fo:block>
										</fo:table-cell>
										<fo:table-cell>
											<fo:block text-align="end">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE_LABELS" />
												</xsl:attribute>
												DC
											</fo:block>
										</fo:table-cell>
									</fo:table-row>
									<fo:table-row>
										<fo:table-cell>
											<fo:block text-align="start" font-size="24pt">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE" />
												</xsl:attribute>
												<xsl:value-of select="concat($DAY,'.' , $MONTH,'.' , $YEAR)" />
											</fo:block>
										</fo:table-cell>
										<fo:table-cell>
											<fo:block text-align="center" font-size="22pt">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE" />
												</xsl:attribute>
												<xsl:value-of select="$BATCH" />
											</fo:block>
										</fo:table-cell>
										<fo:table-cell vertical-align="top">
											<fo:block text-align="end" font-size="16pt">
												<xsl:attribute name="font-size">
													<xsl:value-of select="$FONT_SIZE_LABELS" />
												</xsl:attribute>
												<xsl:value-of select="$STORECODE" />
											</fo:block>
										</fo:table-cell>
									</fo:table-row>
								</fo:table-body>
							</fo:table>
							<fo:block line-height="1pt" text-align="center" left="0mm" space-before="0pt" space-after="10pt" padding="0pt">
								<fo:leader line-height="1pt" leader-pattern="rule" rule-thickness="1pt" leader-length="100%" />
							</fo:block>
							<fo:block text-align="center">
								<fo:instream-foreign-object>
									<xsl:variable name="PAD_QTY" select="format-number($QTY, '00')" />
									<xsl:variable name="BARCODE" select="concat('02' , substring($EAN,1,13) ,'&#x00f0;37' , $PAD_QTY, '&#x00f1;15' , $YEAR , $MONTH , $DAY, '10', $BATCH )" />
									<barcode:barcode xmlns:barcode="http://barcode4j.krysalis.org/ns" orientation="0">
										<xsl:message>
											<xsl:value-of select="$BARCODE" />
										</xsl:message>
										<xsl:attribute name="message">
											<xsl:value-of select="$BARCODE" />
										</xsl:attribute>
										<barcode:ean-128>
											<barcode:check-digit-marker>&#x00f0;</barcode:check-digit-marker>
											<barcode:module-width>0.32mm</barcode:module-width>
											<barcode:template>(02)n13+cd(37)n1-8(15)n6(10)an1-20</barcode:template>
											<barcode:height>36mm</barcode:height>
											<barcode:human-readable>
												<barcode:placement>bottom</barcode:placement>
												<barcode:font-size>10pt</barcode:font-size>
											</barcode:human-readable>
										</barcode:ean-128>
									</barcode:barcode>
								</fo:instream-foreign-object>
							</fo:block>
							<fo:block text-align="center" space-after="0pt">
								<fo:instream-foreign-object>
									<barcode:barcode xmlns:barcode="http://barcode4j.krysalis.org/ns" message="REPLACEDBYATTRIBUTE" orientation="0">
										<xsl:attribute name="message">
											<!-- strip the checkdigit and calc for ourselves '&#x00f0;' is the char that barcode4j replaces with its
												own checkdigit
											-->
											<xsl:value-of select="concat(substring($SSCC,1,19), '&#x00f0;')" />
										</xsl:attribute>
										<barcode:ean-128>
											<barcode:module-width>0.49mm</barcode:module-width>
											<barcode:template>(00)n17+cd</barcode:template>
											<barcode:height>36mm</barcode:height>
											<barcode:human-readable>
												<barcode:font-size>10pt</barcode:font-size>
											</barcode:human-readable>
										</barcode:ean-128>
									</barcode:barcode>
								</fo:instream-foreign-object>
							</fo:block>
						</fo:block-container>
					</xsl:for-each>
				</fo:flow>
			</fo:page-sequence>
		</fo:root>
	</xsl:template>
</xsl:stylesheet>

Save the above two files as asn-sample.xml and bc2fo.xsl and then run them using Apache fop with Barcode4j jars added to it’s lib/ directory (see below for instructions on integrating barcode4j with FOP)

Run the following command from the fop/ directory e.g. the folder that has the fop, fop.bat, fop.cmd, fop.js files in it. In my install look under fop-2.6-bin\fop-2.6\fop

# one step  
fop -xml asn-sample.xml -xsl bc2fo.xsl output.pdf

# two step generate xml-fo and then create pdf using fop.
 xsltproc bc2fo.xsl asn-sample.xml  > bc.fo
fop bc.fo -pdf out1.pdf

And here is a screenshot of the output. In the XML above it creates one label for each TARE node.

To get Apache FOP and Barcode4j working together

Install OpenJDK

You need java. These are the versions that I’m using on both Windows and Linux

On Windows

java -version
openjdk version “11.0.12” 2021-07-20
OpenJDK Runtime Environment Microsoft-25199 (build 11.0.12+7)
OpenJDK 64-Bit Server VM Microsoft-25199 (build 11.0.12+7, mixed mode)

On Ubuntu Linux WSL

java -version
openjdk version “11.0.11” 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)

On Ubuntu Linux

java -version
openjdk version “1.8.0_292”
OpenJDK Runtime Environment (build 1.8.0_292-8u292-b10-0ubuntu1~20.04-b10)
OpenJDK 64-Bit Server VM (build 25.292-b10, mixed mode)

Download Barcode4J and Apache FOP

barcode4j-2.1.0-bin from https://sourceforge.net/projects/barcode4j/files/barcode4j/

fop-2.6-bin from http://xmlgraphics.apache.org/fop/download.html

unpack fop-2.6-bin and barcode4j-2.1.0-bin

Integrate barcode4j with Apache FOP

Copy the following two jars from barcode4j into the Apache FOP lib/ directory

  • avalon-framework-4.2.0.jar
  • barcode4j-fop-ext-complete.jar

FInd the above two files in the \barcode4j-2.1.0\barcode4j-2.1.0\lib directory copy them to the fop-2.6-bin\fop-2.6\fop\lib directory

Depending on your zip program you may have some more parent folders to navigate down but this gives you enough to know where and what to copy to and from.

Print FOP Output using Acrobat Reader on Windows

# create the PDF
cmd /c fop -xsl toggen\bc2fo.xsl -xml toggen\asn-sample.xml toggen\barcode%1.pdf

# send it to the printer using Acrobat Reader
start "" "C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" /t toggen\barcode%1.pdf "CAB A6+/300" "CAB A6+/300" "192.168.0.27" 

Arguments for AcroRd32.exe are

/t <pathto/pdf_file.pdf> <printer-name> <printer-driver> <portname>

To get the last three arguments use Powershell

PS C:\Users\rudolph> Get-Printer | select Name, DriverName,PortName

Name                          DriverName                          PortName
----                          ----------                          --------
OneNote for Windows 10        Microsoft Software Printer Driver   Microsoft.Office.OneNote_16001.14326.20018.0_x64__8w…
OneNote (Desktop)             Send to Microsoft OneNote 16 Driver nul:
Microsoft XPS Document Writer Microsoft XPS Document Writer v4    PORTPROMPT:
Microsoft Print to PDF        Microsoft Print To PDF              PORTPROMPT:
HP Officejet Pro 8620         HP Officejet Pro 8620               10.19.80.102
Fax                           Microsoft Shared Fax Driver         SHRFAX:
CutePDF Writer                CutePDF Writer v4.0                 CPW4:
CAB A6+/300                   CAB A6+/300                         192.168.0.27

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

You May Also Like…