Component.java
/*
* Copyright 2012 The Apache Software Foundation.
*
* 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 java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.odftoolkit.odfdom.dom.element.OdfStylableElement;
import org.odftoolkit.odfdom.dom.element.dr3d.Dr3dSceneElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawCaptionElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawCircleElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawConnectorElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawControlElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawCustomShapeElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawEllipseElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawGElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawLineElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawPageThumbnailElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawPathElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawPolygonElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawPolylineElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawRectElement;
import org.odftoolkit.odfdom.dom.element.draw.DrawRegularPolygonElement;
import org.odftoolkit.odfdom.dom.element.form.FormConnectionResourceElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeAnnotationElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeAnnotationEndElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeChartElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeDatabaseElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeDrawingElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeImageElement;
import org.odftoolkit.odfdom.dom.element.office.OfficePresentationElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeSpreadsheetElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeTextElement;
import org.odftoolkit.odfdom.dom.element.style.StyleFooterElement;
import org.odftoolkit.odfdom.dom.element.style.StyleFooterLeftElement;
import org.odftoolkit.odfdom.dom.element.style.StyleHeaderElement;
import org.odftoolkit.odfdom.dom.element.style.StyleHeaderLeftElement;
import org.odftoolkit.odfdom.dom.element.table.TableCoveredTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableColumnGroupElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableColumnsElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowGroupElement;
import org.odftoolkit.odfdom.dom.element.table.TableTableRowsElement;
import org.odftoolkit.odfdom.dom.element.text.TextAElement;
import org.odftoolkit.odfdom.dom.element.text.TextAuthorInitialsElement;
import org.odftoolkit.odfdom.dom.element.text.TextBookmarkElement;
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.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.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.props.OdfStylePropertiesSet;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* The component is a logical modular entity, to abstract from the implementation details of the
* XML.
*
* @author svante.schubertATgmail.com
*/
public class Component {
private static final Logger LOG = Logger.getLogger(Component.class.getName());
//
// public enum OPERATION {
// INSERT_PARAGRAPH ("insertParagraph", 2),
// INSERT_TEXT ("insertText", 3);
//
// private final String name; // function name
// private final int parameterCount; // number of arguments
// Operation(String name, int parameterCount) {
// this.name = name;
// this.parameterCount = parameterCount;
// }
// public String name() { return parameterCount; }
// public name parameterCount() { return parameterCount; }
//
// public double surfaceGravity() {
// return G * mass / (radius * radius);
// }
// public double surfaceWeight(double otherMass) {
// return otherMass * surfaceGravity();
// }
// }
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_LOCALNAME = "fieldmark";
/**
* Tests if the given element is the start of a component
*
* @return true if the given element is the root of an ODF component
*/
public static boolean isComponentRoot(Element element) {
boolean isComponent = false;
if (element instanceof OdfElement) {
isComponent =
isComponentRoot(
((OdfElement) element).getNamespaceURI(), ((OdfElement) element).getLocalName());
}
return isComponent;
}
/**
* Tests if the given element is the start of a component
*
* @return true if the given element is the root of an ODF component
*/
public static boolean isComponentRoot(String uri, String localName) {
boolean isComponent = false;
if (uri != null) {
if (uri.equals(TextPElement.ELEMENT_NAME.getUri())) {
if (localName.equals(TextPElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
} else if (localName.equals(TextHElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
} else if (localName.equals(TextTabElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
} else if (localName.equals(TextLineBreakElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
}
} else if (uri.equals(TableTableElement.ELEMENT_NAME.getUri())) {
if (localName.equals(TableTableElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
} else if (localName.equals(TableTableRowElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
} else if (localName.equals(TableTableCellElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
}
} else if (uri.equals(DrawFrameElement.ELEMENT_NAME.getUri())
&& localName.equals(DrawFrameElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
} else if (isShapeElement(uri, localName)) {
isComponent = true;
} else if ((uri.equals(OfficeAnnotationElement.ELEMENT_NAME.getUri())
&& localName.equals(OfficeAnnotationElement.ELEMENT_NAME.getLocalName()))
|| (uri.equals(OfficeAnnotationEndElement.ELEMENT_NAME.getUri())
&& localName.equals(OfficeAnnotationEndElement.ELEMENT_NAME.getLocalName()))) {
isComponent = true;
}
if (isField(uri, localName)) {
isComponent = true;
}
}
return isComponent;
}
/**
* Tests if the given element is the wrapper around a descendant component root element
*
* @return true if the given element is a potential wrapper around an ODF component
*/
public static boolean isComponentWrapper(Element element) {
boolean isWrapper = false;
if (element instanceof OdfElement) {
isWrapper =
isComponentWrapper(
((OdfElement) element).getNamespaceURI(), ((OdfElement) element).getLocalName());
}
return isWrapper;
}
/**
* Tests if the given element is the wrapper around a descendant component root element
*
* @return true if the given element is a potential wrapper around an ODF component
*/
public static boolean isComponentWrapper(String uri, String localName) {
boolean isWrapper = false;
if (uri != null && uri.equals(TextListElement.ELEMENT_NAME.getUri())) {
if (localName.equals(TextListElement.ELEMENT_NAME.getLocalName())) {
isWrapper = true;
} else if (localName.equals(TextListItemElement.ELEMENT_NAME.getLocalName())) {
isWrapper = true;
} else if (localName.equals(TextListHeaderElement.ELEMENT_NAME.getLocalName())) {
isWrapper = true;
} else if (localName.equals(TextBookmarkElement.ELEMENT_NAME.getLocalName())) {
isWrapper = true;
}
} else if (uri != null && uri.equals(TableTableElement.ELEMENT_NAME.getUri())) {
if (localName.equals(TableTableRowsElement.ELEMENT_NAME.getLocalName())) {
isWrapper = true;
} else if (localName.equals(TableTableRowGroupElement.ELEMENT_NAME.getLocalName())) {
isWrapper = true;
} else if (localName.equals(TableTableColumnsElement.ELEMENT_NAME.getLocalName())) {
isWrapper = true;
} else if (localName.equals(TableTableColumnGroupElement.ELEMENT_NAME.getLocalName())) {
isWrapper = true;
}
}
return isWrapper;
}
// /** Includes text delimiter */
// private boolean isTextElement(String uri, String localName) {
// boolean isTextElement = false;
// // Check for component root element by combination of URI and localName
// if (uri.equals(TextPElement.ELEMENT_NAME.getUri()) &&
// (localName.equals(TextPElement.ELEMENT_NAME.getLocalName())
// || localName.equals(TextSpanElement.ELEMENT_NAME.getLocalName())
// || localName.equals(TextHElement.ELEMENT_NAME.getLocalName()))) {
// isTextElement = true;
// }
// return isTextElement;
// }
/**
* Returns true if the Node is an TextPElement or TextHElement. Both are the root elements of text
* containers. Text container have special handline of whitespace, see
* http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#White-space_Characters
*/
public static boolean isTextComponentRoot(Node textContainer) {
boolean isTextElement = false;
if (textContainer instanceof TextPElement || textContainer instanceof TextHElement) {
isTextElement = true;
}
return isTextElement;
}
public static boolean isTextComponentRoot(String uri, String localName) {
boolean isComponent = false;
if (uri != null && uri.equals(TextPElement.ELEMENT_NAME.getUri())) {
if (localName.equals(TextPElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
} else if (localName.equals(TextHElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
}
}
return isComponent;
}
public static boolean isRowComponentRoot(Node textContainer) {
boolean isRowElement = false;
if (textContainer instanceof TableTableRowElement) {
isRowElement = true;
}
return isRowElement;
}
public static boolean isRowComponentRoot(String uri, String localName) {
boolean isComponent = false;
if (uri != null && uri.equals(TableTableRowElement.ELEMENT_NAME.getUri())) {
if (localName.equals(TableTableRowElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
} else if (localName.equals(TextHElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
}
}
return isComponent;
}
public static boolean isField(String uri, String localName) {
boolean isField = false;
if (uri != null && uri.equals(TextAuthorInitialsElement.ELEMENT_NAME.getUri())) {
if (localName.equals("author-initials")
|| localName.equals("author-name")
|| localName.equals("bookmark-ref")
|| localName.equals("chapter")
|| localName.equals("character-count")
|| localName.equals("conditional-text")
|| localName.equals("creation-date")
|| localName.equals("creation-time")
|| localName.equals("creator")
|| localName.equals("database-display")
|| localName.equals("database-name")
|| localName.equals("database-row-number")
|| localName.equals("date")
|| localName.equals("dde-connection")
|| localName.equals("description")
|| localName.equals("editing-cycles")
|| localName.equals("editing-duration")
|| localName.equals("execute-macro")
|| localName.equals("expression")
|| localName.equals("file-name")
|| localName.equals("hidden-paragraph")
|| localName.equals("hidden-text")
|| localName.equals("image-count")
|| localName.equals("initial-creator")
|| localName.equals("keywords")
|| localName.equals("measure")
|| localName.equals("meta-field")
|| localName.equals("modification-date")
|| localName.equals("modification-time")
|| localName.equals("note-ref")
|| localName.equals("object-count")
|| localName.equals("page-continuation")
|| localName.equals("page-count")
|| localName.equals("page-number")
|| localName.equals("page-variable-get")
|| localName.equals("page-variable-set")
|| localName.equals("paragraph-count")
|| localName.equals("placeholder")
|| localName.equals("print-date")
|| localName.equals("print-time")
|| localName.equals("printed-by")
|| localName.equals("reference-ref")
|| localName.equals("script")
|| localName.equals("sender-city")
|| localName.equals("sender-company")
|| localName.equals("sender-country")
|| localName.equals("sender-email")
|| localName.equals("sender-fax")
|| localName.equals("sender-firstname")
|| localName.equals("sender-initials")
|| localName.equals("sender-lastname")
|| localName.equals("sender-phone-private")
|| localName.equals("sender-phone-work")
|| localName.equals("sender-position")
|| localName.equals("sender-postal-code")
|| localName.equals("sender-state-or-province")
|| localName.equals("sender-street")
|| localName.equals("sender-title")
|| localName.equals("sequence-ref")
|| localName.equals("sequence")
|| localName.equals("sheet-name")
|| localName.equals("subject")
|| localName.equals("table-count")
|| localName.equals("template-name")
|| localName.equals("text-input")
|| localName.equals("time")
|| localName.equals("title")
|| localName.equals("user-defined")
|| localName.equals("user-field-get")
|| localName.equals("user-field-input")
|| localName.equals("variable-get")
|| localName.equals("variable-input")
|| localName.equals("variable-set")
|| localName.equals("word-count")) {
isField = true;
}
} else if (uri != null && uri.equals(FormConnectionResourceElement.ELEMENT_NAME.getUri())) {
if (localName.equals("connection-resource")) {
isField = true;
}
} else if (uri != null && uri.equals(LIBRE_OFFICE_MS_INTEROP_NAMESPACE)) {
if (localName.equals(LIBRE_OFFICE_MS_INTEROP_LOCALNAME)) {
isField = true;
}
}
return isField;
}
/**
* Tests if the given element is the start of a document
*
* @return true if the given element is the root of an ODF document (e.g. office:text)
*/
public static boolean isDocumentRoot(String uri, String localName) {
boolean isRoot = false;
if (uri.equals(OfficeTextElement.ELEMENT_NAME.getUri())) {
if (localName.equals(OfficeTextElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
} else if (localName.equals(OfficeSpreadsheetElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
} else if (localName.equals(OfficePresentationElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
} else if (localName.equals(OfficeChartElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
} else if (localName.equals(OfficeDrawingElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
} else if (localName.equals(OfficeImageElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
} else if (localName.equals(OfficeDatabaseElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
}
} else if (isHeaderRoot(uri, localName) || isFooterRoot(uri, localName)) {
isRoot = true;
}
return isRoot;
}
/**
* Tests if the given element is the start of a header within a page style. The content of a
* header is equal to the content of a usual ODT text file (ie. <office:text>).
*
* @return true if the given element is the root of a header (i.e. style:header)
*/
public static boolean isHeaderRoot(String uri, String localName) {
boolean isRoot = false;
if (uri != null && uri.equals(StyleHeaderElement.ELEMENT_NAME.getUri())) {
// style:header
if (localName.equals(StyleHeaderElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
}
}
if (uri != null && uri.equals(StyleHeaderLeftElement.ELEMENT_NAME.getUri())) {
// style:header-left
if (localName.equals(StyleHeaderLeftElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
}
}
if (uri != null && uri.equals(StyleHeaderLeftElement.ELEMENT_NAME.getUri())) {
// style:header-first
if (localName.equals("header-first")) {
isRoot = true;
}
}
return isRoot;
}
/**
* Tests if the given element is the start of a footer within a page style. The content of a
* footer is equal to the content of a usual ODT text file (ie. <office:text>).
*
* @return true if the given element is the root of a footer (i.e. style:footer)
*/
public static boolean isFooterRoot(String uri, String localName) {
boolean isRoot = false;
if (uri != null && uri.equals(StyleFooterElement.ELEMENT_NAME.getUri())) {
// style:footer
if (localName.equals(StyleFooterElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
}
}
if (uri != null && uri.equals(StyleFooterLeftElement.ELEMENT_NAME.getUri())) {
// style:footer-left
if (localName.equals(StyleFooterLeftElement.ELEMENT_NAME.getLocalName())) {
isRoot = true;
}
}
if (uri != null && uri.equals(StyleFooterLeftElement.ELEMENT_NAME.getUri())) {
// style:footer-first
if (localName.equals("footer-first")) {
isRoot = true;
}
}
return isRoot;
}
/**
* Tests if the given element is a shape element Shapes are in general those with elements with a
*
* @svg:width and
* @text:anchor-type. With the exception of office:annotation, usually viewed aside the document
* as note. The list of placeholder elements is therefore: dr3d:scene draw:caption draw:circle
* draw:control draw:custom-shape draw:ellipse draw:page-thumbnail draw:path draw:polygon
* draw:polyline draw:rect draw:regular-polygon
* @return true if the given element is the root of an ODF shape element
*/
public static boolean isShapeElement(String uri, String localName) {
boolean isShape = false;
if (uri != null && localName != null) {
if (uri.equals(DrawRectElement.ELEMENT_NAME.getUri())
&& (localName.equals(DrawCaptionElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawCircleElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawControlElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawCustomShapeElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawEllipseElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawGElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawPageThumbnailElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawPathElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawPolylineElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawPolygonElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawRectElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawRegularPolygonElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawLineElement.ELEMENT_NAME.getLocalName())
|| localName.equals(DrawConnectorElement.ELEMENT_NAME.getLocalName()))) {
isShape = true;
}
if (uri.equals(Dr3dSceneElement.ELEMENT_NAME.getUri())) {
if (localName.equals(Dr3dSceneElement.ELEMENT_NAME.getLocalName())) {
isShape = true;
}
}
}
return isShape;
}
/**
* Tests if the given element is a whitespace element
*
* @return true if the given element is an ODF whitespace element
*/
public static boolean isWhiteSpaceElement(String uri, String localName) {
boolean isWhiteSpace = false;
if (uri.equals(TextSElement.ELEMENT_NAME.getUri())) {
if (localName.equals(TextSElement.ELEMENT_NAME.getLocalName())) {
isWhiteSpace = true;
}
}
return isWhiteSpace;
}
public static boolean isCoveredComponentRoot(String uri, String localName) {
boolean isComponent = false;
if (uri.equals(TableTableElement.ELEMENT_NAME.getUri())) {
if (localName.equals(TableCoveredTableCellElement.ELEMENT_NAME.getLocalName())) {
isComponent = true;
}
}
return isComponent;
}
/** @return true if the node is a text delimiter element */
public static boolean isTextSelection(Node textSelection) {
boolean isTextElement = false;
if (textSelection instanceof TextSpanElement || textSelection instanceof TextAElement) {
isTextElement = true;
}
return isTextElement;
}
/**
* Only being used to create the root of all components, representing the document without a
* parent element
*/
public Component(OdfElement componentElement) {
mRootElement = componentElement;
componentElement.setComponent(this);
}
protected Component(OdfElement componentElement, Component parent) {
mRootElement = componentElement;
componentElement.setComponent(this);
mParent = parent;
}
// All component children
List<Component> mChildren;
// the root XML element of the component
public OdfElement mRootElement;
// the parent component
private Component mParent;
private Component mRootComponent;
/** if a repeated attribute was set at the component. In this case the positioning will change. */
boolean mHasRepeated = false;
/** Returns the parent component */
public Component getParent() {
return mParent;
}
/**
* Sometimes (e.g. if the child is a paragraph within list elements). The parent root element of
* the child component root element will not be directly children. It will be checked if there is
* a child element or list level 10 has reached.
*/
public static OdfElement getCorrectStartElementOfChild(
OdfElement parentElement, OdfElement existingChildElement) {
// element usually used for positioning of insertion
// lists elements are boilerplate between paragraph parent and child component (paragraph),
OdfElement existingParentElement = (OdfElement) existingChildElement.getParentNode();
// if the existing component is a paragraph with list properities
if ((existingParentElement instanceof TextListItemElement
|| existingParentElement instanceof TextListHeaderElement)
&& existingChildElement instanceof TextParagraphElementBase) {
TextParagraphElementBase existingParagraph = (TextParagraphElementBase) existingChildElement;
JsonOperationConsumer.isolateListParagraph(existingParagraph);
OdfElement potentialParent = (OdfElement) existingParagraph.getParentNode();
while (potentialParent != null && !parentElement.equals(potentialParent)) {
potentialParent = (OdfElement) potentialParent.getParentNode();
if (parentElement.equals(potentialParent)) {
break;
} else {
existingChildElement = potentialParent;
}
}
}
return existingChildElement;
}
public Component getLastChild() {
Component lastChild = null;
if (mChildren != null) {
lastChild = mChildren.get(mChildren.size());
}
return lastChild;
}
public Document getOwnerDocument() {
return mRootElement.getOwnerDocument();
}
/** @return the child at the given position */
public Node getChildNode(int position) {
Node rootElement = null;
Component c = null;
if (mChildren != null && mChildren.size() > position) {
c = mChildren.get(position);
}
if (c != null) {
rootElement = c.getRootElement();
}
return rootElement;
}
public Component get(JSONArray position) {
return get(position, false, false, 0);
}
/**
* Get descendant component by its relative position to this component. Counting starts with 0.
*
* @param position relative position of the desired component relative to the current component
* @param needParent if true the parent of the given position is returned
* @param needFollowingSibling if true the next sibling of the given position is returned
* (exclusive to getPositionsFollowingSibling)
*/
protected Component get(
JSONArray position, boolean needParent, boolean needFollowingSibling, int depth) {
// check recursion end conditions
Component c = null;
final int maxDepth = position.length() - 1;
// if not the correct depth is reached, go deeper
if (!needParent && maxDepth > depth || (needParent && (maxDepth - 1 != depth))) {
try {
// get from this level the currect component
Node startNode = getChildNode(position.getInt(depth));
if (startNode instanceof OdfElement) {
c = ((OdfElement) startNode).getComponent();
}
// call recursive this method with an additional depth for getting its child
if (c != null) {
c = c.get(position, needParent, needFollowingSibling, depth + 1);
} else {
// we need a special component for table row and cell, to expand if necessary!
// take the last existing row/cell and insert as many empty before the last one.
// get the position of the last XML and the last XML as well
// split up repeated
// do not split up covered, but .. hmm... get an empty cell back
// shall we track the position on-the-fly via DOM events?
// OdfElement lastElement = mRootElement.getLastChildElement();
// Component c2 = lastElement.getComponent();
// String pos = c2.getPosition(c2);
LOG.fine("Component yet missing!");
}
} catch (JSONException ex) {
Logger.getLogger(Component.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
try {
// get the desired child
if (needFollowingSibling) {
Node startNode = getChildNode(position.getInt(depth) + 1);
if (startNode instanceof OdfElement) {
c = ((OdfElement) startNode).getComponent();
}
} else {
Node startNode = getChildNode(position.getInt(depth));
if (startNode instanceof OdfElement) {
c = ((OdfElement) startNode).getComponent();
Component parent = c.getParent();
parent.getPosition(c);
}
}
// call recursive this method with an additional depth for getting its child
if (c == null) {
// we need a special component for table row and cell, to expand if necessary!
// take the last existing row/cell and insert as many empty before the last one.
// get the position of the last XML and the last XML as well
// split up repeated
// do not split up covered, but .. hmm... get an empty cell back
// shall we track the position on-the-fly via DOM events?
// OdfElement lastElement = mRootElement.getLastChildElement();
// Component c2 = lastElement.getComponent();
// String pos = c2.getPosition(c2);
LOG.fine("Component yet missing!");
}
} catch (JSONException ex) {
Logger.getLogger(Component.class.getName()).log(Level.SEVERE, null, ex);
}
}
return c;
}
/** Get next sibling component of the given position. Counting start with 0. */
public Component getNextSiblingOf(JSONArray position) {
return get(position, false, true, 0);
}
/** Get parent component of the given position */
public Component getParentOf(JSONArray position) {
Component c = null;
if (position.length() == 1) {
c = getRootComponent();
} else {
c = get(position, true, false, 0);
}
return c;
}
public Component getRootComponent() {
if (mRootComponent == null) {
Component parent = this;
while (parent != null) {
mRootComponent = parent;
parent = parent.getParent();
}
}
return mRootComponent;
}
public List<Component> getChildren() {
return mChildren;
}
/** @return the root element of the component */
public OdfElement getRootElement() {
return mRootElement;
}
/**
* @param odfElement the new root element of the component Used after splitting a list containing
* paragraphs and assigning the new cloned paragraph elements to the existing components.
*/
void setRootElement(OdfElement odfElement) {
mRootElement = odfElement;
}
/** Appending a child element to the component */
public Component createChildComponent(OdfElement componentRoot) {
componentRoot.markAsComponentRoot(true);
return createChildComponent(-1, this, componentRoot);
}
/**
* Inserts a component at the given position as child
*
* @param position of the component, a -1 is going to append the element
*/
public static Component createChildComponent(
int position, Component parentComponent, OdfElement newChildElement) {
Component c = createComponent(parentComponent, newChildElement);
if (!(parentComponent instanceof Table
|| parentComponent instanceof Row
|| parentComponent instanceof Cell
|| parentComponent instanceof TextContainer)) {
addComponent(position, parentComponent, c);
}
LOG.log(
Level.FINEST,
"***\n*** New Component: {0}\n*** {1}\n***",
new Object[] {parentComponent.getPosition(c), newChildElement.toString()});
return c;
}
public static Component createComponent(Component parentComponent, OdfElement newChildElement) {
Component c;
// Mark the element as component, so for instance an ODF Frame with image can be recognized as
// component
newChildElement.markAsComponentRoot(true);
// if the component is a table container
if (newChildElement instanceof TableTableElement) {
c = new Table<Component>(newChildElement, parentComponent);
} else if (newChildElement instanceof TableTableRowElement) {
c = new Row<Component>(newChildElement, parentComponent);
} else if (newChildElement instanceof TableTableCellElement
|| newChildElement instanceof TableCoveredTableCellElement) {
c = new Cell<Component>(newChildElement, parentComponent);
} else if (isTextComponentRoot(newChildElement)) {
c = new TextContainer<Component>(newChildElement, parentComponent);
// if the component is a text container (have to deal with text nodes and elements)
} else if (newChildElement instanceof OfficeAnnotationElement) {
c = new Annotation(newChildElement, parentComponent);
} else {
c = new Component(newChildElement, parentComponent);
}
newChildElement.setComponent(c);
return c;
}
/**
* Adds the given component as new child component. No XML elements are being changed!
*
* @param index starting with 0 representing the position of the child, if -1 the new child will
* be appended
*/
static void addComponent(int pos, Component parent, Component child) {
parent.addChild(pos, child);
}
/**
* Adds the given component as new child component. No XML elements are being changed!
*
* @param index starting with 0 representing the position of the child, if -1 the new child will
* be appended
*/
public void addChild(int index, Component c) {
if (mChildren == null) {
if (mChildren == null) {
mChildren = new LinkedList<Component>();
}
}
if (index >= 0) {
mChildren.add(index, c);
} else {
mChildren.add(c);
}
}
/** Only removes from the component list, not from the DOM */
public Node remove(int position) {
Node n = null;
if (mChildren != null) {
Component c = mChildren.remove(position);
if (c != null) {
n = c.getRootElement();
}
}
return n;
}
/** Returns the number of child components */
public int size() {
int size = 0;
if (mChildren != null) {
size = mChildren.size();
}
return size;
}
public void hasRepeated(boolean hasRepeated) {
mHasRepeated = hasRepeated;
}
public boolean hasRepeated() {
return mHasRepeated;
}
/** @return the position as a slash separated string */
protected String getPosition(Component c) {
String s;
List<Integer> position;
int childPos;
if (c.mParent != null) {
position = new LinkedList();
Component parent;
while ((parent = c.getParent()) != null) {
childPos = parent.indexOf(c);
// if (childPos < 0) {
// childPos = indexOf(c);
// }
position.add(childPos);
c = parent;
}
StringBuilder sb = new StringBuilder();
for (int i = position.size() - 1; i >= 0; i--) {
sb.append("/").append(position.get(i));
}
s = sb.toString();
} else {
s = "/";
}
return s;
}
/** @return the position as a slash separated string */
protected static String getPositionString(Component c) {
String s;
List<Integer> position;
int childPos;
if (c.mParent != null) {
position = new LinkedList();
Component parent;
while ((parent = c.getParent()) != null) {
childPos = parent.indexOf(c);
// if (childPos < 0) {
// childPos = indexOf(c);
// }
position.add(childPos);
c = parent;
}
StringBuilder sb = new StringBuilder();
for (int i = position.size() - 1; i >= 0; i--) {
sb.append("/").append(position.get(i));
}
s = sb.toString();
} else {
s = "/";
}
return s;
}
/**
* @returns the position of the child component c or given Node within the parents children list.
* Returns -1 if it is not a child.
*/
public int indexOf(Object o) {
int position = -1;
// Either use the previous way of creating lists of child components
if (mChildren != null) {
position = mChildren.indexOf(o);
} else {
// Either use the previous way of creating lists of child components
Node targetNode = null;
OdfElement parentNode = null;
if (o instanceof Component) {
Component c = ((Component) o);
targetNode = c.getRootElement();
parentNode = c.getParent().getRootElement();
} else if (o instanceof Node) {
targetNode = (Node) o;
parentNode = (OdfElement) targetNode.getParentNode();
}
if (targetNode != null && targetNode instanceof OdfElement) {
if (parentNode != null && parentNode.equals(mRootElement)) {
position = findPosition(parentNode, (OdfElement) targetNode);
}
}
}
return position;
}
/** Recursive traverse the text container and count the children */
private Integer findPosition(Element parentComponentElement, OdfElement targetNode) {
int pos = -1;
// Only start the recursion if the parameters are not null and in an ancestor relationship
if (parentComponentElement != null
&& targetNode != null
&& targetNode.hasAncestor(parentComponentElement)) {
findChild(0, parentComponentElement, targetNode);
}
return pos;
}
/** Recursive traverse the text container and count the children */
private Integer findChild(Integer pos, Element parentComponentElement, Node child) {
// Traverse first horizontal backwards and if no further sibling, traverse up (e.g. for
// paragraphs within list required)
if (child != null) {
if (child instanceof OdfElement) {
OdfElement childElement = ((OdfElement) child);
if (childElement.isComponentRoot()) {
// if it is a component we found one (even ourselves in the beginning) at one
pos += childElement.getRepetition();
pos = findChild(pos, parentComponentElement, childElement.getPreviousSibling());
} else if (Component.isComponentWrapper(childElement)) {
pos = findChild(pos, parentComponentElement, childElement.getLastChildElement());
} else {
pos = findChild(pos, parentComponentElement, childElement.getPreviousSibling());
}
} else {
// do the recursion even for text nodes, comments, etc.
pos = findChild(pos, parentComponentElement, child.getPreviousSibling());
}
}
return pos;
}
@Override
public String toString() {
String s = "POS:" + getPosition(this);
if (mRootElement != null) {
s += mRootElement.getPrefix() + ":" + mRootElement.getLocalName();
} else {
s += "NO ROOT ELEMENT!!!";
}
return s;
}
// These sets should be static initialized and a central place, best generated at the family
// superclass
public static Map<String, OdfStylePropertiesSet> getAllStyleGroupingIdProperties(
OdfStylableElement styleElement) {
return getAllStyleGroupingIdProperties(styleElement.getStyleFamily());
}
public static Map<String, OdfStylePropertiesSet> getAllStyleGroupingIdProperties(
OdfStyleFamily styleFamily) {
Map<String, OdfStylePropertiesSet> familyProperties =
new HashMap<String, OdfStylePropertiesSet>();
if (styleFamily.equals(OdfStyleFamily.Paragraph)) {
familyProperties.put("paragraph", OdfStylePropertiesSet.ParagraphProperties);
familyProperties.put("character", OdfStylePropertiesSet.TextProperties);
} else if (styleFamily.equals(OdfStyleFamily.Text)) {
familyProperties.put("character", OdfStylePropertiesSet.TextProperties);
} else if (styleFamily.equals(OdfStyleFamily.Table)) {
familyProperties.put("table", OdfStylePropertiesSet.TableProperties);
} else if (styleFamily.equals(OdfStyleFamily.TableRow)) {
familyProperties.put("row", OdfStylePropertiesSet.TableRowProperties);
} else if (styleFamily.equals(OdfStyleFamily.TableCell)) {
familyProperties.put("cell", OdfStylePropertiesSet.TableCellProperties);
familyProperties.put("paragraph", OdfStylePropertiesSet.ParagraphProperties);
familyProperties.put(
"character", OdfStylePropertiesSet.TextProperties); // changed from text to character
} else if (styleFamily.equals(OdfStyleFamily.TableColumn)) {
familyProperties.put("column", OdfStylePropertiesSet.TableColumnProperties);
} else if (styleFamily.equals(OdfStyleFamily.Section)) {
familyProperties.put("section", OdfStylePropertiesSet.SectionProperties);
} else if (styleFamily.equals(OdfStyleFamily.List)) {
familyProperties.put("list", OdfStylePropertiesSet.ListLevelProperties);
} else if (styleFamily.equals(OdfStyleFamily.Chart)) {
familyProperties.put("chart", OdfStylePropertiesSet.ChartProperties);
familyProperties.put(
"drawing", OdfStylePropertiesSet.GraphicProperties); // changed from graphic to drawing
familyProperties.put("paragraph", OdfStylePropertiesSet.ParagraphProperties);
familyProperties.put(
"character", OdfStylePropertiesSet.TextProperties); // changed to text from character
} else if (styleFamily.equals(OdfStyleFamily.Graphic)
|| styleFamily.equals(OdfStyleFamily.Presentation)) {
familyProperties.put(
"drawing", OdfStylePropertiesSet.GraphicProperties); // changed from graphic to drawing
familyProperties.put("paragraph", OdfStylePropertiesSet.ParagraphProperties);
familyProperties.put(
"character", OdfStylePropertiesSet.TextProperties); // changed from text to character
} else if (styleFamily.equals(OdfStyleFamily.DrawingPage)) {
familyProperties.put("drawing", OdfStylePropertiesSet.DrawingPageProperties);
} else if (styleFamily.equals(OdfStyleFamily.Ruby)) {
familyProperties.put("ruby", OdfStylePropertiesSet.RubyProperties);
}
return familyProperties;
}
// In the end, this meths should be better moved to the component class
public static String getFamilyID(OdfStylableElement styleElement) {
return getFamilyID(styleElement.getStyleFamily());
}
// In the end, this meths should be better moved to the component class
public static String getMainStyleGroupingId(OdfStylableElement styleElement) {
return Component.getMainStyleGroupingId(styleElement.getStyleFamily());
}
// In the end, this meths should be better moved to the component class
public static String getStyleNamePrefix(OdfStylableElement styleElement) {
return getStyleNamePrefix(styleElement.getStyleFamily());
}
// In the end, this meths should be better moved to the component class
public static String getMainStyleGroupingId(OdfStyleFamily styleFamily) {
String familyID = null;
if (styleFamily.equals(OdfStyleFamily.Paragraph)) {
familyID = "paragraph";
} else if (styleFamily.equals(OdfStyleFamily.Text)) {
familyID = "character";
} else if (styleFamily.equals(OdfStyleFamily.Table)) {
familyID = "table";
} else if (styleFamily.equals(OdfStyleFamily.TableRow)) {
familyID = "row";
} else if (styleFamily.equals(OdfStyleFamily.TableCell)) {
familyID = "cell";
} else if (styleFamily.equals(OdfStyleFamily.TableColumn)) {
familyID = "column";
} else if (styleFamily.equals(OdfStyleFamily.Section)) {
familyID = "section";
} else if (styleFamily.equals(OdfStyleFamily.List)) {
familyID = "list";
} else if (styleFamily.equals(OdfStyleFamily.Chart)) {
familyID = "chart";
} else if (styleFamily.equals(OdfStyleFamily.Graphic)
|| styleFamily.equals(OdfStyleFamily.Presentation)) {
familyID = "drawing";
} else if (styleFamily.equals(OdfStyleFamily.DrawingPage)) {
familyID = "drawing";
} else if (styleFamily.equals(OdfStyleFamily.Ruby)) {
familyID = "ruby";
}
return familyID;
}
// In the end, this meths should be better moved to the component class
public static String getStyleNamePrefix(OdfStyleFamily styleFamily) {
String familyID = null;
if (styleFamily.equals(OdfStyleFamily.Paragraph)
|| styleFamily.equals(OdfStyleFamily.Text)
|| styleFamily.equals(OdfStyleFamily.Section)
|| styleFamily.equals(OdfStyleFamily.List)
|| styleFamily.equals(OdfStyleFamily.Ruby)) {
familyID = "text";
} else if (styleFamily.equals(OdfStyleFamily.Table)
|| styleFamily.equals(OdfStyleFamily.TableRow)
|| styleFamily.equals(OdfStyleFamily.TableCell)
|| styleFamily.equals(OdfStyleFamily.TableColumn)) {
familyID = "table";
} else if (styleFamily.equals(OdfStyleFamily.Chart)) {
familyID = "chart";
} else if (styleFamily.equals(OdfStyleFamily.Graphic)
|| styleFamily.equals(OdfStyleFamily.Presentation)
|| styleFamily.equals(OdfStyleFamily.DrawingPage)) {
familyID = "draw";
} else if (styleFamily.equals(OdfStyleFamily.Presentation)) {
familyID = "presentation";
}
return familyID;
}
// In the end, this meths should be better moved to the component class
public static String getFamilyID(OdfStyleFamily styleFamily) {
String familyID = null;
if (styleFamily.equals(OdfStyleFamily.Paragraph)) {
familyID = "paragraph";
} else if (styleFamily.equals(OdfStyleFamily.Text)) {
familyID = "character";
} else if (styleFamily.equals(OdfStyleFamily.Table)) {
familyID = "table";
} else if (styleFamily.equals(OdfStyleFamily.TableRow)) {
familyID = "row";
} else if (styleFamily.equals(OdfStyleFamily.TableCell)) {
familyID = "cell";
} else if (styleFamily.equals(OdfStyleFamily.TableColumn)) {
familyID = "column";
} else if (styleFamily.equals(OdfStyleFamily.Section)) {
familyID = "section";
} else if (styleFamily.equals(OdfStyleFamily.List)) {
familyID = "list";
} else if (styleFamily.equals(OdfStyleFamily.Chart)) {
familyID = "chart";
} else if (styleFamily.equals(OdfStyleFamily.Graphic)
|| styleFamily.equals(OdfStyleFamily.Presentation)) {
familyID = "drawing";
} else if (styleFamily.equals(OdfStyleFamily.DrawingPage)) {
familyID = "drawing";
} else if (styleFamily.equals(OdfStyleFamily.Ruby)) {
familyID = "ruby";
}
return familyID;
}
// In the end, this meths should be better moved to the component class
public static String getFamilyDisplayName(OdfStyleFamily styleFamily) {
String familyID = null;
if (styleFamily.equals(OdfStyleFamily.Paragraph)) {
familyID = "Paragraph";
} else if (styleFamily.equals(OdfStyleFamily.Text)) {
familyID = "Character";
} else if (styleFamily.equals(OdfStyleFamily.Table)) {
familyID = "Table";
} else if (styleFamily.equals(OdfStyleFamily.TableRow)) {
familyID = "Row";
} else if (styleFamily.equals(OdfStyleFamily.TableCell)) {
familyID = "Cell";
} else if (styleFamily.equals(OdfStyleFamily.TableColumn)) {
familyID = "Column";
} else if (styleFamily.equals(OdfStyleFamily.Section)) {
familyID = "Section";
} else if (styleFamily.equals(OdfStyleFamily.List)) {
familyID = "List";
} else if (styleFamily.equals(OdfStyleFamily.Presentation)) {
familyID = "Presentation";
} else if (styleFamily.equals(OdfStyleFamily.Chart)) {
familyID = "Chart";
} else if (styleFamily.equals(OdfStyleFamily.Graphic)) {
familyID = "Graphic";
} else if (styleFamily.equals(OdfStyleFamily.DrawingPage)) {
familyID = "Drawing";
} else if (styleFamily.equals(OdfStyleFamily.Ruby)) {
familyID = "Ruby";
}
return familyID;
}
// In the end, this meths should be better moved to the component class
/**
* @return styleFamilyValue the <code>String</code> value * * * * * * * of <code>
* StyleFamilyAttribute</code>,
*/
public static String getFamilyName(String styleId) {
String familyID = null;
if (styleId.equals("paragraph")) {
familyID = OdfStyleFamily.Paragraph.getName();
} else if (styleId.equals("character")) {
familyID = OdfStyleFamily.Text.getName();
} else if (styleId.equals("table")) {
familyID = OdfStyleFamily.Table.getName();
} else if (styleId.equals("row")) {
familyID = OdfStyleFamily.TableRow.getName();
} else if (styleId.equals("cell")) {
familyID = OdfStyleFamily.TableCell.getName();
} else if (styleId.equals("column")) {
familyID = OdfStyleFamily.TableColumn.getName();
} else if (styleId.equals("graphic")) {
familyID = OdfStyleFamily.Graphic.getName();
}
return familyID;
}
// In the end, this meths should be better moved to the component class
/**
* @return styleFamily the <code>OdfStyleFamily</code> representation * * * of <code>
* StyleFamilyAttribute</code>,
*/
public static OdfStyleFamily getFamily(String styleId) {
OdfStyleFamily family = null;
if (styleId.equals("paragraph")) {
family = OdfStyleFamily.Paragraph;
} else if (styleId.equals("character")) {
family = OdfStyleFamily.Text;
} else if (styleId.equals("table")) {
family = OdfStyleFamily.Table;
} else if (styleId.equals("row")) {
family = OdfStyleFamily.TableRow;
} else if (styleId.equals("cell")) {
family = OdfStyleFamily.TableCell;
} else if (styleId.equals("column")) {
family = OdfStyleFamily.TableColumn;
} else if (styleId.equals("drawing")) {
family = OdfStyleFamily.Graphic;
}
return family;
}
/**
* A multiple components can be represented by a single XML element
*
* @return the number of components the elements represents
*/
public int repetition() {
return 1;
}
}