TextContainingElement.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.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
import org.odftoolkit.odfdom.dom.element.OdfStylableElement;
import org.odftoolkit.odfdom.dom.element.text.TextAElement;
import org.odftoolkit.odfdom.dom.element.text.TextSpanElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.odfdom.pkg.OdfName;
/**
* This DOM container can be used for all ODF element with an text:style-name.
*
* <p>All text handling is capsulated into this class.
*
* @author svante.schubertATgmail.com
*/
public abstract class TextContainingElement extends OdfStylableElement {
private static final Logger LOG = Logger.getLogger(TextContainingElement.class.getName());
private TreeSet<TextSelection> mSelections = null;
/**
* Create the instance of <code>TextParagraphElementBase</code>
*
* @param ownerDoc The type is <code>OdfFileDom</code>
*/
public TextContainingElement(
OdfFileDom ownerDoc, OdfName elementName, OdfStyleFamily styleFamily, OdfName styleAttrName) {
super(ownerDoc, elementName, styleFamily, styleAttrName);
}
@Override
public OdfName getOdfName() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* only being used to create the root of all components, representing the document without a
* parent element
*/
private class SelectionComparator implements Comparator<TextSelection> {
@Override
public int compare(TextSelection o1, TextSelection o2) {
int firstGreater = o1.compareTo(o2);
return firstGreater;
}
}
/**
* @param outSelection as the end position was prior just given for a merge the an outer selection
* will be added. Earlier added selection as similar positions are therefore called "inner"
* selections and do have a higher priority (as inner overwrites outer)
* @return the element that is being kept (sometimes the element of the given selection had to be
* dismissed, sometimes even the parent, when an empty element was deleted).
*/
public OdfElement appendTextSelection(TextSelection outerSelection) {
if (this.mSelections == null) {
Comparator c = new SelectionComparator();
mSelections = new TreeSet<TextSelection>(c);
}
OdfElement parentElement = (OdfElement) outerSelection.mSelectionElement.getParentNode();
// empty anchor does not make any sense..
if ((outerSelection.mSelectionElement instanceof TextSpanElement
|| outerSelection.mSelectionElement instanceof TextAElement)
&& outerSelection.mSelectionElement.getChildNodes().getLength() != 0) {
/* Working NOTE:
* We need to keep the Anchors, what about the element with URL null, can I remove the anchor right away, or shall I do it here?
* Adjust upper SAX parsing part
*/
// there can always be only one <text:a> element in a range
// Anchor <text:a> will be triggering an operation after the text content is parsed
if (outerSelection.mSelectionElement instanceof TextAElement) {
TextAElement outerAnchor = (TextAElement) outerSelection.mSelectionElement;
String url = ((TextAElement) outerAnchor).getXlinkHrefAttribute();
TextHyperlinkSelection overlappingInnerSelection =
((TextHyperlinkSelection) outerSelection).getOverLappingHyperlinkSelection(mSelections);
if (overlappingInnerSelection != null) { // we need to split the old anchor
// split the outer element at the end of the inner anchor element (end first, to avoid
// counting invalidity)
outerAnchor.split(
overlappingInnerSelection.mEndPosition.get(
overlappingInnerSelection.mEndPosition.size() - 1));
// split the outer element at the beginning of the inner anchor element
OdfElement newMiddlePart =
outerAnchor.split(
overlappingInnerSelection.mStartPosition.get(
overlappingInnerSelection.mStartPosition.size() - 1));
// remove the outer anchor element of the middle part (now top element)
OdfElement.removeSingleElement(newMiddlePart);
} else if (url == null || url.isEmpty() || url.equals("null")) {
parentElement = (OdfElement) OdfElement.removeSingleElement(outerAnchor);
} else if (mSelections.contains(outerSelection)) { // if there are is another span/anchor
// we need to remove the new outerAnchor from the DOM keeping its children/ancestors
SortedSet<TextSelection> equalSet =
mSelections.subSet(outerSelection, true, outerSelection, true);
if (equalSet.size() < 2) {
TextSelection innerSelection;
Iterator<TextSelection> it = equalSet.iterator();
while (it.hasNext()) {
innerSelection = it.next();
if (innerSelection.mSelectionElement instanceof TextSpanElement) {
if (innerSelection.getURL() == null) {
innerSelection.setURL(url);
parentElement = (OdfElement) outerAnchor.getParentNode();
break; // as there can only be one anchor
} else {
// if there is only a span selection, but the URL was already set, remove the new
// anchor!
parentElement = (OdfElement) OdfElement.removeSingleElement(outerAnchor);
break;
}
} else if (innerSelection.mSelectionElement instanceof TextAElement) {
parentElement = (OdfElement) OdfElement.removeSingleElement(outerAnchor);
} else { // field
mSelections.add(outerSelection);
parentElement = (OdfElement) outerSelection.mSelectionElement.getParentNode();
}
}
} else {
// if there are more than two selections (span & anchor) remove the new anchor!)
parentElement = (OdfElement) OdfElement.removeSingleElement(outerAnchor);
}
} else {
mSelections.add(outerSelection);
// parentElement = (OdfElement) outerSelection.mSelectionElement.getParentNode();
}
// if there is already a <text:span> at the same position, merge the styles
} else if (mSelections.contains(outerSelection)) {
// receives all spans and anchors that already exist for this position
SortedSet<TextSelection> equalSet =
mSelections.subSet(outerSelection, true, outerSelection, true);
TextSelection innerSelection = null;
Iterator<TextSelection> it = equalSet.iterator();
parentElement = (OdfElement) outerSelection.mSelectionElement.getParentNode();
while (it.hasNext()) {
innerSelection = it.next();
if (innerSelection.mSelectionElement instanceof TextSpanElement) {
OdfStylableElement innerElement =
(OdfStylableElement) innerSelection.getSelectionElement();
OdfStylableElement outerElement =
(OdfStylableElement) outerSelection.getSelectionElement();
OdfStyle.mergeSelectionWithSameRange(innerElement, outerElement);
} else {
String url = innerSelection.getURL();
mSelections.remove(innerSelection);
outerSelection.setURL(url);
mSelections.add(outerSelection);
}
}
} else {
mSelections.add(outerSelection);
}
} else {
// EMPTY ELEMENT
// Remove empty elements, unless it is the last and only text span
// if (outerSelection.mSelectionElement instanceof TextSpanElement &&
// parentElement.getChildNodes().getLength() != 1) {
parentElement.removeChild(outerSelection.mSelectionElement);
// }
}
return parentElement;
}
public Collection<TextSelection> getTextSelections() {
return mSelections;
}
}