With a little side of applesauce...

Tuesday, August 26, 2008

Coldfusion / OpenDocument Format - starter library for working with OpenDocument zip files

This is my beginnings of a library of functions to work with OpenDocument Format text files. It isn't much, but can probably be used to extrapolate out how to update the zip files, create the shell files needed for the zip, etc. This is largely based on the createodt.cfm, whose author and site I can't seem to find right now. (My humble thanks :) ).

<cfcomponent name="OpenDocumentFormat"
displayname="OpenDocumentFormat"
hint="Creates and manipulates OpenDocument Format files">

<cffunction name="createWorkingDirectories"
returnType="string"
access="public"
hint="create the working directories for building the ODF file">
<!--- create working directories --->
<cfset errorMessage = "">
<cftry>
<cfdirectory action="create" directory="#Session.newFormDirectory#/odt">
<cfdirectory action="create" directory="#Session.newFormDirectory#/odt/META-INF">
<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>
</cffunction>

<cffunction name="getDocumentCreationDateTime"
returnType="string"
access="public"
hint="get the datetime for the creation of the document">

<cfset filedate = CreateDateTime(Year(Now()),Month(Now()),Day(Now()),Hour(Now()),Minute(Now()),Second(Now()))>

<cfreturn filedate>
</cffunction>

<cffunction name="setDocumentContent"
returnType="string"
access="public"
hint="get the datetime for the creation of the document (TODO -- this needs to be more robust">
<cfargument name="myDocumentContent" default="testContent" required="no">


<cfif not IsStruct( myDocumentContent )>

<cfset docbody = '
<text:p text:style-name="right-align">#DateFormat(Now(),"dddd d, yyyy")#</text:p>

<text:p></text:p>

<text:p text:style-name="centered">
ColdFusion ODF Generation
</text:p>

<text:p></text:p>

<text:p text:style-name="Standard">
See how easy this document generation stuff is???
</text:p>
'>
<cfelse>

<!--- we need to get givenname and sn for the users --->
<cfset docheader = '
<text:p text:style-name="right-align">#DateFormat(Now(),"dddd d, yyyy")#</text:p>
<text:p></text:p>
<text:p text:style-name="centered">
#Session.appTitle# #Replace( Session.FormName , "_", " ", "ALL" )#
</text:p>
<text:p text:style-name="centered">
#Session.userfullname#
</text:p>
<text:p></text:p>'>

<!--- let's create the body --->
<cfset docbodytext = ''>
<!--- loop through forms and values here --->
<cfloop collection="#myDocumentContent#" item="key">
<cfif key NEQ "FIELDNAMES">
<cfset docbodytext = docbodytext & '<text:p text:style-name="Standard">#key#: #myDocumentContent[key]#</text:p> <br />'>
</cfif>
</cfloop>

<cfset docfooter = ''>

<cfset docbody = docheader & docbodytext & docfooter>
</cfif>

<cfreturn docbody>
</cffunction>

<cffunction name="createGenericManifestXml"
returnType="string"
access="public"
hint="creates a generic META-INF/manifest.xml file">
<cfset errorMessage = "">
<cftry>
<!--- META-INF/manifest.xml (done) --->
<cffile action="write" file="#Session.newFormDirectory#/odt/META-INF/manifest.xml"
output='<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0">
<manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.text" manifest:full-path="/"/>
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/>
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml"/>
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/>
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml"/>
</manifest:manifest>
'>
<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>
</cffunction>

<cffunction name="createGenericContentXml"
returnType="string"
access="public"
hint="creates a generic content.xml file">
<cfargument name="docBody" type="string">

<cfset errorMessage = "">
<cftry>
<!--- content.xml --->
<cffile action="write" file="#Session.newFormDirectory#/odt/content.xml"
output='<?xml version="1.0" encoding="UTF-8"?>
<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
xmlns:math="http://www.w3.org/1998/Math/MathML"
xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"
xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"
xmlns:dom="http://www.w3.org/2001/xml-events"
xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" office:version="1.0">

<office:automatic-styles>

<style:style style:name="P1" style:family="paragraph">
<style:paragraph-properties fo:text-align="start"/>
</style:style>

<style:style style:name="P2" style:family="paragraph">
<style:paragraph-properties fo:text-align="end"/>
</style:style>

<style:style style:name="P3" style:family="paragraph">
<style:paragraph-properties fo:text-align="left"/>
</style:style>

<style:style style:name="right-align" style:family="paragraph">
<style:paragraph-properties fo:text-align="right"/>
</style:style>

<style:style style:name="centered" style:family="paragraph">
<style:paragraph-properties fo:text-align="center"/>
</style:style>

<style:style style:name="P6" style:family="paragraph">
<style:paragraph-properties fo:text-align="justify"/>
</style:style>

</office:automatic-styles>

<office:body>
<office:text>
#docBody#
</office:text>
</office:body>
</office:document-content>
'>

<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>

</cffunction>


<cffunction name="createGenericMetaXml"
returnType="string"
access="public"
hint="creates a generic meta.xml file">
<cfargument name="filetitle" type="string" default="000_document.xml">
<cfargument name="fileauthor" type="string">
<cfargument name="filedate" type="string">

<cfset errorMessage = "">
<cftry>
<!--- meta.xml --->
<cffile action="write" file="#Session.newFormDirectory#/odt/meta.xml"
output='<?xml version="1.0" encoding="UTF-8"?>
<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" office:version="1.0">
<office:meta>
<meta:generator>
#filetitle#
</meta:generator>

<meta:initial-creator>
#fileauthor#
</meta:initial-creator>

<meta:creation-date>
#DateFormat(filedate,"yyyy-mm-dd")#T#TimeFormat(filedate,"hh:mm:ss")#
</meta:creation-date>

<dc:creator>#fileauthor#</dc:creator>
<dc:date>#DateFormat(filedate,"yyyy-mm-dd")#T#TimeFormat(filedate,"hh:mm:ss")#</dc:date>
<dc:language>en-US</dc:language>

<meta:editing-cycles>1</meta:editing-cycles>
<meta:editing-duration>PT19S</meta:editing-duration>

<meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0"
meta:page-count="1" meta:paragraph-count="1" meta:word-count="12" meta:character-count="69"/>
</office:meta>
</office:document-meta>
'>

<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>
</cffunction>


<cffunction name="createMimetype"
returnType="string"
access="public"
hint="creates a mimetype file. Default is application/vnd.oasis.opendocument.text">
<cfargument name="mimetype" type="string" default="application/vnd.oasis.opendocument.text">

<cfset errorMessage = "">
<cftry>
<!--- mimetype --->
<cffile action="write" file="#Session.newFormDirectory#/odt/mimetype" output="#mimetype#">
<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>
</cffunction>


<cffunction name="createGenericSettingsXml"
returnType="string"
access="public"
hint="creates a settings.xml file.">

<cfset errorMessage = "">
<cftry>
<!--- settings.xml --->
<cffile action="write" file="#Session.newFormDirectory#/odt/settings.xml"
output='<?xml version="1.0" encoding="UTF-8"?>
<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"
office:version="1.0">
<office:settings>
</office:settings>
</office:document-settings>
'>
<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>

</cffunction>

<cffunction name="createGenericStylesXml"
returnType="string"
access="public"
hint="creates a generic Styles.xml file.">

<!--- styles.xml --->
<cfset errorMessage = "">
<cftry>
<cffile action="write" file="#Session.newFormDirectory#/odt/styles.xml"
output='<?xml version="1.0" encoding="UTF-8"?>
<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
xmlns:math="http://www.w3.org/1998/Math/MathML"
xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"
xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"
xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
office:version="1.0">

<office:automatic-styles>
<style:page-layout style:name="PageLayout-1" >
<style:page-layout-properties fo:page-width="8.5in" fo:page-height="11.0in" fo:margin-top="1.0in" fo:margin-bottom="1.0in" fo:margin-left="1.0in" fo:margin-right="1.0in" style:print-orientation="portrait"/>
</style:page-layout>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="PageLayout-1" >
</style:master-page>
</office:master-styles>

</office:document-styles>
'>

<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>
</cffunction>

<cffunction name="zipOpenDocument"
returnType="string"
access="public"
hint="zips up the OpenDocument files. args -- filename">
<cfargument name="filename" type="string" default="000_document.odt">

<cfset errorMessage = "">
<cftry>
<!--- zip up files as odt --->
<cfzip action="zip" source="#Session.newFormDirectory#/odt" file="#Session.newFormDirectory#/#filename#" />
<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>

</cffunction>


<cffunction name="zipReadFile"
returnType="string"
access="public"
hint="grabs <filename> from the zip file, and returns an XmlObject.">
<cfargument name="filename" type="string" default="content.xml">
<cfargument name="path" type="string">

<cfset errorMessage = "">
<cftry>
<!--- Read in the contents of content.xml --->
<cfzip action="read" file="#path#" entrypath="#filename#" variable="xmlContent">
<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
<cfreturn errorMessage>
</cfcatch>
</cftry>

<cfreturn xmlContent>

</cffunction>


<cffunction name="zipUpdateFile"
returnType="string"
access="public"
hint="updates a zip file at <path> with the <filename> with the content of the <xmlComment> struct.">
<cfargument name="filename" type="string" default="content.xml">
<cfargument name="path" type="string">
<cfargument name="xmlObject" type="xml">


<cfset errorMessage = "">
<cftry>
<!--- update the zip file with new content --->
<cfzip file = "#path#" action = "zip">
<cfzipparam content="#xmlObject#" entrypath="#filename#">
</cfzip>

<cfzip action="read" file="#path#" entrypath="#filename#" variable="xmlContent">
<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
<cfreturn errorMessage>
</cfcatch>
</cftry>

<cfreturn xmlContent>

</cffunction>


<cffunction name="deleteWorkingDirectory"
returnType="string"
access="public"
hint="deletes our pre-zip files.">
<cfset errorMessage = "">
<cftry>
<!--- delete working directory --->
<cfdirectory action="delete" directory="#Session.newFormDirectory#/odt" recurse="true">

<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>
</cffunction>



<cffunction name="sendFile"
returnType="string"
access="public"
hint="deletes our pre-zip files. (Should this be in Utilities.cfc??">
<cfargument name="filename" type="string" default="000_document.odt">
<cfargument name="mimetype" type="string" default="application/vnd.oasis.opendocument.text">

<cfset errorMessage = "">
<cftry>
<!--- send file to browser --->
<cfheader name="content-disposition" value="attachment; filename=#filename#">
<cfcontent file="#filename#" type="application/vnd.oasis.opendocument.text" deletefile="true">
<cfcatch type="Any">
<cfset errorMessage = #cfcatch.message#>
</cfcatch>
</cftry>

<cfreturn errorMessage>
</cffunction>

<cffunction name="createOpendocumentFormatStandard"
returnType="string"
access="public"
hint="used for most instances of creating an OpenDocument file (text). Acts as a glue function.">
<cfargument name="formContents">
<cfargument name="filename" type="string" default="000_document.odt">
<cfargument name="mimetype" type="string" default="application/vnd.oasis.opendocument.text">


<!--- so we create everything, but if anything fails, we need to clean-up the problem files, and alert the user to an error --->
<cfset errorMessage = "">
<cftry>
<cfset createDir = createWorkingDirectories()>
<cfset createDateTime = getDocumentCreationDateTime()>
<cfset createManifest = createGenericManifestXml()>
<cfset createMeta = createGenericMetaXml()>
<cfset createMimetype = createMimetype()>
<cfset createSettings = createGenericSettingsXml()>
<cfset createStyles = createGenericStylesXml()>
<cfset createDocBody = setDocumentContent(formContents)>
<cfset createDocumentContent = createGenericContentXml(createDocBody)>
<cfset zipOpenDocument = zipOpenDocument()>
<cfset deleteWorkingDirectory = deleteWorkingDirectory()>
<cfcatch type="Any">
<cfset errorMessage = "#cfcatch.message# <br> #cfcatch.detail# <br> #cfcatch.type#">
</cfcatch>
</cftry>

<!--- if errorMessage NEQ "" then we call our clean-up scripts, and kick a message back to the user --->
<!--- TODO --->
<!--- for now, we just return the message --->
<cfreturn errorMessage>
</cffunction>


</cfcomponent>

No comments: