/*
 */
import java.io.*;
import java.util.*;
import org.w3c.dom.*;
//import org.apache.xerces.parsers.*;	// DOMParser, if you're using Xerces
import com.ibm.xml.parsers.*;	// DOMParser, if you're using XML4j (Notes agent)

/**
 * This class allows you to access the LotusScript from an XML/DXL document
 * that was created using the Lotus XML Toolkit (normally, the LotusScript
 * in Agents and Script Libraries is encoded on the DXL documents). I tested
 * it using XML/DXL documents created with the dxlexport program that comes
 * with the XML Toolkit samples.
 * 

* By running this as a stand-alone program, you can export the LotusScript * from an existing DXL document to either the console or to a file. If you * call it from another Java class, you can send the information to a * PrintWriter of your choosing. Or you can just modify the decodeScript * class to send the information back however you want. *

* In this version of the class (0.95), I used basic Node functionality to parse * the XML document instead of using JDom, so that you could use all of this * easily within a Notes agent as well as in a stand-alone program. It's a bit * more code this way, but it keeps the external dependencies down. I also * wrote my own Base64 decode method, because the Base64 functions weren't * included in the XML4j distribution that ships with the version of Notes that * I was testing with. *

* A few things you might notice:
* 1. If you export the information on a Windows machine, the line terminators * are Unix line terminators, not DOS line terminators. That's just how the * script comes out of the DXL, so I tried to stay consistent with the other * information coming out of this class. Changing the line terminators from * Unix to DOS should be fairly trivial if it really bothers you.

* 2. Sometimes you'll get an agent where the LotusScript is just a bunch of * non-ASCII characters. That's usually because the agent is really formula- * language, not LotusScript. The itemdata type is the same for both. You * can always check the $Flags and filter non-LotusScript elements that way * if you'd like.

* 3. The LotusScript is preceded by the XML elements that lead up to the script * so you can figure out where the script resides. As I'm building the * element list in this class, I add $TITLE and $Flags attributes to the * elements whenever possible, to make it that much easier to navigate. In * the original DXL output, $TITLE and $Flags will be individual elements, * not element attributes. I was trying to help.

* 4. You'll get an error if the domino.dtd file is not in the current directory * or on the PATH. This file is included in the Lotus XML Toolkit. If you're * running this code within a Notes agent, it should be in your Domino program * directory. * * @author Julian Robichaux ( http://www.nsftools.com ) * @version 0.9.2 */ public class DxlScript2 { public static String separator = "\n<=============================================>\n"; public static void main(String args[]) { String inFileName = ""; String outFileName = ""; PrintWriter out; String inString = ""; String outString = ""; switch (args.length) { case 0: System.out.println("USAGE: DxlScript2 InputFileName [OutputFileName]\n"); System.out.println("This program will decode the encoded LotusScript from"); System.out.println("a Notes DXL file. If no OutputFileName is provided,"); System.out.println("output will be sent to the console."); return; case 1: inFileName = args[0]; break; case 2: default: inFileName = args[0]; outFileName = args[1]; break; } try { if (outFileName.length() > 0) out = new PrintWriter( new FileWriter(outFileName) ); else out = new PrintWriter(System.out); decodeScript(inFileName, out); out.close(); } catch (Exception e) { e.printStackTrace(); } } /** * Gets the LotusScript from your DXL file. * * @param fileName the name of the DXL file we're getting information from * @param out a PrintWriter to send the resulting information to */ public static void decodeScript (String fileName, PrintWriter out) { try { // build the DOM document DOMParser parser = new DOMParser(); parser.parse(fileName); org.w3c.dom.Document doc = parser.getDocument(); // get the root Node root = doc.getDocumentElement(); // get the LotusScript beneath the root findScriptElements(root, out, "<" + root.getNodeName() + getAttr(root) + ">"); } catch (Exception e) { e.printStackTrace(); } } /** * Private method that recursively finds and decodes the LotusScript * entries beneath a given XML node. * * @param parent the Node we're searching beneath * @param out the PrintWriter to send any LotusScript to * @param branch a String of the Node tags leading up to parent */ private static void findScriptElements(Node parent, PrintWriter out, String branch) { if (parent.hasChildNodes()) { NodeList kids = parent.getChildNodes(); for (int i = 0; i < kids.getLength(); i++) { Node thisKid = kids.item(i); if (thisKid.getNodeType() == Node.ELEMENT_NODE) { if (thisKid.getNodeName().equals("itemdata")) { String kidType = getAttrValue(thisKid, "type"); if ((kidType.equals("10")) || (kidType.equals("500"))) { // itemdata of type 10 is an agent, and type 500 is a // script library. Itemdata is encoded as Base64. String outString = decodeBase64(getNodeText(thisKid)); out.println(branch); // agents have a 14-byte header we need to step over if (kidType.compareTo("10") == 0) out.println(outString.substring(14)); else out.println(outString); out.println(separator); } } else if (thisKid.getNodeName().equals("lotusscript")) { // lotusscript elements (found in forms, views, etc.) are // just straight, unencoded text out.println(branch); out.println(getNodeText(thisKid)); out.println(separator); } else { // we didn't find anything here, so we can make a recursive // call to search the children under this branch (append // the element info to the branch before making the call, // so we know where we came from) StringBuffer tag = new StringBuffer("<" + thisKid.getNodeName()); tag.append(getAttr(thisKid)); // so we know what we're dealing with... // grab the Title of this branch, if there is one String title = getItem(thisKid, "$TITLE"); if (title.length() > 0) tag.append(" title='" + title + "'"); // and try to get the Flags too String flags = getItem(thisKid, "$Flags"); if (flags.length() > 0) tag.append(" flags='" + flags + "'"); tag.append(">"); findScriptElements(thisKid, out, branch + "\n" + tag.toString()); } } } } } /** * Returns a String of all the attributes of a given Node. * * @param parent the Node you want the attributes of * @return an attribute String you can use in an element tag */ private static String getAttr (Node n) { StringBuffer tags = new StringBuffer(); NamedNodeMap nodeMap = n.getAttributes(); if (nodeMap != null) { for (int i = 0; i < nodeMap.getLength(); i++) { Node nm = nodeMap.item(i); tags.append(" " + nm.getNodeName() + "=\"" + nm.getNodeValue() + "\""); } } return tags.toString(); } /** * Returns the value of a given "item" Node beneath the given Node. * * @param parent the Node that should contain the item as a sub-Node * @param itemName the "name" Attribute the item should have * @return either the value of the item as a String, or an empty * String ("") if the item is not found */ private static String getItem (Node parent, String itemName) { String value = ""; if (parent.hasChildNodes()) { NodeList kids = parent.getChildNodes(); for (int i = 0; i < kids.getLength(); i++) { Node thisKid = kids.item(i); if (thisKid.getNodeType() == Node.ELEMENT_NODE) { String nodeName = thisKid.getNodeName(); String nodeAttr = getAttrValue(thisKid, "name"); if (nodeName.equalsIgnoreCase("item") && nodeAttr.equalsIgnoreCase(itemName)) { NodeList grandKids = thisKid.getChildNodes(); for (int j = 0; j < grandKids.getLength(); j++) { Node thisGKid = grandKids.item(j); if (thisGKid.getNodeType() == Node.ELEMENT_NODE) { if (thisGKid.getNodeName().equals("text")) { value = getNodeText(thisGKid); break; } } } break; } } } } return value; } /** * Returns the value of a given Attribute of the given Node. * * @param parent the Node that you're looking at * @param attrName the Attribute you're looking for * @return either the value of the Attribute as a String, or an empty * String ("") if the Attribute is not found */ private static String getAttrValue (Node parent, String attrName) { String value = ""; NamedNodeMap nodeMap = parent.getAttributes(); if (nodeMap != null) { for (int i = 0; i < nodeMap.getLength(); i++) { Node att = nodeMap.item(i); if (att.getNodeName().equalsIgnoreCase(attrName)) { value = att.getNodeValue(); break; } } } return value; } /** * Returns the text the given Node, if any. If there are multiple text items * for this Node (perhaps separated by sub-Nodes), all the items are concatenated * into a single string separated by spaces. * * @param parent the Node that you're looking at * @return a concatenation of all text values of this Node, or an empty * String ("") if there is no text */ private static String getNodeText (Node parent) { StringBuffer sb = new StringBuffer(""); if (parent.hasChildNodes()) { NodeList kids = parent.getChildNodes(); for (int i = 0; i < kids.getLength(); i++) { Node thisKid = kids.item(i); if ((thisKid.getNodeType() == Node.TEXT_NODE) && (thisKid.getNodeValue() != null)) sb.append(thisKid.getNodeValue() + " "); } } return sb.toString().trim(); } /** * Attempts to decode a Base64-encoded String. I started to write this routine * myself, but then I saw some JavaScript code at: * http://www.mrb411.com/javascript/convert/base6401.html * that translated over pretty nicely into a compact function, so I used that * code instead. It could probably do with a little more error checking. * * @param parent the Node that you're looking at * @return a concatenation of all text values of this Node, or an empty * String ("") if there is no text */ private static String decodeBase64 (String encString) { String b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; StringBuffer encSB = new StringBuffer(""); StringBuffer decText = new StringBuffer(""); int encNum = 0; // trim whitespace first for (int i = 0; i < encString.length(); i++) { if (!Character.isWhitespace(encString.charAt(i))) encSB.append(encString.charAt(i)); } encString = encSB.toString(); try { for (int i = 0; i < encString.length(); i+=4) { encNum = (b64chars.indexOf(encString.charAt(i)) & 0xFF) << 18; encNum |= (b64chars.indexOf(encString.charAt(i+1)) & 0xFF) << 12; encNum |= (b64chars.indexOf(encString.charAt(i+2)) & 0xFF) << 6; encNum |= (b64chars.indexOf(encString.charAt(i+3)) & 0xFF) << 0; decText.append((char)((encNum & 0xFF0000) >> 16)); if (encString.charAt(i + 2) != '=') decText.append((char)((encNum & 0xFF00) >> 8)); if (encString.charAt(i + 3) != '=') decText.append((char)((encNum & 0xFF) >> 0)); } } catch (Exception e) {} return decText.toString(); } }