| /************************************************************************ |
| * |
| * 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.odfdom.incubator.search; |
| |
| import java.net.URL; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| import org.odftoolkit.odfdom.pkg.OdfElement; |
| import org.odftoolkit.odfdom.pkg.OdfFileDom; |
| import org.odftoolkit.odfdom.doc.OdfDocument; |
| import org.odftoolkit.odfdom.dom.OdfDocumentNamespace; |
| import org.odftoolkit.odfdom.dom.element.OdfStylableElement; |
| import org.odftoolkit.odfdom.dom.element.OdfStyleBase; |
| import org.odftoolkit.odfdom.dom.element.text.TextAElement; |
| import org.odftoolkit.odfdom.dom.element.text.TextSElement; |
| 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.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.incubator.doc.text.OdfWhitespaceProcessor; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| /** |
| * A TextSelection can describe a sub element in a mParagraph element or a mHeading element. |
| * it is recognized by the container element(which type should be OdfTextParagraph or |
| * OdfTextHeadingt), the start index of the text content of the container element and |
| * the text content of this selection. |
| * |
| * @deprecated As of release 0.8.8, replaced by {@link org.odftoolkit.simple.common.navigation.TextSelection} in Simple API. |
| */ |
| public class TextSelection extends Selection { |
| |
| private String mMatchedText; |
| private OdfTextParagraph mParagraph; |
| private OdfTextHeading mHeading; |
| private int mIndexInContainer; |
| private boolean mIsInserted; |
| |
| /** |
| * Constructor of TextSelection |
| * @param text the text content of this TextSelection |
| * @param containerElement the mParagraph element or mHeading element that contain this TextSelection |
| * @param index the start index of the text content of the container element |
| * |
| */ |
| TextSelection(String text, OdfElement containerElement, int index) { |
| mMatchedText = text; |
| if (containerElement instanceof OdfTextParagraph) { |
| mParagraph = (OdfTextParagraph) containerElement; |
| } else if (containerElement instanceof OdfTextHeading) { |
| mHeading = (OdfTextHeading) containerElement; |
| } |
| mIndexInContainer = index; |
| } |
| |
| /** |
| * Get the mParagraph element or mHeading element that contain this TextSelection |
| * @return OdfElement the container element |
| */ |
| @Override |
| public OdfElement getElement() { |
| return getContainerElement(); |
| } |
| |
| /** |
| * Get the mParagraph element or mHeading element that contain 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; |
| } |
| |
| /* |
| * 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; |
| } |
| OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); |
| String content = textProcessor.getText(container); |
| if (content.indexOf(mMatchedText, mIndexInContainer) == mIndexInContainer) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * 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); |
| |
| } |
| |
| /* |
| * 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(); |
| OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); |
| |
| 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) { |
| if (node.getLocalName().equals("s")) // text: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 = textProcessor.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????????? |
| if (node.getLocalName().equals("s")) // text: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(); |
| } |
| } |
| |
| /** |
| * 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); |
| /*if (startElement instanceof OdfStyleBase) |
| textSpan.setProperties(((OdfStyleBase) startElement) |
| .getStyleProperties());*/ |
| mIsInserted = false; |
| insertSpan(textSpan, index, parentElement); |
| // optimize the parent element |
| optimize(parentElement); |
| int offset = newText.length() - leftLength; |
| SelectionManager.refresh(this.getContainerElement(), offset, index + getText().length()); |
| mMatchedText = newText; |
| } |
| |
| /** |
| * 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; |
| insertSpan(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"); |
| } |
| int indexOfNew = 0;//TODO: think about and test if searchitem is a element selection |
| 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; |
| insertSpan(textSpan, indexOfNew, newElement); |
| adjustStyle(newElement, textSpan, null); |
| SelectionManager.refreshAfterPasteAtEndOf(this, positionItem); |
| } |
| |
| /** |
| * Add a hypertext reference to the selection |
| * |
| * @param url the url of the 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 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(); |
| OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); |
| |
| 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) { |
| if (node.getLocalName().equals("s")) // text: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 = textProcessor.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????????? |
| if (node.getLocalName().equals("s")) // text: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(); |
| } |
| } |
| /* |
| * 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(); |
| OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); |
| |
| 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) { |
| if (node.getLocalName().equals("s")) // text: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 = textProcessor.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????????? |
| if (node.getLocalName().equals("s")) // text: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(); |
| } |
| } |
| |
| @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; |
| } |
| |
| /** |
| * 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() { |
| OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); |
| |
| return "[" + mMatchedText + "] started from " + mIndexInContainer + " in paragraph:" + textProcessor.getText(getContainerElement()); |
| } |
| |
| // 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) { |
| Node copyParentNode = parentElement.cloneNode(true); |
| if (ownerDoc != parentElement.getOwnerDocument()) { |
| copyParentNode = 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 |
| OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); |
| delete(eIndex, textProcessor.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 |
| * |
| * @param element |
| */ |
| private void optimize(Node pNode) { |
| // check if the text:a can be optimized |
| OdfWhitespaceProcessor textProcess = new OdfWhitespaceProcessor(); |
| 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 (textProcess.getText(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<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 textSpan into the from index of pNode |
| */ |
| private void insertSpan(OdfTextSpan textSpan, int fromindex, Node pNode) { |
| if (fromindex < 0) { |
| fromindex = 0; |
| } |
| if (fromindex == 0 && mIsInserted) { |
| return; |
| } |
| OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); |
| 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(textSpan, nextNode); |
| parNode.insertBefore(newNode, nextNode); |
| } else { |
| parNode.appendChild(textSpan); |
| parNode.appendChild(newNode); |
| } |
| mIsInserted = true; |
| return; |
| } |
| } else if (node.getNodeType() == Node.ELEMENT_NODE) { |
| if (node.getLocalName().equals("s")) // text: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 = textProcessor.getText(node).length(); |
| insertSpan(textSpan, 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<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"); |
| } |
| } |
| |
| /* |
| * get a map containing text properties of the specified styleable element. |
| * @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 = ((OdfDocument) ((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 element. |
| * 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 = ((OdfDocument) ((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; |
| } |
| } |