| /* |
| 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; |
| |
| /** |
| * <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--; |
| } 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(); |
| } |
| } |
| } |