Yesterday we dealt with relatively simple barcode requirements, just apply the barcode to the incoming data ... done. Today we take it to the next level, we need to pre-process the data before we apply the barcode font. Typically you need to calculate checksum characters and perform various manipulations to the data so that once its rendered as a barcode your (or your customers/supplier) hardware can read it.
We break this process into two, we need an algorithm component to encode the data, this will be a small java class and the second is the template component, we need to let the renedering engine know that we need to take some XML data and apply process X to it.
Encoding Algorithm Component
Just about all the font vendors I have come across, that supply barcodes that require enconding provide the required encoding algorithms in a java format that once you purchase the font you have the right to use in your programs. We are going to deal with the Code 128 barcode, this has encoding formats a, b or c depending on the font and the hardware requirement. We need to write a java class that will do the encoding, for publisher to be able to use it, it needs to conform to a format with specific methods thus:
/**
* Return a unique ID for this bacode encoder
* @return the id as a string
*/
public String getVendorID();
/**
* Return true if this encoder support a specific type of barcode
* @param type the type of the barcode
* @return true if supported
*/
public boolean isSupported(String type)
/**
* Encode a barcode string by given a specific type
* @param data the original data for the barcode
* @param type the type of the barcode
* @return the formatted data
*/
public String encode(String data, String type);
Not too strenuous even for a java novice and Im going to provide a cut and paste example in just a minute. Once you have the class it needs to sit in the classpath of the java virtual machine you're using. What does this mean practically, well the class needs to be available to the publisher engine, its not enough to drop it into a directory under $APPL_TOP for apps or a lib directory for standalone. When the java machine starts up that hosts the publisher engine, it is given a set of paths/libraries of classes, called a 'CLASSPATH', it can then 'find' these as an when they are needed. Now you do not need to update the classpath for either flavor. For EBS put the compiled class under the java_top area in my example I use a new directoy under oracle/apps/xdo/. For the standalone release just drop the class (wrapped into a jar or zip) into the lib directory under the xmlpserver/xmlpserver/WEB-INF/lib and then bounce the server.
Heres my sample class to encode based on code128, a, b and c:
package oracle.apps.xdo.template.rtf.util.barcoder;
import java.util.Hashtable;
import java.lang.reflect.Method;
import oracle.apps.xdo.template.rtf.util.XDOBarcodeEncoder;
import oracle.apps.xdo.common.log.Logger;
// This class name will be used in the register vendor field in the template.
public class BarcodeUtil implements XDOBarcodeEncoder
// The class implements the XDOBarcodeEncoder interface
{
// This is the barcode vendor id that is used in the register vendor field and
// format-barcode fields
public static final String BARCODE_VENDOR_ID = "XMLPBarVendor";
// The hastable is used to store references to the encoding methods
public static final Hashtable ENCODERS = new Hashtable(10);
// The BarcodeUtil class needs to be instantiated
public static final BarcodeUtil mUtility = new BarcodeUtil();
// This is the main code that is executed in the class, it is loading the methods
// for the encoding into the hashtable. In this case we are loading the three code128
// encoding methods we have created.
static {
try {
Class[] clazz = new Class[] { "".getClass() }
ENCODERS.put("code128a",mUtility.getClass().getMethod("code128a", clazz));
ENCODERS.put("code128b",mUtility.getClass().getMethod("code128b", clazz));
ENCODERS.put("code128c",mUtility.getClass().getMethod("code128c", clazz));
} catch (Exception e) {
// This is using the XML Publisher logging class to push errors to the XMLP log file.
Logger.log(e,5);
}
}
// The getVendorID method is called from the template layer at runtime to ensure the correct
// encoding method are used
public final String getVendorID()
{
return BARCODE_VENDOR_ID;
}
// The isSupported method is called to ensure that the encoding method
// called from the template is actually present in this class. If not
// then XMLP will report this in the log.
public final boolean isSupported(String s)
{
if(s != null)
return ENCODERS.containsKey(s.trim().toLowerCase());
else
return false;
}
// The encode method is called to then call the appropriate encoding method,
// in this example the code128a/b/c methods.
public final String encode(String s, String s1)
{
if(s != null && s1 != null)
{
try
{
Method method = (Method)ENCODERS.get(s1.trim().toLowerCase());
if(method != null)
return (String)method.invoke(this, new Object[] {
s
});
else
return s;
}
catch(Exception exception)
{
Logger.log(exception,5);
}
return s;
} else
{
return s;
}
}
/** This is the complete method for Code128a */
public static final String code128a( String DataToEncode )
{
char C128_Start = (char)203;
char C128_Stop = (char)206;
String Printable_string = "";
char CurrentChar;
int CurrentValue=0;
int weightedTotal=0;
int CheckDigitValue=0;
char C128_CheckDigit='w';
DataToEncode = DataToEncode.trim();
weightedTotal = ((int)C128_Start) - 100;
for( int i = 1; i <= DataToEncode.length(); i++ )
{
//get the value of each character
CurrentChar = DataToEncode.charAt(i-1);
if( ((int)CurrentChar) < 135 )
CurrentValue = ((int)CurrentChar) - 32;
if( ((int)CurrentChar) > 134 )
CurrentValue = ((int)CurrentChar) - 100;
CurrentValue = CurrentValue * i;
weightedTotal = weightedTotal + CurrentValue;
}
//divide the WeightedTotal by 103 and get the remainder,
//this is the CheckDigitValue
CheckDigitValue = weightedTotal % 103;
if( (CheckDigitValue < 95) && (CheckDigitValue > 0) )
C128_CheckDigit = (char)(CheckDigitValue + 32);
if( CheckDigitValue > 94 )
C128_CheckDigit = (char)(CheckDigitValue + 100);
if( CheckDigitValue == 0 ){
C128_CheckDigit = (char)194;
}
Printable_string = C128_Start + DataToEncode + C128_CheckDigit + C128_Stop + " ";
return Printable_string;
}
/** This is the complete method for Code128b ***/
public static final String code128b( String DataToEncode )
{
char C128_Start = (char)204;
char C128_Stop = (char)206;
String Printable_string = "";
char CurrentChar;
int CurrentValue=0;
int weightedTotal=0;
int CheckDigitValue=0;
char C128_CheckDigit='w';
DataToEncode = DataToEncode.trim();
weightedTotal = ((int)C128_Start) - 100;
for( int i = 1; i <= DataToEncode.length(); i++ )
{
//get the value of each character
CurrentChar = DataToEncode.charAt(i-1);
if( ((int)CurrentChar) < 135 )
CurrentValue = ((int)CurrentChar) - 32;
if( ((int)CurrentChar) > 134 )
CurrentValue = ((int)CurrentChar) - 100;
CurrentValue = CurrentValue * i;
weightedTotal = weightedTotal + CurrentValue;
}
//divide the WeightedTotal by 103 and get the remainder,
//this is the CheckDigitValue
CheckDigitValue = weightedTotal % 103;
if( (CheckDigitValue < 95) && (CheckDigitValue > 0) )
C128_CheckDigit = (char)(CheckDigitValue + 32);
if( CheckDigitValue > 94 )
C128_CheckDigit = (char)(CheckDigitValue + 100);
if( CheckDigitValue == 0 ){
C128_CheckDigit = (char)194;
}
Printable_string = C128_Start + DataToEncode + C128_CheckDigit + C128_Stop + " ";
return Printable_string;
}
/** This is the complete method for Code128c **/
public static final String code128c( String s )
{
char C128_Start = (char)205;
char C128_Stop = (char)206;
String Printable_string = "";
String DataToPrint = "";
String OnlyCorrectData = "";
int i=1;
int CurrentChar=0;
int CurrentValue=0;
int weightedTotal=0;
int CheckDigitValue=0;
char C128_CheckDigit='w';
DataToPrint = "";
s = s.trim();
for(i = 1; i <= s.length(); i++ )
{
//Add only numbers to OnlyCorrectData string
CurrentChar = (int)s.charAt(i-1);
if((CurrentChar < 58) && (CurrentChar > 47))
{
OnlyCorrectData = OnlyCorrectData + (char)s.charAt(i-1);
}
}
s = OnlyCorrectData;
//Check for an even number of digits, add 0 if not even
if( (s.length() % 2) == 1 )
{
s = "0" + s;
}
//<<<< Calculate Modulo 103 Check Digit and generate DataToPrint >>>>
//Set WeightedTotal to the Code 128 value of the start character
weightedTotal = ((int)C128_Start) - 100;
int WeightValue = 1;
for( i = 1; i <= s.length(); i += 2 )
{
//Get the value of each number pair (ex: 5 and 6 = 5*10+6 =56)
//And assign the ASCII values to DataToPrint
CurrentChar = ((((int)s.charAt(i-1))-48)*10) + (((int)s.charAt(i))-48);
if((CurrentChar < 95) && (CurrentChar > 0))
DataToPrint = DataToPrint + (char)(CurrentChar + 32);
if( CurrentChar > 94 )
DataToPrint = DataToPrint + (char)(CurrentChar + 100);
if( CurrentChar == 0)
DataToPrint = DataToPrint + (char)194;
//multiply by the weighting character
//add the values together to get the weighted total
weightedTotal = weightedTotal + (CurrentChar * WeightValue);
WeightValue = WeightValue + 1;
}
//divide the WeightedTotal by 103 and get the remainder,
//this is the CheckDigitValue
CheckDigitValue = weightedTotal % 103;
if((CheckDigitValue < 95) && (CheckDigitValue > 0))
C128_CheckDigit = (char)(CheckDigitValue + 32);
if( CheckDigitValue > 94 )
C128_CheckDigit = (char)(CheckDigitValue + 100);
if( CheckDigitValue == 0 ){
C128_CheckDigit = (char)194;
}
Printable_string = C128_Start + DataToPrint + C128_CheckDigit + C128_Stop + " ";
Logger.log(Printable_string,5);
return Printable_string;
}
}
You can get a copy of the java class here. Looks complex but most of the code you can get from your font vendor for the encoding. The important pieces for publisher are:
1. public static final String BARCODE_VENDOR_ID = "XMLPBarVendor";
You'll use this name in the RTF template
2. public final String encode(String s, String s1)
The encode method, this redirects to the appropriate encoding method as defined next
3. ENCODERS.put("code128a",mUtility.getClass().getMethod("code128a", clazz));
ENCODERS.put("code128b",mUtility.getClass().getMethod("code128b", clazz));
ENCODERS.put("code128c",mUtility.getClass().getMethod("code128c", clazz));
This defines your encoder methods, in this case code128a, b and c. If you have a new encoding method just add it here e.g.
ENCODERS.put("myEncoder",mUtility.getClass().getMethod("myEncoder", clazz));
You can take this class and just add the encoding method you have and add it to the list as above, compile and you're off.
We now have the back end piece as it were, we now need the code to add to the template.
Template Component
All we need in the template is a means to let the rendering engine know that we need the data to be encoded before the barcode is applied. Let's assume we have a field called 'INVOICE_CODE' that we want to apply the Code128a encoding to prior to applying the barcode. We have defined our encoding class called 'BarcodeUtil' and its under the following path in EBS, JAVA_TOP/oracle/apps/xdo/template/rtf/util/barcoder or its zipped or jarred using the same path.
In our template we are going to use two formfields, the first will declare the barcode encoder. It takes the following format:
<?register-barcode-vendor:男ava_class_name?;鍛arcode_vendor_id??>
where 'java_class_name' is the 'path' to our class and the 'barcode_vendor_id' is the name given in the java class. For our case we would have:
<?register-barcode-vendor:弛racle.apps.xdo.template.rtf.util.barcoder.BarcodeUtil?;湛MLPBarVendor??>
Now the engine knows where to look for the class. Next we need the data to be encoded. It takes the format:
<?format-barcode:data;鍛arcode_type?;鍛arcode_vendor_id??>
where 'data' is the element name to be encoded
barcode_type is the encoding method and
barcode_vendor_id is the vendor name we defined in our class again
For our scenario we have:
<?format-barcode:INVOICE_CODE;辰ode128a?;湛MLPBarVendor??>
remember all of these strings are case sensitive, its the first thing to check if things go wrong.
Thats pretty much it, so far as trouble shooting:
1. Check that the class is in the classpath
2. Check that the package name in the class matches the directory or path you have put the class in to
3. check the case sensitivity
Update
Thanks to Jorgen for being sharp eyed on the class, please check this update on the case sensitivity.
Actually when you look at the class, the call to the method encode() makes the string, that contains the encoding method to call, lowercase by default. So, it's probably more important that when user create new encoding methods in the class, they are added to the ENCODERS hash table in lowercase letters.
You can add some logging into your class for debugging purposes. Im going to cover that in the next article, it has more appeal than just for barcode encoding. If you take a closer look at the code you'll see the Logger method and can probably work out whats going on anyway.
Good Luck!