All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.docx4j.convert.out.common.preprocess.ParagraphStylesInTableFix Maven / Gradle / Ivy

Go to download

docx4j is a library which helps you to work with the Office Open XML file format as used in docx documents, pptx presentations, and xlsx spreadsheets.

There is a newer version: 11.4.11
Show newest version
/*
   (c) Plutext Pty Ltd, 2014
   
 *  This file is part of docx4j.

    docx4j is licensed under the Apache License, Version 2.0 (the "License"); 
    you may not use this file except in compliance with the License. 

    You may obtain a copy of the License at 

        http://www.apache.org/licenses/LICENSE-2.0 

    Unless required by applicable law or agreed to in writing, software 
    distributed under the License is distributed on an "AS IS" BASIS, 
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    See the License for the specific language governing permissions and 
    limitations under the License.

 */
package org.docx4j.convert.out.common.preprocess;

import org.docx4j.TraversalUtil;
import org.docx4j.TraversalUtil.CallbackImpl;
import org.docx4j.XmlUtils;
import org.docx4j.jaxb.Context;
import org.docx4j.model.PropertyResolver;
import org.docx4j.model.styles.StyleUtil;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.DocumentSettingsPart;
import org.docx4j.wml.CTCompatSetting;
import org.docx4j.wml.HpsMeasure;
import org.docx4j.wml.Jc;
import org.docx4j.wml.JcEnumeration;
import org.docx4j.wml.P;
import org.docx4j.wml.PPr;
import org.docx4j.wml.PPrBase.PStyle;
import org.docx4j.wml.RPr;
import org.docx4j.wml.SdtBlock;
import org.docx4j.wml.Style;
import org.docx4j.wml.Styles;
import org.docx4j.wml.Tbl;
import org.docx4j.wml.TblPr;
import org.jvnet.jaxb2_commons.ppp.Child;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;



/**
 * A typical case this would fix is where the spacing between paragraphs 
 * is wrong in the PDF|XHTML output (because that is set in DocDefaults).
 * 
 * In a cell, a paragraph uses the table's paragraph properties,
 * plus the relevant paragraph style (Normal, by default).
 * The relevant paragraph style trumps the values from the
 * table's paragraph properties, so that would mean giving
 * the doc defaults (which we've made part of our styles) priority
 * over the table's paragraph properties, which is wrong.
 * TO avoid this, this preprocessor creates a new style, which encapsulates the
 * paragraph style, with DocDefaults given lower priority 
 * than table style.  This created style has no w:basedOn setting.
 * This preprocessor is required if paragraphs in tables are being styled incorrectly.
 * @since 3.0.2
 */
public class ParagraphStylesInTableFix {
	
	protected static Logger log = LoggerFactory.getLogger(ParagraphStylesInTableFix.class);	
	
	private static final CTCompatSetting defaultSetting; // see comments on overrideTableStyleFontSizeAndJustification at line 350 below
	
	static {
		
		defaultSetting=Context.getWmlObjectFactory().createCTCompatSetting();
		defaultSetting.setVal("0"); // default is false
	}

	public static void process(WordprocessingMLPackage wmlPackage) {

		/* Are we invoked from FOPAreaTreeHelper?
		 * 
		 * 	at org.docx4j.convert.out.fo.FOPAreaTreeHelper.getAreaTreeViaFOP(FOPAreaTreeHelper.java:187)
			at org.docx4j.convert.out.fo.LayoutMasterSetBuilder.fixExtents(LayoutMasterSetBuilder.java:136)
			at org.docx4j.convert.out.fo.LayoutMasterSetBuilder.getLayoutMasterSetFragment(LayoutMasterSetBuilder.java:97)
			at org.docx4j.convert.out.fo.XsltFOFunctions.getLayoutMasterSetFragment(XsltFOFunctions.java:82)

		 */
		Throwable t = new Throwable();
		StackTraceElement[] trace = t.getStackTrace();
//		boolean inFOPAreaTreeHelper=false;
		for (int i=0; i < trace.length; i++) {
			if (trace[i].getClassName().contains("FOPAreaTreeHelper")) {
				return;  // don't do this, especially changing overrideTableStyleFontSizeAndJustification!
			}
		}
		
		StyleRenamer styleRenamer = new StyleRenamer();

		try { // see comments on overrideTableStyleFontSizeAndJustification at line 350 below
			DocumentSettingsPart dsp = wmlPackage.getMainDocumentPart().getDocumentSettingsPart();
			if (dsp==null) {
				dsp = new DocumentSettingsPart();
				wmlPackage.getMainDocumentPart().addTargetPart(dsp);
				
				dsp.setContents( Context.getWmlObjectFactory().createCTSettings() );
				
				// no need to set styleRenamer.overrideTableStyleFontSizeAndJustification,
				// since the default is what we want in this case
				
			} else {
				styleRenamer.overrideTableStyleFontSizeAndJustification 
					= dsp.getWordCompatSetting("overrideTableStyleFontSizeAndJustification");
				if (styleRenamer.overrideTableStyleFontSizeAndJustification==null) {
					styleRenamer.overrideTableStyleFontSizeAndJustification=defaultSetting;
					// TODO,consider making the function return the default value?
				}
			}

			// For our output docx, we always want:-
			dsp.setWordCompatSetting("overrideTableStyleFontSizeAndJustification", "1");
			// since the p styles we make/use take the table style into account 
			
		} catch (Docx4JException e) {
			log.error(e.getMessage(), e);
		}
		
		try {
			styleRenamer.setDefaultParagraphStyle(wmlPackage.getMainDocumentPart()
					.getStyleDefinitionsPart().getDefaultParagraphStyle().getStyleId());
		} catch (NullPointerException npe) {
			log.warn("No default paragraph style!!");
		}
		
		Style defaultTableStyle = wmlPackage.getMainDocumentPart()
				.getStyleDefinitionsPart().getDefaultTableStyle();
		if (defaultTableStyle != null) {
			styleRenamer.setDefaultTableStyle(defaultTableStyle);
		}
		
		Styles styles = wmlPackage.getMainDocumentPart().getStyleDefinitionsPart().getJaxbElement();
        
        styleRenamer.propertyResolver = wmlPackage.getMainDocumentPart().getPropertyResolver();
        // do that first, since it creates virtual styles for DocDefaults,
        // which we need to include in the below
        styleRenamer.setStyles(styles);
        
		try {
			new TraversalUtil(wmlPackage.getMainDocumentPart().getContents(), styleRenamer);
		} catch (Docx4JException e) {
			// TODO Auto-generated catch block
			log.error(e.getMessage(), e);
		}
		
		// TODO, headers/footers as well
		
//		System.out.println(wmlPackage.getMainDocumentPart().getStyleDefinitionsPart().getXML());
		

	}	
	
	public static class StyleRenamer extends CallbackImpl {
		
		protected static Logger log = LoggerFactory.getLogger(StyleRenamer.class);
		
		CTCompatSetting overrideTableStyleFontSizeAndJustification=defaultSetting; // see comments on overrideTableStyleFontSizeAndJustification at line 350 below
		
		private PropertyResolver propertyResolver;
		
		
		/* We need to know the default styles so that we can handle
		 * implicit usage. 
		 */   
		
	    private String defaultParagraphStyle;
		public void setDefaultParagraphStyle(String defaultParagraphStyle) {
			
			
			log.debug(defaultParagraphStyle);
			
			this.defaultParagraphStyle = defaultParagraphStyle;
		}


		private Style defaultTableStyle;    // Need the actual Style here (see below) 
		public void setDefaultTableStyle(Style defaultTableStyle) {
			this.defaultTableStyle = defaultTableStyle;
		}
		
	    
		/*
		 * Style with ID DefaultParagraphFont is never used implicitly.
		 * A style is IGNORED if its name is "Default Paragraph Font",
		 * whether inside table or not.
		 */
//	    private String docDefaultsCharacterStyle="DocDefaultsChar";
	    
		
	    private LinkedList tblStack = new LinkedList();
	    // We don’t have to treat nested tables in any special way, 
	    // since a nested table does not inherit any of the properties of its parent table.
		
//	    private Styles newStyles=null;
	    private Map allStyles=null;
	    public void setStyles(Styles newStyles) {
	    	
//	    	this.newStyles = newStyles;
	    	allStyles = new HashMap(); 
//	    	cellPStyles = new HashSet(); 
	    	
	    	for (Style s : newStyles.getStyle()) {
//	    		System.out.println(s.getStyleId());
	    		allStyles.put(s.getStyleId(), s);
	    	}
	    }
	    private Set cellPStyles=new HashSet(); 
	    
	    
	    private boolean isFalse(CTCompatSetting overrideTableStyleFontSizeAndJustification) {
	    	
	    	return  ( overrideTableStyleFontSizeAndJustification.getVal().equals("0")
					|| overrideTableStyleFontSizeAndJustification.getVal().toLowerCase().equals("false")
					|| overrideTableStyleFontSizeAndJustification.getVal().toLowerCase().equals("no")
					);
	    }
	    
		/**
		 * In a cell, a paragraph uses the table's paragraph properties,
		 * plus the relevant paragraph style (Normal, by default).
		 * The relevant paragraph style trumps the values from the
		 * table's paragraph properties, so that would mean giving
		 * the doc defaults (which we've made part of our styles) priority
		 * over the table's paragraph properties, which is wrong.
		 * TO avoid this, we create a new style, which encapsulates the
		 * paragraph style, with DocDefaults given lower priority 
		 * than table style.  This created style has no w:basedOn setting.
		 */
		private String getCellPStyle(String styleVal, boolean pStyleIsDefault) {
			
			// Font size and jc for the style (which could be the default style), 
			// without following its based on values
			Style expressStyle = allStyles.get(styleVal);
			Jc expressStyleJc = expressStyle.getPPr()==null ? null : expressStyle.getPPr().getJc();
			HpsMeasure expressStyleFontSize = null;
			if (expressStyle.getRPr()!=null) {
				expressStyleFontSize=expressStyle.getRPr().getSz();
			}
			// Font size and jc for the style following its based on values
			PPr effectivePPr = propertyResolver.getEffectivePPr(styleVal);
			Jc effectiveJc = effectivePPr.getJc();
			
			RPr effectiveRPr = propertyResolver.getEffectiveRPr(styleVal);
			HpsMeasure effectiveFontSize = null;
			if (effectiveRPr!=null) {
				effectiveFontSize=effectiveRPr.getSz();
			}
			
			String tableStyle=null;
			TblPr tblPr = tblStack.peek().getTblPr(); 
			if (tblPr!=null && tblPr.getTblStyle()!=null) {
				tableStyle = tblPr.getTblStyle().getVal();
			} else if (defaultTableStyle==null) {
				log.warn("No default table style defined in docx Style Definitions part"); 
				return null;						
			} else {
				if (defaultTableStyle.getName()!=null
						&& defaultTableStyle.getName().getVal()!=null
						&& defaultTableStyle.getName().getVal().equals("Normal Table")) {
					// Word 2010 x64 ignores any table style with that name!
					log.debug("Ignoring style with name 'Normal Table' (mimicking Word)"); 
					return null;
				} else {
					// We have a default table style
					tableStyle = defaultTableStyle.getStyleId();
					// shouldn't happen, but just in case..
					if (tableStyle==null) {
                        if(log.isErrorEnabled()) {
                            log.error("Default table style has no ID!");
                            log.error(XmlUtils.marshaltoString(tableStyle));
                        }
						return null;						
					}
				}
			}
			String resultStyleID = styleVal+"-"+tableStyle;
			if (tableStyle.endsWith("-BR")) {
				// don't want to add this twice
			} else {
				resultStyleID = resultStyleID +"-BR";
			}
					
			if (cellPStyles.contains(resultStyleID)) return resultStyleID;
			
			List