Wednesday, September 16, 2009

FlexPMD Client for Viewing Generated XML files - Source Code

I recently came across FlexPMD, a neat little Java tool that runs through your Flex/AS3/Air code and creates an XML file of all the problems (violations). It should be something everyone does before committing files to their own repository. I didn't see a nice UI tool to view what can sometimes be a massive XML file, so I created a little AIR app that allows a user to select the xml file, navigate the classes, and view the detailed information of each error. Simply copy/paste this MXML code into your Flex Builder 4 AIR application (or remove the 's' and 'fx' namespaces if you're still on Flex 3).



<?xml version="1.0" encoding="utf-8"?>
<!--
    UI Client for viewing PMD xml generated files.
-->
<mx:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"
minWidth="1024" minHeight="768" layout="vertical">
<fx:Script>
<![CDATA[
/** Browse a file to select */
private var file : FileReference;

/** Selected violation to display on right */
[Bindable] private var selectedViolation : XML;

/** Handler for when user selects a node */
protected function tree_itemClickHandler(event:Event):void
{
if(XML(tree.selectedItem).name() == "violation")
selectedViolation = XML(tree.selectedItem);
}

/** Label Function for Tree*/
protected function showLabel(item:Object):String
{
var itemXML : XML = item as XML;
if(item.name() == "violation")
return itemXML;
else
{
var firstChildXML : XML = XML(itemXML.violation[0]);
var numViolations : int = itemXML.violation.length();
return firstChildXML['@package'] + "." + firstChildXML['@class'] + " (" + numViolations + ")";
}
}


/** Load the selected file*/
protected function onSelect(event:Event):void
{
file.load();
}

protected function onComplete(event:Event):void
{
var pdmXmlFile : XML = new XML(file.data);
var allFiles : XMLList = pdmXmlFile.file;
var allErrors : XMLList = pdmXmlFile..violation;
//set tree and labels
resultsLabel.text = allFiles.length() + " classes with " + allErrors.length() + " violations."
tree.dataProvider = allFiles;
//clean up on aisle 3
file.removeEventListener(Event.SELECT, onSelect);
file.removeEventListener(Event.COMPLETE, onComplete);
}


protected function openFileHandler(event:MouseEvent):void
{
file = new FileReference();
file.addEventListener(Event.SELECT,onSelect);
file.addEventListener(Event.COMPLETE,onComplete);
                var arr:Array = [];
                arr.push(new FileFilter("XML", "*.xml"));
file.browse(arr);
}


]]>
</fx:Script>
<mx:HBox width="100%" horizontalAlign="left">
<s:Button label="Open File" click="openFileHandler(event)"/>
<mx:Text text="FlexPMD Client Violations" fontWeight="bold" fontSize="14"/>
<mx:Label id="resultsLabel"/>
</mx:HBox>
<mx:HDividedBox width="100%" height="100%">
<mx:Tree id="tree" width="50%" height="100%" labelField="@name"
itemClick="tree_itemClickHandler(event)"
labelFunction="showLabel"/>
<mx:VBox width="50%" height="100%" >
<mx:HBox width="100%">
<mx:Label text="Package:" fontWeight="bold"/>
<mx:Label text="{this.selectedViolation['@package']}"/>
</mx:HBox>
<mx:HBox width="100%">
<mx:Label text="Class:" fontWeight="bold"/>
<mx:Label text="{this.selectedViolation['@class']}"/>
</mx:HBox>
<mx:HBox width="100%">
<mx:Label text="Problem:" fontWeight="bold" />
<mx:Text text="{this.selectedViolation}" maxWidth="350"/>
</mx:HBox>
<mx:HBox width="100%">
<mx:Label text="Priority:" fontWeight="bold"/>
<mx:Label text="{this.selectedViolation.@priority}"/>
</mx:HBox>
<mx:HBox width="100%">
<mx:Label text="Line Number:" fontWeight="bold"/>
<mx:Label text="{this.selectedViolation.@beginline}"/>
</mx:HBox>
<mx:HBox width="100%">
<mx:Label text="Ruleset:" fontWeight="bold"/>
<mx:Label text="{this.selectedViolation.@ruleset}"/>
</mx:HBox>
<mx:HBox width="100%">
<mx:Label text="Rule:" fontWeight="bold"/>
<mx:Label text="{this.selectedViolation.@rule}"/>
</mx:HBox>
<mx:Spacer height="25"/>
<mx:Label text="Note: Priorities rank from 1-5, 1 being the most severe"/>
</mx:VBox>
</mx:HDividedBox>
</mx:WindowedApplication>
How to Write and Populate XML data into a PDF - Only on the Client.

I am currently on a project where a user will have very limited internet access, and they need the ability to enter form data which in the end, populates a PDF file and writes the PDF to their system (OS). One tends to think the only way to do this is with Java libraries or LiveCycle, but this is not the case. Both xdp and pdfxml are viable solutions.

The end solution I ended up going with is pdfxml. First, you need to install the Adobe Mars plugin for your Adobe Reader or Acrobat (I highly recommend using Reader/Pro version 9+).

In short, you take a PDF form, open it in Acrobat 9 Pro (I used the trial version), and click menu Forms->Add/Edit Fields. Here you can add fields using the field names you chose. Because you installed the Mars plugin for Acrobat 9, you will now see the option in File->Save As for a type "pdfxml". Save it.

Now, in your AIR Flex Builder project, add the pdfxml file to a 'pdfs' directory in your main 'src' directory, so it's included in the AIR file, and load the file like so:

var pdfxmlFile : File = File.applicationDirectory.resolvePath("pdfs/myFile.pdfxml");
mars = new MarsPackage(pdfxmlFile, this); //this being the mxml this code sits on

The MarsPackage class is from the Adobe examples source code. Adobe doesn't explicitly give you the Actionscript to parse out the XML, but I was able to piece it together. I would post the code, but I don't want to step on Adobe's toes since it's 99% their code.

To really understand how PDFXML works, install Adobe's PDFXML Inspector air app. Run the app and click File->Open and open either the pdfxml you created in Acrobat Pro or one of the sample pdfxml files from the Mars website. Click the 'form' node on the tree and then the 'form_data.xdfd' node. You'll see the XML data load to the right. If you don't see any data (fields) for the pdfxml you created, open your pdfxml in Pro, add a blank space or data, re-save it, and then re-open it in the PDFXML Inspector.

From this AIR app, you can udpate the data, and if you re-open the pdfxml in Reader/Pro..you'll see the data. No server needed!!

Note: If you add the fields with the LiveCycle Designer, the data instead is saved as xml...which I have had a lot of inconsistency with opening properly in Reader. Sometimes it works, sometimes it doesn't. Either way, use the 'Edit Fields' in Acrobat Pro and avoid Livecycle Designer and you will be worry free.