All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.odftoolkit.simple.common.navigation.TextSelection Maven / Gradle / Ivy

There is a newer version: 0.8.2-incubating
Show newest version
/* 
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.element.text.TextVariableDeclElement;
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;

/**
 * TextSelection 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 Selection.
 */
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 TextSelection.
	 * 
	 * @param text
	 *            the text content of this TextSelection
	 * @param containerElement
	 *            the paragraph element or heading element that contains this
	 *            TextSelection
	 * @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 TextSelection.
	 * 
	 * @param text
	 *            the text content of this TextSelection
	 * @param containerElement
	 *            the paragraph element or heading element that contains this
	 *            TextSelection
	 * @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
	 * TextSelection.
	 * 
	 * @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 TextSelection.
	 * 
	 * @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.
	 * 

* The right way to use this replaceWithTextDocument(TextDocument textDocument) method should like this: * *

search = new TextNavigation("SIMPLE", doc);

*

TextSelection currtenTextSelection,nextTextSelection=null;

*

while (search.hasNext()) {

*

if(nextTextSelection!=null){

*

currtenTextSelection=nextTextSelection;

*

}else {

*

currtenTextSelection = (TextSelection) search.nextSelection();

*

}

*

nextTextSelection = (TextSelection) search.nextSelection();

*

if(currtenTextSelection!=null){

*

try {

*

nextTextSelection.replaceWithTextDocument(sourcedoc);

*

} catch (Exception e) {

*

e.printStackTrace();

*

}

*

}

*

}

*

if(nextTextSelection!=null){

*

try {

*

nextTextSelection.replaceWithTextDocument(sourcedoc);

*

} catch (Exception e) {

*

e.printStackTrace();

*

}

*

}

*
* * @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 creator is null, * the value of System.getProperty("user.name") 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 * TextSelection */ @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 Selection. */ 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 styleMap to the toElement reserve the * style property of toElement, if it is also exist in styleMap */ private void applyTextStyleProperties(Map 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 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 odfElement, span or annotation, into the from index of pNode. */ 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 textSpan must be the child element of * parentNode this method is used to keep the style of text * span when it has been insert into the parentNode if we don't * deal with the style, the inserted span will also have the style of * parentNode. */ private void adjustStyle(Node parentNode, OdfTextSpan textSpan, Map 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 styles = getTextStylePropertiesDeep(pStyleNode); Map 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 pNode from the fromIndex text, and * delete leftLength 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--; } 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 pNode from the * fromIndex text, and the href will cover * leftLength 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 * element. * * @return a map of text properties. */ private Map 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 result = new TreeMap(); 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 * element. The map will also include any properties set by * parent styles. * * @return a map of text properties. */ private Map getTextStylePropertiesDeep(OdfStylableElement element) { String styleName = element.getStyleName(); OdfStyleBase styleElement = element.getAutomaticStyles().getStyle(styleName, element.getStyleFamily()); if (styleElement == null) { styleElement = element.getDocumentStyle(); } TreeMap result = new TreeMap(); 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 Selection is still available. * * @return true if the selection is available; false if the * Selection 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 pNode from * fromIndex and cover leftLength */ 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(); } } }