blob: a6275c3ba7e5120ba13502dcef21feb7ded306cc [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.simple.common.navigation;
import java.net.URI;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
import org.odftoolkit.odfdom.dom.OdfContentDom;
import org.odftoolkit.odfdom.dom.OdfDocumentNamespace;
import org.odftoolkit.odfdom.dom.OdfStylesDom;
import org.odftoolkit.odfdom.dom.element.OdfStylableElement;
import org.odftoolkit.odfdom.dom.element.OdfStyleBase;
import org.odftoolkit.odfdom.dom.element.dc.DcCreatorElement;
import org.odftoolkit.odfdom.dom.element.dc.DcDateElement;
import org.odftoolkit.odfdom.dom.element.office.OfficeAnnotationElement;
import org.odftoolkit.odfdom.dom.element.style.StyleParagraphPropertiesElement;
import org.odftoolkit.odfdom.dom.element.style.StyleTextPropertiesElement;
import org.odftoolkit.odfdom.dom.element.text.TextAElement;
import org.odftoolkit.odfdom.dom.element.text.TextConditionalTextElement;
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.TextUserFieldDeclElement;
import org.odftoolkit.odfdom.dom.style.OdfStyleFamily;
import org.odftoolkit.odfdom.dom.style.props.OdfStylePropertiesSet;
import org.odftoolkit.odfdom.dom.style.props.OdfStyleProperty;
import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeAutomaticStyles;
import org.odftoolkit.odfdom.incubator.doc.style.OdfStyle;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextHeading;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextParagraph;
import org.odftoolkit.odfdom.incubator.doc.text.OdfTextSpan;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.odftoolkit.simple.Document;
import org.odftoolkit.simple.TextDocument;
import org.odftoolkit.simple.common.TextExtractor;
import org.odftoolkit.simple.common.field.ConditionField;
import org.odftoolkit.simple.common.field.Field;
import org.odftoolkit.simple.common.field.Field.FieldType;
import org.odftoolkit.simple.common.field.Fields;
import org.odftoolkit.simple.common.field.VariableField;
import org.odftoolkit.simple.draw.Image;
import org.odftoolkit.simple.table.Table;
import org.odftoolkit.simple.text.Paragraph;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* <code>TextSelection</code> describes a sub element in a paragraph element or
* a heading element. It is recognized by the container element, which type
* should be {@link org.odftoolkit.odfdom.incubator.doc.text.OdfTextParagraph
* OdfTextParagraph} or
* {@link org.odftoolkit.odfdom.incubator.doc.text.OdfTextHeading
* OdfTextHeading}, the start index of text content in container element and the
* text content of this <code>Selection</code>.
*/
public class TextSelection extends Selection {
String mMatchedText;
private OdfTextParagraph mParagraph;
private OdfTextHeading mHeading;
private int mIndexInContainer;
private boolean mIsInserted;
private boolean isSelectionReplaced = false;
/**
* Constructor of <code>TextSelection</code>.
*
* @param text
* the text content of this <code>TextSelection</code>
* @param containerElement
* the paragraph element or heading element that contains this
* <code>TextSelection</code>
* @param index
* the start index of the text content in container element
*
*/
TextSelection(Navigation search, String text, OdfElement containerElement,
int index) {
this.search = search;
mMatchedText = text;
if (containerElement instanceof OdfTextParagraph) {
mParagraph = (OdfTextParagraph) containerElement;
} else if (containerElement instanceof OdfTextHeading) {
mHeading = (OdfTextHeading) containerElement;
}
mIndexInContainer = index;
}
public TextNavigation getTextNavigation() {
if (search instanceof TextNavigation) {
return (TextNavigation) search;
}
return null;
}
/**
* Create a new <code>TextSelection</code>.
*
* @param text
* the text content of this <code>TextSelection</code>
* @param containerElement
* the paragraph element or heading element that contains this
* <code>TextSelection</code>
* @param index
* the start index of the text content in container element
*
* @since 0.5.5
*/
public static TextSelection newTextSelection(Navigation search,
String text, OdfElement containerElement, int index) {
TextSelection selection = new TextSelection(search, text,
containerElement, index);
Selection.SelectionManager.registerItem(selection);
return selection;
}
/**
* Get the paragraph element or heading element that contains this
* <code>TextSelection</code>.
*
* @return OdfElement the container element
*/
@Override
public OdfElement getElement() {
return getContainerElement();
}
/**
* Get the paragraph element or heading element that contains this text.
*
* @return OdfElement
*/
public OdfElement getContainerElement() {
if (mParagraph != null) {
return mParagraph;
} else {
return mHeading;
}
}
/**
* Get the start index of the text content of its container element.
*
* @return index the start index of the text content of its container
* element
*/
@Override
public int getIndex() {
return mIndexInContainer;
}
/**
* Get the text content of this <code>TextSelection</code>.
*
* @return text the text content
*/
public String getText() {
return mMatchedText;
}
/**
* Delete the selection from the document the other matched selection in the
* same container element will be updated automatically because the start
* index of the following selections will be changed when the previous
* selection has been deleted.
*
* @throws InvalidNavigationException
* if the selection is unavailable.
*/
@Override
public void cut() throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
OdfElement container = getContainerElement();
delete(mIndexInContainer, mMatchedText.length(), container);
SelectionManager.refreshAfterCut(this);
mMatchedText = "";
}
/**
* Apply a style to the selection so that the text style of this selection
* will append the specified style.
*
* @param style
* the style can be from the current document or user defined
* @throws InvalidNavigationException
* if the selection is unavailable.
*/
public void applyStyle(OdfStyleBase style) throws InvalidNavigationException {
// append the specified style to the selection
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
OdfElement parentElement = getContainerElement();
int leftLength = getText().length();
int index = mIndexInContainer;
appendStyle(index, leftLength, parentElement, style);
}
/**
* Replace the text content of selection with a new string.
*
* @param newText
* the replace text String
* @throws InvalidNavigationException
* if the selection is unavailable.
*/
public void replaceWith(String newText) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
OdfElement parentElement = getContainerElement();
int leftLength = getText().length();
int index = mIndexInContainer;
delete(index, leftLength, parentElement);
OdfTextSpan textSpan = new OdfTextSpan((OdfFileDom) parentElement.getOwnerDocument());
textSpan.addContentWhitespace(newText);
mIsInserted = false;
insertOdfElement(textSpan, index, parentElement);
// optimize the parent element
optimize(parentElement);
int offset = newText.length() - leftLength;
SelectionManager.refresh(getContainerElement(), offset, index + getText().length());
mMatchedText = newText;
}
/**
* Replace the text content of selection with a new Table.
*
* @param newTable
* the replace Table
* @return
* @throws InvalidNavigationException
* if the selection is unavailable.
* @return the new Table in the TextDocument
*/
public Table replaceWith(Table newTable) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
TableSelection nextTableSelection=new TableSelection(this);
return nextTableSelection.replaceWithTable(newTable);
}
/**
* Replace the text content of selection with a new Image.
*
* @param newImage
* the replace Image
* @return
* @throws InvalidNavigationException
* if the selection is unavailable.
* @return the new Image in the TextDocument,the image name is set to "replace" + System.currentTimeMillis(), please update the name to others by yourself.
*/
public Image replaceWith(Image newImage) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
ImageSelection nextImageSelection=new ImageSelection(this);
return nextImageSelection.replaceWithImage(newImage);
}
/**
* Replace the text content of selection with a new Image.
*
* @param imageUri
* the replace Image URI
* @throws InvalidNavigationException
* if the selection is unavailable.
* @return the new Image in the TextDocument,the image name is set to "replace" + System.currentTimeMillis(), please update the name to others by yourself.
*/
public Image replaceWith(URI imageUri) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
ImageSelection nextImageSelection=new ImageSelection(this);
return nextImageSelection.replaceWithImage(imageUri);
}
/**
* Replace the content with a Field
*
* @param orgField
* the reference Field to replace.
* @throws InvalidNavigationException
* if the selection is unavailable.
* @return the created field.
*/
public Field replaceWith(Field orgField) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
Field newfield=null;
OdfElement parentElement = getContainerElement();
Paragraph orgparagraph = Paragraph.getInstanceof((TextParagraphElementBase) parentElement);
TextDocument document = (TextDocument) orgparagraph.getOwnerDocument();
FieldSelection nextFieldSelection=new FieldSelection(this);
FieldType fieldType = orgField.getFieldType();
switch (fieldType) {
case DATE_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case FIXED_DATE_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case TIME_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case FIXED_TIME_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case PREVIOUS_PAGE_NUMBER_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case CURRENT_PAGE_NUMBER_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case NEXT_PAGE_NUMBER_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case PAGE_COUNT_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case TITLE_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case SUBJECT_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case AUTHOR_NAME_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case AUTHOR_INITIALS_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case CHAPTER_FIELD:
newfield=nextFieldSelection.replaceWithSimpleField(fieldType);
break;
case SIMPLE_VARIABLE_FIELD:
VariableField SimpleVariableField = (VariableField)orgField;
String simplefieldname = SimpleVariableField.getVariableName();
VariableField simplefield=Fields.createSimpleVariableField(document, simplefieldname);
nextFieldSelection.replaceWithVariableField(simplefield);
newfield=simplefield;
break;
case USER_VARIABLE_FIELD:
VariableField userVariableField = (VariableField)orgField;
TextUserFieldDeclElement textUserFieldDeclElement =(TextUserFieldDeclElement) userVariableField.getOdfElement();
String fieldname = userVariableField.getVariableName();
String value=textUserFieldDeclElement.getOfficeStringValueAttribute();
VariableField variableField=Fields.createUserVariableField(document, fieldname,value);
nextFieldSelection.replaceWithVariableField(variableField);
newfield=variableField;
break;
case CONDITION_FIELD:
ConditionField conditionField = (ConditionField)orgField;
TextConditionalTextElement textConditionalTextElement =(TextConditionalTextElement) conditionField.getOdfElement();
String StringValueIfFalse=textConditionalTextElement.getTextStringValueIfFalseAttribute();
String StringValueIfTrue=textConditionalTextElement.getTextStringValueIfTrueAttribute();
String StringCondition=textConditionalTextElement.getTextConditionAttribute();
boolean CurrentValue=textConditionalTextElement.getTextCurrentValueAttribute();
ConditionField newdConditionField = nextFieldSelection.replaceWithConditionField(StringCondition, StringValueIfTrue, StringValueIfFalse);
TextConditionalTextElement newTextConditionalTextElement=(TextConditionalTextElement)newdConditionField.getOdfElement();
newTextConditionalTextElement.setTextCurrentValueAttribute(CurrentValue);
newfield=newdConditionField;
break;
case HIDDEN_TEXT_FIELD:
ConditionField conditionFieldHIDDEN = (ConditionField)orgField;
TextConditionalTextElement textConditionalTextElementHIDDEN =(TextConditionalTextElement) conditionFieldHIDDEN.getOdfElement();
String StringValueIfFalseHIDDEN=textConditionalTextElementHIDDEN.getTextStringValueIfFalseAttribute();
String StringConditionHIDDEN=textConditionalTextElementHIDDEN.getTextConditionAttribute();
boolean CurrentValueHIDDEN=textConditionalTextElementHIDDEN.getTextCurrentValueAttribute();
ConditionField newdConditionFieldHIDDEN = nextFieldSelection.replaceWithHiddenTextField(StringConditionHIDDEN, StringValueIfFalseHIDDEN);
TextConditionalTextElement newTextConditionalTextElementHIDDEN=(TextConditionalTextElement)newdConditionFieldHIDDEN.getOdfElement();
newTextConditionalTextElementHIDDEN.setTextCurrentValueAttribute(CurrentValueHIDDEN);
newfield=newdConditionFieldHIDDEN;
break;
case REFERENCE_FIELD:
default: throw new IllegalArgumentException("Simple Java API for ODF doesn't support this type now.");
}
return newfield;
}
/**
* Replace the content with a paragraph, the paragraph can be in the same TextDocument or in a different Document.
*
* @param newParagraph
* the reference paragraph to replace.
* @throws InvalidNavigationException
* if the selection is unavailable.
* @return the replaced Paragraph.
*/
public Paragraph replaceWith(Paragraph newParagraph) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
ParagraphSelection nextParagraphSelection=new ParagraphSelection(this);
return nextParagraphSelection.replaceWithParagraph(newParagraph);
}
/**
* Replace the content with a TextDocument with Styles.
* Note: You need cache the TextNavigation.nextSelection item because after
* you replace currtenTextSelection with TextDocument, TextNavigation.nextSelection will search from the inserted Content,
* it will make you into a loop if the Search keyword also can be found in the new inserted Content.
* </p>
* The right way to use this replaceWithTextDocument(TextDocument textDocument) method should like this:
* <Code>
* <p> search = new TextNavigation("SIMPLE", doc); </p>
* <p> TextSelection currtenTextSelection,nextTextSelection=null;</p>
* <p> while (search.hasNext()) {</p>
* <p> if(nextTextSelection!=null){</p>
* <p> currtenTextSelection=nextTextSelection;</p>
* <p> }else {</p>
* <p> currtenTextSelection = (TextSelection) search.nextSelection();</p>
* <p> }</p>
* <p> nextTextSelection = (TextSelection) search.nextSelection();</p>
* <p> if(currtenTextSelection!=null){</p>
* <p> try {</p>
* <p> nextTextSelection.replaceWithTextDocument(sourcedoc);</p>
* <p> } catch (Exception e) {</p>
* <p> e.printStackTrace();</p>
* <p> }</p>
* <p> }</p>
* <p> }</p>
* <p> if(nextTextSelection!=null){</p>
* <p> try {</p>
* <p> nextTextSelection.replaceWithTextDocument(sourcedoc);</p>
* <p> } catch (Exception e) {</p>
* <p> e.printStackTrace();</p>
* <p> }</p>
* <p> }</p>
* </Code>
*
* @param newTextDocument
* the reference TextDocument to replace.
* @throws InvalidNavigationException
*/
public void replaceWith(TextDocument newTextDocument) throws InvalidNavigationException{
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
TextDocumentSelection nextTextDocumentSelection=new TextDocumentSelection(this);
try {
nextTextDocumentSelection.replaceWithTextDocument(newTextDocument);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Create a span element for this text selection.
*
* @return the created text span element for this selection
* @throws InvalidNavigationException
* if the selection is unavailable.
* @since 0.5.5
*/
public TextSpanElement createSpanElement() throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
OdfElement parentElement = getContainerElement();
int leftLength = getText().length();
int index = mIndexInContainer;
delete(index, leftLength, parentElement);
OdfTextSpan textSpan = new OdfTextSpan((OdfFileDom) parentElement.getOwnerDocument());
textSpan.addContentWhitespace(getText());
mIsInserted = false;
insertOdfElement(textSpan, index, parentElement);
// optimize the parent element
optimize(parentElement);
return textSpan;
}
/**
* Paste this selection just before a specific selection.
*
* @param positionItem
* a selection that is used to point out the position
* @throws InvalidNavigationException
* if the selection is unavailable.
*/
@Override
public void pasteAtFrontOf(Selection positionItem) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
int indexOfNew = 0;
OdfElement newElement = positionItem.getElement();
if (positionItem instanceof TextSelection) {
indexOfNew = ((TextSelection) positionItem).getIndex();
newElement = ((TextSelection) positionItem).getContainerElement();
}
OdfTextSpan textSpan = getSpan((OdfFileDom) positionItem.getElement().getOwnerDocument());
mIsInserted = false;
insertOdfElement(textSpan, indexOfNew, newElement);
adjustStyle(newElement, textSpan, null);
SelectionManager.refreshAfterPasteAtFrontOf(this, positionItem);
}
/**
* Paste this selection just after a specific selection.
*
* @param positionItem
* a selection that is used to point out the position
* @throws InvalidNavigationException
* if the selection is unavailable.
*/
@Override
public void pasteAtEndOf(Selection positionItem) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
// TODO: think about and test if search item is a element selection
int indexOfNew = 0;
OdfElement newElement = positionItem.getElement();
if (positionItem instanceof TextSelection) {
indexOfNew = ((TextSelection) positionItem).getIndex() + ((TextSelection) positionItem).getText().length();
newElement = ((TextSelection) positionItem).getContainerElement();
}
OdfTextSpan textSpan = getSpan((OdfFileDom) positionItem.getElement().getOwnerDocument());
mIsInserted = false;
insertOdfElement(textSpan, indexOfNew, newElement);
adjustStyle(newElement, textSpan, null);
SelectionManager.refreshAfterPasteAtEndOf(this, positionItem);
}
public void setSelectionReplaced(boolean b) {
this.isSelectionReplaced = b;
}
public boolean isSelectionReplaced() {
return this.isSelectionReplaced;
}
/**
* Add a hypertext reference to the selection.
*
* @param url
* the URL of this hypertext reference
* @throws InvalidNavigationException
* if the selection is unavailable.
*/
public void addHref(URL url) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
OdfElement parentElement = getContainerElement();
int leftLength = getText().length();
int index = mIndexInContainer;
addHref(index, leftLength, parentElement, url.toString());
}
/**
* Add a comment to the selection.
*
* @param content
* the content of this comment.
* @param creator
* the creator of this comment, if <code>creator</code> is null,
* the value of <code>System.getProperty("user.name")</code> will
* be used.
* @throws InvalidNavigationException
* if the selection is unavailable.
* @since 0.6.5
*/
public void addComment(String content, String creator) throws InvalidNavigationException {
if (validate() == false) {
throw new InvalidNavigationException("No matched string at this position");
}
// create annotation element
OdfElement parentElement = getContainerElement();
OdfFileDom dom = (OdfFileDom) parentElement.getOwnerDocument();
OfficeAnnotationElement annotationElement = dom.newOdfElement(OfficeAnnotationElement.class);
// set creator
DcCreatorElement dcCreatorElement = annotationElement.newDcCreatorElement();
if (creator == null) {
creator = System.getProperty("user.name");
}
dcCreatorElement.setTextContent(creator);
// set date
String dcDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(new Date());
DcDateElement dcDateElement = annotationElement.newDcDateElement();
dcDateElement.setTextContent(dcDate);
TextPElement notePElement = annotationElement.newTextPElement();
TextSpanElement noteSpanElement = notePElement.newTextSpanElement();
// set comment style
OdfOfficeAutomaticStyles styles = null;
if (dom instanceof OdfContentDom) {
styles = ((OdfContentDom) dom).getAutomaticStyles();
} else if (dom instanceof OdfStylesDom) {
styles = ((OdfStylesDom) dom).getAutomaticStyles();
}
OdfStyle textStyle = styles.newStyle(OdfStyleFamily.Text);
StyleTextPropertiesElement styleTextPropertiesElement = textStyle.newStyleTextPropertiesElement(null);
styleTextPropertiesElement.setStyleFontNameAttribute("Tahoma");
styleTextPropertiesElement.setFoFontSizeAttribute("10pt");
styleTextPropertiesElement.setStyleFontNameAsianAttribute("Lucida Sans Unicode");
styleTextPropertiesElement.setStyleFontSizeAsianAttribute("12pt");
noteSpanElement.setStyleName(textStyle.getStyleNameAttribute());
// set comment content
noteSpanElement.setTextContent(content);
// insert comment to its position
insertOdfElement(annotationElement, mIndexInContainer, parentElement);
// three text length plus two '\r'
int offset = content.length() + 1 + dcDate.length() + 1 + creator.length();
SelectionManager.refresh(getContainerElement(), offset, getIndex());
}
/**
* return a String Object representing this selection value the text content
* of the selection, start index in the container element and the text
* content of the container element will be provided.
*
* @return a String representation of the value of this
* <code>TextSelection</code>
*/
@Override
public String toString() {
return "[" + mMatchedText + "] started from " + mIndexInContainer + " in paragraph:"
+ TextExtractor.getText(getContainerElement());
}
@Override
protected void refreshAfterFrontalDelete(Selection deleteItem) {
if (deleteItem instanceof TextSelection) {
mIndexInContainer -= ((TextSelection) deleteItem).getText().length();
}
}
@Override
protected void refreshAfterFrontalInsert(Selection pasteItem) {
if (pasteItem instanceof TextSelection) {
mIndexInContainer += ((TextSelection) pasteItem).getText().length();
}
}
@Override
protected void refresh(int offset) {
mIndexInContainer += offset;
if (mIndexInContainer < 0) {
mIndexInContainer = 0;
}
}
void cleanBreakProperty(Paragraph paragraph) {
TextNavigation search = this.getTextNavigation();
if (search == null)
throw new IllegalStateException("Navigation is null");
OdfStyleBase styleElement = paragraph.getStyleHandler()
.getStyleElementForRead();
String name = styleElement.getAttribute("style:name");
String newName = null;
OdfElement modifiedStyleElement = search
.getModifiedStyleElement(styleElement);
if (modifiedStyleElement == null) {
modifiedStyleElement = (OdfElement) styleElement.cloneNode(true);
search.addModifiedStyleElement(styleElement, modifiedStyleElement);
NodeList paragraphProperties = modifiedStyleElement
.getElementsByTagName("style:paragraph-properties");
if (paragraphProperties != null
&& paragraphProperties.getLength() > 0) {
StyleParagraphPropertiesElement property = (StyleParagraphPropertiesElement) paragraphProperties
.item(0);
property.removeAttribute("fo:break-before");
property.removeAttribute("fo:break-after");
property.removeAttribute("style:page-number");
}
modifiedStyleElement.removeAttribute("style:master-page-name");
newName = name + "-" + makeUniqueName();
NamedNodeMap attributes = modifiedStyleElement.getAttributes();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
Node item = attributes.item(i);
String value = item.getNodeValue();
if (name.equals(value)) {
item.setNodeValue(newName);
break;
}
}
}
styleElement.getParentNode().appendChild(modifiedStyleElement);
} else {
newName = modifiedStyleElement.getAttribute("style:name");
}
NamedNodeMap attributes = paragraph.getOdfElement().getAttributes();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
Node item = attributes.item(i);
String value = item.getNodeValue();
if (name.equals(value)) {
item.setNodeValue(newName);
break;
}
}
}
this.getTextNavigation().setHandlePageBreak(true);
}
String makeUniqueName() {
return String.format("p%06x", (int) (Math.random() * 0xffffff));
}
/*
* Return a new span that cover this selection and keep the original style
* of this <code>Selection</code>.
*/
private OdfTextSpan getSpan(OdfFileDom ownerDoc) {
OdfElement parentElement = getContainerElement();
if (parentElement != null) {
OdfElement copyParentNode = (OdfElement) parentElement.cloneNode(true);
if (ownerDoc != parentElement.getOwnerDocument()) {
copyParentNode = (OdfElement) ownerDoc.adoptNode(copyParentNode);
}
OdfTextSpan textSpan = new OdfTextSpan(ownerDoc);
int sIndex = mIndexInContainer;
int eIndex = sIndex + mMatchedText.length();
// delete the content except the selection string
// delete from the end to start, so that the postion will not be
// impact by delete action
delete(eIndex, TextExtractor.getText(copyParentNode).length() - eIndex, copyParentNode);
delete(0, sIndex, copyParentNode);
optimize(copyParentNode);
Node childNode = copyParentNode.getFirstChild();
while (childNode != null) {
textSpan.appendChild(childNode.cloneNode(true));
childNode = childNode.getNextSibling();
}
// apply text style for the textSpan
if (copyParentNode instanceof OdfStylableElement) {
applyTextStyleProperties(getTextStylePropertiesDeep((OdfStylableElement) copyParentNode), textSpan);
}
return textSpan;
}
return null;
}
/*
* Optimize the text element by deleting the empty text node.
*/
private void optimize(Node pNode) {
// check if the text:a can be optimized
Node node = pNode.getFirstChild();
while (node != null) {
Node nextNode = node.getNextSibling();
// if ((node.getNodeType() == Node.ELEMENT_NODE) &&
// (node.getPrefix().equals("text"))) {
if (node instanceof OdfTextSpan) {
if (TextExtractor.getText((OdfTextSpan) node).length() == 0) {
node.getParentNode().removeChild(node);
} else {
optimize(node);
}
}
node = nextNode;
}
}
/*
* Apply the <code>styleMap</code> to the <code>toElement</code> reserve the
* style property of toElement, if it is also exist in <code>styleMap</code>
*/
private void applyTextStyleProperties(Map<OdfStyleProperty, String> styleMap, OdfStylableElement toElement) {
if (styleMap != null) {
// preserve the style property of toElement if it is also exist in
// styleMap
OdfStyle resultStyleElement = toElement.getAutomaticStyles().newStyle(OdfStyleFamily.Text);
for (Map.Entry<OdfStyleProperty, String> entry : styleMap.entrySet()) {
if (toElement.hasProperty(entry.getKey())) {
resultStyleElement.setProperty(entry.getKey(), toElement.getProperty(entry.getKey()));
} else {
resultStyleElement.setProperty(entry.getKey(), entry.getValue());
}
}
toElement.setStyleName(resultStyleElement.getStyleNameAttribute());
}
}
/*
* Insert <code>odfElement</code>, span or annotation, into the from index of <code>pNode<code>.
*/
private void insertOdfElement(OdfElement odfElement, int fromIndex, Node pNode) {
if (fromIndex < 0) {
fromIndex = 0;
}
if (fromIndex == 0 && mIsInserted) {
return;
}
int nodeLength = 0;
Node node = pNode.getFirstChild();
while (node != null) {
if (fromIndex <= 0 && mIsInserted) {
return;
}
if (node.getNodeType() == Node.TEXT_NODE) {
nodeLength = node.getNodeValue().length();
if ((fromIndex != 0) && (nodeLength < fromIndex)) {
fromIndex -= nodeLength;
} else {
// insert result after node, and insert an new text node
// after the result node
String value = node.getNodeValue();
StringBuffer buffer = new StringBuffer();
buffer.append(value.substring(0, fromIndex));
// insert the text span in appropriate position
node.setNodeValue(buffer.toString());
Node nextNode = node.getNextSibling();
Node parNode = node.getParentNode();
Node newNode = node.cloneNode(true);
newNode.setNodeValue(value.substring(fromIndex, value.length()));
if (nextNode != null) {
parNode.insertBefore(odfElement, nextNode);
parNode.insertBefore(newNode, nextNode);
} else {
parNode.appendChild(odfElement);
parNode.appendChild(newNode);
}
mIsInserted = true;
return;
}
} else if (node.getNodeType() == Node.ELEMENT_NODE) {
// text:s
if (node.getLocalName().equals("s")) {
try {
nodeLength = Integer.parseInt(((Element) node).getAttributeNS(OdfDocumentNamespace.TEXT
.getUri(), "c"));
} catch (Exception e) {
nodeLength = 1;
}
fromIndex -= nodeLength;
} else if (node.getLocalName().equals("line-break")) {
nodeLength = 1;
fromIndex--;
} else if (node.getLocalName().equals("tab")) {
nodeLength = 1;
fromIndex--;
} else {
nodeLength = TextExtractor.getText((OdfElement) node).length();
insertOdfElement(odfElement, fromIndex, node);
fromIndex -= nodeLength;
}
}
node = node.getNextSibling();
}
}
/*
* The <code>textSpan</code> must be the child element of
* <code>parentNode</code> this method is used to keep the style of text
* span when it has been insert into the <code>parentNode</code> if we don't
* deal with the style, the inserted span will also have the style of
* <code>parentNode</code>.
*/
private void adjustStyle(Node parentNode, OdfTextSpan textSpan, Map<OdfStyleProperty, String> styleMap) {
if (parentNode instanceof OdfStylableElement) {
OdfStylableElement pStyleNode = (OdfStylableElement) parentNode;
if (styleMap == null) {
styleMap = getTextStylePropertiesDeep(pStyleNode);
}
Node node = parentNode.getFirstChild();
while (node != null) {
if (node.getNodeType() == Node.TEXT_NODE) {
if (node.getTextContent().length() > 0) {
Node nextNode = node.getNextSibling();
OdfTextSpan span = new OdfTextSpan((OdfFileDom) node.getOwnerDocument());
span.appendChild(node);
if (nextNode != null) {
parentNode.insertBefore(span, nextNode);
} else {
parentNode.appendChild(span);
}
node = span;
applyTextStyleProperties(styleMap, (OdfStylableElement) node);
}
} else if ((node instanceof OdfStylableElement)) {
if (!node.equals(textSpan)) {
Map<OdfStyleProperty, String> styles = getTextStylePropertiesDeep(pStyleNode);
Map<OdfStyleProperty, String> styles1 = getTextStylePropertiesDeep((OdfStylableElement) node);
if (styles == null) {
styles = styles1;
} else if (styles1 != null) {
styles.putAll(styles1);
}
int comp = node.compareDocumentPosition(textSpan);
// if node contains textSpan, then recurse the node
if ((comp & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0) {
adjustStyle(node, textSpan, styles);
} else {
applyTextStyleProperties(styles, (OdfStylableElement) node);
}
}
}
node = node.getNextSibling();
}
// change the parentNode to default style
// here we don't know the default style name, so here just
// remove the text:style-name attribute
pStyleNode.removeAttributeNS(OdfDocumentNamespace.TEXT.getUri(), "style-name");
}
}
/*
* Delete the <code>pNode<code> from the <code>fromIndex</code> text, and
* delete <code>leftLength</code> text.
*/
private void delete(int fromIndex, int leftLength, Node pNode) {
if ((fromIndex == 0) && (leftLength == 0)) {
return;
}
int nodeLength = 0;
Node node = pNode.getFirstChild();
while (node != null) {
if ((fromIndex == 0) && (leftLength == 0)) {
return;
}
if (node.getNodeType() == Node.TEXT_NODE) {
nodeLength = node.getNodeValue().length();
} else if (node.getNodeType() == Node.ELEMENT_NODE) {
// text:s
if (node.getLocalName().equals("s")) {
try {
nodeLength = Integer.parseInt(((Element) node).getAttributeNS(OdfDocumentNamespace.TEXT
.getUri(), "c"));
} catch (Exception e) {
nodeLength = 1;
}
} else if (node.getLocalName().equals("line-break")) {
nodeLength = 1;
} else if (node.getLocalName().equals("tab")) {
nodeLength = 1;
} else {
nodeLength = TextExtractor.getText((OdfElement) node).length();
}
}
if (nodeLength <= fromIndex) {
fromIndex -= nodeLength;
} else {
// the start index is in this node
if (node.getNodeType() == Node.TEXT_NODE) {
String value = node.getNodeValue();
StringBuffer buffer = new StringBuffer();
buffer.append(value.substring(0, fromIndex));
int endLength = fromIndex + leftLength;
int nextLength = value.length() - endLength;
fromIndex = 0;
if (nextLength >= 0) {
// delete the result
buffer.append(value.substring(endLength, value.length()));
leftLength = 0;
} else {
leftLength = endLength - value.length();
}
node.setNodeValue(buffer.toString());
} else if (node.getNodeType() == Node.ELEMENT_NODE) {
// if text:s?????????
// text:s
if (node.getLocalName().equals("s")) {
// delete space
((TextSElement) node).setTextCAttribute(new Integer(nodeLength - fromIndex));
leftLength = leftLength - (nodeLength - fromIndex);
fromIndex = 0;
} else if (node.getLocalName().equals("line-break") || node.getLocalName().equals("tab")) {
fromIndex = 0;
leftLength--;
Node nodeMerker = node.getNextSibling();
pNode.removeChild(node);
node = nodeMerker;
continue;
} else {
delete(fromIndex, leftLength, node);
int length = (fromIndex + leftLength) - nodeLength;
leftLength = length > 0 ? length : 0;
fromIndex = 0;
}
}
}
node = node.getNextSibling();
}
}
/*
* Add href for a range text of <code>pNode<code> from the
* <code>fromIndex</code> text, and the href will cover
* <code>leftLength</code> text.
*/
private void addHref(int fromIndex, int leftLength, Node pNode, String href) {
if ((fromIndex == 0) && (leftLength == 0)) {
return;
}
int nodeLength = 0;
Node node = pNode.getFirstChild();
while (node != null) {
if ((fromIndex == 0) && (leftLength == 0)) {
return;
}
if (node.getNodeType() == Node.TEXT_NODE) {
nodeLength = node.getNodeValue().length();
} else if (node.getNodeType() == Node.ELEMENT_NODE) {
// text:s
if (node.getLocalName().equals("s")) {
try {
nodeLength = Integer.parseInt(((Element) node).getAttributeNS(OdfDocumentNamespace.TEXT
.getUri(), "c"));
} catch (Exception e) {
nodeLength = 1;
}
} else if (node.getLocalName().equals("line-break")) {
nodeLength = 1;
} else if (node.getLocalName().equals("tab")) {
nodeLength = 1;
} else {
nodeLength = TextExtractor.getText((OdfElement) node).length();
}
}
if (nodeLength <= fromIndex) {
fromIndex -= nodeLength;
} else {
// the start index is in this node
if (node.getNodeType() == Node.TEXT_NODE) {
String value = node.getNodeValue();
node.setNodeValue(value.substring(0, fromIndex));
int endLength = fromIndex + leftLength;
int nextLength = value.length() - endLength;
Node nextNode = node.getNextSibling();
Node parNode = node.getParentNode();
// init text:a
TextAElement textLink = new TextAElement((OdfFileDom) node.getOwnerDocument());
Node newNode = null;
if (nextLength >= 0) {
textLink.setTextContent(value.substring(fromIndex, endLength));
newNode = node.cloneNode(true);
newNode.setNodeValue(value.substring(endLength, value.length()));
leftLength = 0;
} else {
textLink.setTextContent(value.substring(fromIndex, value.length()));
leftLength = endLength - value.length();
}
textLink.setXlinkTypeAttribute("simple");
textLink.setXlinkHrefAttribute(href);
if (nextNode != null) {
parNode.insertBefore(textLink, nextNode);
if (newNode != null) {
parNode.insertBefore(newNode, nextNode);
}
} else {
parNode.appendChild(textLink);
if (newNode != null) {
parNode.appendChild(newNode);
}
}
fromIndex = 0;
if (nextNode != null) {
node = nextNode;
} else {
node = textLink;
}
} else if (node.getNodeType() == Node.ELEMENT_NODE) {
// if text:s?????????
// text:s
if (node.getLocalName().equals("s")) {
// delete space
((TextSElement) node).setTextCAttribute(new Integer(nodeLength - fromIndex));
leftLength = leftLength - (nodeLength - fromIndex);
fromIndex = 0;
} else if (node.getLocalName().equals("line-break") || node.getLocalName().equals("tab")) {
fromIndex = 0;
leftLength--;
} else {
addHref(fromIndex, leftLength, node, href);
int length = (fromIndex + leftLength) - nodeLength;
leftLength = length > 0 ? length : 0;
fromIndex = 0;
}
}
}
node = node.getNextSibling();
}
}
/*
* Get a map containing text properties of the specified styleable
* <code>element</code>.
*
* @return a map of text properties.
*/
private Map<OdfStyleProperty, String> getTextStyleProperties(OdfStylableElement element) {
String styleName = element.getStyleName();
OdfStyleBase styleElement = element.getAutomaticStyles().getStyle(styleName, element.getStyleFamily());
if (styleElement == null) {
styleElement = element.getDocumentStyle();
}
if (styleElement != null) {
// check if it is the style:defaut-style
if ((styleElement.getPropertiesElement(OdfStylePropertiesSet.ParagraphProperties) == null)
&& (styleElement.getPropertiesElement(OdfStylePropertiesSet.TextProperties) == null)) {
styleElement = ((Document) ((OdfFileDom) styleElement.getOwnerDocument()).getDocument())
.getDocumentStyles().getDefaultStyle(styleElement.getFamily());
}
TreeMap<OdfStyleProperty, String> result = new TreeMap<OdfStyleProperty, String>();
OdfStyleFamily family = OdfStyleFamily.Text;
if (family != null) {
for (OdfStyleProperty property : family.getProperties()) {
if (styleElement.hasProperty(property)) {
result.put(property, styleElement.getProperty(property));
}
}
}
return result;
}
return null;
}
/*
* Get a map containing text properties of the specified styleable
* <code>element</code>. The map will also include any properties set by
* parent styles.
*
* @return a map of text properties.
*/
private Map<OdfStyleProperty, String> getTextStylePropertiesDeep(OdfStylableElement element) {
String styleName = element.getStyleName();
OdfStyleBase styleElement = element.getAutomaticStyles().getStyle(styleName, element.getStyleFamily());
if (styleElement == null) {
styleElement = element.getDocumentStyle();
}
TreeMap<OdfStyleProperty, String> result = new TreeMap<OdfStyleProperty, String>();
while (styleElement != null) {
// check if it is the style:defaut-style
if ((styleElement.getPropertiesElement(OdfStylePropertiesSet.ParagraphProperties) == null)
&& (styleElement.getPropertiesElement(OdfStylePropertiesSet.TextProperties) == null)) {
styleElement = ((Document) ((OdfFileDom) styleElement.getOwnerDocument()).getDocument())
.getDocumentStyles().getDefaultStyle(styleElement.getFamily());
}
OdfStyleFamily family = OdfStyleFamily.Text;
if (family != null) {
for (OdfStyleProperty property : family.getProperties()) {
if (styleElement.hasProperty(property)) {
result.put(property, styleElement.getProperty(property));
}
}
}
styleElement = styleElement.getParentStyle();
}
return result;
}
/*
* Validate if the <code>Selection</code> is still available.
*
* @return true if the selection is available; false if the
* <code>Selection</code> is not available.
*/
private boolean validate() {
if (getContainerElement() == null) {
return false;
}
OdfElement container = getContainerElement();
if (container == null) {
return false;
}
String content = TextExtractor.getText(container);
if (content.indexOf(mMatchedText, mIndexInContainer) == mIndexInContainer) {
return true;
} else {
return false;
}
}
/*
* Append specified style for a range text of <code>pNode<code> from
* <code>fromIndex</code> and cover <code>leftLength</code>
*/
private void appendStyle(int fromIndex, int leftLength, Node pNode, OdfStyleBase style) {
if ((fromIndex == 0) && (leftLength == 0)) {
return;
}
int nodeLength = 0;
Node node = pNode.getFirstChild();
while (node != null) {
if ((fromIndex == 0) && (leftLength == 0)) {
return;
}
if (node.getNodeType() == Node.TEXT_NODE) {
nodeLength = node.getNodeValue().length();
} else if (node.getNodeType() == Node.ELEMENT_NODE) {
// text:s
if (node.getLocalName().equals("s")) {
try {
nodeLength = Integer.parseInt(((Element) node).getAttributeNS(OdfDocumentNamespace.TEXT
.getUri(), "c"));
} catch (Exception e) {
nodeLength = 1;
}
} else if (node.getLocalName().equals("line-break")) {
nodeLength = 1;
} else if (node.getLocalName().equals("tab")) {
nodeLength = 1;
} else {
nodeLength = TextExtractor.getText((OdfElement) node).length();
}
}
if (nodeLength <= fromIndex) {
fromIndex -= nodeLength;
} else {
// the start index is in this node
if (node.getNodeType() == Node.TEXT_NODE) {
String value = node.getNodeValue();
node.setNodeValue(value.substring(0, fromIndex));
int endLength = fromIndex + leftLength;
int nextLength = value.length() - endLength;
Node nextNode = node.getNextSibling();
Node parNode = node.getParentNode();
// init text:a
OdfTextSpan textSpan = new OdfTextSpan((OdfFileDom) node.getOwnerDocument());
Node newNode = null;
if (nextLength >= 0) {
textSpan.setTextContent(value.substring(fromIndex, endLength));
newNode = node.cloneNode(true);
newNode.setNodeValue(value.substring(endLength, value.length()));
leftLength = 0;
} else {
textSpan.setTextContent(value.substring(fromIndex, value.length()));
leftLength = endLength - value.length();
}
textSpan.setProperties(style.getStyleProperties());
if (nextNode != null) {
parNode.insertBefore(textSpan, nextNode);
if (newNode != null) {
parNode.insertBefore(newNode, nextNode);
}
} else {
parNode.appendChild(textSpan);
if (newNode != null) {
parNode.appendChild(newNode);
}
}
fromIndex = 0;
if (nextNode != null) {
node = nextNode;
} else {
node = textSpan;
}
} else if (node.getNodeType() == Node.ELEMENT_NODE) {
// if text:s?????????
// text:s
if (node.getLocalName().equals("s")) {
// delete space
((TextSElement) node).setTextCAttribute(new Integer(nodeLength - fromIndex));
leftLength = leftLength - (nodeLength - fromIndex);
fromIndex = 0;
} else if (node.getLocalName().equals("line-break") || node.getLocalName().equals("tab")) {
fromIndex = 0;
leftLength--;
} else {
appendStyle(fromIndex, leftLength, node, style);
int length = (fromIndex + leftLength) - nodeLength;
leftLength = length > 0 ? length : 0;
fromIndex = 0;
}
}
}
node = node.getNextSibling();
}
}
}