org.odftoolkit.odfdom.changes.ChangesFileSaxHandler Maven / Gradle / Ivy
Show all versions of odfdom-java Show documentation
/**
* **********************************************************************
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
*
Use is subject to license terms.
*
*
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
*
Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied.
*
*
See the License for the specific language governing permissions and limitations under the
* License.
*
*
**********************************************************************
*/
package org.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 final String COMMENT_PREFIX = "cmt";
// the empty XML file to which nodes will be added
private OdfFileDom mFileDom;
private JsonOperationProducer mJsonOperationProducer;
private Map mAutoListStyles = null;
private Map 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 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 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 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 mLastComponentPositions = new LinkedList();
/** DOM is created by default, but is in general not needed */
private final boolean domCreationEnabled = true;
// private final ArrayDeque 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 mColumns;
// The relative widths of the columns of a table
private List 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 mTableHardFormatting = null;
// *** LIST Properties ***
// @text:start-value is provided to the first paragraph only
private int mListStartValue = -1;
private final ArrayDeque mListStyleStack;
// used to track in a text:h/text:p if currently whitespace is being deleted/trimmed
private final ArrayDeque 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 mLinkedLists = new HashMap();
// *** 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 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();
}
public void add(String listId) {
mSortedIds.add(listId);
}
public List 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 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();
mUserFieldDecls = new HashMap();
// Stack to remember/track the nested delimiters not being components (spans) open-up by SAX
// events
mTextSelectionStack = new ArrayDeque();
mListStyleStack = new ArrayDeque();
// mShapePropertiesStack = new ArrayDeque();
mWhitespaceStatusStack = new ArrayDeque();
}
@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
}
} 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
}
} 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 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 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 hardFormatting = mJsonOperationProducer.getHardStyles(p);
if (hardFormatting == null) {
hardFormatting = new HashMap();
}
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 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) element).getTextOutlineLevelAttribute();
if (outlineLevel != null) {
paraProps.put("outlineLevel", outlineLevel);
}
}
} catch (JSONException ex) {
Logger.getLogger(ChangesFileSaxHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
List 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 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 opIter = component.iterator();
while (opIter.hasNext()) {
CachedOperation op = opIter.next();
List start = op.mStart;
if (!op.mAbsolutePosition) {
if (op.mComponentType.equals(OperationConstants.ATTRIBUTES)) {
@SuppressWarnings("unchecked")
List end = (List) 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 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();
}
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();
}
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);
}
}
//
//
// page
// frame
// paragraph
// char
// as-char
//
//
/* 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 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 tablePosition = new LinkedList(mLastComponentPositions);
cacheTableOperation(
OperationConstants.TABLE,
tablePosition,
mTableHardFormatting,
mColumnRelWidths,
mTableName);
mTableHardFormatting = null;
isTableNew = false;
mTableName = null;
mColumnCount = 0;
mColumnRelWidths = null;
}
List 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 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();
}
}
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 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();
}
}
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 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 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 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 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 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 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 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 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 and , 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();
}
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);
}
//
} 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 hardFormatting = new HashMap();
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 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
// 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 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 textStyleIter =
officeStyles.getStylesForFamily(OdfStyleFamily.Text).iterator();
while (textStyleIter.hasNext()) {
mJsonOperationProducer.triggerStyleHierarchyOps(
officeStyles, OdfStyleFamily.Text, textStyleIter.next());
}
final Iterator 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 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",
(shapeProps.mHoriOffsetMax
- (shapeProps.mHoriOffsetMin == null ? 0 : shapeProps.mHoriOffsetMin)),
null);
mCurrentNode.setUserData(
"groupHeight",
(shapeProps.mVertOffsetMax
- (shapeProps.mVertOffsetMin == null ? 0 : shapeProps.mVertOffsetMin)),
null);
}
// flush the inner operations of the shape
Iterator 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 selections =
((TextParagraphElementBase) mCurrentNode).getTextSelections();
if (selections != null) {
for (TextSelection s : selections) {
OdfStylableElement selectionElement = (OdfStylableElement) s.getSelectionElement();
Map 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();
}
}
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