Wednesday, May 6, 2009

Create a PDF of Your RPG Source Code

To produce a PDF of an XML file, Apache FOP provides Windows batch and Unix script commands to do this easily when you have the XSL ready.
fop-0.95\fop -xml ..\data\xml\rpg.xml -xsl ..\data\xml\rpg.xsl c:\rpg.pdf 
We don't generally have RPG source formatted in an XML file, ready for this task but, we can download Apache FOP to the AS400's file system and using the AS400's QShell, combine the previous commands with the following and with a utility to produce the XML and pipe this XML to the FOP command.
db2 'create alias MYLIB.myalias for MYLIB.QRPGLESRC(MYRPGPGM)' 
db2 'select SRCDTA from MYLIB.myalias'
But I want to do this on my desktop machine and develop an application I can reuse in a later project. Here is a how to create a PDF of iSeries source code.  This code uses SQL to get the RPG source code and then it creates a simple XML document by wrapping each line of code in XML tags.  The XML document is passed to the methods of Apache FOP which do the work of creating the PDF.  I've placed the Apache FOP jars and the jt400.jar in a lib folder which sits alongside the folder in which this project resides.

ANT Build File

<project name="RPG2PDF" default="run" basedir=".">
 <property name="src"     location="src/main/java"/>
 <property name="build"   location="build"/>
 <property name="dist"    location="dist"/>
 <property name="lib.dir" location="../lib/"/>
 <path id="classpath">
  <fileset dir="${lib.dir}" includes="**/*.jar"/>
 </path>
 
 <target name="clean">
  <delete dir="${build}"/>
  <delete dir="${dist}"/>
 </target>
 
 <target name="init" depends="clean">
  <mkdir dir="${build}"/>
  <mkdir dir="${dist}"/>
 </target>
 
 <target name="compile" depends="init">
  <javac srcdir="${src}"
      destdir="${build}"
      classpathref="classpath"/>
 </target>
 
 <target name="dist" depends="compile">
  <jar jarfile="${dist}/${ant.project.name}.jar" basedir="${build}">
   <manifest>
    <attribute name="Main-Class" value="PdfTransformer"/>
   </manifest> 
  </jar>
 </target>
 
 <target name="run" depends="dist">
  <java fork="true"
   classname="RpgPdfCreator">
   <classpath>
    <path refid="classpath"/>
    <path location = "${dist}/${ant.project.name}.jar"/>
   </classpath>
  </java>
 </target>
 
</project>

Main Class of JAR File

import java.io.File;

public class RpgPdfCreator {
 public static void main(String... args) throws Exception {
  String srcFile = "UNBKT8F4/QRPGLESRC";
  String srcMember = "FEE700R";
  if (args.length != 0) {
   srcFile = args[0];
   srcMember = args[1];
  }
  File file = RpgReader.getData(srcFile, srcMember);
  PdfTransformer.createPdf(file);
 }
}

Read the RPG Source Code By Executing SQL

import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.DriverManager;
import java.sql.SqlException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.File;

public class RpgReader {
 static Connection conn;
 
 public static Connection setConn() 
    throws SQLException, ClassNotFoundException, IOException {
  String AS400SYSTEM="AS400DEV";
  Class.forName("com.ibm.as400.access.AS400JDBCDriver");
  conn = DriverManager.getConnection("jdbc:as400://" + AS400SYSTEM + ";naming=system;prompt=true");
  return conn;
 }
 
 public static File getData(String srcFile, String srcMember) throws Exception {
  Statement stmt = null;
  ResultSet rs = null;
  try {
   conn = RpgReader.setConn();
   stmt = conn.createStatement();
   String sql = "create alias qtemp/myalias for " + srcFile + "(" + srcMember + ")";
   stmt.execute(sql);
   rs = stmt.executeQuery("select srcdta from myalias");
   File xmlFile = RpgToXml.getXmlFromResultSet(rs);
   return xmlFile;
  } finally {
   try {
    rs.close();
   } catch(Exception e) {}
   try {
    stmt.close();
   } catch(Exception e) {}
   try {
    conn.close();
   } catch(Exception e) {}
  }
 }
}

Wrap RPG Code in XML Tags

import java.sql.ResultSet;
import java.io.File;
import java.io.FileOutputStream;

public class RpgToXml {
 static File getXmlFromResultSet(ResultSet rs) throws Exception {
  File file = new File("xml/rpg.xml");
  FileOutputStream fos = new FileOutputStream(file);
  fos.write("<program>\n".getBytes());
  while (rs.next()) {
   String s = rs.getString(1);
   s = s.replaceAll("<", "&lt;")
        .replaceAll(">", "&gt;")
        .replaceAll("&", "&amp;")
        .replaceAll("'", "&apos;")
        .substring(6,80);
   fos.write( ("<line>" + s + "</line>").getBytes() );
  }
  fos.write("</program>".getBytes());
  fos.flush();
  return file;
 }
}

Execute Apache FOP to Create PDF

import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.MimeConstants;
import javax.xml.transform.*;
import javax.xml.transform.sax.*;
import javax.xml.transform.stream.*;
import java.io.*;

/* Here is the basic pattern to render an XSL-FO file to PDF */
public class PdfTransformer {
 public static void createPdf(File xmlFile) throws Exception {
  OutputStream out = null;
  try {
   out = new BufferedOutputStream(
                 new FileOutputStream(new File("rpg.pdf"))); 
   Source xsl = new StreamSource(new File("xml/rpg.xsl"));
   Source src = new StreamSource(xmlFile);
   
   TransformerFactory factory = TransformerFactory.newInstance();
   Transformer transformer = factory.newTransformer(xsl);
   
   FopFactory fopFactory = FopFactory.newInstance();
   Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out);
   Result result = new SAXResult(fop.getDefaultHandler());
   
   transformer.transform(src, result);
   
  } finally {
   try {
    out.close();
   } catch(Exception e) {} 
  }
 }
}

The Stylesheet, rpg.xsl

<?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:output method="xml" indent="yes"/>
 <xsl:param name="page-width">8.5in</xsl:param>
 <xsl:param name="page-height">11in</xsl:param>
 <xsl:param name="margin">2cm</xsl:param>

 <xsl:template match="/">
    <fo:root>
       <fo:layout-master-set>
          <fo:simple-page-master master-name="A4-portrait" 
                 page-height="{$page-height}" 
                 page-width="{$page-width}" 
                 margin="{$margin}">
              <fo:region-body/>
          </fo:simple-page-master>
       </fo:layout-master-set>
       
       <fo:page-sequence master-reference="A4-portrait">
          <fo:flow flow-name="xsl-region-body">
             <xsl:apply-templates/> 
          </fo:flow>
       </fo:page-sequence>
    </fo:root>
 </xsl:template>
 
  <xsl:template match="program">          
    <fo:block>
      <xsl:apply-templates/>
    </fo:block>
  </xsl:template>

  <xsl:template match="line">           
    <fo:block font-family="monospace" white-space-collapse="false">
      <xsl:apply-templates/>
    </fo:block>
  </xsl:template>
</xsl:stylesheet>

Notes

For this example, I chose a simple, less robust design.  Fewer parameters and more hard-coding make for a more concrete example which is easier to follow.  The first thing to change is the name of the PDF that is produced to the name of the source member.  This change should be made in PdfTransformer, the class that produces the PDF.  Another change you may want to consider is landscape printing.  You may find that some lines of RPG code are continued on another line in the PDF.  You can exchange the width and height parameters in the XSL to get landscape format.

I think the next thing to do with this code is to provide Google-like search capabilities of the code in the PDFs by adding Apache Lucene into the mix.  The user should be able to request a search of the RPG code in the PDFs and receive a list of the PDFs in which any of the search terms appears.  Apache Lucene can be run in a simple script just like Apache FOP.  Of course, the documents won't be searched when the user makes his request.  The search will essentially have been completed before the user requests it.  We will use Apache Lucene to avoid costly document scans by building the search index as the RPG is read for the production of the PDF.