Friday, April 24, 2009

ColdFusion: In search of Word/RTF to PDF Converters

So I have foolishly wandered into the open source jungle, in search of non-commercial Word/RTF to PDF converters. While I have not found the holy grail (or perfect solution) yet, it has been interesting learning about some of the different projects. I particularly liked the flying saucer project. (Though it essentially serves the same purpose as cfdocument, you have gotta' love that name!)

For those that have already gone down this route, you probably will not read anything new here. But I thought I would write up some of my findings and experiments in the event it is helpful to others on a similar quest.

Where to start
I rolled the dice and started with google and sourceforge.net. After looking over about a gazillion projects I came to the conclusion there were two main possibilities:

  1. Using a single application (typically an executable) to do a direct conversion: rtf to pdf
  2. Using a combination of tools: converting rtf to html/xml -> then html/xml to pdf
I decided to try a few approaches and compare the results, pros and cons of each. The tools I tested were a combination of old and new. I suspected some would work better than others, but decided to try them all for comparison purposes. Note, the advantages and disadvantages are just my own opinions based on my tests.

...Read More

Embedding PDF form in CFDOCUMENT - java.lang.NullPointerException

I came across an interesting post on the adobe forums, about embedding a pdf form in cfdocument. Never having that exact need before, I was a little surprised to discover it was possible.

But when I tested it, I did encounter one issue. As mentioned in the documentation, you must use at least one cfdocumentsection tag alongside the cfpdfform tag. But apparently it also needs to be placed before the cfpdform tag. When I placed it after cfpdfform tag and tried to write the output to a file, an error occurred

An exception occurred when performing document processing. The cause of this exception was that:

java.lang.NullPointerException at com.adobe.internal.pdftoolkit.services.manipulations.impl.PMMPages.propagateAnnots(Unknown Source)


Save pdf to a file:

<!---
This FAILS
--->
<cfdocument format="pdf" filename="#pathToOutputFile#" overwrite="true">
<cfpdfform action="populate" source="#pathToForm#">
<cfpdfformparam name="firstName" value="Cookie">
<cfpdfformparam name="lastName" value="Monster">
</cfpdfform>
<cfdocumentsection>
<p>Some content</p>
</cfdocumentsection>
</cfdocument>

<!---
This works
--->
<cfdocument format="pdf" filename="#pathToOutputFile#" overwrite="true">
<cfdocumentsection>
<p>Some content</p>
</cfdocumentsection>
<cfpdfform action="populate" source="#pathToForm#">
<cfpdfformparam name="firstName" value="Cookie">
<cfpdfformparam name="lastName" value="Monster">
</cfpdfform>
</cfdocument>

When I attempted to display the pdf in the browser, no output appeared in Firefox. Though when I checked the CF logs they contained a java.lang.NullPointerException error.

Display in browser:

<!---
This FAILS
--->
<cfdocument format="pdf">
<cfpdfform action="populate" source="#pathToForm#">
<cfpdfformparam name="firstName" value="Cookie">
<cfpdfformparam name="lastName" value="Monster">
</cfpdfform>
<cfdocumentsection>
<p>Some content</p>
</cfdocumentsection>
</cfdocument>

<!---
This works
--->
<cfdocument format="pdf">
<cfdocumentsection>
<p>Some content</p>
</cfdocumentsection>
<cfpdfform action="populate" source="#pathToForm#">
<cfpdfformparam name="firstName" value="Cookie">
<cfpdfformparam name="lastName" value="Monster">
</cfpdfform>
</cfdocument>

The one exception was when I saved the output to a variable. That did work without error.

Save output to variable:

<!---
This works
--->
<cfdocument format="pdf" name="pdfOutput">
<cfpdfform action="populate" source="#pathToForm#">
<cfpdfformparam name="firstName" value="Cookie">
<cfpdfformparam name="lastName" value="Monster">
</cfpdfform>
<cfdocumentsection>
<p>Some content</p>
</cfdocumentsection>
</cfdocument>

So I guess the options are either move the cfdocumentsection before the cfpdfform tag, or write the output to a variable.

iText - Preview of things to come .. someday (RTF to PDF)

Update November 19, 2009: Unfortunately, it looks like RTF will be abandoned and moved to an incubator project.
http://www.mail-archive.com/itext-questions%40lists.sourceforge.net/msg47892.html

So I have been searching around for non-commercial tools for converting rtf/word documents to pdf. (Yes, I know some of you are chuckling as you read this). The search has been interesting, and though I have not found the magic bullet yet, I did learn about some neat tools along the way, which I may write about later.

While commericial tools are probably still the best option at this point, I did come across some promising updates on the iText site. They mentioned expanding the rtf functionality to include partial support for
  • reading rtf files
  • converting rtf to pdf format.

Of course it is still under development right now. But I am looking forward to the first official release. I played around with version 2.1.5 a bit, and surprisingly I was actually able to convert an rtf file to pdf. Now since the feature is not finished, I was pleased to get any output at all. Mind you some of the rtfs I tested worked a bit better than others. A few were a bit garbled, but I am impressed. It is definitely coming along nicely. I do not know where the developers find the time ..

Now, examples for the new RTF jars are understandably a little sparse at this point. (The code has undergone some drastic changes). But for the curious, here is the code I came up with. Keep in mind I am still learning from the api. So do not take this as a model for correct code usage ;)

Java Example

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.rtf.parser.RtfParser;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class ConvertRTFToPDF {


public static void main(String[] args) {
String inputFile = "sample.rtf";
String outputFile = "sample_converted.pdf";

// create a new document
Document document = new Document();

try {
// create a PDF writer to save the new document to disk
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(outputFile));
// open the document for modifications
document.open();

// create a new parser to load the RTF file
RtfParser parser = new RtfParser(null);
// read the rtf file into a compatible document
parser.convertRtfDocument(new FileInputStream(inputFile), document);

// save the pdf to disk
document.close();

System.out.println("Finished");

} catch (DocumentException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

}

ColdFusion
Requirements: iText 2.1.5 and JavaLoader

Instructions for installing a newer version of iText without breaking ColdFusion


<h1>Convert RTF to PDF Example</h1>
As of 04/24/2009, the feature is <b>not</b> fully implemented
in iText. (In other words, do not expect it to work perfectly at this
time)<br><hr>

<cfscript>
savedErrorMessage = "";

// get a reference to the javaLoader
javaLoader = server[application.myJavaLoaderKey];

// initliaze file paths
pathToInputFile = ExpandPath("./sample.rtf");
pathToOutputFile = ExpandPath("./sample.pdf");

// create a new document
document = javaLoader.create("com.lowagie.text.Document").init();

try {
// create a PDF writer to save the new document to disk
outStream = createObject("java", "java.io.FileOutputStream").init( pathToOutputFile );
PdfWriter = javaLoader.create("com.lowagie.text.pdf.PdfWriter");
writer = PdfWriter.getInstance( document, outStream );

// open the document for modifications first
document.open();

// create a new parser to load the RTF file
parser = javaLoader.create("com.lowagie.text.rtf.parser.RtfParser").init( javacast("null", "") );

// read the rtf file into the document
inStream = createObject("java", "java.io.FileInputStream").init( pathToInputFile );
parser.convertRtfDocument( inStream, document );

// save the converted document (ie pdf) to disk
document.close();

WriteOutput("Finished! File saved: "& pathToOutputFile);

}
catch (Exception e) {
savedErrorMessage = e;
}

// always close the streams
if ( isDefined("inStream") )
{
inStream.close();
}
if ( isDefined("outStream") )
{
outStream.close();
}
</cfscript>

<!--- show any errors --->
<cfif len(savedErrorMessage) gt 0>
Error - unable to create document
<cfdump var="#savedErrorMessage#">
</cfif>

Tuesday, April 21, 2009

SOT: Watch those java jars (follow the bouncing server)

Well this is a new one for me. Earlier I was testing a java jar I had read about on an older blog entry. But when I ran the test code the sample output I was expecting never appeared. Just a lot of whitespace and the standard debug output. At first I thought maybe I had used the wrong parameters or that the jar had changed. But there was no error anywhere. Not even in the logs. At least as far as I could tell.

Finally, I checked the windows services panel and noticed the ColdFusion 8 service said "Starting ...". After it started, I ran the script again and re-checked the services panel. Sure enough the status of the ColdFusion service was back to "Starting..". What the heck ...?

After scratching my head for a few minutes, I finally checked the jar source. (Thank goodness for open source!). That is when I noticed System.exit(..) was called in several places, for some reason. Perhaps because it has a gui component as well. So instead of throwing an exception, the code shut down the jvm ... and of course the ColdFusion Server! Shutting down might be okay for desktop usage, but it is usually an undesirable behavior on a server.

Today's note to self: Always check the java source of new jars. Especially those with gui components. Bad System.exit(..)!

Sunday, April 19, 2009

ColdFusion: Small tip for using java objects

While searching the documentation the other day, I came across this small item that falls under the "I cannot believe I never knew that" category.

ColdFusion can automatically invoke getPropertyName() and setPropertyName(value) methods if a Java class conforms to the JavaBeans pattern. As a result, you can set or get the property by referencing it directly, without having to explicitly invoke a method.

In other words, in some cases you can just use myObject.propertyName instead of myObject.getPropertyName() or myObject.setPropertyName(). I do not know that I will use it just yet. But it is good to know, and might come in handy down the road ;)


Example using java.net.URL

<cfset urlString = "http://www.google.com/intl/en_ALL/images/logo.gif">
<cfset imageURL = createObject("java", "java.net.URL").init(urlString)>
<cfset img = createObject("java", "javax.imageio.ImageIO").read(imageURL)>
<cfdump var="#imageURL#" label="Methods of java.net.URL class"><br>

<b>Using Methods: getPropertyName</b><hr>
<cfoutput>
imageURL.getHost() = #imageURL.getHost()#<br>
imageURL.getPath() = #imageURL.getPath()#<br>
imageURL.getPort() = #imageURL.getPort()#<br>
imageURL.getProtocol() = #imageURL.getProtocol()#<br>
imageURL.getFile() = #imageURL.getFile()#<br>
</cfoutput>
<br><br>

<b>Using Properties: PropertyName</b><hr>
<cfoutput>
imageURL.Host = #imageURL.Host#<br>
imageURL.Path = #imageURL.Path#<br>
imageURL.Port = #imageURL.Port#<br>
imageURL.Protocol = #imageURL.Protocol#<br>
imageURL.File = #imageURL.File#<br>
</cfoutput>

Friday, April 17, 2009

MS SQL 2005: More on the underused OUTPUT clause

In a previous entry, I mentioned the sometimes under-used OUTPUT clause as an alternative to CF8's result.IDENTITYCOL. If you have used MS SQL for a while, you are no doubt aware of it already. But for those that are not, OUTPUT can be very useful. Especially when working with multiple records.

When manipulating sets of data, I often need to answer questions like "which records were updated?", "what were the original values, before the update?", "which records were was deleted?". The OUTPUT clause lets you do just that.

When using OUTPUT, two special prefixes are available: Inserted and Deleted. The Inserted prefix is used to obtain values that were added. Such as a newly inserted record, or the new value of updated records. The Deleted prefix is used to grab information about deleted records, or the original values of updated records.

Grabbing the new ids of multiple records
Say you are inserting multiple records into a table, and need to grab the ids of the new records. Simply add an OUTPUT clause and use the Inserted prefix, to grab the new ids. You can either return the values as a query, or insert them into another table:

Example


-- Create sample table
-- Use a table variable (no messy clean up)
DECLARE @Customer TABLE
(
CustomerID INT IDENTITY(1,1),
FirstName VARCHAR(50),
LastName VARCHAR(50)
)

--- Create a table for storing the new id's
DECLARE @NewRecords TABLE
(
InsertedCustomerID INT
)

--- A) Insert the new ids into a table variable
INSERT INTO @Customer ( FirstName, LastName )
OUTPUT Inserted.CustomerID INTO @NewRecords ( InsertedCustomerID )
SELECT 'Alan', 'Mercury' UNION ALL
SELECT 'Michelle', 'Dupree' UNION ALL
SELECT 'Josh', 'Echor' UNION ALL
SELECT 'Alan', 'Roberts'

--- Display the id's of the inserted records
SELECT InsertedCustomerID
FROM @NewRecords

--- B) Return the new ids as a resultset
INSERT INTO @Customer ( FirstName, LastName )
OUTPUT Inserted.CustomerID
SELECT 'Alan', 'Mercury' UNION ALL
SELECT 'Michelle', 'Dupree' UNION ALL
SELECT 'Josh', 'Echor' UNION ALL
SELECT 'Alan', 'Roberts'


Finding out which records changed
You can apply the same technique to UPDATE statements. But with updates, you can take advantage of both prefixes: using the Deleted prefix to grab the original values (before the update) and Inserted to grab the new values (after the update).



--- Change a few records
UPDATE @Customer
SET FirstName = 'Adam'
OUTPUT Deleted.CustomerID AS UpdatedCustomerID,
Deleted.FirstName AS OldFirstNameValue,
Inserted.FirstName AS NewFirstNameValue
WHERE FirstName = 'Alan'


Determing which records were deleted
Of course the same applies to DELETE statements. Just use the Deleted prefix to return information about the deleted records.


--- Delete some records randomly
DELETE FROM @Customer
OUTPUT Deleted.CustomerID AS DeletedCustomerID,
Deleted.FirstName AS DeletedFirstName,
Deleted.LastName AS DeletedLastName
WHERE LastName LIKE '%o%'


Just another tool to keep in mind when using 2005 ;)

MS SQL 2005: The under-used OUTPUT clause

While not new, some of the nicer features first introduced with MS SQL 2005 are often overlooked. After spending time working with different, or older, databases I sometimes forget about them myself. One of the more useful features worth remembering is the OUTPUT clause.

If you are not familiar with it, OUTPUT clause allows you to retrieve information about records that were just inserted, updated or deleted. This information can be returned as resultset or even inserted into another table (or table variable). It is incredibly useful when working multiple records, but comes in handy in other situations as well.

result.IDENTITYCOL alternative
One example is with INSERT statements. While ColdFusion 8 introduced the much needed result attribute (which can return the ID of a newly inserted record) it has a few limitations. It only works for single record inserts, and also has a few quirks. The OUTPUT clause offers an interesting alternative to using CF 8's result.IDENTITYCOL value.

Take the simple insert below that adds a record into a table with an identity column. By adding an OUTPUT clause, and using the special Inserted prefix, you can access the id of the newly created record (or any other value in the record).

By default, OUTPUT returns the information as resultset. So you end up with a query containing the new record ID. Just as if you were using a SELECT statement instead. On the "upside", this seems to work consistently with the CF 8, MS SQL JDBC 1.0 and 1.2 drivers, unlike the result attribute. Unfortunately ... it did not work with the jtds driver. At least not in my tests. Still an interesting alternative to result.IDENTITYCOL.

Using OUTPUT


CREATE TABLE Customer
(
CustomerID INT IDENTITY(1,1),
FirstName VARCHAR(50),
LastName VARCHAR(50)
)

<cfquery name="addRecord" datasource="#dsn#">
INSERT INTO Customer ( FirstName, LastName )
OUTPUT Inserted.CustomerID
VALUES
(
<cfqueryparam value="Alan" cfsqltype="cf_sql_varchar"> ,
<cfqueryparam value="Mercury" cfsqltype="cf_sql_varchar">
)
</cfquery>

<cfdump var="#addRecord#" label="Show the new Record ID">


One of these days I will have chart out all of the options, the various issues with each, and see which one equates to the least headaches.

* Improving inserts, updates and deletes with OUTPUT

SOT: jQuery Sparklines

I just came across this cool jQuery plugin for creating sparklines and thought I would pass it along for anyone else living under a jQuery rock like me.

http://omnipotent.net/jquery.sparkline/

Tuesday, April 14, 2009

Concatenating values in SQL

A relatively common need is concatenating a bunch values in a query. While cfoutput with "group" is one option, there are also some elegant sql options. Ones that do not involve temp tables or descending into cursor psychosis. There are different reasons for each, but if you are not aware of the sql options, Barney Boisvert sums them up quite nicely in this recent post at houseoffusion.com:

http://www.houseoffusion.com/groups/cf-talk/thread.cfm/threadid:59194

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep