ChangesFileSaxHandler.java
/**
* **********************************************************************
*
* <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* <p>Use is subject to license terms.
*
* <p>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.
*
* <p>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.
*
* <p>See the License for the specific language governing permissions and limitations under the
* License.
*
* <p>**********************************************************************
*/
package org.odftoolkit.odfdom.changes;
import static org.odftoolkit.odfdom.changes.OperationConstants.CONFIG_MAX_TABLE_CELLS;
import static org.odftoolkit.odfdom.changes.OperationConstants.CONFIG_MAX_TABLE_COLUMNS;
import static org.odftoolkit.odfdom.changes.OperationConstants.CONFIG_MAX_TABLE_ROWS;
import static org.odftoolkit.odfdom.changes.OperationConstants.OPK_STYLE_ID;
import static org.odftoolkit.odfdom.changes.PageArea.FOOTER_DEFAULT;
import static org.odftoolkit.odfdom.changes.PageArea.FOOTER_EVEN;
import static org.odftoolkit.odfdom.changes.PageArea.FOOTER_FIRST;
import static org.odftoolkit.odfdom.changes.PageArea.HEADER_DEFAULT;
import static org.odftoolkit.odfdom.changes.PageArea.HEADER_EVEN;
import static org.odftoolkit.odfdom.changes.PageArea.HEADER_FIRST;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.OdfMetaDom;
import org.odftoolkit.odfdom.dom.OdfSchemaConstraint;
import org.odftoolkit.odfdom.dom.OdfSchemaDocument;
import org.odftoolkit.odfdom.dom.OdfSettingsDom;
import org.odftoolkit.odfdom.dom.OdfStylesDom;
import org.odftoolkit.odfdom.dom.element.OdfStylableElement;
import org.odftoolkit.odfdom.dom.element.OdfStyleableShapeElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawConnectorElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawGElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawImageElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawLineElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawMeasureElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawShapeElementBase;
import org.odftoolkit.odfdom.dom.element.draw.DrawTextBoxElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeAnnotationElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeAnnotationEndElement;
import org.odftoolkit.odfdom.dom.element.style.StyleFontFaceElement;
import org.odftoolkit.odfdom.dom.element.style.StyleFooterStyleElement;
import org.odftoolkit.odfdom.dom.element.style.StyleHeaderFooterPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleHeaderStyleElement;
import org.odftoolkit.odfdom.dom.element.style.StyleMasterPageElement;
import org.odftoolkit.odfdom.dom.element.style.StyleStyleElement;
import org.odftoolkit.odfdom.dom.element.svg.SvgDescElement;
import org.odftoolkit.odfdom.dom.element.table.TableCoveredTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableColumnElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowElement;
import org.odftoolkit.odfdom.dom.element.text.TextAElement;
import org.odftoolkit.odfdom.dom.element.text.TextHElement;
import org.odftoolkit.odfdom.dom.element.text.TextLineBreakElement;
import org.odftoolkit.odfdom.dom.element.text.TextListElement;
import org.odftoolkit.odfdom.dom.element.text.TextListHeaderElement;
import org.odftoolkit.odfdom.dom.element.text.TextListItemElement;
import org.odftoolkit.odfdom.dom.element.text.TextListStyleElement;
import org.odftoolkit.odfdom.dom.element.text.TextNoteCitationElement;
import org.odftoolkit.odfdom.dom.element.text.TextPElement;
import org.odftoolkit.odfdom.dom.element.text.TextParagraphElementBase;
import org.odftoolkit.odfdom.dom.element.text.TextSElement;
import org.odftoolkit.odfdom.dom.element.text.TextSpanElement;
import org.odftoolkit.odfdom.dom.element.text.TextTabElement;
import org.odftoolkit.odfdom.dom.element.text.TextUserFieldDeclElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStylePageLayout;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextListStyle;
import org.odftoolkit.odfdom.pkg.OdfAttribute;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
import org.odftoolkit.odfdom.pkg.OdfNamespace;
import org.odftoolkit.odfdom.pkg.OdfValidationException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/** @author svante.schubertATgmail.com */
public class ChangesFileSaxHandler extends org.odftoolkit.odfdom.pkg.OdfFileSaxHandler {
private static final Logger LOG = Logger.getLogger(ChangesFileSaxHandler.class.getName());
private static final String ROW_SPAN = "rowSpan";
// ToDo: Fix API with its 'ugly' property name
private static final String COLUMN_SPAN = "gridSpan";
// ODF value types used for cell content
private static final String LIBRE_OFFICE_MS_INTEROP_NAMESPACE =
"urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0";
private static final String LIBRE_OFFICE_MS_INTEROP_TYPE_CHECKBOX =
"vnd.oasis.opendocument.field.FORMCHECKBOX";
private static final String LIBRE_OFFICE_MS_INTEROP_CHECKBOX_UNICODE = "\u25A1";
private static final Integer ONE = 1;
public static String COMMENT_PREFIX = "cmt";
// the empty XML file to which nodes will be added
private OdfFileDom mFileDom;
private JsonOperationProducer mJsonOperationProducer;
private Map<String, TextListStyleElement> mAutoListStyles = null;
private Map<String, TextUserFieldDeclElement> mUserFieldDecls = null;
/**
* Represents a stack of TextSpanElement. The text span will be added during startElement(..) with
* the start address of text span And during endElement(..) the correct span will be returned and
* the end address can be provided as well.
*/
private final ArrayDeque<TextSelection> mTextSelectionStack;
private final StringBuilder mCharsForElement = new StringBuilder();
// Text for operations will be collected separately to allow to push not at every new delimiter
// (e.g. span).
// In addition will be <text:s/> in the output string exchange to spaces
private final StringBuilder mCharsForOperation = new StringBuilder();
// Gatheres the start text position for operations
private boolean mIsCharsBeginning = true;
List<Integer> mCharsStartPosition = null;
private int mComponentDepth = -1; // as component depth starts with zero
// the actual component. Linking each other building the tree view of the document
private Component mCurrentComponent;
// the position of the component, being updated for the operations being generated
private final LinkedList<Integer> mLastComponentPositions = new LinkedList<Integer>();
/** DOM is created by default, but is in general not needed */
private final boolean domCreationEnabled = true;
// private final ArrayDeque<ShapeProperties> mShapePropertiesStack;
// *** TABLE PROPERTIES ***
// ToDo: Move this table member variables to a special Table related parser/evaluator (Strategy
// Pattern?)
// name of the table or spreadsheet
private String mTableName;
private TableTableElement mTableElement;
private List<TableTableColumnElement> mColumns;
// The relative widths of the columns of a table
private List<Integer> mColumnRelWidths;
private int mColumnCount;
// Required as the component table/draw can only be delayed created,
// After the first child has been parsed.. And should ONLY ONCE created!
private boolean isTableNew = false;
Map<String, Object> mTableHardFormatting = null;
// *** LIST Properties ***
// @text:start-value is provided to the first paragraph only
private int mListStartValue = -1;
private final ArrayDeque<ParagraphListProperties> mListStyleStack;
// used to track in a text:h/text:p if currently whitespace is being deleted/trimmed
private final ArrayDeque<WhitespaceStatus> mWhitespaceStatusStack;
/**
* Quick cache to get the correct linked list. Key is the xml:id of the first list. The sequence
* of all continued lists and usability functions are provided by ContinuedList
*/
private final Map<String, ContinuedList> mLinkedLists = new HashMap<String, ContinuedList>();
// *** FOR BLOCKING OPERATIONS
// the number of elements above the current element during parsing.
// Required to find out if the correct blocking element for the UI was found
int mElementDepth = 0;
// The depth of the element responsible of blocking further operations
int mBlockingElementDepth = 0;
boolean mNoOperationsAllowed = false;
// All following blocking modes have different behavior
boolean mIsBlockingFrame = false; // itself and children are allowed
boolean mIsIgnoredElement = false; // not even itself allowed
boolean mIsBlockingShape = false; // itself allowed
// RunTimeConfiguration given by the caller of the ODF Adapter
private int mMaxAllowedColumnCount;
private int mMaxAllowedRowCount;
private int mMaxAllowedCellCount;
/**
* LO/AOO/Calligra are applying to Hyperlinks the "Internet_20_link" style, without writing out
* the dependency into XML. Therefore whenever a Hyperlink exists without character style
* properties, the reference will be set.
*/
private static final String HYERLINK_DEFAULT_STYLE = "Internet_20_link";
private boolean mHasHyperlinkTemplateStyle = false;
/** Properties for the HEADER_DEFAULT and FOOTER_DEFAULT page area. Defining the page layout */
private String mMasterPageStyleName = null;
private String mPageLayoutName = null;
/** ODF attribute on pageLayout */
private String mPageStyleUsage = null;
/** indication of being a first page */
private boolean mHasNextMasterPage = false;
private JSONObject headerAttrs = null;
private JSONObject footerAttrs = null;
/**
* In the beginning it is only the styleId of the masterPage plus "HeaderDefault" or
* "FooterDefault"
*/
private String mContextName = null;
public static final String CONTEXT_DELIMITER = "_";
PageArea mPageArea = null;
/**
* "footer_default_" "footer_even_" "footer_first_" "header_default_" "header_even_"
* "header_first_"
*/
/** The document might be of different types */
String mMediaType = null;
/**
* Required as the order of linked-list is important! All xml:ids of a connected/linked lists are
* put into a single list. This collection is used to get the correct reference to the xml:id of
* the preceding list and have to be updated, when linked lists are created, deleted or moved.
* Only the text:continue-list of a new list will be evaluated
*/
class ContinuedList {
private String mListId;
private List<String> mSortedIds = null;
public ContinuedList(String precedingListId, String currentListId) {
if (precedingListId != null && !precedingListId.isEmpty()) {
mListId = precedingListId;
} else {
if (currentListId != null && !currentListId.isEmpty()) {
mListId = currentListId;
}
}
mSortedIds = new LinkedList<String>();
}
public void add(String listId) {
mSortedIds.add(listId);
}
public List<String> getListIds() {
return mSortedIds;
}
public String getListId() {
return mListId;
}
}
/**
* Checks if the preceding list is already part of a continued list, otherwise creates a new
* continued list and adds both ids to it
*/
ContinuedList newContinuedList(String precedingListId, String currentListId) {
ContinuedList continuedList;
if (!mLinkedLists.containsKey(precedingListId)) {
continuedList = new ContinuedList(precedingListId, currentListId);
continuedList.add(precedingListId);
mLinkedLists.put(precedingListId, continuedList);
} else {
continuedList = mLinkedLists.get(precedingListId);
}
if (currentListId != null && !currentListId.isEmpty()) {
continuedList.add(currentListId);
mLinkedLists.put(currentListId, continuedList);
}
return continuedList;
}
/**
* Checks if the preceding list is already part of a continued list, otherwise creates a new
* continued list and adds both id to it
*/
ContinuedList newContinuedList(String currentListId) {
ContinuedList continuedList = null;
if (currentListId != null && !currentListId.isEmpty()) {
if (!mLinkedLists.containsKey(currentListId)) {
continuedList = new ContinuedList(null, currentListId);
mLinkedLists.put(currentListId, continuedList);
} else {
continuedList = mLinkedLists.get(currentListId);
}
}
return continuedList;
}
/**
* The whitespace status of a text container (ie. paragraph or heading). Required for whitespace
* handling
*/
class WhitespaceStatus {
WhitespaceStatus(boolean isParagraphIgnored, int depth) {
mDepth = depth;
// mIsParagraphIgnored = isParagraphIgnored;
}
int mDepth = -1;
public int getParagraphDepth() {
return mDepth;
}
boolean mOnlyWhiteSpaceSoFar = true;
int mFirstSpaceCharPosition = -1;
public boolean hasOnlyWhiteSpace() {
return mOnlyWhiteSpaceSoFar;
}
public void setOnlyWhiteSpace(boolean onlyWhiteSpace) {
mOnlyWhiteSpaceSoFar = onlyWhiteSpace;
}
/** During parsing the first character of space siblings. -1 if there is no space sibling */
public int getFirstSpaceCharPosition() {
return mFirstSpaceCharPosition;
}
/** During parsing the first character of space siblings. -1 if there is no space sibling */
public void setFirstSpaceCharPosition(int currentSpaceCharPosition) {
mFirstSpaceCharPosition = currentSpaceCharPosition;
}
/** @return true if the previous character was a white space character */
public boolean hasSpaceBefore() {
return mFirstSpaceCharPosition > -1;
}
}
OdfSchemaDocument mSchemaDoc = null;
// Candidate Component Mode
// Some components consist of multiple XML elements.
// Even some ODF components start with the same
// 2DO - DRAGON BOOK - Parser Look-ahead does not work with SAX? ;)
// private boolean isCandidateComponentMode = true;
public ChangesFileSaxHandler(Node rootNode) throws SAXException {
super(rootNode);
// Initialize starting DOM node
if (rootNode instanceof OdfFileDom) {
mFileDom = (OdfFileDom) rootNode;
} else {
mFileDom = (OdfFileDom) rootNode.getOwnerDocument();
}
mCurrentNode = rootNode;
// *** COMPONENT HANDLING ***
// Initialize starting Component
// Make the root of component tree (to be created) accessible via the ODF schema document
mSchemaDoc = (OdfSchemaDocument) mFileDom.getDocument();
if (mSchemaDoc != null) {
// cash the unfinished DOM otherwise, styles.xml might be tried to be parsed again
if (mFileDom instanceof OdfContentDom) {
mSchemaDoc.setContentDom((OdfContentDom) mFileDom);
} else if (mFileDom instanceof OdfStylesDom) {
mSchemaDoc.setStylesDom((OdfStylesDom) mFileDom);
} else if (mFileDom instanceof OdfMetaDom) {
mSchemaDoc.setMetaDom((OdfMetaDom) mFileDom);
} else if (mFileDom instanceof OdfSettingsDom) {
mSchemaDoc.setSettingsDom((OdfSettingsDom) mFileDom);
}
}
// The current component is the root component
mCurrentComponent = null;
// Getting Configuration
Map<String, Object> configuration = mSchemaDoc.getPackage().getRunTimeConfiguration();
mMaxAllowedColumnCount = OperationConstants.MAX_SUPPORTED_COLUMNS_NUMBER;
mMaxAllowedRowCount = OperationConstants.MAX_SUPPORTED_ROWS_NUMBER;
mMaxAllowedCellCount = OperationConstants.MAX_SUPPORTED_CELLS_NUMBER;
mMediaType = mSchemaDoc.getMediaTypeString();
if (configuration != null) {
if (configuration.containsKey(CONFIG_MAX_TABLE_COLUMNS)) {
mMaxAllowedColumnCount = (Integer) configuration.get(CONFIG_MAX_TABLE_COLUMNS);
}
if (configuration.containsKey(CONFIG_MAX_TABLE_ROWS)) {
mMaxAllowedRowCount = (Integer) configuration.get(CONFIG_MAX_TABLE_ROWS);
}
if (configuration.containsKey(CONFIG_MAX_TABLE_CELLS)) {
mMaxAllowedCellCount = (Integer) configuration.get(CONFIG_MAX_TABLE_CELLS);
}
}
LOG.log(Level.FINEST, "mMaxTableColumnCount{0}", mMaxAllowedColumnCount);
LOG.log(Level.FINEST, "mMaxTableRowCount{0}", mMaxAllowedRowCount);
LOG.log(Level.FINEST, "mMaxTableCellCount{0}", mMaxAllowedCellCount);
// Make the Operation Queue to be created accessible via the Schema Document
mJsonOperationProducer = mSchemaDoc.getJsonOperationQueue();
if (mJsonOperationProducer == null) {
// temporary initiated here as all the tests are not using the OperationTextDocument
mJsonOperationProducer = new JsonOperationProducer();
mSchemaDoc.setJsonOperationQueue(mJsonOperationProducer);
}
mAutoListStyles = new HashMap<String, TextListStyleElement>();
mUserFieldDecls = new HashMap<String, TextUserFieldDeclElement>();
// Stack to remember/track the nested delimiters not being components (spans) open-up by SAX
// events
mTextSelectionStack = new ArrayDeque<TextSelection>();
mListStyleStack = new ArrayDeque<ParagraphListProperties>();
// mShapePropertiesStack = new ArrayDeque<ShapeProperties>();
mWhitespaceStatusStack = new ArrayDeque<WhitespaceStatus>();
}
@Override
public void startDocument() throws SAXException {}
@Override
public void endDocument() throws SAXException {}
/**
* There are areas that are not allowed to addChild further components beyond. All further
* operations have to be blocked, but the creation of the DOM tree must not be disturbed.
*/
private boolean isBlockedSubTree() {
return mNoOperationsAllowed;
}
/**
* There are areas that are not allowed to addChild further components beyond. All further
* operations have to be blocked, but the creation of the DOM tree must not be disturbed.
*/
private boolean checkEndOfBlockedSubTree(String uri, String localName) {
boolean isBlocked = mNoOperationsAllowed;
if (mNoOperationsAllowed) {
isBlocked = isBlockedSubTree(uri, localName, false);
}
mElementDepth--;
return isBlocked;
}
private boolean checkStartOfBlockedSubTree(String uri, String localName) {
mElementDepth++;
boolean isBlocked = mNoOperationsAllowed;
if (!mNoOperationsAllowed) {
isBlocked = isBlockedSubTree(uri, localName, true);
} else if (mIsBlockingFrame) {
if (mBlockingElementDepth == mElementDepth - 1 && !localName.equals("table")) {
isBlocked = false;
} else {
isBlocked = true;
}
}
return isBlocked;
}
// ToDo: Differentiate if there is a shapeBlock, ImageBlock or ParagraphBlock
private boolean isBlockedSubTree(String uri, String localName, boolean isStart) {
// within a paragraph within a paragraph
boolean isBlocked = mNoOperationsAllowed;
boolean isMasterPage =
uri != null
&& uri.equals(StyleMasterPageElement.ELEMENT_NAME.getUri())
&& localName.equals(StyleMasterPageElement.ELEMENT_NAME.getLocalName());
if (isStart) {
// if it is a second text component (ie. text:p or text:h element)
if (
/*!mWhitespaceStatusStack.isEmpty() && Component.isTextComponentRoot(uri, localName) || */ OdfElement
.isIgnoredElement(uri, localName)
|| ((isMasterPage
|| Component.isHeaderRoot(uri, localName)
|| Component.isFooterRoot(uri, localName))
&& OdfDocument.OdfMediaType.TEXT.getMediaTypeString() != mMediaType
&& OdfDocument.OdfMediaType.SPREADSHEET.getMediaTypeString() != mMediaType)) {
isBlocked = true;
mNoOperationsAllowed = true;
mIsIgnoredElement = true;
mBlockingElementDepth = mElementDepth;
// if it is a <draw:frame>
}
} else { // if this is the closing event of an element
if (mNoOperationsAllowed) {
if (mBlockingElementDepth == mElementDepth) {
if (mIsIgnoredElement
&& (
/*!mWhitespaceStatusStack.isEmpty() && Component.isTextComponentRoot(uri, localName) || */ OdfElement
.isIgnoredElement(uri, localName))
|| ((isMasterPage
|| Component.isHeaderRoot(uri, localName)
|| Component.isFooterRoot(uri, localName))
&& OdfDocument.OdfMediaType.TEXT.getMediaTypeString() != mMediaType
&& OdfDocument.OdfMediaType.SPREADSHEET.getMediaTypeString() != mMediaType)) {
mIsIgnoredElement = false;
mBlockingElementDepth = -1;
mNoOperationsAllowed = false;
isBlocked = true;
// if it is a <draw:frame>
}
} else if (mIsBlockingFrame
&& mBlockingElementDepth == mElementDepth - 1
&& !localName.equals("table")) {
isBlocked = false;
}
} else { // closing will never enabled a blocking
if (mIsIgnoredElement || mIsBlockingShape) {
// close this element, but afterwards
mNoOperationsAllowed = true;
isBlocked = false;
} else if (mIsBlockingFrame) {
if (mBlockingElementDepth == mElementDepth - 1 && !localName.equals("table")) {
isBlocked = false;
} else {
isBlocked = true;
}
}
}
}
return isBlocked;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
flushTextAtStart(uri, localName, qName);
// if there is a specilized handler on the stack, dispatch the event
OdfElement element = null;
// ToDo: Should be able to create operations without creating the DOM Tree
// ToDo: Are there use-cases that text:s still resides in the DOM? if(isWhiteSpaceElement) ??
// If Paragraph is not being edited, it will be saved as it is..
if (domCreationEnabled) {
if (uri.equals(Constants.EMPTY_STRING) || qName.equals(Constants.EMPTY_STRING)) {
element = mFileDom.createElement(localName);
} else {
// == correct: if localName is the same object as qName, there is a default namespace set
if (localName == qName) {
element =
mFileDom.createElementNS(
OdfName.getOdfName(OdfNamespace.newNamespace(null, uri), localName));
} else {
element = mFileDom.createElementNS(uri, qName);
}
}
addAttributes(element, attributes);
}
// if it is the last page bound object then move all the nodes to a temporary location
if (mComponentDepth < 0
&& m_cachedPageShapes.size() > 0
&& (localName.equals("p") || localName.equals("h") || localName.equals("table"))) {
// move nodes
Node bodyNode = mCurrentNode.getParentNode();
Iterator<ShapeProperties> it = m_cachedPageShapes.iterator();
while (it.hasNext()) {
ShapeProperties component = it.next();
bodyNode.insertBefore(component.mOwnNode, bodyNode.getFirstChild());
}
mLastComponentPositions.clear();
}
// Font declarations are before the component
if (element instanceof StyleFontFaceElement) {
String fontName = element.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name");
if (fontName != null && !fontName.isEmpty()) {
Set<String> fontNames = ((OdfDocument) mSchemaDoc).getFontNames();
if (!fontNames.contains(fontName)) {
mJsonOperationProducer.addFontData(
fontName,
null,
element.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "font-family"),
element.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "font-family-generic"),
element.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "font-pitch"),
element.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "panose-1"));
fontNames.add(fontName);
}
}
}
if (element instanceof TextListStyleElement) {
// We need the reference for later gettin the list styles
TextListStyleElement listStyle = (TextListStyleElement) element;
String styleName = listStyle.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(), "name");
if (styleName != null && !styleName.isEmpty()) {
mAutoListStyles.put(styleName, listStyle);
}
} else if (element instanceof TextUserFieldDeclElement) {
TextUserFieldDeclElement fieldDecl = (TextUserFieldDeclElement) element;
mUserFieldDecls.put(fieldDecl.getAttribute("text:name"), fieldDecl);
}
if (!checkStartOfBlockedSubTree(uri, localName)) {
if (Component.isComponentRoot(
uri, localName)) { // || Component.isCoveredComponentRoot(uri, localName)) {
// It is not allowed to addChild further components..,
// within a paragraph within a paragraph
// ToDo ? -- HashMap with KEY - URL+localname, VALUE - ComponentName
if (element instanceof TextPElement || element instanceof TextHElement) {
// Paragraphs that are not child of a known component should be ignored, otherwise the
// client gets into trouble with nested paragraphs
boolean isNestedParagraph = false;
if (!isNestedParagraph) {
mComponentDepth++;
TextParagraphElementBase p = (TextParagraphElementBase) element;
Map<String, Object> hardFormatting = mJsonOperationProducer.getHardStyles(p);
if (hardFormatting == null) {
hardFormatting = new HashMap<String, Object>();
}
if (element instanceof TextHElement || !mListStyleStack.isEmpty()) {
if (!hardFormatting.containsKey("paragraph")) {
// if there are absolute styles, but not the main property set, where the
// templateStyleId should be placed in
hardFormatting.put("paragraph", new JSONObject());
}
JSONObject paraProps = (JSONObject) hardFormatting.get("paragraph");
try {
if (!mListStyleStack.isEmpty()) {
paraProps.put("listLevel", mListStyleStack.size() - 1);
// Only the first paragraph within a list item should show a label!
ParagraphListProperties listProps = mListStyleStack.getLast();
if (listProps.hasListLabel()) {
listProps.showListLabel(Boolean.FALSE);
} else {
paraProps.put("listLabelHidden", Boolean.TRUE);
}
String listId = listProps.getListId();
if (listId != null && !listId.isEmpty()) {
paraProps.put("listId", listId);
}
boolean foundListXmlId = false;
boolean foundListItemXmlId = false;
Iterator<ParagraphListProperties> listPropsIter =
mListStyleStack.descendingIterator();
while ((!foundListXmlId || !foundListItemXmlId) && listPropsIter.hasNext()) {
ParagraphListProperties currentListProp = listPropsIter.next();
String listXmlId = currentListProp.getListXmlId();
if (!foundListXmlId && listXmlId != null && !listXmlId.isEmpty()) {
foundListXmlId = true;
paraProps.put("listXmlId", listXmlId);
}
String listItemXmlId = currentListProp.getListItemXmlId();
if (!foundListItemXmlId && listItemXmlId != null && !listItemXmlId.isEmpty()) {
foundListItemXmlId = true;
paraProps.put("listItemXmlId", listItemXmlId);
}
}
if (listProps.isListStart()) {
paraProps.put("listStart", Boolean.TRUE);
}
String listStyleId = JsonOperationProducer.getListStyle(mListStyleStack, p);
if (listStyleId != null && !listStyleId.isEmpty()) {
mJsonOperationProducer.addListStyle(mSchemaDoc, mAutoListStyles, listStyleId);
paraProps.put("listStyleId", listStyleId);
} else {
paraProps.put("listStyleId", Constants.ODFTK_DEFAULT_LIST);
}
if (mListStartValue != -1) {
paraProps.put("listStartValue", mListStartValue);
mListStartValue = -1;
}
}
// Add heading outline numbering
if (element instanceof TextHElement) {
Integer outlineLevel = ((TextHElement) p).getTextOutlineLevelAttribute();
if (outlineLevel != null) {
paraProps.put("outlineLevel", outlineLevel);
}
}
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
List<Integer> position = updateComponentPosition();
OdfStyle templateStyle = p.getDocumentStyle();
String styleId = null;
if (templateStyle != null) {
styleId = templateStyle.getStyleNameAttribute();
if (styleId != null && !styleId.isEmpty()) {
hardFormatting.put(OPK_STYLE_ID, styleId);
}
}
mCurrentComponent = mCurrentComponent.createChildComponent(p);
boolean paragraphOpCreated = false;
if (!mPageBoundObjectsRelocated && !m_cachedPageShapes.isEmpty()) {
// first document paragraph might be inside of a table
boolean isFirstDocumentParagraph =
mComponentStack.empty() || mComponentStack.peek() instanceof CachedTable;
if (isFirstDocumentParagraph && m_cachedPageShapes.size() > 0) {
cacheOperation(
false,
OperationConstants.PARAGRAPH,
position,
false,
hardFormatting,
mContextName);
paragraphOpCreated = true;
Iterator<ShapeProperties> it = m_cachedPageShapes.iterator();
while (it.hasNext()) {
ShapeProperties component = it.next();
Component frameComponent = component.getDrawFrameElement().getComponent();
Component frameComponentParent = frameComponent.getParent();
int framePosition = frameComponentParent.indexOf(frameComponent);
frameComponentParent.remove(framePosition);
element.appendChild(component.mOwnNode);
component.mShapePosition.addAll(0, position);
component.createShapeOperation(
this,
mComponentStack,
component.mDescription,
component.hasImageSibling()
? ShapeType.ImageShape
: component.isGroupShape() ? ShapeType.GroupShape : ShapeType.NormalShape,
component.mContext);
Iterator<CachedOperation> opIter = component.iterator();
while (opIter.hasNext()) {
CachedOperation op = opIter.next();
List<Integer> start = op.mStart;
if (!op.mAbsolutePosition) {
if (op.mComponentType.equals(OperationConstants.ATTRIBUTES)) {
@SuppressWarnings("unchecked")
List<Integer> end = (List<Integer>) op.mComponentProperties[0];
// TODO: add _real_ position of the current paragraph (could be in a
// table...)
end.addAll(0, position);
}
// TODO: add _real_ position of the current paragraph (could be in a table...)
start.addAll(0, position);
}
cacheOperation(
false,
op.mComponentType,
start,
false,
op.mHardFormattingProperties,
op.mComponentProperties);
}
}
m_cachedPageShapes.clear();
}
mPageBoundObjectsRelocated |= isFirstDocumentParagraph;
}
if (!paragraphOpCreated) {
cacheOperation(
false,
OperationConstants.PARAGRAPH,
position,
false,
hardFormatting,
mContextName);
}
// For each new paragraph/heading addChild a new context information for their
// whitespace, required for normalization
mWhitespaceStatusStack.add(new WhitespaceStatus(false, mComponentDepth));
element.markAsComponentRoot(true);
// ToDo: NEW COMPONENTS - SECTION
// } else if (element instanceof TextSectionElement) {
// mJsonOperationProducer.addChild("Section", position);
// mCurrentComponent = mCurrentComponent.addChild((TextSectionElement)
// element);
} else {
// a nested text component without known component in-between
// ignore nested paragraph content
mWhitespaceStatusStack.add(new WhitespaceStatus(true, mComponentDepth));
element.ignoredComponent(true);
}
} else if (element instanceof DrawFrameElement
|| Component.isShapeElement(uri, localName)) {
OdfElement shape = element;
Map<String, Object> hardFormatting = null;
if (element instanceof OdfStyleableShapeElement) {
hardFormatting = mJsonOperationProducer.getHardStyles((OdfStyleableShapeElement) shape);
}
if (hardFormatting == null || !hardFormatting.containsKey("drawing")) {
// if there are absolute styles, but not the main property set, where the
// templateStyleId should be placed in
if (hardFormatting == null) {
hardFormatting = new HashMap<String, Object>();
}
hardFormatting.put("drawing", new JSONObject());
}
JSONObject drawingProps = (JSONObject) hardFormatting.get("drawing");
if (hardFormatting == null || !hardFormatting.containsKey("image")) {
// if there are absolute styles, but not the main property set, where the
// templateStyleId should be placed in
if (hardFormatting == null) {
hardFormatting = new HashMap<String, Object>();
}
hardFormatting.put("image", new JSONObject());
}
int anchorHorOffset = 0;
int anchorVertOffset = 0;
int anchorLayerOrder = 0;
int width = 0;
int height = 0;
if (shape instanceof DrawShapeElementBase) {
Integer zIndex = ((DrawShapeElementBase) shape).getDrawZIndexAttribute();
if (null != zIndex) {
anchorLayerOrder = zIndex;
}
}
if (element instanceof DrawLineElement
|| element instanceof DrawConnectorElement
|| element instanceof DrawMeasureElement) {
if (shape.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "y1")
&& shape.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "x1")
&& shape.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "y2")
&& shape.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "x2")) {
int x1 =
MapHelper.normalizeLength(
shape.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "x1"));
int x2 =
MapHelper.normalizeLength(
shape.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "x2"));
int y1 =
MapHelper.normalizeLength(
shape.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "y1"));
int y2 =
MapHelper.normalizeLength(
shape.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "y2"));
anchorHorOffset = Math.min(x1, x2);
width = Math.abs(x2 - x1) + 1;
anchorVertOffset = Math.min(y1, y2);
height = Math.abs(y2 - y1) + 1;
}
} else {
if (shape.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "width")) {
width =
MapHelper.normalizeLength(
shape.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "width"));
}
if (shape.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "height")) {
height =
MapHelper.normalizeLength(
shape.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "height"));
}
if (shape.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "x")) {
anchorHorOffset =
MapHelper.normalizeLength(
shape.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "x"));
}
if (shape.hasAttributeNS(OdfDocumentNamespace.SVG.getUri(), "y")) {
anchorVertOffset =
MapHelper.normalizeLength(
shape.getAttributeNS(OdfDocumentNamespace.SVG.getUri(), "y"));
}
}
try {
if (height != 0) {
drawingProps.put("height", height);
}
if (width != 0) {
drawingProps.put("width", width);
}
if (anchorHorOffset != 0) {
drawingProps.put("anchorHorOffset", anchorHorOffset);
drawingProps.put("left", anchorHorOffset);
}
if (anchorVertOffset != 0) {
drawingProps.put("anchorVertOffset", anchorVertOffset);
drawingProps.put("top", anchorVertOffset);
}
if (anchorLayerOrder != 0) {
drawingProps.put("anchorLayerOrder", anchorLayerOrder);
}
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
if (shape.hasAttributeNS(OdfDocumentNamespace.DRAW.getUri(), "transform")) {
try {
String transform =
shape.getAttributeNS(OdfDocumentNamespace.DRAW.getUri(), "transform");
int index = transform.indexOf("translate");
if (index >= 0) {
index = transform.indexOf('(', index);
transform = transform.substring(index, transform.length());
int separator = transform.indexOf(' ');
String leftValue = transform.substring(1, separator);
index = transform.indexOf(')', separator);
String rightValue = transform.substring(separator + 1, index);
anchorHorOffset += MapHelper.normalizeLength(leftValue);
anchorVertOffset += MapHelper.normalizeLength(rightValue);
}
if (anchorVertOffset != 0) {
drawingProps.put("anchorVertOffset", anchorVertOffset);
}
if (anchorHorOffset != 0) {
drawingProps.put("anchorHorOffset", anchorHorOffset);
}
} catch (IndexOutOfBoundsException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
// <attribute name="text:anchor-type">
// <choice>
// <value>page</value>
// <value>frame</value>
// <value>paragraph</value>
// <value>char</value>
// <value>as-char</value>
// </choice>
// </attribute>
/* API:
anchorHorBase: Horizontal anchor mode: One of 'margin', 'page', 'column', 'character', 'leftMargin', 'rightMargin', 'insideMargin', or 'outsideMargin'.
/*
@text:anchor-type: h=anchorHorBase & v=anchorVerBase
page => h=page v=page
frame => h=column v=margin
paragraph => h=column v=paragraph
char => h=character v=paragraph
as-char => inline & h & v weglassen*/
if (shape.hasAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "anchor-type")) {
try {
String anchorVertBase = null;
String anchorHorBase = null;
String anchorType =
shape.getAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "anchor-type");
if (anchorType.equals("page")) {
// Changes API: true: image as character, false: floating mode
drawingProps.put("inline", Boolean.FALSE);
// page anchor requires page relation
drawingProps.put("anchorHorBase", "page");
drawingProps.put("anchorVertBase", "page");
} else if (anchorType.equals("frame")) {
// Changes API: true: image as character, false: floating mode
drawingProps.put("inline", Boolean.FALSE);
anchorVertBase = "column";
anchorVertBase = "margin";
} else if (anchorType.equals("paragraph")) {
// Changes API: true: image as character, false: floating mode
drawingProps.put("inline", Boolean.FALSE);
anchorHorBase = "column";
anchorVertBase = "paragraph";
} else if (anchorType.equals("char")) {
// Changes API: true: image as character, true: floating mode
drawingProps.put("inline", Boolean.FALSE);
anchorHorBase = "character";
anchorVertBase = "paragraph";
} else if (anchorType.equals("as-char")) {
// Changes API: true: image as character, false: floating mode
drawingProps.put("inline", Boolean.TRUE);
}
if (anchorVertBase != null && !drawingProps.has("anchorVertBase")) {
drawingProps.put("anchorVertBase", anchorVertBase);
}
if (anchorHorBase != null && !drawingProps.has("anchorHorBase")) {
drawingProps.put("anchorHorBase", anchorHorBase);
}
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
hardFormatting.put("drawing", drawingProps);
mComponentDepth++;
List<Integer> pos = updateComponentPosition();
// the delay of the operation was not the solution, as the children would be added fist
// instead a setAttribute would be more appropriate
// even if there is a automatic style, only the template style is required
if (element instanceof OdfStyleableShapeElement) {
String styleId = ((OdfStyleableShapeElement) shape).getDocumentStyleName();
if (styleId != null && !styleId.isEmpty()) {
hardFormatting.put(OPK_STYLE_ID, styleId);
}
}
ShapeProperties shapeProps = new ShapeProperties(pos, hardFormatting);
// special handling for frames as together with the image child they are a single user
// component
if (element instanceof DrawFrameElement) {
shapeProps.setDrawFrameElement((DrawFrameElement) shape);
if (!mComponentStack.isEmpty()) {
final CachedComponent comp = mComponentStack.peek();
if (comp instanceof ShapeProperties
&& ((ShapeProperties) comp).getDrawFrameElement() != null) {
LOG.warning("Feature 'Frame attached to Frame' yet unsupported");
}
}
} else if (element instanceof DrawGElement) {
shapeProps.setGroupShape();
element.markAsComponentRoot(true);
}
if (mCurrentComponent != null) {
mComponentStack.push(shapeProps);
mCurrentComponent = mCurrentComponent.createChildComponent(element);
}
// mShapePropertiesStack.push(shapeProps);
// table component (table within a text document or a spreadsheet)
} else if (element instanceof TableTableElement) {
mComponentDepth++;
// The table will be created with column width, after columns are parsed (just before
// first row!)
updateComponentPosition();
// tables are not written out directly, but its operation collected and only flushed
// if they are not exceeding a maximum size
isTableNew = true;
mTableElement = (TableTableElement) element;
mCurrentComponent = mCurrentComponent.createChildComponent(mTableElement);
// initialize a new list for the relative column widths
// ToDo: Receive the styles from the root component
// ToDo: If I do not want a DOM, do I have to parse the styles and addChild them to
// component?
// Do I have to parse the styles.xml first to get the props as maps (hashmaps)?
if (mTableElement.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "style-name")) {
mTableHardFormatting = mJsonOperationProducer.getHardStyles(mTableElement);
String styleId = mTableElement.getDocumentStyleName();
if (styleId != null && !styleId.isEmpty()) {
if (mTableHardFormatting == null) {
mTableHardFormatting = new HashMap<>();
}
mTableHardFormatting.put(OPK_STYLE_ID, styleId);
// All ODF styles are hard formatted
// JSONObject tableProps = mTableHardFormatting.get("table");
// mTableHardFormatting.put("templateStyleId",
// table.getDocumentStyle().getStyleNameAttribute());
// OdfStyle tableStyle = table.getDocumentStyle();
// if(tableStyle != null){
// mTableDisplayName = tableStyle.getAttributeNS(OdfDocumentNamespace.STYLE.getUri(),
// "display-name");
}
} else {
mTableHardFormatting = new HashMap<>();
}
mTableName = mTableElement.getAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "name");
mColumnRelWidths = new LinkedList<>();
element.markAsComponentRoot(true);
} else if (element instanceof TableTableRowElement) {
mComponentDepth++;
if (isTableNew) {
// In case neiter relative nor absolute table column width were given, all column are
// equal sized (given rel size of '1')
mColumnRelWidths = Table.collectColumnWidths(mTableElement, mColumns);
mColumns.clear();
if (mColumnRelWidths != null && mColumnRelWidths.isEmpty()) {
for (int i = 0; i < mColumnCount; i++) {
mColumnRelWidths.add(ONE);
}
}
// The grid is known after columns had been parsed, updating later to row positino
List<Integer> tablePosition = new LinkedList<Integer>(mLastComponentPositions);
cacheTableOperation(
OperationConstants.TABLE,
tablePosition,
mTableHardFormatting,
mColumnRelWidths,
mTableName);
mTableHardFormatting = null;
isTableNew = false;
mTableName = null;
mColumnCount = 0;
mColumnRelWidths = null;
}
List<Integer> position = updateComponentPosition();
TableTableRowElement row = (TableTableRowElement) element;
mCurrentComponent = mCurrentComponent.createChildComponent(row);
// repeatition can cause a different positioning
int repeatedRows = 1;
if (row.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "number-rows-repeated")) {
repeatedRows =
Integer.parseInt(
row.getAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-rows-repeated"));
mCurrentComponent.hasRepeated(true);
}
boolean isVisible = Boolean.TRUE;
if (row.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "visibility")) {
isVisible =
Constants.VISIBLE.equals(
row.getAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "visibility"));
}
Map<String, Object> hardFormatting = mJsonOperationProducer.getHardStyles(row);
OdfStyle templateStyle = row.getDocumentStyle();
String styleId = null;
if (templateStyle != null) {
styleId = templateStyle.getStyleNameAttribute();
if (styleId != null && !styleId.isEmpty()) {
hardFormatting.put(OPK_STYLE_ID, styleId);
}
}
if (!isVisible) {
JSONObject rowProps;
if (hardFormatting == null) {
// if there are absolute styles, but not the main property set, where the
// templateStyleId should be placed in
if (hardFormatting == null) {
hardFormatting = new HashMap<String, Object>();
}
}
if (!hardFormatting.containsKey("row")) {
rowProps = new JSONObject();
hardFormatting.put("row", rowProps);
} else {
rowProps = (JSONObject) hardFormatting.get("row");
if (rowProps == null) {
rowProps = new JSONObject();
}
}
try {
rowProps.put("visible", Boolean.FALSE);
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
cacheTableOperation(OperationConstants.ROWS, position, hardFormatting, repeatedRows);
element.markAsComponentRoot(true);
} else if (element instanceof TableTableCellElement
|| element instanceof TableCoveredTableCellElement) {
boolean covered = element instanceof TableCoveredTableCellElement;
mComponentDepth++;
TableTableCellElement cell = covered ? null : (TableTableCellElement) element;
if (cell != null) {
mCurrentComponent = mCurrentComponent.createChildComponent(cell);
} else {
mCurrentComponent = mCurrentComponent.createChildComponent(element);
}
CachedTable cachedTableOps = (CachedTable) mComponentStack.peek();
cachedTableOps.setCellRepetition(1);
int repetition = 1;
Map<String, Object> hardFormatting = null;
if (!covered) {
hardFormatting = mJsonOperationProducer.getHardStyles(cell);
}
// repeatition and covering can cause a different positioning
// ToDo: To make DOM optional, work on the component instead of the element. Check
// directly SAX attributes parameter!
if (element.hasAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated")) {
// cellProps.put("repeatedColumns",
// cell.getAttributeNS(OdfDocumentNamespace.TABLE.getUri(),
// "number-columns-repeatedColumns"));
cachedTableOps.setCellRepetition(
Integer.parseInt(
element.getAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated")));
repetition =
Integer.parseInt(
element.getAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated"));
mCurrentComponent.hasRepeated(true);
}
if (cell != null && cell.hasAttributes()) {
try {
// if there are absolute styles, but not the main property set, where the
// templateStyleId should be placed in
if (hardFormatting == null || !hardFormatting.containsKey("cell")) {
if (hardFormatting == null) {
hardFormatting = new HashMap<String, Object>();
}
}
JSONObject cellProps = (JSONObject) hardFormatting.get("cell");
if (cellProps == null) {
cellProps = new JSONObject();
}
if (cell.hasAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-spanned")) {
cellProps.put(
COLUMN_SPAN,
Integer.parseInt(
cell.getAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-spanned")));
}
if (cell.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "number-rows-spanned")) {
cellProps.put(
ROW_SPAN,
Integer.parseInt(
cell.getAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-rows-spanned")));
}
if (cellProps.length() != 0) {
hardFormatting.put("cell", cellProps);
}
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
List<Integer> position = updateComponentPosition();
OdfStyle templateStyle = covered ? null : cell.getDocumentStyle();
if (templateStyle != null) {
String styleId = templateStyle.getStyleNameAttribute();
if (styleId != null && !styleId.isEmpty()) {
hardFormatting.put(OPK_STYLE_ID, styleId);
}
}
cacheTableOperation(
OperationConstants.CELLS, position, hardFormatting, mCurrentComponent, repetition);
element.markAsComponentRoot(true);
} else if (element instanceof TextLineBreakElement) {
mComponentDepth++;
TextLineBreakElement lineBreak = (TextLineBreakElement) element;
List<Integer> position = updateComponentPosition();
mCurrentComponent = mCurrentComponent.createChildComponent(lineBreak);
cacheOperation(false, OperationConstants.LINE_BREAK, position, false, null, null, null);
element.markAsComponentRoot(true);
} else if (element instanceof TextTabElement) {
mComponentDepth++;
TextTabElement tab = (TextTabElement) element;
List<Integer> position = updateComponentPosition();
mCurrentComponent = mCurrentComponent.createChildComponent(tab);
cacheOperation(false, OperationConstants.TAB, position, false, null, null, null);
element.markAsComponentRoot(true);
} else if (Component.isField(uri, localName)) {
mComponentDepth++;
List<Integer> position = updateComponentPosition();
mCurrentComponent = mCurrentComponent.createChildComponent(element);
TextFieldSelection selection = null;
if (element.hasAttributeNS(LIBRE_OFFICE_MS_INTEROP_NAMESPACE, "type")
&& element
.getAttributeNS(LIBRE_OFFICE_MS_INTEROP_NAMESPACE, "type")
.equals(LIBRE_OFFICE_MS_INTEROP_TYPE_CHECKBOX)) {
selection =
new TextFieldSelection(element, position, LIBRE_OFFICE_MS_INTEROP_CHECKBOX_UNICODE);
} else {
if (mFileDom instanceof OdfContentDom) {
selection =
new TextFieldSelection(
element,
position,
((OdfContentDom) mFileDom).getAutomaticStyles(),
mUserFieldDecls);
} else {
selection =
new TextFieldSelection(
element,
position,
((OdfStylesDom) mFileDom).getAutomaticStyles(),
mUserFieldDecls);
}
// kann auch (OdfStylesDom) sein!
// element.getParentNode();
// TextTimeElement telem = (TextTimeElement)element;
// Map<String, Object> hardFormatting =
// mJsonOperationProducer.getHardStyles(telem);
}
mTextSelectionStack.add(selection);
} else if (element instanceof OfficeAnnotationElement) {
++mComponentDepth;
if (mIsCharsBeginning) {
updateTextPosition();
}
mCurrentComponent = mCurrentComponent.createChildComponent(element);
String annotationName = ((OfficeAnnotationElement) element).getOfficeNameAttribute();
if (annotationName == null) {
// annotations without range don't have a name attribute
annotationName = ((OdfDocument) mSchemaDoc).getUniqueAnnotationName();
}
CommentComponent commentProps =
new CommentComponent(mLastComponentPositions, annotationName);
((OdfDocument) mSchemaDoc)
.addAnnotation(annotationName, ((OfficeAnnotationElement) element));
mComponentStack.push(commentProps);
element.markAsComponentRoot(true);
} else if (element instanceof OfficeAnnotationEndElement) {
mComponentDepth++;
List<Integer> position = updateComponentPosition();
String id = COMMENT_PREFIX;
id += ((OfficeAnnotationEndElement) element).getOfficeNameAttribute();
cacheOperation(
false, OperationConstants.COMMENTRANGE, position, false, null, id, mContextName);
mCurrentComponent = mCurrentComponent.createChildComponent(element);
element.markAsComponentRoot(true);
} else {
mComponentDepth++;
element.markAsComponentRoot(true);
}
} else if (element instanceof TextSpanElement) {
// Span <text:span> will be triggering an operation after the text content is parsed
TextSpanSelection selection =
new TextSpanSelection((TextSpanElement) element, getTextPosition());
mTextSelectionStack.add(selection);
} else if (element instanceof TextAElement) {
TextHyperlinkSelection selection =
new TextHyperlinkSelection((TextAElement) element, getTextPosition());
mTextSelectionStack.add(selection);
} else if (element
instanceof
TextSElement) { // IMPROVABLE: Currently no component, as it will be removed anyway and
// would burden removal from automatic path counting
mComponentDepth++;
List<Integer> position = updateComponentPosition();
if (mIsCharsBeginning) {
mCharsStartPosition = position;
mIsCharsBeginning = false;
}
// No operation triggering as client knows only space characters. We keep the
// parsing/mapping to the more performant server
TextSElement spaces = (TextSElement) element;
mCurrentComponent = mCurrentComponent.createChildComponent(spaces);
Integer quantity = spaces.getTextCAttribute();
if (quantity == null) {
addText(/*mCachedTableOps, */ "\u0020");
// mCharsForOperation.append('\u0020');
} else {
for (int i = 0; i < quantity; i++) {
mCharsForOperation.append('\u0020');
}
addText(/*mCachedTableOps, */ mCharsForOperation);
}
} else if (element instanceof TableTableColumnElement) {
// Columns can be grouped by <table:table-columns> and <table:table-column-group>, these
// would addChild metadata to the following columns
// Column command should be triggered when one of the grouping starts or closes or if the
// first row arrives
TableTableColumnElement column = (TableTableColumnElement) element;
// Adjust Column Count
mColumnCount++;
int repeatedColumns = 1;
if (column.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated")) {
repeatedColumns =
Integer.parseInt(
column.getAttributeNS(
OdfDocumentNamespace.TABLE.getUri(), "number-columns-repeated"));
if (repeatedColumns > 1) {
mColumnCount += (repeatedColumns - 1);
}
}
if (mColumns == null) {
mColumns = new ArrayList<TableTableColumnElement>();
}
mColumns.add(column);
} else if (element instanceof TextListElement) {
TextListElement list = (TextListElement) element;
// in case it is a new list
if (mListStyleStack.isEmpty()) {
// Add always a style, so it can be popped of the stack EVERY time a list element ends
ParagraphListProperties paragraphListProps = new ParagraphListProperties();
paragraphListProps.setListStart(true);
// There are two continuation mechanisms for lists in ODF.
// ODF 1.0/1.1 uses @text:continue-numbering using true/false
// ODF 1.2 added @text:continue-list using an IDRef to an xml:id of another list.
String continuedListId = list.getTextContinueListAttribute();
String listXmlId = list.getXmlIdAttribute();
if (continuedListId != null && !continuedListId.isEmpty()) {
paragraphListProps.setListId(newContinuedList(continuedListId, listXmlId).getListId());
} else if (listXmlId != null && !listXmlId.isEmpty()) {
paragraphListProps.setListId(newContinuedList(listXmlId).getListId());
}
if (listXmlId != null && !listXmlId.isEmpty()) {
paragraphListProps.setListXmlId(listXmlId);
} else {
paragraphListProps.setListXmlId(null);
}
mListStyleStack.add(paragraphListProps);
} else {
// Add always a style, so it can be popped of the stack EVERY time a list element ends
mListStyleStack.add(new ParagraphListProperties());
}
// @text:continue-numbering LATER
// @text:continue-list LATER
// @xml-id - LATER
// @text:style-name is the given list style unless overwritten by a decendent list
// Check if the list style was used already in the document,
// if not, map the list properties. Check first in auto than in template.
// (Due to MSO issue the style might be even in auto in styles.xml - shall I move them back
// to content?)
if (list.hasAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "style-name")) {
String listStyle = list.getAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "style-name");
mListStyleStack.getLast().setListStyleName(listStyle);
}
} else if (element instanceof TextListItemElement
|| element instanceof TextListHeaderElement) {
ParagraphListProperties paragraphListStyle = mListStyleStack.getLast();
OdfElement listItem = element;
if (listItem instanceof TextListHeaderElement) {
// list header never show a label
paragraphListStyle.showListLabel(false);
} else {
// As a new list item starts, the next paragraph needs to provide the list label
paragraphListStyle.showListLabel(true);
}
// @text:start-value is provided to the first paragraph only
if (listItem.hasAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "start-value")) {
mListStartValue =
Integer.parseInt(
listItem.getAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "start-value"));
}
// @text:style-override overrides within this list item the list style
if (listItem.hasAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "style-override")) {
String styleOverride =
listItem.getAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "style-override");
if (styleOverride != null && !styleOverride.isEmpty()) {
paragraphListStyle.overrideListStyle(styleOverride);
} else {
paragraphListStyle.overrideListStyle(null);
}
} else {
paragraphListStyle.overrideListStyle(null);
}
// @xml-id
String listXmlId = null;
if (listItem instanceof TextListItemElement) {
listXmlId = ((TextListItemElement) listItem).getXmlIdAttribute();
} else if (listItem instanceof TextListHeaderElement) {
listXmlId = ((TextListHeaderElement) listItem).getXmlIdAttribute();
}
if (listXmlId != null && !listXmlId.isEmpty()) {
mListStyleStack.getLast().setListItemXmlId(listXmlId);
} else {
mListStyleStack.getLast().setListItemXmlId(null);
}
// <style:master-page style:name="Standard" style:page-layout-name="Mpm1">
} else if (element instanceof StyleMasterPageElement) {
StyleMasterPageElement masterPage = (StyleMasterPageElement) element;
mMasterPageStyleName = masterPage.getStyleNameAttribute();
mPageLayoutName = masterPage.getStylePageLayoutNameAttribute();
footerAttrs = headerAttrs = null;
if (mPageLayoutName != null) {
OdfStylesDom stylesDom;
try {
stylesDom = mSchemaDoc.getStylesDom();
OdfOfficeAutomaticStyles autoStyles = stylesDom.getAutomaticStyles();
if (autoStyles != null) {
OdfStylePageLayout pageLayout = autoStyles.getPageLayout(mPageLayoutName);
if (pageLayout != null) {
mPageStyleUsage = pageLayout.getStylePageUsageAttribute();
headerAttrs =
getHeaderFooterAttrs(
(OdfElement)
pageLayout.getChildElement(
StyleHeaderStyleElement.ELEMENT_NAME.getUri(), "header-style"));
footerAttrs =
getHeaderFooterAttrs(
(OdfElement)
pageLayout.getChildElement(
StyleFooterStyleElement.ELEMENT_NAME.getUri(), "footer-style"));
}
}
} catch (IOException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
String nextMasterPageStyle = masterPage.getStyleNextStyleNameAttribute();
if (nextMasterPageStyle != null && !nextMasterPageStyle.isEmpty()) {
mHasNextMasterPage = true;
} else {
mHasNextMasterPage = false;
}
} else if (Component.isHeaderRoot(uri, localName)) {
PageArea pageArea = null;
if (localName.equals("header")) {
pageArea = HEADER_DEFAULT;
} else if (localName.equals("header-left")) {
pageArea = HEADER_EVEN;
} else {
pageArea = HEADER_FIRST;
}
mContextName = pageArea.getPageAreaName() + CONTEXT_DELIMITER + mMasterPageStyleName;
// insert the Header style
// {"name":"addHeaderFooter","id":"Standard_header_default","type":"header_default"}
mJsonOperationProducer.addHeaderFooter(mContextName, pageArea, headerAttrs);
} else if (Component.isFooterRoot(uri, localName)) {
// insert the Footer style
PageArea pageArea = null;
if (localName.equals("footer")) {
pageArea = FOOTER_DEFAULT;
} else if (localName.equals("footer-left")) {
pageArea = FOOTER_EVEN;
} else {
pageArea = FOOTER_FIRST;
}
mContextName = pageArea.getPageAreaName() + CONTEXT_DELIMITER + mMasterPageStyleName;
mJsonOperationProducer.addHeaderFooter(mContextName, pageArea, footerAttrs);
} else if (element instanceof DrawImageElement) {
DrawImageElement image = (DrawImageElement) element;
ShapeProperties frameProps = (ShapeProperties) mComponentStack.peek();
// ShapeProperties frameProps = mShapePropertiesStack.peekFirst();
int childNo = frameProps.incrementChildNumber();
if (childNo == 1) {
Map<String, Object> hardFormatting = new HashMap<String, Object>();
hardFormatting.putAll(frameProps.getShapeHardFormatting());
JSONObject drawingProps = (JSONObject) hardFormatting.get("drawing");
JSONObject imageProps = (JSONObject) hardFormatting.get("image");
if (image.hasAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href")) {
try {
String href = image.getAttributeNS(OdfDocumentNamespace.XLINK.getUri(), "href");
imageProps.put("imageUrl", href);
// if there is cropping from the frame, we need to do further calculation based on
// real graphic size
if (imageProps.has("cropRight")
&& (imageProps.has("height") || imageProps.has("width"))) {
JsonOperationProducer.calculateCrops(image, href, imageProps);
}
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
if (image.hasAttributeNS(OdfDocumentNamespace.XML.getUri(), "id")) {
try {
drawingProps.put(
"imageXmlId", image.getAttributeNS(OdfDocumentNamespace.XML.getUri(), "id"));
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
// ToDo: Need test document with child element having office:binary-data with base64
// content
DrawFrameElement frameElement = frameProps.getDrawFrameElement();
frameElement.markAsComponentRoot(true);
mComponentStack.pop();
// mShapePropertiesStack.pollFirst();
mComponentStack.push(frameProps);
// mShapePropertiesStack.addFirst(frameProps);
frameProps.declareImage();
hardFormatting.put("drawing", drawingProps);
// }else {
// drawingProps.put("viewAlternative", childNo - 1);
}
// if (!frameProps.hasImageSibling()) {
// mComponentDepth++;
// frameProps.setFramePosition(updateComponentPosition());
// mCurrentComponent = mCurrentComponent.createChildComponent(frameElement);
// // position.set(position.size() - 1, position.get(position.size() - 1) +1);
// }
// if (childNo == 1) { // DISABLING REPLACEMENT IMAGE FEATURE AS LONG CLIENT DOES NOT
// SUPPORT IT
// ToDo: Dependencies for frame replacement feature has to be updated in
// OdfElement.raiseComponentSize() for every Frame child/feature enabled
// frameProps.saveShapeProps(frameProps.getShapePosition(), hardFormatting);
// }
} else if (element instanceof DrawTextBoxElement) {
// element.getAttributeNodeNS(namespaceURI, localName);
// Map<String, Object> hardFormatting = null;
// if (element instanceof OdfStyleableShapeElement) {
// hardFormatting =
// mJsonOperationProducer.getHardStyles((OdfStyleableShapeElement) element);
// }
// JSONObject drawingProps = (JSONObject) hardFormatting.get("drawing");
// JSONObject drawingProps = new JSONObject();
if (!mComponentStack.empty()) {
ShapeProperties parentShapeProps = (ShapeProperties) mComponentStack.peek();
JSONObject originalDrawingProps =
(JSONObject) parentShapeProps.mShapeHardFormatations.get("drawing");
if (originalDrawingProps != null && !originalDrawingProps.has("height")) {
try {
if (!parentShapeProps.mShapeHardFormatations.containsKey("shape")) {
parentShapeProps.mShapeHardFormatations.put("shape", new JSONObject());
}
JSONObject originalShapeProps =
(JSONObject) parentShapeProps.mShapeHardFormatations.get("shape");
originalShapeProps.put("autoResizeHeight", "true");
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
// but within a shape being a frame, the child elements are still of interest (currently only
// <draw:image> supported)
// } else if (!mShapePropertiesStack.isEmpty() &&
// mShapePropertiesStack.peekLast().mDrawFrameElement != null) {
if (Component.isDocumentRoot(uri, localName)
|| Component.isHeaderRoot(uri, localName)
|| Component.isFooterRoot(uri, localName)) {
// temporary initated here as all the tests are not using the OperationTextDocument
mCurrentComponent = new Component(element);
mSchemaDoc.setRootComponent(mCurrentComponent);
// for every header and footer restart counting
if (Component.isHeaderRoot(uri, localName) || Component.isFooterRoot(uri, localName)) {
mLastComponentPositions.clear();
} else {
mPageArea = PageArea.BODY;
}
}
} else {
if (element instanceof OdfElement) {
element.ignoredComponent(true);
}
}
// add the new element as child & make it the current context node
mCurrentNode = mCurrentNode.appendChild(element);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
flushTextAtEnd(uri, localName, qName);
// at the end of a table check if it can be flushed
if (uri != null
&& localName != null
&& localName.equals(TableTableElement.ELEMENT_NAME.getLocalName())
&& uri.equals(OdfDocumentNamespace.TABLE.getUri())) {
endTableSizeEvaluation();
}
// office:styles only exist in styles.xml
if (qName.equals("office:styles")) {
if (mFileDom instanceof OdfStylesDom) {
Integer defaultTabStopWidth = null;
JSONObject defaultPageStyles = null;
OdfStylesDom stylesDom = (OdfStylesDom) mFileDom;
// reset the position context used for header/footer
mContextName = null;
OdfOfficeStyles officeStyles = (OdfOfficeStyles) mCurrentNode;
if (officeStyles != null) {
// check if the default hyperlinkstyle do exist
mHasHyperlinkTemplateStyle =
officeStyles.getStyle(HYERLINK_DEFAULT_STYLE, OdfStyleFamily.Text) != null;
final Iterator<OdfStyle> paragraphStyleIter =
officeStyles.getStylesForFamily(OdfStyleFamily.Paragraph).iterator();
// The sort is for testing purpose to receive across different JDK an equal result
Integer _defaultTabStopWidth = null;
while (paragraphStyleIter.hasNext()) {
// defaulTableWidth is part of the paragraph default style (optional)
_defaultTabStopWidth =
mJsonOperationProducer.triggerStyleHierarchyOps(
officeStyles, OdfStyleFamily.Paragraph, paragraphStyleIter.next());
if (_defaultTabStopWidth != null) {
defaultTabStopWidth = _defaultTabStopWidth;
}
}
final Iterator<OdfStyle> textStyleIter =
officeStyles.getStylesForFamily(OdfStyleFamily.Text).iterator();
while (textStyleIter.hasNext()) {
mJsonOperationProducer.triggerStyleHierarchyOps(
officeStyles, OdfStyleFamily.Text, textStyleIter.next());
}
final Iterator<OdfStyle> graphicStyleIter =
officeStyles.getStylesForFamily(OdfStyleFamily.Graphic).iterator();
while (graphicStyleIter.hasNext()) {
mJsonOperationProducer.triggerStyleHierarchyOps(
officeStyles, OdfStyleFamily.Graphic, graphicStyleIter.next());
}
// always generate graphic default style
mJsonOperationProducer.triggerDefaultStyleOp(
OdfStyleFamily.Graphic, officeStyles.getDefaultStyle(OdfStyleFamily.Graphic));
// for(OdfStyle style : officeStyles.getStylesForFamily(OdfStyleFamily.Table)){
// mJsonOperationProducer.triggerStyleHierarchyOps(officeStyles,
// OdfStyleFamily.Table, style);
// }
// for(OdfStyle style : officeStyles.getStylesForFamily(OdfStyleFamily.TableRow)){
// mJsonOperationProducer.triggerStyleHierarchyOps(officeStyles,
// OdfStyleFamily.TableRow, style);
// }
// for(OdfStyle style :
// officeStyles.getStylesForFamily(OdfStyleFamily.TableColumn)){
// mJsonOperationProducer.triggerStyleHierarchyOps(officeStyles,
// OdfStyleFamily.TableColumn, style);
// }
for (OdfStyle style : officeStyles.getStylesForFamily(OdfStyleFamily.TableCell)) {
mJsonOperationProducer.triggerStyleHierarchyOps(
officeStyles, OdfStyleFamily.TableCell, style);
// mJsonOperationProducer.triggerStyleHierarchyOps(officeStyles,
// OdfStyleFamily.TableCell, (OdfStyleBase)
// officeStyles.getDefaultStyle(OdfStyleFamily.TableCell));
}
// for(OdfStyle style : officeStyles.getStylesForFamily(OdfStyleFamily.Section)){
// mJsonOperationProducer.triggerStyleHierarchyOps(officeStyles,
// OdfStyleFamily.Section, style);
// }
// for(OdfStyle style : officeStyles.getStylesForFamily(OdfStyleFamily.List)){
// mJsonOperationProducer.triggerStyleHierarchyOps(officeStyles,
// OdfStyleFamily.List, style);
// }
final Iterator<OdfTextListStyle> textListStyleIter =
officeStyles.getListStyles().iterator();
while (textListStyleIter.hasNext()) {
mJsonOperationProducer.addListStyle(textListStyleIter.next());
}
// maps page properties, but returns the default page properties
defaultPageStyles = mJsonOperationProducer.addPageProperties(stylesDom);
// dispatches default document attributes
mJsonOperationProducer.addDocumentProperties(
stylesDom, defaultTabStopWidth, defaultPageStyles);
} else {
mJsonOperationProducer.addDocumentProperties(stylesDom, null, null);
}
}
}
// if we remove the current element, the current node shall not be changed in the end
boolean selectionNormalization = false;
// SPECIAL HANDLING FOR DESCRIPTION OF SHAPES: draw:frame as the shape is one of the children,
// e.g a draw:image child
if (!checkEndOfBlockedSubTree(
uri, localName) /*&& !(mContextName != null && localName.equals("annotation-end"))*/) {
boolean isImageComponent = false;
if ((uri != null
&& uri.equals(DrawFrameElement.ELEMENT_NAME.getUri())
&& localName.equals(DrawFrameElement.ELEMENT_NAME.getLocalName())
|| Component.isShapeElement(uri, localName))) {
if (!mComponentStack.empty()) {
ShapeProperties shapeProps = (ShapeProperties) mComponentStack.pop();
mComponentDepth--;
// Check for description of shape/frame about to be closed
// ShapeProperties shapeProps = mShapePropertiesStack.removeLast();
isImageComponent = shapeProps.hasImageSibling();
NodeList descList =
mCurrentComponent.mRootElement.getElementsByTagNameNS(
OdfDocumentNamespace.SVG.getUri(), SvgDescElement.ELEMENT_NAME.getLocalName());
String description = null;
if (descList.getLength() > 0) {
SvgDescElement desc = (SvgDescElement) descList.item(0);
Node descText = desc.getFirstChild();
if (descText != null && descText instanceof Text) {
description = ((Text) descText).getTextContent();
}
}
// it is root shape if the parent office:text
shapeProps.createShapeOperation(
this,
mComponentStack,
description,
isImageComponent
? ShapeType.ImageShape
: shapeProps.isGroupShape() ? ShapeType.GroupShape : ShapeType.NormalShape,
mContextName);
if (shapeProps.isGroupShape()) {
mCurrentNode.setUserData(
"groupWidth",
new Integer(
shapeProps.mHoriOffsetMax
- (shapeProps.mHoriOffsetMin == null ? 0 : shapeProps.mHoriOffsetMin)),
null);
mCurrentNode.setUserData(
"groupHeight",
new Integer(
shapeProps.mVertOffsetMax
- (shapeProps.mVertOffsetMin == null ? 0 : shapeProps.mVertOffsetMin)),
null);
}
// flush the inner operations of the shape
Iterator<CachedOperation> opIter = shapeProps.iterator();
while (opIter.hasNext()) {
CachedOperation op = opIter.next();
cacheOperation(
true,
op.mComponentType,
op.mStart,
false,
op.mHardFormattingProperties,
op.mComponentProperties);
}
mCurrentComponent = mCurrentComponent.getParent();
}
// } else if (Component.isCoveredComponentRoot(uri, localName)) { // adjust counting for
// table cells without numbering
// //ToDO: Instead to count the covered someone should count the spanning (BUT this is
// against OOXML cell numbering!)
// mComponentDepth--;
} else if (isSpaceElement(uri, localName)) {
mComponentDepth--;
mCurrentComponent = mCurrentComponent.getParent();
// } else if (uri != null && uri.equals(DrawImageElement.ELEMENT_NAME.getUri()) &&
// localName.equals(DrawImageElement.ELEMENT_NAME.getLocalName())) {
// mFramePropertiesStack.getFirst().decrementChildNumber();
} else if (uri != null && Component.isComponentRoot(uri, localName)) {
// if (Component.isTextComponentRoot(uri, localName) &&
// mWhitespaceStatusStack.size() > 0 &&
// mWhitespaceStatusStack.getLast().mIsParagraphIgnored) {
/* no ignored paragraphs anymore
if (Component.isTextComponentRoot(uri, localName) && mWhitespaceStatusStack.size() > 0 && mWhitespaceStatusStack.getLast().mIsParagraphIgnored) {) {
// do nothing for a ignored paragraph
mWhitespaceStatusStack.removeLast();
// SPECIAL HANDLING FOR IMAGE: draw:image (not a component root T- replacement for draw:frame)
} else */
if (localName.equals(DrawFrameElement.ELEMENT_NAME.getLocalName())
&& uri.equals(DrawFrameElement.ELEMENT_NAME.getUri())
&& isImageComponent
|| !(localName.equals(DrawFrameElement.ELEMENT_NAME.getLocalName())
&& uri.equals(DrawFrameElement.ELEMENT_NAME.getUri()))) {
// if the current component is a text container flush spans
if (Component.isTextComponentRoot(mCurrentNode)) {
Collection<TextSelection> selections =
((TextParagraphElementBase) mCurrentNode).getTextSelections();
if (selections != null) {
for (TextSelection s : selections) {
OdfStylableElement selectionElement = (OdfStylableElement) s.getSelectionElement();
Map<String, Object> hardFormatting =
mJsonOperationProducer.getHardStyles(selectionElement);
String styleId = null;
OdfStyle templateStyle = selectionElement.getDocumentStyle();
if (templateStyle != null) {
styleId = templateStyle.getStyleNameAttribute();
}
if (s.hasUrl() || styleId != null) {
try {
JSONObject charProps;
if (hardFormatting == null) {
// if there are absolute styles, but not the main property set, where the
// templateStyleId should be placed in
if (hardFormatting == null) {
hardFormatting = new HashMap<String, Object>();
}
}
if (s.hasUrl()) {
if (!hardFormatting.containsKey("character")) {
charProps = new JSONObject();
hardFormatting.put("character", charProps);
} else {
charProps = (JSONObject) hardFormatting.get("character");
}
charProps.put("url", s.getURL());
}
if (styleId != null && !styleId.isEmpty()) {
hardFormatting.put(OPK_STYLE_ID, styleId);
} else {
// add the implicit by LO/AOO used hyperlink style
if (mHasHyperlinkTemplateStyle) {
hardFormatting.put(OPK_STYLE_ID, HYERLINK_DEFAULT_STYLE);
}
}
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName())
.log(Level.SEVERE, null, ex);
}
}
if (hardFormatting != null) {
// if (mWithinTable) {
cacheOperation(
false,
OperationConstants.ATTRIBUTES,
s.getStartPosition(),
false,
hardFormatting,
s.getEndPosition(),
mContextName);
}
}
}
// // in this case check if the closing descendent (this element)
// // had any none whitespace text and apply if necessary the change
// // remove the current whitespace properties from the stack
// int depth = mWhitespaceStatusStack.size();
// boolean childHasWhiteSpace = true;
// boolean parentHasOnlyWhiteSpace;
// if there is a parent text container
if (mWhitespaceStatusStack.size() > 0) {
mWhitespaceStatusStack.removeLast();
// BEFORE WE DID NOT ALLOWED TO FOLLOWING PARAGRAPHS
// // see if the child only had whitespaces
// childHasWhiteSpace = mWhitespaceStatusStack.getLast().hasOnlyWhiteSpace();
// // switch to parent
// mWhitespaceStatusStack.pop();
// // see if the parent had only whitespaces
// WhitespaceStatus parentWhiteSpaceStatus = mWhitespaceStatusStack.getLast();
// parentHasOnlyWhiteSpace = parentWhiteSpaceStatus.hasOnlyWhiteSpace();
// // if the parent had only whitespaces, but not the child
// if (parentHasOnlyWhiteSpace && !childHasWhiteSpace) {
// // remove the only whitespace modus from the parent
// parentWhiteSpaceStatus.setOnlyWhiteSpace(childHasWhiteSpace);
// }
// } else {
// // otherwise just end the state collection of this paragraph/heading
// mWhitespaceStatusStack.pop();
}
}
// removing the last in the list of positions, when a component is closed
if (localName.equals("annotation")) {
CommentComponent commProps = (CommentComponent) mComponentStack.pop();
if (!commProps.isInHeaderFooter()) {
String id = COMMENT_PREFIX;
id += commProps.getCommentName();
cacheOperation(
false,
OperationConstants.COMMENT,
commProps.getComponentPosition(),
false,
null,
id,
commProps.getAuthor(),
commProps.getDate(),
mContextName);
int parentPosSize = commProps.getComponentPosition().size();
for (CachedOperation op : commProps) {
// TODO: add id as target, remove comments own position from op.mStart;
CachedOperation newOp = op.clone();
for (int r = 0; r < parentPosSize; ++r) {
newOp.mStart.remove(0);
}
ArrayList<Object> componentProperties = new ArrayList<Object>();
int propIndex = 0;
while (newOp.mComponentProperties.length > propIndex
&& newOp.mComponentProperties[propIndex] != null) {
if (propIndex == 0
&& newOp.mComponentType.equals(OperationConstants.ATTRIBUTES)) {
@SuppressWarnings("unchecked")
List<Integer> endArray = (List<Integer>) newOp.mComponentProperties[propIndex];
for (int r = 0; r < parentPosSize; ++r) {
endArray.remove(0);
}
componentProperties.add(endArray);
} else if (mContextName == null
|| !newOp.mComponentProperties[propIndex].equals(mContextName)) {
componentProperties.add(newOp.mComponentProperties[propIndex]);
}
++propIndex;
}
componentProperties.add(id);
componentProperties.add(null);
cacheOperation(
false,
newOp.mComponentType,
newOp.mStart,
true,
newOp.mHardFormattingProperties,
componentProperties.toArray());
}
}
// } else if (localName.equals("annotation-end")){ //no action
// required
}
if (mCurrentComponent.hasRepeated()) {
// if it is a cell or row not in spreadsheets all operations back from addCell/addRows
// need to be repeated with incremented positions (TODO: needs to be recursive! )
boolean isCell = localName.equals("table-cell");
boolean isRow = localName.equals("table-row");
if (isRow || isCell) {
CachedTable currentTable = (CachedTable) mComponentStack.peek();
int opSize = currentTable.size();
int pos = 0;
for (pos = opSize - 1; pos >= 0; --pos) {
CachedOperation op = currentTable.get(pos);
if (op.mComponentType.equals(
isRow ? OperationConstants.ROWS : OperationConstants.CELLS)) {
break;
}
}
// now we have a start index - add all ops from pos to opSize- 1 again
// repetition-times with modified position
if (pos > 0) {
CachedOperation cellInsertOp = currentTable.get(pos);
int incrementPos = cellInsertOp.mStart.size() - 1;
int repetition = (Integer) cellInsertOp.mComponentProperties[isRow ? 0 : 1];
for (int rep = 0; rep < repetition - 1; ++rep) {
for (int opPos = pos; opPos < opSize; ++opPos) {
CachedOperation newOp = currentTable.get(opPos).clone();
if (newOp.mStart != null) {
int oldIndex = newOp.mStart.get(incrementPos);
newOp.mStart.set(incrementPos, oldIndex + rep + 1);
}
cacheOperation(
false,
newOp.mComponentType,
newOp.mStart,
false,
newOp.mHardFormattingProperties,
newOp.mComponentProperties);
}
}
}
}
mLastComponentPositions.set(
mComponentDepth,
mLastComponentPositions.get(mComponentDepth) + mCurrentComponent.repetition() - 1);
}
mComponentDepth--;
mCurrentComponent = mCurrentComponent.getParent();
}
} // if text delimiter - addChild text
else if (Component.isTextSelection(mCurrentNode)) {
// dropping the last (most inner) selection from the stack
TextSelection textSelection = mTextSelectionStack.pollLast();
if (textSelection != null) {
textSelection.setEndPosition(getTextPosition());
OdfElement root = ((OdfElement) mCurrentNode).getComponentRoot();
if (Component.isTextComponentRoot(root)) {
// sometimes when spans share the same text area, they are condensed to one. The
// remaining one is being returned, or in case of an empty element the parent
mCurrentNode = ((TextContainingElement) root).appendTextSelection(textSelection);
// selectionNormalization might delete an element in this case the change of the current
// node would be an error!
selectionNormalization = true;
}
}
} else if (uri.equals(OdfDocumentNamespace.TEXT.getUri()) && localName.equals("list-item")) {
mListStyleStack.getLast().overrideListStyle(null);
} else if (uri.equals(OdfDocumentNamespace.TEXT.getUri()) && localName.equals("list")) {
// POP UP NEW LIST STYLE
mListStyleStack.removeLast();
} else if (localName.equals("creator")) {
CachedComponent commProps = mComponentStack.isEmpty() ? null : mComponentStack.peek();
if (commProps != null && commProps instanceof CommentComponent) {
((CommentComponent) commProps).setAuthor(mCurrentNode.getTextContent());
}
} else if (localName.equals("date")) {
CachedComponent commProps = mComponentStack.isEmpty() ? null : mComponentStack.peek();
if (commProps != null && commProps instanceof CommentComponent) {
((CommentComponent) commProps).setDate(mCurrentNode.getTextContent());
}
}
}
// selectionNormalization might delete an element in this case the change of the current node
// would be an error!
if (mCurrentNode != null && !selectionNormalization) {
// pop to the parent node
mCurrentNode = mCurrentNode.getParentNode();
}
}
private void addAttributes(Element element, Attributes attributes) {
String attrQname;
String attrURL;
OdfAttribute attr;
for (int i = 0; i < attributes.getLength(); i++) {
attrURL = attributes.getURI(i);
attrQname = attributes.getQName(i);
// if no namespace exists
if (attrURL.equals(Constants.EMPTY_STRING) || attrQname.equals(Constants.EMPTY_STRING)) {
// create attribute without prefix
attr = mFileDom.createAttribute(attributes.getLocalName(i));
} else {
if (attrQname.startsWith("xmlns:")) {
// in case of xmlns prefix we have to create a new OdfNamespace
OdfNamespace namespace =
mFileDom.setNamespace(attributes.getLocalName(i), attributes.getValue(i));
// if the file Dom is already associated to parsed XML addChild the new namespace to the
// root element
Element root = mFileDom.getRootElement();
if (root == null) {
root = element;
}
root.setAttributeNS(
"http://www.w3.org/2000/xmlns/",
"xmlns:" + namespace.getPrefix(),
namespace.getUri());
}
// create all attributes, even namespace attributes
attr = mFileDom.createAttributeNS(attrURL, attrQname);
}
// namespace attributes will not be created and return null
if (attr != null) {
element.setAttributeNodeNS(attr);
try {
// set Value in the attribute to allow validation in the attribute
attr.setValue(attributes.getValue(i));
} // if we detect an attribute with invalid value: remove attribute node
catch (IllegalArgumentException e) {
ErrorHandler errorHandler = mFileDom.getDocument().getPackage().getErrorHandler();
if (errorHandler != null) {
try {
errorHandler.error(
new OdfValidationException(
OdfSchemaConstraint.DOCUMENT_XML_INVALID_ATTRIBUTE_VALUE,
attr.getValue(),
attr.getPrefix() + ":" + attr.getLocalName()));
} catch (SAXException ex) {
Logger.getLogger(StyleStyleElement.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
LOG.severe(
"ERROR / EXCEPTION DURING XML PARSING: INVALID ATTRIBUTE: '"
+ attr.getPrefix()
+ ":"
+ attr.getLocalName()
+ "' with value '"
+ attr.getValue()
+ "'!");
}
element.removeAttributeNode(attr);
}
}
}
}
private void flushTextAtStart(String uri, String localName, String qName) {
flushText(uri, localName, qName, false);
}
private void flushTextAtEnd(String uri, String localName, String qName) {
flushText(uri, localName, qName, true);
}
/**
* Consumers shall collapse white space characters that occur in
*
* <ul>
* <li>a <text:p> or <text:h> element (so called paragraph elements), and
* <li>in their descendant elements, if the OpenDocument schema permits the inclusion of
* character data for the element itself and all its ancestor elements up to the paragraph
* element.
* </ul>
*
* Collapsing white space characters is defined by the following algorithm: 1)The following
* [UNICODE] characters are replaced by a " " (U+0020, SPACE) character: \ue570HORIZONTAL
* TABULATION (U+0009) \ue570CARRIAGE RETURN (U+000D) \ue570LINE FEED (U+000A) 2)The character
* data of the paragraph element and of all descendant elements for which the OpenDocument schema
* permits the inclusion of character data for the element itself and all its ancestor elements up
* to the paragraph element, is concatenated in document order. 3)Leading " " (U+0020, SPACE)
* characters at the start of the resulting text and trailing SPACE characters at the end of the
* resulting text are removed. 4)Sequences of " " (U+0020, SPACE) characters are replaced by a
* single " " (U+0020, SPACE) character.
*/
private void flushText(String uri, String localName, String qName, boolean isEndOfElement) {
// check if there is was text found to be added to the element
if (mCharsForElement.length() > 0) {
// every text will be kept from the XML file (e.g. indent)
String newString = mCharsForElement.toString();
mCharsForElement.setLength(0);
Text text = mFileDom.createTextNode(newString);
if (isEndOfElement && Component.isField(uri, localName)) {
TextSelection textSelection = mTextSelectionStack.pollLast();
if (!isBlockedSubTree()) {
// Currently only check-box have an UTF-8 square as replacementText
TextFieldSelection textFieldSelection = (TextFieldSelection) textSelection;
String replacementText = textFieldSelection.getReplacementText();
Map<String, Object> attrMap = textFieldSelection.getAttributes();
cacheOperation(
false,
OperationConstants.FIELD,
textSelection.getStartPosition(),
false,
null,
localName,
replacementText != null ? replacementText : newString,
attrMap,
mContextName);
}
mCurrentNode.appendChild(text);
} else {
// if the text is within a text aware component
if (mCurrentNode instanceof OdfElement) {
// ToDo: Uncertain what with text should happen that is not within a text component?
// Neglectable by
// if ((Component.isTextComponentRoot(mCurrentNode) ||
// Component.isTextComponentRoot(((OdfElement) mCurrentNode).getComponentRoot())) &&
// !isBlockedSubTree() && mWhitespaceStatusStack.size() > 0 &&
// !mWhitespaceStatusStack.getLast().isParagraphIgnored() && !(mCurrentNode instanceof
// TextNoteCitationElement)) {
if ((Component.isTextComponentRoot(mCurrentNode)
|| Component.isTextComponentRoot(((OdfElement) mCurrentNode).getComponentRoot()))
&& !isBlockedSubTree()
&& mWhitespaceStatusStack.size() > 0
&& !(mCurrentNode instanceof TextNoteCitationElement)) {
mComponentDepth++;
if (mIsCharsBeginning) {
mCharsStartPosition = updateTextPosition();
mIsCharsBeginning = false;
}
mComponentDepth--;
// The new charPosition adds the text lenght, but inserted will be without
// 1) insertion
addText(/*mCachedTableOps, */ newString);
// the following would cumulate the text of a paragraph to a single large string
// mCharsForOperation.append(newString);
// muss ich rekursiv die gr\u00f6sse nach oben reichen? f\u00fcr jeden none component
// descendant? K\u00f6nnte ich in OdfElement implemenentieren!
// \u00fcberschreibe alle addChild/delete Funktionalit\u00e4t!
// Merge/split/delete Text Funktionalit\u00e4t f\u00fcr alle ELEMENTE? Komponenten
// m\u00fcssen mit einbezogen werden! Reuse of Recursion -- ACTION KLASSE.operate()
// aufruf!?!?
// OdfElement element = (OdfElement) mCurrentNode;
// element.appendChild(text);
}
if (isSpaceElement(mCurrentNode)) {
mCurrentNode.getParentNode().appendChild(text);
} else {
mCurrentNode.appendChild(text);
}
}
}
} else {
if (isEndOfElement && Component.isField(uri, localName)) {
TextSelection textSelection = mTextSelectionStack.pollLast();
if (!isBlockedSubTree()) {
// Currently only check-box have an UTF-8 square as replacementText
TextFieldSelection textFieldSelection = (TextFieldSelection) textSelection;
String replacementText = textFieldSelection.getReplacementText();
Map<String, Object> attrMap = textFieldSelection.getAttributes();
if (replacementText == null) {
replacementText = new String();
}
cacheOperation(
false,
OperationConstants.FIELD,
textSelection.getStartPosition(),
false,
null,
localName,
replacementText,
attrMap,
mContextName);
}
}
}
if (mCharsForOperation.length() > 0
&& Component.isComponentRoot(uri, localName)
&& !isSpaceElement(uri, localName)) {
addText(/*mCachedTableOps, */ mCharsForOperation);
}
}
private void addText(CharSequence newText) {
cacheOperation(
false,
OperationConstants.TEXT,
mCharsStartPosition,
false,
null,
newText.toString(),
mContextName);
mCharsForOperation.setLength(0);
mIsCharsBeginning = true;
}
static boolean isSpaceElement(Node node) {
return node instanceof TextSElement;
}
static boolean isSpaceElement(String uri, String localName) {
return uri != null
&& uri.equals(TextSElement.ELEMENT_NAME.getUri())
&& localName.equals(TextSElement.ELEMENT_NAME.getLocalName());
}
@Override
/**
* http://xerces.apache.org/xerces2-j/faq-sax.html#faq-2 : SAX may deliver contiguous text as
* multiple calls to characters, for reasons having to do with parser efficiency and input
* buffering. It is the programmer's responsibility to deal with that appropriately, e.g. by
* accumulating text until the next non-characters event. This method will finalize the text of an
* element, by flushing/appending it to the element node. It is called at the beginning of
* startElement/endElement. In case of startElement the text will be referred to the previous
* element node (before the new started). In case of endElement the text will be referred to the
* current element node.
*/
public void characters(char[] ch, int startPosition, int length) {
if (mCurrentComponent instanceof TextContainer) {
// ODF Whitespacehandling
WhitespaceStatus currentWhiteSpaceStatus = mWhitespaceStatusStack.getLast();
// Note: The delta between startPosition and endPosition marks the text to be written out
// startPosition will only be raised to endposition, when characters have to be skipped!
int endPosition = startPosition;
int lastPos = startPosition + length;
boolean previousContentWritten = false;
char c;
// Go through all characters found by the parser..
for (int i = startPosition; i < lastPos; i++) {
c = ch[i];
// first part is trimming in the beginning of the element
if (currentWhiteSpaceStatus.hasOnlyWhiteSpace()) {
// \t (tabulator = 0x09)
if (c == '\u0020' // space
|| c == '\t'
// \r (carriage return = 0x0D)
|| c == '\r'
// \n (line feed = 0x0A)
|| c == '\n') {
// skipt this character, keeping the difference between start & end (length) equal
startPosition++;
endPosition++;
} else {
// first character being found worth to be written
currentWhiteSpaceStatus.setOnlyWhiteSpace(false);
endPosition++;
}
// second part is about collapsing multiple whitespaces
} else {
if (c == '\u0020' // space
|| c == '\t' // \t (tabulator = 0x09)
// \r (carriage return = 0x0D)
|| c == '\r'
// \n (line feed = 0x0A)
|| c == '\n') {
// if we have aleady a preceding whitespace character
if (currentWhiteSpaceStatus.hasSpaceBefore()) {
if (!previousContentWritten) {
// as we have to skip a character in the array, write what we have
if (endPosition - startPosition > 0) {
mCharsForElement.append(ch, startPosition, endPosition - startPosition);
}
previousContentWritten = true;
}
// NOT including this character
endPosition++;
startPosition = endPosition;
} else {
currentWhiteSpaceStatus.setFirstSpaceCharPosition(i);
ch[i] = '\u0020'; // overwrite all
endPosition++;
}
} else {
if (currentWhiteSpaceStatus.hasSpaceBefore()) {
currentWhiteSpaceStatus.setFirstSpaceCharPosition(-1);
}
endPosition++; // including this character
}
}
}
if (endPosition - startPosition > 0) {
mCharsForElement.append(ch, startPosition, endPosition - startPosition);
}
} else {
/*
* ToDo: The following will be ignored for now:
In addition, OpenDocument Consumers shall ignore all element children ([RNG] section 5,
Data Model) of elements defined in this specification that are strings consisting entirely of whitespace characters and
which do not satisfy a pattern of the OpenDocument schema definition for the element.
*/
// See
// http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#a3_18White_Space_Processing_and_EOL_Handling
mCharsForElement.append(ch, startPosition, length);
}
}
@Override
public InputSource resolveEntity(String publicId, String systemId)
throws IOException, SAXException {
return super.resolveEntity(publicId, systemId);
}
/**
* This method is being called whenever an element being the root element is found, within
* startElement function. It considers mComponentDepth and the size of the list
* mLastComponentPositions. It updates the position (ie. list mLastComponentPositions) by adding a
* child or a sibling. This ONLY works if mComponentDepth has already been updated before this
* method is being called. Currently at the beginning of startElement, when realizing it is a
* component root element.
*
* <p>The only reason to update the position (mLastComponentPositions) outside of this function is
* to handle attributes representing repeatedColumns components * (i.e. for cells & rows).
*
* @return the path of the current found component as Integer array
*/
private List<Integer> updateComponentPosition() {
List<Integer> pos = updatePosition(true);
return pos;
}
/**
* This method is being called whenever an element being the root element is found, within
* startElement function. It considers mComponentDepth and the size of the list
* mLastComponentPositions. It updates the position (ie. list mLastComponentPositions) by adding a
* child or a sibling. This ONLY works if mComponentDepth has already been updated before this
* method is being called. Currently at the beginning of startElement, when realizing it is a
* component root element.
*
* <p>The only reason to update the position (mLastComponentPositions) outside of this function is
* to handle attributes representing repeatedColumns components * (i.e. for cells & rows).
*
* @return the path of the current found component as Integer array
*/
private List<Integer> updateTextPosition() {
return updatePosition(false);
}
/**
* This method is being called whenever an element being the root element is found, within
* startElement function. It considers mComponentDepth and the size of the list
* mLastComponentPositions. It updates the position (ie. list mLastComponentPositions) by adding a
* child or a sibling. This ONLY works if mComponentDepth has already been updated before this
* method is being called. Currently at the beginning of startElement, when realizing it is a
* component root element.
*
* <p>The only reason to update the position (mLastComponentPositions) outside of this function is
* to handle attributes representing repeatedColumns components * (i.e. for cells & rows).
*
* @return the path of the current found component as Integer array
*/
private List<Integer> updatePosition(boolean isComponent) {
/**
* Used components being siblings of text, where the position is determined by the text count,
* not by the previous component number
*/
// take care of position handling
// NEW COMPONENT CHILD: If the component Level is deeper than the last position
// Actually at the first component: componentDepth start with 0, but the size with 1
if (mComponentDepth == mLastComponentPositions.size()) {
// addChild a new level
mLastComponentPositions.add(mComponentDepth, 0);
// NEW SIBLING: If the depth is "equal" (ie. lower) than addChild a sibling
} else if (mComponentDepth == mLastComponentPositions.size() - 1) {
int positionUpdate;
if (mCurrentComponent instanceof TextContainer) {
// increment the last position number as a sibling was added
positionUpdate = mCurrentComponent.size();
} else if (mCurrentComponent instanceof Cell) {
positionUpdate = mLastComponentPositions.get(mComponentDepth) + 1;
// } else if (mCurrentComponent instanceof Table) {
// positionUpdate =mCurrentComponent.size();
} else if (mCurrentComponent.getRootElement() instanceof OfficeAnnotationEndElement) {
positionUpdate = mCurrentComponent.getParent().size();
} else {
positionUpdate = mLastComponentPositions.get(mComponentDepth) + 1;
}
mLastComponentPositions.set(mComponentDepth, positionUpdate);
// FINISHED COMPONENT - REMOVE DEPTH - If a component was closed and the position needds to be
// added
} else if (mComponentDepth < mLastComponentPositions.size() - 1) {
// remove the last position and addChild a new one
mLastComponentPositions.removeLast();
updatePosition(isComponent);
} else {
LOG.warning("Houston, we have a problem..");
}
// ToDo: Do I need a new LIST for every component? Or may I addChild a position to the
// component?
return new LinkedList<Integer>(mLastComponentPositions);
// ToDo: Below a bad idea?
// return Collections.unmodifiableList(mLastComponentPositions);
}
/** @return the path of the current found component as Integer array */
private List<Integer> getTextPosition() {
// The text is one level further down
mComponentDepth++;
List<Integer> position = updateTextPosition();
mComponentDepth--;
return position;
}
// CachedTable mCachedTableOps = null;
// based on document position the tables and subtables are being flushed after each end
// HashMap<List<Integer>, CachedTable> mAllTables = null;
// private ArrayList<CachedTable> mTableStack;
// boolean mWithinTable = false; //TODO: Detect via type of top element of mComponentStack
// private int mNumberOfNestedTables = 0;
Stack<CachedComponent> mComponentStack = new Stack<CachedComponent>();
// cache objects bound to page that are on top level of the document
ArrayDeque<ShapeProperties> m_cachedPageShapes = new ArrayDeque<ShapeProperties>();
boolean mPageBoundObjectsRelocated = false;
HashMap<String, Integer> mTopLevelTables =
new HashMap<String, Integer>(); // mapping of spreadsheet index to their names
@SuppressWarnings("unchecked")
public void cacheOperation(
boolean fillCacheOnly,
String componentType,
List<Integer> start,
boolean absolutePosition,
Map<String, Object> hardFormattingProperties,
Object... componentProperties) {
if (mComponentStack.empty()) {
// send to producer
if (componentType.equals(OperationConstants.TEXT)) {
String text = (String) componentProperties[0];
String context = (String) componentProperties[1];
mJsonOperationProducer.addText(start, text, context);
} else if (componentType.equals(OperationConstants.PARAGRAPH)) {
String context = (String) componentProperties[0];
mJsonOperationProducer.add(componentType, start, hardFormattingProperties, context);
} else if (componentType.equals(OperationConstants.TABLE)) {
List<Integer> tableGrid = (List<Integer>) componentProperties[0];
String tableName = (String) componentProperties[1];
String context = (String) componentProperties[2];
mJsonOperationProducer.addTable(
start, hardFormattingProperties, tableGrid, tableName, context);
} else if (componentType.equals(OperationConstants.EXCEEDEDTABLE)) {
int columns = (Integer) componentProperties[0];
int rows = (Integer) componentProperties[1];
List<Integer> tableGrid = (List<Integer>) componentProperties[2];
String context = (String) componentProperties[3];
// addExceededTable(final List<Integer> start, int columns, int rows, final List<Integer>
// tableGrid) {
mJsonOperationProducer.addExceededTable(start, columns, rows, tableGrid, context);
} else if (componentType.equals(OperationConstants.ATTRIBUTES)) {
List<Integer> end = (List<Integer>) componentProperties[0];
String context = (String) componentProperties[1];
mJsonOperationProducer.format(start, end, hardFormattingProperties, context);
} else if (componentType.equals(OperationConstants.FORMATROWS)) {
Integer firstRow = (Integer) componentProperties[0];
Integer lastRow = (Integer) componentProperties[1];
String context = (String) componentProperties[3];
Integer repeatedRowOffset = (Integer) componentProperties[2];
mJsonOperationProducer.formatRows(
start, hardFormattingProperties, firstRow, lastRow, repeatedRowOffset, context);
} else if (componentType.equals(OperationConstants.FORMATCOLUMNS)) {
Integer firstColumn = (Integer) componentProperties[0];
Integer lastColumn = (Integer) componentProperties[1];
String context = (String) componentProperties[2];
mJsonOperationProducer.formatColumns(
start, hardFormattingProperties, firstColumn, lastColumn, context);
} else if (componentType.equals(OperationConstants.SHAPE)
|| componentType.equals(OperationConstants.SHAPE_GROUP)) {
String context = (String) componentProperties[0];
mJsonOperationProducer.addShape(
start,
hardFormattingProperties,
context,
componentType.equals(OperationConstants.SHAPE_GROUP));
} else if (componentType.equals(OperationConstants.IMAGE)) {
String context = (String) componentProperties[0];
mJsonOperationProducer.addImage(start, hardFormattingProperties, context);
} else if (componentType.equals(OperationConstants.FIELD)) {
String fieldType = (String) componentProperties[0];
String fieldContent = (String) componentProperties[1];
Map<String, Object> fieldAttributes = (Map<String, Object>) componentProperties[2];
String context = (String) componentProperties[3];
mJsonOperationProducer.addField(start, fieldType, fieldContent, fieldAttributes, context);
} else if (componentType.equals(OperationConstants.COMMENT)) {
String id = (String) componentProperties[0];
String author = (String) componentProperties[1];
String date = (String) componentProperties[2];
String target = (String) componentProperties[3];
mJsonOperationProducer.addAnnotation(start, id, author, date, target);
} else if (componentType.equals(OperationConstants.COMMENTRANGE)) {
String id = (String) componentProperties[0];
String target = (String) componentProperties[1];
mJsonOperationProducer.addRange(start, id, target);
} else if (componentType.equals(OperationConstants.TAB)) {
String target = (String) componentProperties[0];
mJsonOperationProducer.add(
componentType, start, hardFormattingProperties, target != null ? target : mContextName);
} else {
String target = (String) componentProperties[0];
mJsonOperationProducer.add(
componentType, start, hardFormattingProperties, target != null ? target : mContextName);
}
} else {
CachedComponent topComponent = mComponentStack.peek();
if (!fillCacheOnly && topComponent instanceof CachedTable) {
cacheTableOperation(componentType, start, hardFormattingProperties, componentProperties);
} else {
// collect the operations at the CachedComponent
LinkedList<Integer> position = null;
if (start != null) {
position = new LinkedList<>(start);
}
topComponent.add(
new CachedOperation(
componentType,
position,
absolutePosition,
hardFormattingProperties,
componentProperties));
}
}
}
/** Table operation are being cached in one of two caches */
@SuppressWarnings("unchecked")
private void /*CachedTable */ cacheTableOperation(
/*CachedTable currentTable,*/ String componentType,
List<Integer> start,
Map<String, Object> hardFormattingProperties,
Object... componentProperties) {
LinkedList<Integer> position = null;
CachedTable currentTable =
mComponentStack.empty()
? null
: (mComponentStack.peek() instanceof CachedTable)
? (CachedTable) mComponentStack.peek()
: null;
if (start != null) {
position = new LinkedList<Integer>(start);
}
if (componentType.equals(OperationConstants.CELLS)) { // does not effect a spreadsheet
currentTable.mCellCount++;
if (mMaxAllowedCellCount != 0 && currentTable.mCellCount > mMaxAllowedCellCount) {
currentTable.mIsTooLarge = true;
}
} else if (componentType.equals(OperationConstants.ROWS)) { // Counting Rows
currentTable.mRowCount++; // no repeated in writer
if (mMaxAllowedRowCount != 0 && currentTable.mRowCount > mMaxAllowedRowCount) {
currentTable.mIsTooLarge = true;
}
} else if (componentType.equals(
OperationConstants.TABLE)) { // all formats (Text & Spreadsheet atm)
List<Integer> tableGrid = (List<Integer>) componentProperties[0];
CachedTable newCachedTable = startTableSizeEvaluation(position, tableGrid);
if (newCachedTable.mTableGrid != null) {
if (mMaxAllowedColumnCount != 0
&& newCachedTable.mTableGrid.size() > mMaxAllowedColumnCount) {
newCachedTable.mIsTooLarge = true;
// adding the table now, as it would not be below as already too large..
} else {
newCachedTable.mColumnCount = newCachedTable.mTableGrid.size();
}
}
// if it is the first root table
if (currentTable == null) {
// only for the root table the table itself will be added at the beginning
currentTable = newCachedTable;
// ++currentTable.mNumberOfNestedTables;
mComponentStack.push(currentTable);
currentTable.add(
new CachedInnerTableOperation(
componentType, position, false, hardFormattingProperties, componentProperties));
} else { // as subtable the table will be added twice:
// once for the parent as notifier
currentTable.addSubTable(newCachedTable, start);
currentTable.add(
new CachedInnerTableOperation(
componentType, position, false, hardFormattingProperties, componentProperties));
currentTable = newCachedTable;
// once for the child to create
currentTable.add(
new CachedInnerTableOperation(
componentType, position, false, hardFormattingProperties, componentProperties));
}
}
if (!currentTable.mIsTooLarge
&& !componentType.equals(OperationConstants.TABLE)
&& !componentType.equals(OperationConstants.COLUMNS)) {
currentTable.add(
new CachedInnerTableOperation(
componentType, position, false, hardFormattingProperties, componentProperties));
}
}
/**
* According to user run-time configuration only tables of a certain size are allowed to be
* created. Tables exceeding the limit are being shown by a replacement object, otherwise the
* client performance might not be sufficient.
*/
private CachedTable startTableSizeEvaluation(List<Integer> position, List<Integer> tableGrid) {
CachedTable cachedTable = null;
cachedTable = new CachedTable();
cachedTable.mTableGrid = tableGrid;
return cachedTable;
}
/**
* According to user run-time configuration only tables of a certain size are allowed to be
* created. Tables exceeding the limit are being shown by a replacement object, otherwise the
* client performance might not be sufficient.
*
* <p>As the limit is being checked on sub table level, the complete table have to be parsed
* before giving green light for any table. On the opposite, if a subtable is already too large,
* it can be neglected collecting operations for that subtable.
*
* @throws SAXException
*/
private void endTableSizeEvaluation() throws SAXException {
CachedTable cachedTableOps = (CachedTable) mComponentStack.peek();
if (cachedTableOps.getSubTableCount() == 0) {
cachedTableOps.mMostUsedColumnStyle = getMostUsedStyle(cachedTableOps.columnStyleOccurrence);
cachedTableOps.mMostUsedRowStyle = getMostUsedStyle(cachedTableOps.rowStyleOccurrence);
mComponentStack.pop();
flushTableOperations(cachedTableOps, true);
if (cachedTableOps != null && cachedTableOps.mCachedTableContentOps != null) {
cachedTableOps.mCachedTableContentOps = null;
cachedTableOps.lastRowFormatOperation = null;
}
} else if (cachedTableOps.getSubTableCount() > 0) {
// when leaving a table, continue with the parent table
cachedTableOps.removeSubTable();
} else { // below zero might appear, when table had started in blocked area
// TODO: is it really possible to reach this point?
if (cachedTableOps != null && cachedTableOps.mCachedTableContentOps != null) {
cachedTableOps.mCachedTableContentOps = null;
cachedTableOps.lastRowFormatOperation = null;
}
mComponentStack.pop();
}
}
@SuppressWarnings("rawtypes")
private void flushTableOperations(CachedTable currentTable, boolean isStartOfTable)
throws SAXException {
boolean putPageBreak = false;
boolean isBreakBefore = true;
ListIterator<CachedOperation> cachedOperationIterator = currentTable.listIterator();
while (cachedOperationIterator.hasNext()) {
CachedOperation operation = cachedOperationIterator.next();
if (operation instanceof CachedInnerTableOperation
&& operation.mComponentType.equals(OperationConstants.TABLE)) {
if (isStartOfTable) {
isStartOfTable = false;
if (currentTable.mIsTooLarge) {
// replacement table
cacheOperation(
false,
OperationConstants.EXCEEDEDTABLE,
operation.mStart,
false,
null,
((List) operation.mComponentProperties[0]).size(),
currentTable.mRowCount,
operation.mComponentProperties[0],
mContextName);
break;
} else {
if ((mMaxAllowedRowCount != 0 && currentTable.mRowCount > mMaxAllowedRowCount)
|| (mMaxAllowedColumnCount != 0
&& currentTable.mColumnCount > mMaxAllowedColumnCount)
|| (mMaxAllowedCellCount != 0 && currentTable.mCellCount > mMaxAllowedCellCount)) {
// TODO: Exceeded table operation name
cacheOperation(
false,
OperationConstants.EXCEEDEDTABLE,
operation.mStart,
false,
null,
((List) operation.mComponentProperties[0]).size(),
currentTable.mRowCount,
operation.mComponentProperties[0],
mContextName);
break;
} else {
// the last parameter are: mColumnRelWidths, mTableName, mIsTableVisible);
JSONObject tableAttr = null;
if (operation.mHardFormattingProperties.containsKey("table")
&& (((tableAttr = (JSONObject) operation.mHardFormattingProperties.get("table"))
.has("pageBreakBefore"))
|| tableAttr.has("pageBreakAfter"))) {
isBreakBefore = tableAttr.has("pageBreakBefore");
String breakString = isBreakBefore ? "pageBreakBefore" : "pageBreakAfter";
boolean breakAttr = tableAttr.getBoolean(breakString);
if (breakAttr) {
putPageBreak = true;
}
tableAttr.remove(breakString);
}
cacheOperation(
false,
OperationConstants.TABLE,
operation.mStart,
false,
operation.mHardFormattingProperties,
operation.mComponentProperties[0],
operation.mComponentProperties[1],
mContextName);
}
}
} else {
flushTableOperations(currentTable.getSubTable(operation.mStart), true);
}
} else if (operation.mComponentType.equals(OperationConstants.TEXT)) {
String context = mContextName;
if (operation.mComponentProperties.length > 1
&& operation.mComponentProperties[1] != null) {
context = (String) operation.mComponentProperties[1];
}
cacheOperation(
false,
operation.mComponentType,
operation.mStart,
false,
null,
operation.mComponentProperties[0],
context);
} else if (operation.mComponentType.equals(OperationConstants.ATTRIBUTES)) {
String context = mContextName;
if (operation.mComponentProperties.length > 1
&& operation.mComponentProperties[1] != null) {
context = (String) operation.mComponentProperties[1];
}
cacheOperation(
false,
OperationConstants.ATTRIBUTES,
operation.mStart,
false,
operation.mHardFormattingProperties,
operation.mComponentProperties[0],
context);
} else if (operation.mComponentType.equals(OperationConstants.SHAPE)
|| operation.mComponentType.equals(OperationConstants.IMAGE)
|| operation.mComponentType.equals(OperationConstants.SHAPE_GROUP)) {
cacheOperation(
false,
operation.mComponentType,
operation.mStart,
false,
operation.mHardFormattingProperties,
mContextName);
} else if (operation.mComponentType.equals(OperationConstants.FIELD)) {
// TODO: Why do I have to check for map<> casts but not with String casts?
@SuppressWarnings("unchecked")
Map<String, Object> attrMap = (Map<String, Object>) operation.mComponentProperties[2];
cacheOperation(
false,
operation.mComponentType,
operation.mStart,
false,
null,
operation.mComponentProperties[0],
operation.mComponentProperties[1],
attrMap,
mContextName);
} else if (operation.mComponentType.equals(OperationConstants.TABLE)
|| operation.mComponentType.equals(OperationConstants.COMMENT)
|| operation.mComponentType.equals(OperationConstants.COMMENTRANGE)) {
cacheOperation(
false,
operation.mComponentType,
operation.mStart,
false,
operation.mHardFormattingProperties,
operation.mComponentProperties);
} else if (operation.mComponentType.equals(OperationConstants.COMMENT)
|| operation.mComponentType.equals(OperationConstants.COMMENTRANGE)) {
cacheOperation(
false,
operation.mComponentType,
operation.mStart,
false,
operation.mHardFormattingProperties,
operation.mComponentProperties);
} else {
boolean isParagraphOperation =
operation.mComponentType.equals(OperationConstants.PARAGRAPH);
if (putPageBreak && isParagraphOperation) {
JSONObject paraProps = null;
if (operation.mHardFormattingProperties == null) {
operation.mHardFormattingProperties = new HashMap<String, Object>();
}
if (!operation.mHardFormattingProperties.containsKey("paragraph")) {
paraProps = new JSONObject();
} else {
paraProps = (JSONObject) operation.mHardFormattingProperties.get("paragraph");
}
paraProps.put(isBreakBefore ? "pageBreakBefore" : "pageBreakAfter", true);
operation.mHardFormattingProperties.put("paragraph", paraProps);
putPageBreak = false;
}
String context = mContextName;
if (isParagraphOperation && operation.mComponentProperties[0] != null) {
context = (String) operation.mComponentProperties[0];
}
cacheOperation(
false,
operation.mComponentType,
operation.mStart,
false,
operation.mHardFormattingProperties,
context);
}
}
}
private static String getMostUsedStyle(Map<String, Integer> styleOccurrances) {
String mostUsedStyleName = null;
if (styleOccurrances != null) {
Set<Entry<String, Integer>> entrySet = styleOccurrances.entrySet();
Iterator<Entry<String, Integer>> iter = entrySet.iterator();
Integer styleOccurance = null;
Integer styleOccuranceMax = null;
while (iter.hasNext()) {
Entry<String, Integer> entry = iter.next();
styleOccurance = entry.getValue();
if (styleOccuranceMax == null || styleOccuranceMax < styleOccurance) {
styleOccuranceMax = styleOccurance;
mostUsedStyleName = entry.getKey();
}
}
// if there is a most used style
if (mostUsedStyleName != null) {
// make sure it is not by coincidence the most single used one..
if (styleOccurrances.get(mostUsedStyleName) == 1) {
mostUsedStyleName = null;
}
}
}
return mostUsedStyleName;
}
private JSONObject getHeaderFooterAttrs(OdfElement e) {
JSONObject attrs = null;
JSONObject pageAttrs = null;
if (e != null) {
final Element p =
e.getChildElement(
StyleHeaderFooterPropertiesElement.ELEMENT_NAME.getUri(), "header-footer-properties");
if (p != null) {
pageAttrs = new JSONObject(3);
final String sMinHeight = p.getAttribute("fo:min-height");
if (!sMinHeight.isEmpty()) {
pageAttrs.put("minHeight", MapHelper.normalizeLength(sMinHeight));
}
final String sHeight = p.getAttribute("svg:height");
if (!sHeight.isEmpty()) {
pageAttrs.put("height", MapHelper.normalizeLength(sHeight));
}
final String sMarginTop = p.getAttribute("fo:margin-top");
if (!sMarginTop.isEmpty()) {
pageAttrs.put("marginTop", MapHelper.normalizeLength(sMarginTop));
}
final String sMarginBottom = p.getAttribute("fo:margin-bottom");
if (!sMarginBottom.isEmpty()) {
pageAttrs.put("marginBottom", MapHelper.normalizeLength(sMarginBottom));
}
final String sMarginLeft = p.getAttribute("fo:margin-left");
if (!sMarginLeft.isEmpty()) {
pageAttrs.put("marginLeft", MapHelper.normalizeLength(sMarginLeft));
}
final String sMarginRight = p.getAttribute("fo:margin-right");
if (!sMarginRight.isEmpty()) {
pageAttrs.put("marginRight", MapHelper.normalizeLength(sMarginRight));
}
}
}
if (pageAttrs != null && pageAttrs.length() != 0) {
attrs = new JSONObject(1);
attrs.put("page", pageAttrs);
}
return attrs;
}
/**
* Optimizes the operations of spreadsheet cells neglecting starting/trailing empty cells and for
* cells with content or style bundling similar cells to single operations.
*
* <p>Repeated rows are automatically a range.
*
* <p>There are three pointer (variables), that are updated during parsing the spreadsheet:
* mCurrentCellNo is the actual column number mFirstContentCellNo is mFirstEqualCellNo is set to
* the first cell to be written, after a cell was written out or an empty precessor
*
* <p>ToDo: Refactoring - As soon every component got its own parser, the tableOps. have to be
* replaced by the Context of the component
*
* <p>private CachedTable evaluateSimilarCells(CachedTable tableOps, CachedInnerTableOperation
* cellOperation, JSONObject currentCell, boolean isRow) { // An Operation will always be
* triggered in the end of the function boolean triggerOperation = false;
*
* <p>// every repeatedColumns row will result into a fillRange operation int
* previousContentRepetition = 1;
*
* <p>// if the previous cells are equal if (tableOps.mFirstEqualCellNo > -1) {
* previousContentRepetition = tableOps.mCurrentColumnNo - tableOps.mFirstEqualCellNo; }
*
* <p>boolean isRepeatedRow = tableOps.mLastRow != null &&
* !tableOps.mFirstRow.equals(tableOps.mLastRow);
*
* <p>// do not trigger the operation if the spreadsheetRow is null and its only member is null if
* (tableOps.mSheetNo == null && cellOperation != null && cellOperation.mStart != null) { // we
* have a cell position and require the two above (first parent row, afterwards cell) => already
* -2 // and an additional - 1 as size of 1 would result in zero position ==> finally -3
* tableOps.mSheetNo = cellOperation.mStart.get(cellOperation.mStart.size() - 3); } // ** There
* are four variations for previous/current cell we have to check: // 1) Current Content Cell,
* Previous Content empty if (currentCell != null && tableOps.mPreviousCell != null) { // if the
* two cells are NOT the same if (!currentCell.equals(tableOps.mPreviousCell)) { if
* (previousContentRepetition > MIN_REPEATING_CONTENT_CELLS) { triggerOperation = true; } else {
* if (tableOps.mCurrentRange == null) { tableOps.mCurrentRange = new JSONArray(); } // Resolving
* mColumnRepetition, explicitly adding cells to the range for (int i = 0;
* tableOps.mCurrentColumnNo - tableOps.mFirstEqualCellNo > i; i++) {
* tableOps.mCurrentRange.put(tableOps.mPreviousCell); } // if the row is being repeated, there
* are always vertical spans (fill same content multiple times if (tableOps.mLastRow -
* tableOps.mFirstRow > 0) { triggerOperation = true; } // there is an upcoming fill operation,
* the previous content has to be flushed if (tableOps.getCellRepetition() >
* MIN_REPEATING_CONTENT_CELLS) { triggerOperation = true; } tableOps.mFirstEqualCellNo =
* tableOps.mCurrentColumnNo; } } // 2) Current Content Cell, Previous Empty Cell (never have
* saved anything) } else if (currentCell != null && tableOps.mPreviousCell == null) { // &&
* tableOps.mCurrentRange == null tableOps.mFirstEqualCellNo = tableOps.mCurrentColumnNo; if
* (tableOps.mFirstContentCellNo == -1) { // reset the empty cell counter - if previous was empty
* tableOps.mFirstContentCellNo = tableOps.mCurrentColumnNo; tableOps.mEmptyCellCount = 0; } else
* { if (tableOps.getCellRepetition() > MIN_REPEATING_CONTENT_CELLS) { triggerOperation = true; }
* else { if (tableOps.mCurrentRange == null) { tableOps.mCurrentRange = new JSONArray(); } for
* (int i = 0; tableOps.mEmptyCellCount > i; i++) { tableOps.mCurrentRange.put(JSONObject.NULL); }
* tableOps.mEmptyCellCount = 0; } } // 3) Content Cell empty, Previo Cell full } else if
* (currentCell == null && tableOps.mPreviousCell != null) { tableOps.mEmptyCellCount +=
* tableOps.getCellRepetition(); // as there had been previously content // check if it was
* repeating content if (previousContentRepetition > MIN_REPEATING_CONTENT_CELLS) {
* triggerOperation = true; } else { if (tableOps.mCurrentRange == null) { tableOps.mCurrentRange
* = new JSONArray(); } // save the previous cell for later compressed output for (int i = 0;
* tableOps.mCurrentColumnNo - tableOps.mFirstEqualCellNo > i; i++) {
* tableOps.mCurrentRange.put(tableOps.mPreviousCell); } // if the row is being repeated, there
* are always vertical spans (fill same content multiple times if (tableOps.mLastRow -
* tableOps.mFirstRow > 0) { triggerOperation = true; } } tableOps.mFirstEqualCellNo = -1; // if
* there was previously repeating content cells // 4) Both are null } else if (currentCell == null
* && tableOps.mPreviousCell == null & !isRow) { // note that an empty cell was passed
* tableOps.mEmptyCellCount += tableOps.getCellRepetition(); // if this is the first empty cell if
* (tableOps.mFirstEmptyCell == -1) { // remember when it started tableOps.mFirstEmptyCell =
* tableOps.mCurrentColumnNo;
*
* <p>// else check if the maximum repeated empty cells was reached and existing content has to be
* dispatched as an operation } else if (tableOps.mFirstContentCellNo != -1 &&
* MAX_REPEATING_EMPTY_CELLS > tableOps.mEmptyCellCount) { triggerOperation = true; } }
*
* <p>// RANGE CREATION: for every row we flush previous content OR if we want to flush for other
* reasons if (isRow && tableOps.mFirstContentCellNo > -1 || triggerOperation) { // WRITING
* WHITESPACE TO ROW // if the last cell used content, but there was previous whitespace, the
* whitespace has to be explicitly set if (tableOps.mEmptyCellCount > 0 && currentCell != null) {
* for (int i = 0; tableOps.mEmptyCellCount > i; i++) { if (tableOps.mCurrentRange == null) {
* tableOps.mCurrentRange = new JSONArray(); } tableOps.mCurrentRange.put(JSONObject.NULL); }
* tableOps.mEmptyCellCount = 0; } // WRITING CELL TO ROW // if content to flush exist and the
* operation was triggered // OR there is horizontal repeated content // OR there is vertical
* repeated content if (tableOps.mCurrentRange != null || previousContentRepetition >
* MIN_REPEATING_CONTENT_CELLS || isRepeatedRow) { if(tableOps.mCurrentRange != null &&
* !tableOps.mCurrentRange.isEmpty()) { Component rootComponent = mSchemaDoc.getRootComponent();
* TableTableElement sheet = (TableTableElement)rootComponent.getChildNode(tableOps.mSheetNo); }
*
* <p>mJsonOperationProducer.addRange(tableOps.mSheetNo, tableOps.mFirstRow, tableOps.mLastRow,
* tableOps.mPreviousRepeatedRows, tableOps.mFirstContentCellNo, previousContentRepetition,
* tableOps.mPreviousCell, tableOps.mCurrentRange, previousContentRepetition >
* MIN_REPEATING_CONTENT_CELLS); } // if a fill sufficent repeating is now after a content, the
* previous content was flushed if (tableOps.mFirstEqualCellNo == tableOps.mCurrentColumnNo) { //
* but still a content and repeating content exits tableOps.mFirstContentCellNo =
* tableOps.mFirstEqualCellNo; } else { if (currentCell != null) { tableOps.mFirstContentCellNo =
* tableOps.mCurrentColumnNo; tableOps.mFirstEqualCellNo = tableOps.mCurrentColumnNo; } else {
* tableOps.mFirstContentCellNo = -1; tableOps.mFirstEqualCellNo = -1; } } tableOps.mCurrentRange
* = null; } if (!isRow) { // Making the current cell the previous for next round
* tableOps.mPreviousCell = currentCell; tableOps.mCurrentColumnNo +=
* tableOps.getCellRepetition(); tableOps.setCellRepetition(1); } else { // after the end of a row
* reset all values tableOps.mSheetNo = null; tableOps.mCurrentRange = null;
* tableOps.mPreviousCell = null; tableOps.mFirstContentCellNo = -1; tableOps.mFirstEqualCellNo =
* -1; tableOps.mCurrentColumnNo = 0; tableOps.mEmptyCellCount = 0; tableOps.mFirstEmptyCell = -1;
* tableOps.setCellRepetition(1); } return tableOps; }
*
* <p>static void stashColumnWidths(TableTableElement tableElement) {
* List<TableTableColumnElement> existingColumnList = getTableColumnElements(tableElement, new
* LinkedList<TableTableColumnElement>()); List<Integer> tableColumWidths =
* OdfFileSaxHandler.collectColumnWidths(tableElement, existingColumnList);
* tableElement.pushTableGrid(tableColumWidths); }
*
* <p>static List<Integer> collectColumnWidths(TableTableElement tableElement,
* List<TableTableColumnElement> columns) { boolean hasRelColumnWidth = false; boolean
* hasAbsColumnWidth = false; boolean hasColumnWithoutWidth = false; List<Integer> columnRelWidths
* = new ArrayList(); for (TableTableColumnElement column : columns) { if
* (column.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(), "style-name")) { Length tableWidth
* = getPropertyLength(StyleTablePropertiesElement.Width, tableElement);
*
* <p>int repeatedColumns = 1; if (column.hasAttributeNS(OdfDocumentNamespace.TABLE.getUri(),
* "number-columns-repeated")) { repeatedColumns =
* Integer.parseInt(column.getAttributeNS(OdfDocumentNamespace.TABLE.getUri(),
* "number-columns-repeated")); }
*
* <p>String columnRelWidth = getProperty(StyleTableColumnPropertiesElement.RelColumnWidth,
* column);
*
* <p>// it is being assumed, when the columnRelWidth is once set, it is always set if
* (columnRelWidth != null && !columnRelWidth.isEmpty()) { hasRelColumnWidth = true; if
* (hasAbsColumnWidth) { LOG.warning("******* BEWARE: Absolute and relative width are not supposed
* to be mixed!! ***********"); } columnRelWidth = columnRelWidth.substring(0,
* columnRelWidth.indexOf('*')); Integer relWidth = Integer.parseInt(columnRelWidth); for (int i =
* 0; i < repeatedColumns; i++) { columnRelWidths.add(relWidth); } } else { // if there is no
* relative column width if (hasRelColumnWidth) { LOG.warning("******* BEWARE: Absolute and
* relative width are not supposed to be mixed!! ***********"); }
*
* <p>Length columnWidth = getPropertyLength(StyleTableColumnPropertiesElement.ColumnWidth,
* column); // there can be only table width and .. if (tableWidth != null) { // columnwidth, with
* a single one missing if (columnWidth != null) { hasAbsColumnWidth = true; int widthFactor =
* (int) Math.round((columnWidth.getMillimeters() * 100) / tableWidth.getMillimeters()); for (int
* i = 0; i < repeatedColumns; i++) { columnRelWidths.add(widthFactor); } } else { if
* (hasColumnWithoutWidth) { LOG.warning("******* BEWARE: Two columns without width and no column
* width are not expected!! ***********"); } hasColumnWithoutWidth = true; } // if the table is
* not set, it will always be unset.. } else { if (columnWidth != null) { hasAbsColumnWidth =
* true; int widthFactor = (int) Math.round((columnWidth.getMicrometer() * 10)); for (int i = 0; i
* < repeatedColumns; i++) { columnRelWidths.add(widthFactor); } } else { LOG.warning("*******
* BEWARE: Two columns without width and no column width are not expected!! ***********"); } } } }
* } return columnRelWidths; } /* Returns all TableTableColumn descendants that exist within the
* tableElement, even within groups, columns and header elements
*
* <p>static List<TableTableColumnElement> getTableColumnElements(Element parent, List columns) {
* NodeList children = parent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) {
* Node child = children.item(i); if (child instanceof Element) { if (child instanceof
* TableTableColumnElement) { columns.add(child); } else if (child instanceof
* TableTableColumnGroupElement || child instanceof TableTableHeaderColumnsElement || child
* instanceof TableTableColumnsElement) { columns = getTableColumnElements((Element) child,
* columns); } else if (child instanceof TableTableRowGroupElement || child instanceof
* TableTableHeaderRowsElement || child instanceof TableTableRowElement || child instanceof
* TableTableRowsElement) { break; } } } return columns; }
*/
}