package org.docx4j.model.datastorage.migration; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.docx4j.TraversalUtil; import org.docx4j.TraversalUtil.CallbackImpl; import org.docx4j.XmlUtils; import org.docx4j.customXmlProperties.DatastoreItem; import org.docx4j.customXmlProperties.SchemaRefs; import org.docx4j.customXmlProperties.SchemaRefs.SchemaRef; import org.docx4j.jaxb.Context; import org.docx4j.openpackaging.exceptions.InvalidFormatException; import org.docx4j.openpackaging.io.SaveToZipFile; import org.docx4j.openpackaging.packages.WordprocessingMLPackage; import org.docx4j.openpackaging.parts.CustomXmlDataStoragePropertiesPart; import org.docx4j.openpackaging.parts.opendope.JaxbCustomXmlDataStoragePart; import org.docx4j.openpackaging.parts.relationships.RelationshipsPart.AddPartBehaviour; import org.docx4j.wml.P; import org.docx4j.wml.R; import org.docx4j.wml.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class will help you to migrate * from a string replacement approach, * to use of content control data bindings. * * After migrating, you'll be able to * use the OpenDoPE authoring tool to * add repeats, conditionals, and other * OpenDoPE features, if you need them. * * It assumes your magic strings take the * form ${--}. If they don't, you'll * need to modify this class, or use Word * to search/replace to change them so they * take that form. * * We'd be happy to accept a patch which * specifies a regex the magic string * must match. * * Limitations: this first version * operates only on the main document part * (ie it won't process variables in * headers/footers, footnotes/endnotes, * or comments) * * @author jharrop * @since 3.0.0 */ public class FromVariableReplacement extends AbstractMigrator { private static Logger log = LoggerFactory.getLogger(FromVariableReplacement.class); public WordprocessingMLPackage migrate(WordprocessingMLPackage pkgIn) throws Exception { // TODO - test that OpenDoPE parts aren't already present // or if they are, that this docx is using our answer format // (since only that format is supported here) // Clone it WordprocessingMLPackage pkgOut = (WordprocessingMLPackage)pkgIn.clone(); // Run the cleaner first VariablePrepare.prepare(pkgOut); // Create the CustomXML parts createParts(pkgOut); // Operate at the p level PFinder pFinder = new PFinder(); new TraversalUtil(pkgOut.getMainDocumentPart().getContent(), pFinder); for ( P p : pFinder.pList) { List<Object> replacementContent = new ArrayList<Object>(); for (Object o : p.getContent() ) { o = XmlUtils.unwrap(o); if ( o instanceof R) { for (Object o2 : ((R)o).getContent() ) { o2 = XmlUtils.unwrap(o2); if ( o2 instanceof Text ) { handle((R)o, ((Text)o2).getValue(), 0, replacementContent); } else { // Just add this bit R rnew = new R(); rnew.setRPr( ((R)o).getRPr() ); // point at old rPr, if any rnew.getContent().add(o2); replacementContent.add(rnew); } } } else { replacementContent.add(o); } } p.getContent().clear(); p.getContent().addAll(replacementContent); } return pkgOut; } private void handle(R r, String s, int offset, List<Object> replacementContent) { int startKey = s.indexOf("${", offset); if (startKey == -1) { addTextRun(r, s.substring(offset), replacementContent); return; } else { addTextRun(r, s.substring(offset, startKey), replacementContent); int keyEnd = s.indexOf('}', startKey); String key = s.substring(startKey + 2, keyEnd); createContentControl(r.getRPr(), replacementContent, key); handle(r, s, keyEnd + 1, replacementContent); } } private void addTextRun(R r, String val, List<Object> replacementContent) { R rnew = new R(); rnew.setRPr( r.getRPr() ); // point at old rPr, if any Text text = Context.getWmlObjectFactory().createText(); text.setValue(val); if (val.startsWith(" ") || val.endsWith(" ") ) { text.setSpace("preserve"); } rnew.getContent().add(text); replacementContent.add(rnew); } public String addPropertiesPart(JaxbCustomXmlDataStoragePart<?> customXmlDataStoragePart, String ns) throws InvalidFormatException { CustomXmlDataStoragePropertiesPart part = new CustomXmlDataStoragePropertiesPart(); org.docx4j.customXmlProperties.ObjectFactory of = new org.docx4j.customXmlProperties.ObjectFactory(); DatastoreItem dsi = of.createDatastoreItem(); String newItemId = "{" + UUID.randomUUID().toString().toUpperCase() + "}"; dsi.setItemID(newItemId); SchemaRefs srefs = of.createSchemaRefs(); dsi.setSchemaRefs(srefs); SchemaRef sref = of.createSchemaRefsSchemaRef(); sref.setUri(ns); srefs.getSchemaRef().add(sref); part.setJaxbElement(dsi); customXmlDataStoragePart.addTargetPart(part, AddPartBehaviour.RENAME_IF_NAME_EXISTS); return newItemId; } static class PFinder extends CallbackImpl { List<P> pList = new ArrayList<P>(); @Override public List<Object> apply(Object o) { if (o instanceof P ) { pList.add((P)o); } return null; } } /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { String inputfilepath = System.getProperty("user.dir") + "/sample-docs/word/unmarshallFromTemplateExample.docx"; String outputfilepath = System.getProperty("user.dir") + "/OUT_VariableReplace.docx"; WordprocessingMLPackage pkgIn = WordprocessingMLPackage.load(new java.io.File(inputfilepath)); FromVariableReplacement migrator = new FromVariableReplacement(); WordprocessingMLPackage pkgOut = migrator.migrate(pkgIn); SaveToZipFile saver = new SaveToZipFile(pkgOut); saver.save(outputfilepath); } }