/* $HeadURL:: $
* $Id$
*
* Copyright (c) 2006-2010 by Public Library of Science
* http://plos.org
* http://ambraproject.org
*
* Licensed 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.ambraproject.dom.ranges;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.ranges.DocumentRange;
import org.w3c.dom.ranges.Range;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
/**
* A helper class to maintain selected Ranges in a document.
*
* @author Pradeep Krishnan
*/
public class SelectionRange {
private List userDataList = new ArrayList();
private Range range;
/**
* Creates a new SelectionRange object.
*
* @param range the selected range
* @param userData corresponding user data or <code>null</code>
*/
public SelectionRange(Range range, Object userData) {
this.range = range;
if (userData != null)
userDataList.add(userData);
}
private SelectionRange() {
}
/**
* Creates a clone of this object. Clones the Range and copies all user data.
*
* @return the newly created object.
*/
public SelectionRange cloneRange() {
SelectionRange dup = new SelectionRange();
dup.userDataList.addAll(userDataList);
dup.range = range.cloneRange();
return dup;
}
/**
* Returns the list of user data items
*
* @return the list (not a copy, so modifiable)
*/
public List getUserDataList() {
return userDataList;
}
/**
* Adds the contents of the list to user data list.
*
* @param list the list to add
*/
public void addAllUserData(List list) {
userDataList.addAll(list);
}
/**
* Checks if this SelectionRange is after the other.
*
* @param other the other range to compare
*
* @return returns <code>true</code> if this range is after
*/
public boolean isAfter(SelectionRange other) {
//compare end of other to start of this
int es = range.compareBoundaryPoints(Range.END_TO_START, other.range);
// -1 if this point before other, 0 if equal, 1 if this after other
return (es >= 0); // other is before this
}
/**
* Checks if this SelectionRange is before the other.
*
* @param other the other range to compare
*
* @return returns <code>true</code> if the other range is after this.
*/
public boolean isBefore(SelectionRange other) {
//compare start of other to end of this
int se = range.compareBoundaryPoints(Range.START_TO_END, other.range);
// -1 if this point before other, 0 if equal, 1 if this after other
return (se <= 0); // other is after this
}
/**
* Checks if this range starts before the other
*
* @param other the other range to compare
*
* @return returns <code>true</code> if this range starts before the other
*/
public boolean startsBefore(SelectionRange other) {
//compare start of other to start of this
int ss = range.compareBoundaryPoints(Range.START_TO_START, other.range);
// -1 if this point before other, 0 if equal, 1 if this after other
return (ss < 0); // if this starts before other
}
/**
* Checks if this range ends after the other
*
* @param other the other range to compare
*
* @return returns <code>true</code> if this range ends before the other
*/
public boolean endsAfter(SelectionRange other) {
//compare end of other to end of this
int ee = range.compareBoundaryPoints(Range.END_TO_END, other.range);
// -1 if this point before other, 0 if equal, 1 if this after other
return (ee > 0); // if this ends after other
}
/**
* Splits this range into two and sets this range as starting after the split.
*
* @param splitPoint the range whose start is the split point
*
* @return returns the range before the split point
*/
public SelectionRange splitBefore(SelectionRange splitPoint) {
SelectionRange before = cloneRange();
before.range.setEnd(splitPoint.range.getStartContainer(), splitPoint.range.getStartOffset());
range.setStart(splitPoint.range.getStartContainer(), splitPoint.range.getStartOffset());
return before;
}
/**
* Move the start of this range to the end of the other so that this will become a
* continutation of the other node.
*
* @param other the other node
*/
public void setAsContinuationOf(SelectionRange other) {
range.setStart(other.range.getEndContainer(), other.range.getEndOffset());
}
/**
* Gets the list of largest non-overlapping sub-ranges each of which can be surrounded by a
* parent element. Handles partially selected nodes unlike the DOM Ranges {@link
* org.w3c.dom.ranges.Range#surroundContents surroundContents} method. The ranges returned by
* this call are guaranteed not to contain partially selected ranges and therefore it is safe to
* call <code>surroundContents</code> on the returned ranges.
*
* @return returns an array of sub-ranges. (will at least have 1 element)
*/
public Range[] getSurroundableRanges() {
Node cac = range.getCommonAncestorContainer();
Document document = cac.getOwnerDocument();
// Select all top level nodes and descendants inside the range.
NodeIterator it =
((DocumentTraversal) document).createNodeIterator(cac, NodeFilter.SHOW_ALL,
new RangeNodeFilter(range, false), false);
Node n;
ArrayList list = new ArrayList();
Node prev = null;
Range last = null;
while ((n = it.nextNode()) != null) {
Range r = ((DocumentRange) document).createRange();
boolean start = (n == range.getStartContainer());
boolean end = (n == range.getEndContainer());
short type = n.getNodeType();
if (type == Node.TEXT_NODE) {
r.selectNode(n);
if (start)
r.setStart(n, range.getStartOffset());
if (end)
r.setEnd(n, range.getEndOffset());
/*
* Optimization rule
*
* Rule 1: text nodes of same parent can be appended to previous range
*/
if ((type == Node.TEXT_NODE) && (prev != null) && (prev.getNodeType() == Node.TEXT_NODE) &&
(n.getParentNode() == prev.getParentNode())) {
// Note: we may have appended complete non-text nodes before; but that is fine
last.setEnd(r.getEndContainer(), r.getEndOffset());
continue;
}
list.add(last = r);
prev = n;
}
}
return (Range[]) list.toArray(new Range[0]);
}
/*
* @see java.lang.Object#toString
*/
public String toString() {
return range.toString();
}
}