/**
* CertWare Project
* NASA Langley Research Center
* Kestrel Technology LLC
*/
package net.certware.export.jobs;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import net.certware.core.ICertWareConstants;
import net.certware.core.ui.log.CertWareLog;
import net.certware.export.ExportContributions;
import org.docx4j.XmlUtils;
import org.docx4j.convert.out.flatOpcXml.FlatOpcXmlCreator;
import org.docx4j.convert.out.pdf.PdfConversion;
import org.docx4j.convert.out.pdf.viaXSLFO.Conversion;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.jaxb.NamespacePrefixMapperUtils;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
import org.docx4j.wml.ObjectFactory;
import org.docx4j.wml.R;
import org.docx4j.wml.RPr;
import org.docx4j.wml.Style;
import org.docx4j.wml.Text;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
/**
* Exports a selected argument model element to a document file.
* @author mrb
* @since 1.0
*/
public abstract class AbstractExportJob extends Job {
// protected static String STYLE_MODEL_ELEMENT_CONTENT = "<w:style xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:type=\"character\" w:customStyle=\"1\" w:styleId=\"ModelElementContent\"><w:name w:val=\"Model Element Content\" /><w:basedOn w:val=\"DefaultParagraphFont\" /><w:link w:val=\"Heading3\" /><w:uiPriority w:val=\"9\" /><w:rPr><w:rFonts w:asciiTheme=\"majorHAnsi\" w:hAnsiTheme=\"majorHAnsi\" w:cstheme=\"majorBidi\" /><w:b /><w:bCs /><w:color w:val=\"4F81BD\" w:themeColor=\"accent1\" /></w:rPr></w:style>";
/** for exporting a single node and its descendants */
protected EObject node = null;
/** for exporting a selection of nodes only */
protected Collection<EObject> nodeCollection = null;
/** for exporting an entire resource */
protected Resource resource = null;
/** number of production steps for progress monitor */
protected int steps = 1;
/** previously-selected destination file name */
protected String previousFileName = Messages.AbstractExportJob_0;
/** the word mark-up language package for the document */
protected WordprocessingMLPackage wordMLPackage;
/** the main document part from an incoming document file */
protected MainDocumentPart mainDocumentPart = null;
/** the style definition part from the main document incoming file */
protected StyleDefinitionsPart stylesPart = null;
/** style map populated by extension point */
protected Map<Integer,StyleEntry> styleMap = new HashMap<Integer,StyleEntry>();
/** document object creation factory, created at run-time */
protected ObjectFactory factory;
/** style entry including paragraph indicator */
protected class StyleEntry {
/**
* Records a document style entry.
* @param isParagraph true if style is for paragraphs, false otherwise
* @param style string identifier
*/
public StyleEntry(boolean isParagraph, String style) {
this.isParagraph = isParagraph;
this.style = style;
}
boolean isParagraph = false; // true for paragraph, false for run
String style = ""; // style string identifier
}
/**
* Default constructor uses default job name.
*/
protected AbstractExportJob() {
super(Messages.AbstractExportJob_1);
}
/**
* Create the export job with a name.
* @param name
*/
protected AbstractExportJob(String name) {
super(name);
}
/**
* Create the export job with a given model element node.
* @param name job name
* @param node model element to export including its descendants
*/
protected AbstractExportJob(String name, EObject node) {
this(name);
this.node = node;
nodeCollection = null;
resource = null;
}
/**
* Create the export job with a given collection of model nodes.
* @param name job name
* @param nodes collection of nodes to export
*/
protected AbstractExportJob(String name, Collection<EObject> nodes) {
this(name);
node = null;
nodeCollection = nodes;
resource = null;
}
/**
* Create the export job with a given model resource.
* @param name job name
* @param resource model resource
*/
protected AbstractExportJob(String name, Resource resource) {
this(name);
node = null;
nodeCollection = null;
this.resource = resource;
}
/**
* Sets the number of steps for progress monitoring.
* @param count step count
*/
public void setSteps(int count) {
steps = count;
}
/**
* Gets the number of steps for progress monitoring.
* @return total progress monitoring steps
*/
public int getSteps() {
return steps;
}
/**
* Prompts the user for a destination file name.
* @param shell shell for dialog
* @return filename or null if canceled
*/
public String promptFileName(Shell shell) {
String fileName = previousFileName;
final FileDialog fsd = new FileDialog(shell,SWT.SAVE);
fsd.setOverwrite(true);
fsd.setFileName(fileName);
fsd.setFilterExtensions(new String[] {Messages.AbstractExportJob_2});
fsd.setText(Messages.AbstractExportJob_3);
fileName = fsd.open();
if ( null != fileName ) {
previousFileName = fileName;
}
return fileName;
}
/**
* Do the work of exporting while in the run method.
* @param monitor progress monitor
* @return status indication, passed on to run method return
*/
abstract public IStatus produce(IProgressMonitor monitor);
/**
* Runs the job, starting the progress monitor and performing done after the
* produce() method returns.
* @param monitor progress monitor
* @return same status value from produce() method
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected IStatus run(IProgressMonitor monitor) {
// set some indicator of progress count
// increment the work accomplished in the produce() method
if ( null != resource ) {
setSteps( resource.getContents().size() );
}
else if ( null != nodeCollection ) {
setSteps( nodeCollection.size() );
}
else {
setSteps( node.eContents().size() );
}
// begin the task and produce the content
monitor.beginTask( getName(), getSteps() );
final IStatus rv = produce(monitor);
if ( ! monitor.isCanceled() ) {
monitor.done();
}
return rv;
}
/**
* Returns the chosen destination file name.
* @return returns the file name
*/
public String getDestinationFileName() {
return previousFileName;
}
/**
* Sets the node for export.
* Typically used if a selection is not available when creating the job.
* If null, implementation should export the entire resource.
* @param node GSN model element
*/
public void setNode(EObject node) {
this.node = node;
}
/**
* Returns the model element intended for export.
* @return node for export
*/
public EObject getNode() {
return node;
}
/**
* Get the node collection selected for export.
* @return the nodeCollection
*/
public Collection<EObject> getNodeCollection() {
return nodeCollection;
}
/**
* Sets the node collection for export.
* @param nodeCollection the nodeCollection to set
*/
public void setNodeCollection(Collection<EObject> nodeCollection) {
this.nodeCollection = nodeCollection;
}
/**
* Gets the resource selected for export.
* @return the resource
*/
public Resource getResource() {
return resource;
}
/**
* Sets the resource for export.
* @param resource the resource to set for export
*/
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* Performs standard clean-up after job.
* Writes log message according to completion status.
* Performs the done method on the progress monitor.
* @param monitor progress monitor
* @param rv job completion status
*/
public void cleanupJob(IProgressMonitor monitor, IStatus rv) {
monitor.done();
if ( rv.isOK() ) {
CertWareLog.logInfo(MessageFormat.format(Messages.AbstractExportJob_4, getName(), Messages.AbstractExportJob_5));
} else {
CertWareLog.logInfo(MessageFormat.format(Messages.AbstractExportJob_6, getName(), Messages.AbstractExportJob_7));
}
}
/**
* Loads the generic document style map.
* Assigns a generic style name for each argument model node identifier.
* Uses model package identifiers as keys.
*/
public void loadContributedStyles() {
ExportContributions ec = new ExportContributions();
ec.initialize();
// TODO read plugin contributions into the style map
//System.err.println("contributed ids: " + ec.getStyleIdMappings().size());
//System.err.println("contributed res: " + ec.getStyleResources().size());
//System.err.println("contributed sty: " + ec.getStyleStrings().size());
}
/**
* Assign or reassign a model element's style.
* @param key model element ID from package definition
* @param isParagraph true if style is for a paragraph, false for run
* @param styleId style ID, such as {@code Heading1Char} or {@code TaggedValue}.
*/
protected void assignStyleId(int key, boolean isParagraph, String styleId) {
if ( styleMap.containsKey(styleId)) {
styleMap.remove(styleId);
}
styleMap.put(key, new StyleEntry(isParagraph,styleId));
}
/**
* Assign or reassign a model element's style.
* @param key model element ID from package definition
* @param isParagraph true if style is for paragraph, false otherwise
* @param xml XML string describing style, including {@code xmlns} tags where needed
*/
protected void assignStyle(int key, boolean isParagraph, String xml) {
Style style = addStyle(xml);
if ( style != null ) {
assignStyleId(key,isParagraph,style.getStyleId());
} else {
System.err.println("Unmarshalled null style for " + xml);
}
}
/**
* Add a new XML-described format to the styles list for the main document styles part.
* @param format XML-syntax format
* @return style or null
*/
protected Style addStyle(String format) {
try {
Style newStyle;
newStyle = (Style)XmlUtils.unmarshalString(format);
stylesPart.getJaxbElement().getStyle().add(newStyle);
return newStyle;
} catch (JAXBException e) {
CertWareLog.logError(String.format("%s: %s", "Adding XML style",format),e);
}
return null;
}
/**
*
* @param styleEntry style entry from style map
* @param text text string to write into paragraph or run according to style
* @return object of type {@code org.docx4j.wml.P} for paragraph or {@code org.docx4j.wml.R} for run.
*/
protected Object addStyledText(StyleEntry styleEntry, String text) {
if ( styleEntry.isParagraph ) {
return mainDocumentPart.addStyledParagraphOfText(styleEntry.style, text);
} else {
return addStyledRunOfText(styleEntry,text);
}
}
/**
* Creates a new run with text of the given style.
* Presumes the style is a run style rather than a paragraph style.
* @param style style name to apply from document style part or defaults
* @param styleEntry style entry
* @return the new run, ready to add to the paragraph content list
*/
protected R addStyledRunOfText(StyleEntry styleEntry, String text ) {
R run = addRunOfText(text);
// apply style if available
//System.err.println("style entry " + styleEntry.style + "is paragraph " + styleEntry.isParagraph);
//System.err.println("resolver " + mainDocumentPart.getPropertyResolver().activateStyle( styleEntry.style ));
if (mainDocumentPart.getPropertyResolver().activateStyle(styleEntry.style)) {
Style style = mainDocumentPart.getPropertyResolver().getStyle(styleEntry.style);
RPr runProperties = style.getRPr();
run.setRPr(runProperties);
}
// otherwise create run without style
// create the text and add it to the run
/*
Text tid = factory.createText();
tid.setValue(text);
run.getRunContent().add(tid);
*/
return run;
}
/**
* Creates a new run with text of default style.
* @param text text to insert in run
* @return the new run, ready to add to the paragraph content list
*/
protected R addRunOfText(String text) {
// create and style the run properties
R run = factory.createR();
Text tid = factory.createText();
tid.setValue(text);
run.getRunContent().add(tid);
// return the run
return run;
}
/**
* Write a paragraph for the title of the document.
* @param monitor progress monitor (unused)
*/
protected void writeTitle(IProgressMonitor monitor) {
mainDocumentPart.addStyledParagraphOfText("Heading1", "CertWare Export");
mainDocumentPart.addStyledParagraphOfText("Normal", Calendar.getInstance().getTime().toString());
}
/**
* Perform standard setup for the processor and document.
* Creates the work processing document package.
* @param monitor progress monitor
* @throws InvalidFormatException for creating package
*/
protected void setupDocument(IProgressMonitor monitor) throws InvalidFormatException {
monitor.subTask("Creating package");
if ( mainDocumentPart == null ) { // incoming document not provided
wordMLPackage = WordprocessingMLPackage.createPackage();
mainDocumentPart = wordMLPackage.getMainDocumentPart();
stylesPart = mainDocumentPart.getStyleDefinitionsPart();
}
writeTitle(monitor);
}
/**
* Open a template document for writing into.
* @param monitor progress monitor
* @param file selected template file to populate
* @throws Docx4JException exceptions on loading file
*/
protected void openDocument(IProgressMonitor monitor, IFile file)
throws Docx4JException {
wordMLPackage = WordprocessingMLPackage.load(file.getFullPath().toFile());
mainDocumentPart = wordMLPackage.getMainDocumentPart();
stylesPart = mainDocumentPart.getStyleDefinitionsPart();
}
/**
* Perform standard tear-down of the processor and document.
* @param monitor progress monitor
* @param save whether to save marshaled document
* @throws Docx4JException for word package problems
* @throws JAXBException for marshalling problems
*/
protected void tearDownDocument(IProgressMonitor monitor, boolean save)
throws Docx4JException, JAXBException {
monitor.subTask("Cleaning up");
// save it to the file system according to destination file selection, or dump to standard output
if ( save ) {
// save it to the file
// TODO when run from export extension there is no user-provided file name
if ( getDestinationFileName().endsWith(ICertWareConstants.WORD_EXTENSION)) {
wordMLPackage.save( new File(getDestinationFileName()) );
CertWareLog.logInfo(MessageFormat.format("{0} {1}", "Exported to", getDestinationFileName()));
} else
if (getDestinationFileName().endsWith(ICertWareConstants.PDF_EXTENSION)) {
try {
wordMLPackage.setFontMapper( new IdentityPlusMapper() );
// PdfConversion c = new Conversion(wordMLPackage);
PdfConversion c = new Conversion(wordMLPackage);
OutputStream os = new FileOutputStream( new File(getDestinationFileName()));
c.output(os);
} catch (Exception e) {
CertWareLog.logError(String.format("%s %s","Writing PDF conversion to",getDestinationFileName()),e);
return;
}
CertWareLog.logInfo(MessageFormat.format("{0} {1}", "Exported to", getDestinationFileName()));
} else {
CertWareLog.logWarning(String.format("%s %s","Unknown destination file type for",getDestinationFileName()));
}
} else {
// marshal it to the console
final FlatOpcXmlCreator worker = new FlatOpcXmlCreator(wordMLPackage);
final org.docx4j.xmlPackage.Package pkg = worker.get();
JAXBContext jc = org.docx4j.jaxb.Context.jcXmlPackage;
Marshaller marshaller=jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
NamespacePrefixMapperUtils.setProperty(marshaller, NamespacePrefixMapperUtils.getPrefixMapper());
marshaller.marshal(pkg, System.out);
}
}
/**
* The switch method specific to the visitor of the model structure.
* @param eObject object to visit.
*/
abstract protected void doSwitch(EObject eObject);
/**
* Output instructions depending upon selection type.
* Assumes ML package has been initialized via setupDocument().
* Processes the selection(s) using the model's visitor switch.
* @param monitor progress monitor
* @return {@code OK_STATUS} on success or {@code CANCEL_STATUS} on monitor canceled
* @throws JAXBException
* @throws Docx4JException
*/
protected IStatus exportSelection(IProgressMonitor monitor) throws JAXBException,
Docx4JException {
factory = new ObjectFactory();
if ( null != getResource() ) {
monitor.subTask("Producing resource content");
// iterates over all nodes in the resource using a visitor pattern
for ( final Iterator<EObject> iter = EcoreUtil.getAllContents(getResource(), true); iter.hasNext(); ) {
EObject eObject = iter.next(); // $codepro.audit.disable variableDeclaredInLoop
doSwitch(eObject);
monitor.worked(1);
if ( monitor.isCanceled() ) {
return Status.CANCEL_STATUS;
}
}
} else if ( null != getNodeCollection() ) {
monitor.subTask("Producing node collection content");
// iterates over the given collection in order using a visitor pattern
for ( final Iterator<EObject> iter = EcoreUtil.getAllContents(getNodeCollection(), true); iter.hasNext(); ) {
EObject eObject = iter.next(); // $codepro.audit.disable variableDeclaredInLoop
doSwitch(eObject);
monitor.worked(1);
if ( monitor.isCanceled() ) {
return Status.CANCEL_STATUS;
}
}
} else {
monitor.subTask("Producing node content");
// iterates over a node and its children
// do the node itself, then its contents
doSwitch(getNode());
for ( final Iterator<EObject> iter = EcoreUtil.getAllContents(getNode(), true); iter.hasNext(); ) {
EObject eObject = iter.next(); // $codepro.audit.disable variableDeclaredInLoop
doSwitch(eObject);
monitor.worked(1);
if ( monitor.isCanceled() ) {
return Status.CANCEL_STATUS;
}
}
}
return Status.OK_STATUS;
}
}