diff --git a/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/Navigation.java b/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/Navigation.java index 02f81b521f..dc65ded7b6 100644 --- a/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/Navigation.java +++ b/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/Navigation.java @@ -20,6 +20,8 @@ import org.w3c.dom.Node; +import java.util.Set; + /** * Abstract class Navigation used to navigate the document and find the matched element by the user * defined conditions @@ -51,6 +53,13 @@ public abstract class Navigation { */ public abstract boolean match(Node element); + /** + * Checks if the given node can be matched by the navigation + * @param node the node + * @return true if the node is of interest for the navigation + */ + public abstract boolean isMatchingNode(Node node); + /** * get the next matched element in a whole dom tree * @@ -63,28 +72,40 @@ protected Node getNextMatchElement(Node startpoint) { Node currentpoint = startpoint; while ((matchedNode == null) && (currentpoint != null)) { - Node sibling = currentpoint.getNextSibling(); - if ((sibling != null) - && (sibling.getNodeType() == Node.TEXT_NODE || sibling.getNodeType() == Node.ELEMENT_NODE) - && (match(sibling))) { - matchedNode = sibling; - } - while ((sibling != null) && (matchedNode == null)) { - if ((sibling.getNodeType() == Node.TEXT_NODE - || sibling.getNodeType() == Node.ELEMENT_NODE)) { - matchedNode = traverseTree(sibling); - } - sibling = sibling.getNextSibling(); - if (sibling != null && match(sibling)) { - matchedNode = sibling; - } + if (isMatchingNode(currentpoint) && currentpoint != startpoint && parentMatches(currentpoint, startpoint)){ + matchedNode = currentpoint; + } else { + Node sibling = currentpoint.getNextSibling(); + if ((sibling != null) + && (sibling.getNodeType() == Node.TEXT_NODE || sibling.getNodeType() == Node.ELEMENT_NODE) + && (match(sibling))) { + matchedNode = sibling; + } + while ((sibling != null) && (matchedNode == null)) { + if ((sibling.getNodeType() == Node.TEXT_NODE + || sibling.getNodeType() == Node.ELEMENT_NODE)) { + matchedNode = traverseTree(sibling); + } + sibling = sibling.getNextSibling(); + if (sibling != null && match(sibling)) { + matchedNode = sibling; + } + } + currentpoint = currentpoint.getParentNode(); } - currentpoint = currentpoint.getParentNode(); } return matchedNode; } + /** + * Checks that a parent node matches given the current node + * @param parent The parent node + * @param current The current node + * @return true if the parent matches, false otherwise + */ + protected abstract boolean parentMatches(final Node parent, final Node current); + /** * get the next matched element in a sub tree * diff --git a/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/Selection.java b/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/Selection.java index 5a0676f829..378c7d2a38 100644 --- a/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/Selection.java +++ b/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/Selection.java @@ -20,7 +20,10 @@ import java.util.Hashtable; import java.util.Vector; + +import org.odftoolkit.odfdom.incubator.doc.text.OdfWhitespaceProcessor; import org.odftoolkit.odfdom.pkg.OdfElement; +import org.w3c.dom.Node; /** * Abstract class Selection describe one of the matched results The selection can be recognized by @@ -220,16 +223,51 @@ public static void unregisterItem(Selection item) { * @param offset the offset * @param positionIndex the mIndex of a certain position */ - public static synchronized void refresh( - OdfElement containerElement, int offset, int positionIndex) { - if (repository.containsKey(containerElement)) { - Vector selections = repository.get(containerElement); - for (int i = 0; i < selections.size(); i++) { - if (selections.get(i).getIndex() >= positionIndex) { - selections.get(i).refresh(offset); - } + public synchronized static void refresh(OdfElement containerElement, int offset, int positionIndex) { + refreshParent(containerElement, offset); + if (repository.containsKey(containerElement)) { + Vector selections = repository.get(containerElement); + for (Selection selection : selections) { + if (selection.getIndex() >= positionIndex) { + selection.refresh(offset); + } + } } - } + } + + private static void refreshParent(OdfElement containerElement, int offset) { + OdfElement parent = getOdfParent(containerElement); + while (parent != null) { + if (repository.containsKey(parent)) { + Vector selections = repository.get(parent); + for (Selection selection : selections) { + if (isAfter(selection, containerElement)) { + selection.refresh(offset); + } + } + } + parent = getOdfParent(parent); + } + } + + private static OdfElement getOdfParent(OdfElement element) { + Node parent = element.getParentNode(); + while (parent != null && !(parent instanceof OdfElement) && parent != parent.getParentNode()) { + parent = parent.getParentNode(); + } + return parent instanceof OdfElement ? (OdfElement) parent : null; + } + + private static boolean isAfter(Selection selection, OdfElement reference) { + //Assumes that reference is a child of selection.getElement + final OdfWhitespaceProcessor processor = new OdfWhitespaceProcessor(); + final String text = processor.getText(reference); + final int idx = processor.getText(selection.getElement()).indexOf(text); + if (idx == -1 || idx != processor.getText(selection.getElement()).lastIndexOf(text)) { + //TODO obviously don't do that, need to work with Text nodes perhaps + throw new IllegalStateException(); + } + return selection.getIndex() >= idx + text.length(); } private SelectionManager() {} diff --git a/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/TextNavigation.java b/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/TextNavigation.java index d95b33aa29..13b58aa223 100644 --- a/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/TextNavigation.java +++ b/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/TextNavigation.java @@ -18,10 +18,6 @@ */ package org.odftoolkit.odfdom.incubator.search; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.odftoolkit.odfdom.doc.OdfTextDocument; import org.odftoolkit.odfdom.incubator.doc.office.OdfOfficeMasterStyles; import org.odftoolkit.odfdom.incubator.doc.text.OdfWhitespaceProcessor; @@ -30,188 +26,227 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * A derived Navigation class used for navigate the text content it is used to search the document * and find the matched text and would return TextSelection instance */ public class TextNavigation extends Navigation { - private static final String mMatchedElementName = "text:p,text:h"; - private final Pattern mPattern; - private final OdfTextDocument mTextDocument; - private TextSelection mCurrentSelectedItem; - private String mCurrentText; - private int mCurrentIndex; - private boolean mbFinishFindInHeaderFooter; - - /** - * Construct TextNavigation with matched condition and navigation scope - * - * @param pattern the matched pattern String - * @param doc the navigation scope - */ - public TextNavigation(String pattern, OdfTextDocument doc) { - this(Pattern.compile(pattern), doc); - } - - /** - * Construct TextNavigation with matched condition and navigation scope - * - * @param pattern the Pattern object to search with - * @param doc the navigation scope - */ - public TextNavigation(Pattern pattern, OdfTextDocument doc) { - this.mPattern = pattern; - mTextDocument = doc; - mCurrentSelectedItem = null; - mbFinishFindInHeaderFooter = false; - } - - // the matched text might exist in header/footer - private TextSelection findInHeaderFooter(TextSelection selected) { - OdfFileDom styledom = null; - OdfOfficeMasterStyles masterpage = null; - OdfElement element = null; - - if (selected != null) { - int nextIndex = setCurrentTextAndGetIndex(selected); - if (nextIndex != -1) { - TextSelection item = - new TextSelection(mCurrentText, selected.getContainerElement(), nextIndex); - return item; - } + private static final Set mMatchedElementNames = new HashSet<>(Arrays.asList("text:p", "text:h")); + private final Pattern mPattern; + private final OdfTextDocument mTextDocument; + private TextSelection mCurrentSelectedItem; + private String mCurrentText; + private int mCurrentIndex; + private boolean mbFinishFindInHeaderFooter; + + /** + * Construct TextNavigation with matched condition and navigation scope + * + * @param pattern the matched pattern String + * @param doc the navigation scope + */ + public TextNavigation(String pattern, OdfTextDocument doc) { + this(Pattern.compile(pattern), doc); } - try { - styledom = mTextDocument.getStylesDom(); - NodeList list = styledom.getElementsByTagName("office:master-styles"); - if (styledom == null) { - return null; - } - if (list.getLength() > 0) { - masterpage = (OdfOfficeMasterStyles) list.item(0); - } else { - return null; - } - - if (selected == null) { - element = (OdfElement) getNextMatchElementInTree(masterpage, masterpage); - } else { - element = - (OdfElement) getNextMatchElementInTree(selected.getContainerElement(), masterpage); - } - - if (element != null) { - TextSelection item = new TextSelection(mCurrentText, element, mCurrentIndex); - return item; - } else { - return null; - } - } catch (Exception ex) { - Logger.getLogger(TextNavigation.class.getName()).log(Level.SEVERE, ex.getMessage(), ex); - } - return null; - } - - // found the next selection start from the 'selected' TextSelection - private TextSelection findnext(TextSelection selected) { - if (!mbFinishFindInHeaderFooter) { - TextSelection styleselected = findInHeaderFooter(selected); - if (styleselected != null) { - return styleselected; - } - selected = null; - mbFinishFindInHeaderFooter = true; + /** + * Construct TextNavigation with matched condition and navigation scope + * + * @param pattern the Pattern object to search with + * @param doc the navigation scope + */ + public TextNavigation(Pattern pattern, OdfTextDocument doc) { + this.mPattern = pattern; + mTextDocument = doc; + mCurrentSelectedItem = null; + mbFinishFindInHeaderFooter = false; } - if (selected == null) { - OdfElement element = null; - try { - element = (OdfElement) getNextMatchElement((Node) mTextDocument.getContentRoot()); - } catch (Exception ex) { - Logger.getLogger(TextNavigation.class.getName()).log(Level.SEVERE, ex.getMessage(), ex); - } - if (element != null) { - return new TextSelection(mCurrentText, element, mCurrentIndex); - } else { + // the matched text might exist in header/footer + private TextSelection findInHeaderFooter(TextSelection selected) { + OdfFileDom styledom = null; + OdfOfficeMasterStyles masterpage = null; + OdfElement element = null; + + if (selected != null) { + int nextIndex = setCurrentTextAndGetIndex(selected); + if (nextIndex != -1) { + TextSelection item = + new TextSelection(mCurrentText, selected.getContainerElement(), nextIndex); + return item; + } + } + try { + styledom = mTextDocument.getStylesDom(); + NodeList list = styledom.getElementsByTagName("office:master-styles"); + if (styledom == null) { + return null; + } + if (list.getLength() > 0) { + masterpage = (OdfOfficeMasterStyles) list.item(0); + } else { + return null; + } + + if (selected == null) { + element = (OdfElement) getNextMatchElementInTree(masterpage, masterpage); + } else { + element = + (OdfElement) getNextMatchElementInTree(selected.getContainerElement(), masterpage); + } + + if (element != null) { + TextSelection item = new TextSelection(mCurrentText, element, mCurrentIndex); + return item; + } else { + return null; + } + + } catch (Exception ex) { + Logger.getLogger(TextNavigation.class.getName()).log(Level.SEVERE, ex.getMessage(), ex); + } return null; - } } - OdfElement containerElement = selected.getContainerElement(); - int nextIndex = setCurrentTextAndGetIndex(selected); - if (nextIndex != -1) { - TextSelection item = new TextSelection(mCurrentText, containerElement, nextIndex); - return item; - } else { - OdfElement element = (OdfElement) getNextMatchElement(containerElement); - if (element != null) { - TextSelection item = new TextSelection(mCurrentText, element, mCurrentIndex); - return item; - } else { - return null; - } - } - } - - private int setCurrentTextAndGetIndex(TextSelection selected) { - int index = selected.getIndex(); - OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); - String content = textProcessor.getText(selected.getContainerElement()); - - int nextIndex = -1; - Matcher matcher = mPattern.matcher(content); - // start from the end index of the selected item - if (matcher.find(index + selected.getText().length())) { - // here just consider \n\r\t occupy one char - nextIndex = matcher.start(); - int eIndex = matcher.end(); - mCurrentText = content.substring(nextIndex, eIndex); + // found the next selection start from the 'selected' TextSelection + private TextSelection findnext(TextSelection selected) { + if (!mbFinishFindInHeaderFooter) { + TextSelection styleselected = findInHeaderFooter(selected); + if (styleselected != null) { + return styleselected; + } + selected = null; + mbFinishFindInHeaderFooter = true; + } + + if (selected == null) { + OdfElement element = null; + try { + element = (OdfElement) getNextMatchElement((Node) mTextDocument.getContentRoot()); + } catch (Exception ex) { + Logger.getLogger(TextNavigation.class.getName()).log(Level.SEVERE, ex.getMessage(), ex); + } + if (element != null) { + return new TextSelection(mCurrentText, element, mCurrentIndex); + } else { + return null; + } + } + + OdfElement containerElement = selected.getContainerElement(); + int nextIndex = setCurrentTextAndGetIndex(selected); + if (nextIndex != -1) { + TextSelection item = new TextSelection(mCurrentText, containerElement, nextIndex); + return item; + } else { + OdfElement element = (OdfElement) getNextMatchElement(containerElement); + if (element != null) { + TextSelection item = new TextSelection(mCurrentText, element, mCurrentIndex); + return item; + } else { + return null; + } + } } - return nextIndex; - } - - /* (non-Javadoc) - * @see org.odftoolkit.odfdom.incubator.search.Navigation#getCurrentItem() - */ - @Override - public Selection getCurrentItem() { - Selection.SelectionManager.registerItem(mCurrentSelectedItem); - return mCurrentSelectedItem; - } - - /* (non-Javadoc) - * @see org.odftoolkit.odfdom.incubator.search.Navigation#hasNext() - */ - @Override - public boolean hasNext() { - mCurrentSelectedItem = findnext(mCurrentSelectedItem); - return (mCurrentSelectedItem != null); - } - - /** - * check if the text content of element match the specified pattern string - * - * @param element navigate this element - * @return true if the text content of this element match this pattern; false if not match - */ - @Override - public boolean match(Node element) { - if (element instanceof OdfElement) { - if (mMatchedElementName.contains(element.getNodeName())) { + + private int setCurrentTextAndGetIndex(TextSelection selected) { + int index = selected.getIndex(); OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); - String content = textProcessor.getText(element); + String content = textProcessor.getText(selected.getContainerElement()); + int nextIndex = -1; Matcher matcher = mPattern.matcher(content); - if (matcher.find()) { - // here just consider \n\r\t occupy one char - mCurrentIndex = matcher.start(); - int eIndex = matcher.end(); - mCurrentText = content.substring(mCurrentIndex, eIndex); - return true; + // start from the end index of the selected item + if (matcher.find(index + selected.getText().length())) { + // here just consider \n\r\t occupy one char + nextIndex = matcher.start(); + int eIndex = matcher.end(); + mCurrentText = content.substring(nextIndex, eIndex); + } + return nextIndex; + } + + /* (non-Javadoc) + * @see org.odftoolkit.odfdom.incubator.search.Navigation#getCurrentItem() + */ + @Override + public Selection getCurrentItem() { + Selection.SelectionManager.registerItem(mCurrentSelectedItem); + return mCurrentSelectedItem; + } + + /* (non-Javadoc) + * @see org.odftoolkit.odfdom.incubator.search.Navigation#hasNext() + */ + @Override + public boolean hasNext() { + mCurrentSelectedItem = findnext(mCurrentSelectedItem); + return (mCurrentSelectedItem != null); + } + + /** + * check if the text content of element match the specified pattern string + * + * @param element navigate this element + * @return true if the text content of this element match this pattern; false if not match + */ + @Override + public boolean match(Node element) { + if (element instanceof OdfElement) { + if (mMatchedElementNames.contains(element.getNodeName())) { + OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); + String content = textProcessor.getText(element); + + Matcher matcher = mPattern.matcher(content); + if (matcher.find()) { + // here just consider \n\r\t occupy one char + mCurrentIndex = matcher.start(); + int eIndex = matcher.end(); + mCurrentText = content.substring(mCurrentIndex, eIndex); + return true; + } + } + } + return false; + } + + @Override + public boolean isMatchingNode(Node node) { + return mMatchedElementNames.contains(node.getNodeName()); + } + + @Override + protected boolean parentMatches(final Node parent, final Node current) { + if (parent instanceof OdfElement) { + if (mMatchedElementNames.contains(parent.getNodeName())) { + OdfWhitespaceProcessor textProcessor = new OdfWhitespaceProcessor(); + String content = textProcessor.getText(parent); + String childContent = textProcessor.getText(current); + int idx = content.indexOf(childContent); + if (idx == -1 || content.lastIndexOf(childContent) != idx) { + //TODO + throw new IllegalStateException(); + } + + Matcher matcher = mPattern.matcher(content); + if (matcher.find(idx + childContent.length())) { + // here just consider \n\r\t occupy one char + mCurrentIndex = matcher.start(); + int eIndex = matcher.end(); + mCurrentText = content.substring(mCurrentIndex, eIndex); + return true; + } + } } - } + return false; } - return false; - } } diff --git a/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/TextStyleNavigation.java b/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/TextStyleNavigation.java index ae5e086938..13164949cf 100644 --- a/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/TextStyleNavigation.java +++ b/odfdom/src/main/java/org/odftoolkit/odfdom/incubator/search/TextStyleNavigation.java @@ -155,7 +155,18 @@ public boolean match(Node element) { return match; } - private void getIndex(NodeList nodes, Node element) { + @Override + public boolean isMatchingNode(final Node node) { + return node instanceof OdfStylableElement; + } + + @Override + protected boolean parentMatches(final Node parent, final Node current) { + //TODO? + return false; + } + + private void getIndex(NodeList nodes, Node element) { for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node == element) {