TextNavigation.java

/**
 * **********************************************************************
 *
 * <p>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
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>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.
 *
 * <p>**********************************************************************
 */
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;
import org.odftoolkit.odfdom.pkg.OdfElement;
import org.odftoolkit.odfdom.pkg.OdfFileDom;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

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

    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;
      }
    }
  }

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