API/Samples/Groovy/Writer/CleanUpForHTML
About the CleanUpForHTML Example
This is an example shows how to access properties in a Groovy macro or extension using a category (aka "mixin") UNO API helper. It is the CleanUpForHTML macro used in IFCX Wings to compensate for issues with the HTML generated by OpenOffice for Writer documents.
The Code
/**
* IFCX Wings - CleanUpForHTML.groovy macro.
* Copyright 2007-2008 by James P. White. All rights reserved.
* mailto:jim@pagesmiths.com
* http://www.ifcx.org/
*
* This software may be redistributed under the term of the GNU GENERAL PUBLIC LICENSE Version 2.
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* For expert professional services and/or distribution under other terms, contact the author:
*
* James P. White
* mailto:jim@pagesmiths.com
* 26272 Summerhill Lane
* Laguna Hills, CA
* USA 92653
*
*/
package org.ifcx.wings.openoffice
/*
Import standard LibreOffice API classes. For more information on
these classes and the LibreOffice API, see the LibreOffice
Developers Guide at:
http://api.openoffice.org/
*/
import com.sun.star.uno.UnoRuntime
import com.sun.star.uno.XInterface
/*
Import XScriptContext class. An instance of this class is available
to all BeanShell scripts in the global variable "XSCRIPTCONTEXT". This
variable can be used to access the document for which this script
was invoked.
Methods available are:
XSCRIPTCONTEXT.getDocument() returns XModel
XSCRIPTCONTEXT.getDesktop() returns XDesktop
XSCRIPTCONTEXT.getComponentContext() returns XComponentContext
For more information on using this class see the scripting
developer guides at:
http://framework.openoffice.org/scripting/index.html
*/
import com.sun.star.beans.XPropertySet
import com.sun.star.text.XTextRange
import com.sun.star.text.XTextViewCursorSupplier
import com.sun.star.view.XViewCursor
import com.sun.star.text.XParagraphCursor
class UnoCategory {
public static Object uno(Object unoObj, Class clazz) {UnoRuntime.queryInterface(clazz, unoObj)}
public static Object getAt(XPropertySet pset, String pname) {pset.getPropertyValue(pname)}
public static void putAt(XPropertySet pset, String pname, Object newValue) {pset.setPropertyValue(pname, newValue)}
}
/**
* In order for the paragraph style to apply to an entire code block (a run of code
* paragraphs that Wings evaluates as a single block), we find all multiple line
* code blocks and make sure that they are a single paragraph. It is normal for
* code blocks to consist of multiple paragraphs because pressing 'return' starts
* a new paragraph.
*/
def doMergeCode(document) {
def viewCursor = document.currentController.uno(XTextViewCursorSupplier).viewCursor
// We get the XText from the cursor so that if it is from a frame or table it'll work.
def docText = viewCursor.text
XParagraphCursor textCursor = docText.createTextCursorByRange(viewCursor).uno(XParagraphCursor)
// It's a "paragraph" cursor, but may be positioned inside one.
// We assume that since "GroovyCode" is supposed to be a "paragragh" style
// that we'll still be in "GroovyCode" when we go to the beginning.
textCursor.gotoStartOfParagraph(false)
while (true) {
// Code blocks are runs of paragraphs whose style name ends with "Code".
while (!(textCursor.uno(XPropertySet)['ParaStyleName'] =~ /.*Code$/)) {
// println "Skipping ahead."
if (!textCursor.gotoNextParagraph(false)) {
// Can't find another paragraph, so we're done.
return;
}
}
XParagraphCursor codeRange = docText.createTextCursorByRange(textCursor).uno(XParagraphCursor)
def codeStyle = textCursor.uno(XPropertySet)['ParaStyleName']
// Move forward paragraphs until the end of the document,
// or we find a paragraph that isn't "GroovyCode" then move back one.
while (textCursor.gotoNextParagraph(false)) {
if (textCursor.uno(XPropertySet)['ParaStyleName'] != codeStyle) {
if (!textCursor.gotoPreviousParagraph(false)) doError("Couldn't move back (end)!");
break;
}
}
// The cursor is at the start of the paragraph, move it to the end.
textCursor.gotoEndOfParagraph(false);
// Then select all that text as our code.
codeRange.gotoRange(textCursor.end, true)
def codeString = codeRange.string
// println codeString
// the TextRange.getString() will have replaced paragraph end characters with '\n',
// which is the same as line breaks within a paragraph. So we don't really know
// whether we have multiple paragraphs or not, but we don't really care.
if (codeString.contains('\n')) {
// println "Replacing some"
// There are multiple lines, so we replace the paragraph(s) with a single
// paragraph of multiple lines.
codeRange.text.insertString(codeRange, codeString, true)
// The paragraph style is unchanged so we don't need to do anything about that.
}
if (!textCursor.gotoNextParagraph(false))
return
}
} // def doMergeCode...
/**
* When saving as HTML, OpenOffice doesn't take into consideration that all whitespace runs
* will be collapsed into a single space (or line break). That is disaster for most program
* text (code and output) which typically depend on leading whitespace on lines to be preserved.
* So what this method does is replace all leading whitespace on each line of a style that
* looks like it is used for Wings (those whose nanes end with "Code", "Result", "Exception",
* "Output", "Error", or "Input") and replace it with non-breaking spaces.
*/
def doFixFormatting(document) {
def viewCursor = document.currentController.uno(XTextViewCursorSupplier).viewCursor
// We get the XText from the cursor so that if it is from a frame or table it'll work.
def docText = viewCursor.text
XParagraphCursor textCursor = docText.createTextCursorByRange(viewCursor).uno(XParagraphCursor)
// It's a "paragraph" cursor, but may be positioned inside one.
// We assume that since "GroovyCode" is supposed to be a "paragragh" style
// that we'll still be in "GroovyCode" when we go to the beginning.
textCursor.gotoStartOfParagraph(false)
while (true) {
String codeStyle = textCursor.uno(XPropertySet)['ParaStyleName']
// Match style names of interest, if no match then it just falls thru.
switch (codeStyle) {
case ~/.*Code$/:
case ~/.*Result$/:
case ~/.*Exception$/:
case ~/.*Output$/:
case ~/.*Errors$/:
case ~/.*Input$/:
XParagraphCursor codeRange = docText.createTextCursorByRange(textCursor).uno(XParagraphCursor)
// The cursor is at the start of the paragraph, move it to the end.
textCursor.gotoEndOfParagraph(false);
// Then select all that text as our code.
codeRange.gotoRange(textCursor.end, true)
def codeString = codeRange.string
// println codeString
if (codeString.contains(' ') || codeString.contains('\t')) {
// println "Replacing some"
String[] lines = codeString.split('\n')
lines.length.times {
String line = lines[it]
// Treat tabs as eight spaces.
line = line.replace('\t', ' ')
if (line.startsWith(' ')) {
char[] chars = line.toCharArray()
// Replace the leading spaces with non-breaking spaces.
for (int i = 0; i < chars.length; ++i) {
if (' ' != chars[i]) break
chars[i] = '\u00A0'
}
lines[it] = new String(chars)
}
}
codeRange.text.insertString(codeRange, lines.join('\n'), true)
}
}
// println "Skipping ahead."
if (!textCursor.gotoNextParagraph(false)) {
return;
}
}
} // def doFixFormatting...
////////////// BEGIN //////////////
// This is the XModel for the currently active document.
def document = XSCRIPTCONTEXT.document
use(UnoCategory) {
doMergeCode(document)
doFixFormatting(document)
}
0