/**
* 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.waveprotocol.wave.model.document.util;
import junit.framework.TestCase;
import org.waveprotocol.wave.model.document.MutableDocument;
import org.waveprotocol.wave.model.document.ReadableDocument;
import org.waveprotocol.wave.model.document.ReadableWDocument;
import org.waveprotocol.wave.model.document.indexed.IndexedDocument;
import org.waveprotocol.wave.model.document.indexed.IndexedDocumentImpl;
import org.waveprotocol.wave.model.document.indexed.LocationMapper;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema;
import org.waveprotocol.wave.model.document.raw.RawDocument;
import org.waveprotocol.wave.model.document.raw.impl.Element;
import org.waveprotocol.wave.model.document.raw.impl.Node;
import org.waveprotocol.wave.model.document.raw.impl.RawDocumentImpl;
import org.waveprotocol.wave.model.document.raw.impl.Text;
import org.waveprotocol.wave.model.document.util.ContextProviders.TestDocumentContext;
import org.waveprotocol.wave.model.document.util.DocHelper.NodeAction;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.util.Box;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Test cases for the DocHelper class
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public class DocHelperTest extends TestCase {
/**
* Test the getText method
*/
public void testGetText() {
checkGetText("<x>abc</x>", 0, 0, "");
checkGetText("<x>abc</x>", 1, 1, "");
checkGetText("<x>abc</x>", 4, 4, "");
checkGetText("<x>abc</x>", 1, 4, "abc");
checkGetText("<x>abc</x>", 3, 4, "c");
checkGetText("<x><y>abc</y></x>", 1, 6, "abc");
checkGetText("<x><y>abc</y><z>def</z></x>", 1, 6, "abc");
checkGetText("<x>a<b>b</b>c</x>", 1, 6, "abc");
checkGetText("<x>abc<b></b></x>", 1, 4, "abc");
checkGetText("<x>abc<b></b></x>", 1, 5, "abc");
checkGetText("<x>abc<b></b></x>", 1, 6, "abc");
checkGetText("<x>abc<b></b><c></c></x>", 1, 6, "abc");
checkGetText("<x><a></a>abc<b></b></x>", 1, 6, "abc");
checkGetText("<x>abc<b>x</b>def</x>", 1, 10, "abcxdef");
checkGetText("<x>abc<b>x</b>def</x>", 2, 10, "bcxdef");
checkGetText("<x>abc<b>x</b>def</x>", 1, 9, "abcxde");
checkGetText("<x>abc<b>x</b>def</x>", 2, 9, "bcxde");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 1, 14, "abcdefghi");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 2, 14, "abcdefghi");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 3, 14, "bcdefghi");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 1, 13, "abcdefghi");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 1, 12, "abcdefgh");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 3, 12, "bcdefgh");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 7, 13, "efghi");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 2, 7, "abcd");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 2, 6, "abc");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 9, 14, "ghi");
checkGetText("<x><a>abc</a>def<b>ghi</b></x>", 9, 13, "ghi");
IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abc<x>def</x>");
assertEquals("abcdef", DocHelper.getText(doc, doc, doc.getDocumentElement()));
}
private void checkGetText(String docXml, int start, int end, String expectedText) {
IndexedDocument<?, ?, ?> doc = DocProviders.POJO.parse(docXml);
assertEquals(expectedText, DocHelper.getText(doc, start, end));
}
/**
* Test getLocation for DocHelper.
* TODO(user): Write a more thorough test.
*/
public void testGetLocation() throws OperationException {
checkGetLocation("", 0, 0);
checkGetLocation("hi", 0, 0);
checkGetLocation("hi", 2, 2);
checkGetLocation("hithere", 2, 2);
checkGetLocation("hi<x/>", 2, 2);
checkGetLocation("<x><a><b>a</b>bc</a></x>", 0, 0);
checkGetLocation("<x><a><b>a</b>bc</a></x>", 1, 1);
checkGetLocation("<x><a><b>a</b>bc</a></x>", 2, 2);
checkGetLocation("<x><a><b>a</b>bc</a></x>", 3, 3);
checkGetLocation("<x><a><b>a</b>bc</a></x>", 4, 4);
checkGetLocation("<x><a><b>a</b>bc</a></x>", 9, 9);
checkGetLocation("<x><a><b>a</b>bc</a></x><x/><a/>", 9, 9);
checkGetLocation("<x><a><b>a</b>bc</a></x>hi", 9, 9);
}
private void checkGetLocation(String docXml, int nodeLocation, int expectedLocation)
throws OperationException {
// Persistent state for checkGetLocation.
RawDocument<Node, Element, Text> fullDoc =
RawDocumentImpl.PROVIDER.create("doc", Attributes.EMPTY_MAP);
final Box<Point<Node>> pointBox = Box.create(null);
PersistentContent<Node, Element, Text> persistentDoc =
new PersistentContent<Node, Element, Text>(fullDoc, Element.ELEMENT_MANAGER) {
@Override
public void onBeforeFilter(Point<Node> point) {
// catch the filter callback invocations.
pointBox.boxed = point;
}
};
IndexedDocument<Node, Element, Text> indexedDoc =
new IndexedDocumentImpl<Node, Element, Text, Void>(persistentDoc, null,
DocumentSchema.NO_SCHEMA_CONSTRAINTS);
indexedDoc.consume(DocProviders.POJO.parse(docXml).asOperation());
Point<Node> n = indexedDoc.locate(nodeLocation);
if (!n.isInTextNode()) {
Element newNode =
persistentDoc.transparentCreate("abc", Collections.<String, String> emptyMap(),
(Element) n.getContainer(), n.getNodeAfter());
Element newNode2 =
persistentDoc.transparentCreate("def", Collections.<String, String> emptyMap(), newNode,
null);
Element newNode3 =
persistentDoc.transparentCreate("ghi", Collections.<String, String> emptyMap(), newNode,
newNode2);
assertEquals(expectedLocation, DocHelper.getFilteredLocation(indexedDoc, persistentDoc,
Point.<Node> inElement(newNode, null)));
assertEquals(pointBox.boxed, Point.<Node> inElement(newNode, null));
assertEquals(expectedLocation, DocHelper.getFilteredLocation(indexedDoc, persistentDoc,
Point.<Node> inElement(newNode.getParentElement(), newNode)));
assertEquals(pointBox.boxed, Point.<Node> inElement(newNode.getParentElement(), newNode));
assertEquals(expectedLocation, DocHelper.getFilteredLocation(indexedDoc, persistentDoc,
Point.<Node> inElement(newNode.getParentElement(), newNode.getNextSibling())));
assertEquals(pointBox.boxed,
Point.<Node> inElement(newNode.getParentElement(), newNode.getNextSibling()));
assertEquals(expectedLocation, DocHelper.getFilteredLocation(indexedDoc, persistentDoc,
Point.<Node> inElement(newNode2, null)));
assertEquals(pointBox.boxed, Point.<Node> inElement(newNode2, null));
assertEquals(expectedLocation, DocHelper.getFilteredLocation(indexedDoc, persistentDoc,
Point.<Node> inElement(newNode2.getParentElement(), newNode2)));
assertEquals(pointBox.boxed, Point.<Node> inElement(newNode2.getParentElement(), newNode2));
assertEquals(expectedLocation, DocHelper.getFilteredLocation(indexedDoc, persistentDoc,
Point.<Node> inElement(newNode3, null)));
assertEquals(pointBox.boxed, Point.<Node> inElement(newNode3, null));
assertEquals(expectedLocation, DocHelper.getFilteredLocation(indexedDoc, persistentDoc,
Point.<Node> inElement(newNode3.getParentElement(), newNode3)));
assertEquals(pointBox.boxed, Point.<Node> inElement(newNode3.getParentElement(), newNode3));
}
}
/**
* Test normalize point between two text nodes, i.e. "hello""world"
*/
public void testNormalizePointBetweenTwoTextNodes() {
MutableDocument<Node, Element, Text> doc = initializeMutableDoc();
Element p = doc.asElement(doc.getFirstChild(doc.getDocumentElement()));
assert p != null;
doc.insertText(Point.<Node> end(p), "hello");
insertTextInNewTextNodeHelper(doc, Point.<Node> end(p), "world");
Text world = doc.asText(doc.getLastChild(p));
Text hello = doc.asText(doc.getFirstChild(p));
assertEquals(Point.inText(hello, hello.getLength()), DocHelper.normalizePoint(Point
.<Node> inText(world, 0), doc));
assertEquals(Point.<Node> inText(world, 1), DocHelper.normalizePoint(Point.<Node> inText(world,
1), doc));
assertEquals(Point.<Node> inText(world, 2), DocHelper.normalizePoint(Point.<Node> inText(world,
2), doc));
assertEquals(Point.<Node> inText(hello, 5), DocHelper.normalizePoint(Point.<Node> inText(hello,
5), doc));
assertEquals(Point.<Node> inText(hello, 4), DocHelper.normalizePoint(Point.<Node> inText(hello,
4), doc));
}
/**
* Test normalize points between an element and a text node <a>stuff</a>"hi"
*/
public void testNormalizePointElementFollowedByTextNode() {
MutableDocument<Node, Element, Text> doc = initializeMutableDoc();
Element p = doc.asElement(doc.getFirstChild(doc.getDocumentElement()));
assert p != null;
Element aElement =
doc.createElement(Point.start(doc, p), "a", Collections.<String, String> emptyMap());
doc.insertText(Point.start(doc, aElement), "stuff");
doc.insertText(Point.<Node> end(p), "hi");
Text hi = doc.asText(doc.getLastChild(p));
Text stuff = doc.asText(aElement.getFirstChild());
assertEquals(Point.inText(hi, 0), DocHelper.normalizePoint(Point.<Node> inText(hi, 0), doc));
assertEquals(Point.inText(hi, 0), DocHelper.normalizePoint(Point.<Node>inElement(p, hi), doc));
// In the future, we might want to move the caret out from inline elements.
assertEquals(Point.inText(stuff, stuff.getLength()), DocHelper.normalizePoint(Point
.<Node> inText(stuff, stuff.getLength()), doc));
}
/**
* Test normalize points between text node and element "hi"<a>stuff</a>
*/
public void testNormalizePointTextNodeFollowedByElement() {
MutableDocument<Node, Element, Text> doc = initializeMutableDoc();
Element p = doc.asElement(doc.getFirstChild(doc.getDocumentElement()));
assert p != null;
doc.insertText(Point.<Node> end(p), "hi");
Element aElement =
doc.createElement(Point.<Node>end(p), "a", Collections.<String, String> emptyMap());
doc.insertText(Point.start(doc, aElement), "stuff");
Text hi = doc.asText(doc.getFirstChild(p));
Text stuff = doc.asText(aElement.getFirstChild());
assertEquals(Point.inText(hi, 2), DocHelper.normalizePoint(Point.<Node> inText(hi, 2), doc));
assertEquals(Point.inText(hi, 2), DocHelper.normalizePoint(Point.<Node> inElement(p, aElement),
doc));
// In the future, we might want to move the caret out from inline elements.
assertEquals(Point.inText(stuff, 0), DocHelper.normalizePoint(Point.<Node> inText(stuff, 0),
doc));
assertEquals(Point.inText(stuff, stuff.getLength()), DocHelper.normalizePoint(Point
.<Node> inText(stuff, stuff.getLength()), doc));
}
private static MutableDocument<Node, Element, Text> initializeMutableDoc() {
return DocProviders.MOJO.parse("<p></p>");
}
/**
* Try to insert text into a new text node, by first inserting an element, and
* then removing it after the new text is inserted.
*
* NOTE(user): We assume the document doesn't try to join the text nodes
* after the dummy element is removed.
*
* @param <N>
* @param at
* @param text
*/
// TODO(user): Move somewhere common for TextLocatorTest
static <N, E extends N, T extends N> void insertTextInNewTextNodeHelper(
MutableDocument<N, E, T> doc, Point<N> at, String text) {
E e = doc.createElement(at, "a", Collections.<String, String> emptyMap());
doc.insertText(Point.after(doc, e), text);
doc.deleteNode(e);
}
/**
* Some behavioural tests for aligning left over document views.
*/
public void testLeftAlign() {
TestDocumentContext<Node, Element, Text> cxt = createAlignTestCxt();
LocalDocument<Node, Element, Text> doc = cxt.annotatableContent();
Point<Node> at, other;
// check when the point is already in the view
at = Point.start(doc, doc.getDocumentElement().getFirstChild().asElement());
other = DocHelper.leftAlign(at, doc, cxt.hardView());
assertEquals(at, other); // no change
// check when the point is to the left of shallow transparent elements
at = Point.start(doc, doc.getDocumentElement().getFirstChild().getNextSibling().asElement());
other = DocHelper.leftAlign(at, doc, cxt.hardView());
assertEquals(at, other); // no change
// check when the point is to the right of shallow transparent elements
Element p2 = doc.getDocumentElement().getFirstChild().getNextSibling().asElement();
at = Point.end((Node) p2);
other = DocHelper.leftAlign(at, doc, cxt.hardView());
assertEquals(Point.end(p2.getFirstChild()), other);
// nb: normalization to text node occurs externally
// check when the point is to the left of deep transparent elements
at = Point.start(doc, doc.getDocumentElement().getLastChild().asElement());
other = DocHelper.leftAlign(at, doc, cxt.hardView());
assertEquals(at, other); // no change (nb: normalization to text node occurs externally)
// check when the point is to the right of deep transparent elements
at = Point.end(doc.getDocumentElement().getLastChild());
other = DocHelper.leftAlign(at, doc, cxt.hardView());
assertEquals(Point.before(doc, doc.getDocumentElement().getLastChild().getLastChild()), other);
}
// util for align tests above
private TestDocumentContext<Node, Element, Text> createAlignTestCxt() {
// creates the following, where '<t>' are soft nodes
// <div>
// <p>A</p>
// <p> <t>Z</t> </p>
// <p> <t><t/></t> </p>
// </div>
String initialContent = "<p>A</p><p>Z</p><p></p>";
TestDocumentContext<Node, Element, Text> cxt = ContextProviders.createTestPojoContext(
DocProviders.POJO.parse(initialContent).asOperation(), null, null, null,
DocumentSchema.NO_SCHEMA_CONSTRAINTS);
LocalDocument<Node, Element, Text> doc = cxt.annotatableContent();
Text zText = doc.getDocumentElement().getFirstChild().getNextSibling().getFirstChild().asText();
// First transparent part, moving the Z inside
Element trans = doc.transparentCreate("S", Attributes.EMPTY_MAP,
zText.getParentElement(), zText);
cxt.annotatableContent().transparentMove(trans, zText, null, null);
// Second transparent part, deep transparent (contains another transparent element)
Element lastP = doc.getDocumentElement().getLastChild().asElement();
trans = cxt.annotatableContent().transparentCreate("T", Attributes.EMPTY_MAP, lastP, null);
cxt.annotatableContent().transparentCreate("U", Attributes.EMPTY_MAP, trans, null);
return cxt;
}
/***/
public void testGetNextSiblingElementBackwards() {
ReadableWDocument<Node, Element, Text> doc = DocProviders.POJO.parse(
"<div>abc<p>def<q>hij</q></p><p>def<q>hij</q></p></div>");
Element previous = null;
Node node = doc.getFirstChild(doc.getDocumentElement());
while (node != null) {
Element p = doc.asElement(node);
if (p != null) {
if (previous == null) {
// p is the very first element among nodes of doc.
assertNull(DocHelper.getPreviousSiblingElement(doc, p));
} else {
// p follows a previously seen previous element.
assertSame(previous, DocHelper.getPreviousSiblingElement(doc, p));
}
previous = p;
}
node = doc.getNextSibling(node);
}
// TODO(user): The following fails. Uncomment and fix if it should work.
// assertNull(DocHelper.getPreviousSiblingElement(doc, null));
}
public void testGetNextSiblingElementBackwardsForInvalid() {
ReadableWDocument<Node, Element, Text> doc = DocProviders.POJO.parse(
"<div>abc<p>def<q>hij</q></p><p>def<q>hij</q></p></div>");
try {
DocHelper.getPreviousSiblingElement(doc, null);
fail("Should failed when fetching previous sibling of a null");
} catch (Exception e) {
// Success!
}
try {
DocHelper.getPreviousSiblingElement(null, doc.getFirstChild(doc.getDocumentElement()));
fail("Should failed when fetching previous sibling in a null document");
} catch (Exception e) {
// Success!
}
}
/**
* Tests the getItemSize method
*/
public void testGetItemSize() {
ReadableWDocument<Node, Element, Text> doc = DocProviders.POJO.parse(
"<top>abc<p>def<q>hij</q></p><p>def<q>hij</q></p></top>");
Element top = (Element) doc.getDocumentElement().getFirstChild();
assertEquals(25, DocHelper.getItemSize(doc, top));
Node text = doc.getFirstChild(top);
assertEquals(3, DocHelper.getItemSize(doc, text));
Node pWithSibling = doc.getNextSibling(text);
assertEquals(10, DocHelper.getItemSize(doc, pWithSibling));
Node pWithoutSibling = doc.getNextSibling(pWithSibling);
assertEquals(10, DocHelper.getItemSize(doc, pWithoutSibling));
}
public void testGetElementWithTagName() {
ReadableDocument<Node, Element, Text> doc;
{
// Nothing to find in empty doc.
doc = getDoc("");
assertNull(DocHelper.getElementWithTagName(doc, ""));
}
{
// Container is excluded from search.
doc = getDoc("<x><y></y></x>");
Element container = doc.getFirstChild(doc.getDocumentElement()).asElement();
assertNull(DocHelper.getElementWithTagName(doc, "x", container));
// Finds direct child match.
Element expectedY = doc.getFirstChild(container).asElement();
assertSame(expectedY, DocHelper.getElementWithTagName(doc, "y", container));
// Finds deeper child match.
assertSame(expectedY, DocHelper.getElementWithTagName(doc, "y"));
}
{
doc = getDoc("<x><y></y><z></z></x>");
// Finds a non-first-sibling match.
Element container = doc.getFirstChild(doc.getDocumentElement()).asElement();
Element expectedZ = doc.getLastChild(container).asElement();
assertSame(expectedZ, DocHelper.getElementWithTagName(doc, "z"));
}
{
doc = getDoc("<x><y><z></z></y></x>");
// Doesn't search above subtree.
Element container = doc.getFirstChild(doc.getDocumentElement()).asElement();
Element y = doc.getFirstChild(container).asElement();
assertNull(DocHelper.getElementWithTagName(doc, "x", y));
}
{
doc = getDoc("<x><y></y></x><z></z>");
// Doesn't search right of subtree.
Element x = doc.getFirstChild(doc.getDocumentElement()).asElement();
assertNull(DocHelper.getElementWithTagName(doc, "z", x));
}
{
doc = getDoc("<y><x></x></y><x></x>");
// Finds leftmost match.
Element expectedX = doc.getFirstChild(doc.getFirstChild(
doc.getDocumentElement())).asElement();
assertSame(expectedX, DocHelper.getElementWithTagName(doc, "x"));
}
}
public void testGetLastElementWithTagName() {
ReadableDocument<Node, Element, Text> doc;
{
// Nothing to find in empty doc.
doc = getDoc("");
assertNull(DocHelper.getLastElementWithTagName(doc, ""));
}
{
// Container is excluded from search.
doc = getDoc("<x><y></y></x>");
Element container = doc.getFirstChild(doc.getDocumentElement()).asElement();
assertNull(DocHelper.getLastElementWithTagName(doc, "x", container));
// Finds direct child match.
Element expectedY = doc.getFirstChild(container).asElement();
assertSame(expectedY, DocHelper.getLastElementWithTagName(doc, "y", container));
// Finds deeper child match.
assertSame(expectedY, DocHelper.getLastElementWithTagName(doc, "y"));
}
{
doc = getDoc("<x><y></y><z></z></x>");
// Finds a non-last-sibling match.
Element container = doc.getFirstChild(doc.getDocumentElement()).asElement();
Element expectedY = doc.getFirstChild(container).asElement();
assertSame(expectedY, DocHelper.getLastElementWithTagName(doc, "y"));
}
{
doc = getDoc("<x><y><z></z></y></x>");
// Doesn't search above subtree.
Element container = doc.getFirstChild(doc.getDocumentElement()).asElement();
Element y = doc.getFirstChild(container).asElement();
assertNull(DocHelper.getLastElementWithTagName(doc, "x", y));
}
{
doc = getDoc("<z></z><x><y></y></x>");
// Doesn't search left of subtree.
Element x = doc.getLastChild(doc.getDocumentElement()).asElement();
assertNull(DocHelper.getLastElementWithTagName(doc, "z", x));
}
{
doc = getDoc("<x></x><y><x></x></y>");
// Finds rightmost match.
Element expectedX = doc.getFirstChild(doc.getLastChild(
doc.getDocumentElement())).asElement();
assertSame(expectedX, DocHelper.getLastElementWithTagName(doc, "x"));
}
}
/**
* Tests the testGetElementWithTagName which indirectly tests
* {@link DocHelper#getElementWithTagName(ReadableDocument, String)} and
* {@link DocHelper#getText(ReadableDocument, LocationMapper, Object)} .
*/
public void testGetElementTextWithTagName() {
checkGetElementTextWithTagName("<x>abc</x>", "x", "abc");
checkGetElementTextWithTagName("<x>abc</x>", "z", null);
checkGetElementTextWithTagName("<x><y>abc</y></x>", "x", "abc");
checkGetElementTextWithTagName("<x><y>abc</y></x>", "y", "abc");
checkGetElementTextWithTagName("<x><y>abc</y></x>", "z", null);
checkGetElementTextWithTagName("<x>a<b>b</b>c</x>", "x", "abc");
checkGetElementTextWithTagName("<x>a<b>b</b>c</x>", "a", null);
checkGetElementTextWithTagName("<x>a<b>b</b>c</x>", "b", "b");
checkGetElementTextWithTagName("<x>abc<b></b></x>", "x", "abc");
checkGetElementTextWithTagName("<x>abc<b></b><c></c></x>", "x", "abc");
checkGetElementTextWithTagName("<x>abc<b></b><c></c></x>", "c", "");
checkGetElementTextWithTagName("<x><a>abc</a>def<b>ghi</b></x>", "x", "abcdefghi");
checkGetElementTextWithTagName("<x><a>abc</a>def<b>ghi</b></x>", "a", "abc");
checkGetElementTextWithTagName("<x><a>abc</a>def<b>ghi</b></x>", "b", "ghi");
checkGetElementTextWithTagName("<x><a>abc</a>def<b>ghi</b></x>", "c", null);
}
private void checkGetElementTextWithTagName(String docXml, String tagName,
String expectedElementText) {
IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse(docXml);
assertEquals(expectedElementText, DocHelper.getTextForElement(doc, doc, tagName));
}
public void testEnsureNodeBoundary() {
checkEnsureNodeBoundary("ab", 1, 1, false);
checkEnsureNodeBoundary("ab", 2, 2, true);
checkEnsureNodeBoundary("ab", 3, -1, false);
checkEnsureNodeBoundary("a^b", 3, 3, false);
checkEnsureNodeBoundary("a<x>bc</x><y>de</y>", 2, 2, false);
checkEnsureNodeBoundary("a<x>bc</x><y>de</y>", 5, 6, false);
checkEnsureNodeBoundary("a<x><z>bc</z></x><y>de</y>", 6, 8, false);
checkEnsureNodeBoundary("a<x><z>bc</z></x>", 6, -1, false);
checkEnsureNodeBoundary("a<x><z></z></x>", 4, -1, false);
checkEnsureNodeBoundary("a<x><y></y><z></z></x>", 4, 5, false);
checkEnsureNodeBoundary("a<x><y></y><z></z></x>", 5, 5, false);
}
private void checkEnsureNodeBoundary(String initialContent,
int boundaryLocation, int nextNodeLocation, boolean splitNecessary) {
// Test the two methods at the same time
for (boolean returnNextNode : new boolean[] {true, false}) {
// There can be up to 3 points corresponding to a given location, automatically
// test for all of them as input.
for (int pointBias = 0; pointBias < 3; pointBias++) {
IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse(
"<doc>" + initialContent + "</doc>");
splitTextNodes(doc);
Point<Node> point = doc.locate(boundaryLocation);
boolean detectSplitNecessary =
point.isInTextNode() &&
point.getTextOffset() > 0 &&
point.getTextOffset() < ((Text) point.getContainer()).getLength();
if (splitNecessary != detectSplitNecessary) {
fail("Possible incorrect test case location params " +
"- splitNecessary parameter inaccurate");
}
Point<Node> boundaryPoint = null;
if (splitNecessary) {
if (boundaryLocation != nextNodeLocation) {
fail("Wrong test case - when a text node must be split," +
" nextNodeLocation == boundaryLocation");
}
// There's only one possibility to test - so quit after this.
pointBias = 50;
boundaryPoint = point;
} else {
Node nodeBefore, nodeAfter, parent;
if (point.isInTextNode()) {
if (point.getTextOffset() == 0) {
nodeAfter = point.getContainer();
nodeBefore = doc.getPreviousSibling(nodeAfter);
} else {
nodeBefore = point.getContainer();
nodeAfter = doc.getNextSibling(nodeBefore);
}
parent = point.getContainer().getParentElement();
} else {
nodeAfter = point.getNodeAfter();
parent = point.getContainer();
if (nodeAfter == null) {
nodeBefore = point.getContainer().getLastChild();
} else {
nodeBefore = nodeAfter.getPreviousSibling();
}
}
Text textNodeBefore = doc.asText(nodeBefore);
Text textNodeAfter = doc.asText(nodeAfter);
switch (pointBias) {
case 0:
if (textNodeBefore != null) {
boundaryPoint = Point.<Node>inText(textNodeBefore, textNodeBefore.getLength());
}
break;
case 1:
if (textNodeAfter != null) {
boundaryPoint = Point.<Node>inText(textNodeAfter, 0);
}
break;
case 2:
boundaryPoint = Point.inElement(parent, nodeAfter);
break;
}
}
if (boundaryPoint != null) {
if (returnNextNode) {
Node n = DocHelper.ensureNodeBoundaryReturnNextNode(boundaryPoint, doc, doc);
if (nextNodeLocation >= 0) {
assertEquals(nextNodeLocation, doc.getLocation(n));
} else {
assertNull(n);
}
} else {
Point.El<Node> point2 = DocHelper.ensureNodeBoundary(boundaryPoint, doc, doc);
assertEquals(boundaryLocation, doc.getLocation(point2));
if (!splitNecessary && !boundaryPoint.isInTextNode()) {
// Check no unecessary copying.
assertSame(boundaryPoint, point2);
}
}
}
}
}
}
public void testTransparentSlice() {
final TestDocumentContext<Node, Element, Text> cxt1 = createSliceTestCxt();
withTextNode(cxt1, "TT", new NodeAction<Text>() {
@Override
public void apply(Text node) {
Point.El<Node> point = DocHelper.ensureNodeBoundary(
DocHelper.transparentSlice(Point.<Node>inText(node, 1), cxt1),
cxt1.getIndexedDoc(), cxt1.getIndexedDoc());
assertEquals("he^llo<x>t^<a><b><c>TT</c>here^</b>" +
" how^</a> are you</x>y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt1.annotatableContent()).toString());
assertEquals("here^", ((Text)point.getNodeAfter()).getData());
Element c = node.getParentElement();
cxt1.annotatableContent().transparentMove(c.getParentElement(),
c, c.getNextSibling(), null);
point = DocHelper.ensureNodeBoundary(
DocHelper.transparentSlice(Point.<Node>inText(node, 1), cxt1),
cxt1.getIndexedDoc(), cxt1.getIndexedDoc());
assertEquals("he^llo<x>t^<a><b>here^<c>TT</c></b></a>" +
"<a> how^</a> are you</x>y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt1.annotatableContent()).toString());
assertEquals("a", ((Element)point.getNodeAfter()).getTagName());
assertEquals("a", ((Element)point.getNodeAfter().getPreviousSibling()).getTagName());
Point<Node> point2 = DocHelper.transparentSlice(point, cxt1);
assertEquals("a", ((Element)point2.getNodeAfter()).getTagName());
assertEquals("a", ((Element)point2.getNodeAfter().getPreviousSibling()).getTagName());
point2 = DocHelper.transparentSlice(
Point.end(point2.getNodeAfter().getPreviousSibling()), cxt1);
assertEquals("a", ((Element)point2.getNodeAfter()).getTagName());
assertEquals("a", ((Element)point2.getNodeAfter().getPreviousSibling()).getTagName());
Point<Node> point3 = DocHelper.ensureNodeBoundary(
DocHelper.transparentSlice(Point.end(point2.getContainer()), cxt1),
cxt1.getIndexedDoc(), cxt1.getIndexedDoc());
assertEquals("x", ((Element)point3.getContainer()).getTagName());
assertNull(point3.getNodeAfter());
Element a = (Element) point2.getNodeAfter().getPreviousSibling();
Element d = cxt1.annotatableContent().transparentCreate("d", Attributes.EMPTY_MAP,
a, null);
point2 = DocHelper.transparentSlice(Point.<Node>end(a), cxt1);
assertEquals("a", ((Element)point2.getNodeAfter()).getTagName());
assertEquals("a", ((Element)point2.getNodeAfter().getPreviousSibling()).getTagName());
point2 = DocHelper.transparentSlice(Point.<Node>end(d), cxt1);
assertEquals("a", ((Element)point2.getNodeAfter()).getTagName());
assertEquals("a", ((Element)point2.getNodeAfter().getPreviousSibling()).getTagName());
Element x = (Element) point2.getContainer();
Element e = cxt1.annotatableContent().transparentCreate("e", Attributes.EMPTY_MAP,
x, null);
point2 = DocHelper.ensureNodeBoundary(
DocHelper.transparentSlice(Point.<Node>end(x), cxt1),
cxt1.getIndexedDoc(), cxt1.getIndexedDoc());
assertEquals("x", ((Element)point2.getContainer()).getTagName());
assertNull(point2.getNodeAfter());
assertEquals("he^llo<x>t^<a><b>here^<c>TT</c></b><d></d></a>" +
"<a> how^</a> are you<e></e></x>y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt1.annotatableContent()).toString());
}
});
final TestDocumentContext<Node, Element, Text> cxt2 = createSliceTestCxt();
withTextNode(cxt2, " how^", new NodeAction<Text>() {
@Override
public void apply(Text node) {
Point<Node> point = DocHelper.transparentSlice(Point.<Node>inText(node, 0), cxt2);
assertEquals("he^llo<x>t^<a><b><c>TT</c>here^</b></a>" +
"<a> how^</a> are you</x>y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt2.annotatableContent()).toString());
assertEquals("a", ((Element)point.getNodeAfter()).getTagName());
assertEquals("a", ((Element)point.getNodeAfter().getPreviousSibling()).getTagName());
}
});
final TestDocumentContext<Node, Element, Text> cxt3 = createSliceTestCxt();
withTextNode(cxt3, "here^", new NodeAction<Text>() {
@Override
public void apply(Text node) {
Point<Node> point = DocHelper.transparentSlice(Point.<Node>inText(node, 2), cxt3);
assertEquals("he^llo<x>t^<a><b><c>TT</c>he</b></a><a><b>re^</b>" +
" how^</a> are you</x>y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt3.annotatableContent()).toString());
assertEquals("re^", ((Text)point.getNodeAfter().getFirstChild().getFirstChild()).getData());
assertEquals("he", ((Text)point.getNodeAfter().getPreviousSibling()
.getLastChild().getLastChild()).getData());
}
});
final TestDocumentContext<Node, Element, Text> cxt4 = createSliceTestCxt();
withTextNode(cxt4, "llo", new NodeAction<Text>() {
@Override
public void apply(Text node) {
Point<Node> point = DocHelper.transparentSlice(Point.<Node>inText(node, 2), cxt4);
assertEquals("he^llo<x>t^<a><b><c>TT</c>here^</b>" +
" how^</a> are you</x>y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt4.annotatableContent()).toString());
assertEquals("llo", ((Text)point.getContainer()).getData());
assertEquals(2, point.getTextOffset());
}
});
final TestDocumentContext<Node, Element, Text> cxt5 = createSliceTestCxt();
Node last = cxt5.getIndexedDoc().getDocumentElement().getLastChild();
Point<Node> point = DocHelper.transparentSlice(Point.<Node>end(last), cxt5);
assertEquals("he^llo<x>t^<a><b><c>TT</c>here^</b>" +
" how^</a> are you</x>y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt5.annotatableContent()).toString());
assertSame(last, point.getContainer());
assertNull(point.getNodeAfter());
point = DocHelper.transparentSlice(Point.<Node>inElement(
last, last.getLastChild()), cxt5);
assertEquals("he^llo<x>t^<a><b><c>TT</c>here^</b>" +
" how^</a> are you</x>y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt5.annotatableContent()).toString());
assertSame(last, point.getContainer());
assertSame(last.getLastChild(), point.getNodeAfter());
}
public void testCountChildren() {
IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("<a/>asdf<b/><c/>");
assertEquals(4, DocHelper.countChildren(doc, doc.getDocumentElement()));
}
public void testCountChildrenReturnsZeroWhenThereAreNoChildren() {
IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("");
assertEquals(0, DocHelper.countChildren(doc, doc.getDocumentElement()));
}
private void withTextNode(TestDocumentContext<Node, Element, Text> cxt, final String data,
final NodeAction<Text> action) {
traverse(cxt, new NodeAction<Node>() {
@Override
public void apply(Node node) {
if (node instanceof Text) {
Text t = (Text) node;
if (t.getData().equals(data)) {
action.apply(t);
}
}
}
});
}
private void traverse(TestDocumentContext<Node, Element, Text> cxt, NodeAction<Node> action) {
DocHelper.traverse(
cxt.annotatableContent(), cxt.annotatableContent().getDocumentElement(), action);
}
private TestDocumentContext<Node, Element, Text> createSliceTestCxt() {
String initialContent = "he^llo<x>t^here^ how^ are you</x>y^eah" +
"<p><r></r><q></q></p>";
TestDocumentContext<Node, Element, Text> cxt = ContextProviders.createTestPojoContext(
DocProviders.POJO.parse(initialContent).asOperation(), null, null, null,
DocumentSchema.NO_SCHEMA_CONSTRAINTS);
List<Point<Node>> splitPoints = splitTextNodes(cxt.getIndexedDoc());
Point<Node> t_here = splitPoints.get(1);
Point<Node> there_ = splitPoints.get(2);
Point<Node> how_ = splitPoints.get(3);
Element a1 = cxt.annotatableContent().transparentCreate("a", Attributes.EMPTY_MAP,
(Element) t_here.getContainer(), t_here.getNodeAfter());
cxt.annotatableContent().transparentMove(
a1, t_here.getNodeAfter(), how_.getNodeAfter(), null);
Element a2 = cxt.annotatableContent().transparentCreate("b", Attributes.EMPTY_MAP,
a1, t_here.getNodeAfter());
cxt.annotatableContent().transparentMove(
a2, t_here.getNodeAfter(), there_.getNodeAfter(), null);
Element a3 = cxt.annotatableContent().transparentCreate("c", Attributes.EMPTY_MAP,
a2, t_here.getNodeAfter());
cxt.annotatableContent().transparentCreate("TT", a3, null);
assertEquals("he^llo<x>t^<a><b><c>TT</c>here^</b> how^</a> are you</x>" +
"y^eah<p><r></r><q></q></p>",
XmlStringBuilder.innerXml(cxt.annotatableContent()).toString());
return cxt;
}
private List<Point<Node>> splitTextNodes(IndexedDocument<Node, Element, Text> doc) {
List<Point<Node>> splitPoints = new ArrayList<Point<Node>>();
final List<Text> toSplit = new ArrayList<Text>();
DocHelper.traverse(doc, doc.getDocumentElement(), new NodeAction<Node>() {
public void apply(Node node) {
if (node instanceof Text) {
Text t = (Text) node;
if (t.getData().contains("^")) {
toSplit.add(t);
}
}
}
});
for (Text t : toSplit) {
while (t != null && t.getData().contains("^")) {
t = doc.splitText(t, t.getData().indexOf("^") + 1);
splitPoints.add(Point.before(doc, t));
}
}
return splitPoints;
}
public void testGetNextNodeDepthFirst() {
MutableDocument<Node, Element, Text> doc = getDoc(
"<x>hello</x><y><yy>blah</yy>yeah</y><w/><z>final</z>");
Element root = doc.getDocumentElement();
Node x = root.getFirstChild();
Node y = x.getNextSibling();
Node w = y.getNextSibling();
Node yy = y.getFirstChild();
Node z = root.getLastChild();
assertSame(y, DocHelper.getNextNodeDepthFirst(doc, x, null, false));
assertSame(y, DocHelper.getNextNodeDepthFirst(doc, x, root, false));
assertSame(x.getFirstChild(), DocHelper.getNextNodeDepthFirst(doc, x, x, true));
assertSame(x.getFirstChild(), DocHelper.getNextNodeDepthFirst(doc, x, root, true));
assertSame(x.getFirstChild(), DocHelper.getNextNodeDepthFirst(doc, x, null, true));
assertSame(x.getFirstChild(), DocHelper.getPrevNodeDepthFirst(doc, x, x, true));
assertSame(x.getFirstChild(), DocHelper.getPrevNodeDepthFirst(doc, x, root, true));
assertSame(x.getFirstChild(), DocHelper.getPrevNodeDepthFirst(doc, x, null, true));
assertSame(null, DocHelper.getNextNodeDepthFirst(doc, x, x, false));
assertSame(null, DocHelper.getNextNodeDepthFirst(doc, w, w, true));
assertSame(null, DocHelper.getNextNodeDepthFirst(doc, w, w, false));
assertSame(y, DocHelper.getNextNodeDepthFirst(doc, x.getFirstChild(), null, true));
assertSame(y, DocHelper.getNextNodeDepthFirst(doc, x.getFirstChild(), root, true));
assertSame(x, DocHelper.getPrevNodeDepthFirst(doc, yy.getFirstChild(), root, true));
assertSame(null, DocHelper.getNextNodeDepthFirst(doc, yy, y.getLastChild(), false));
}
public void testFindById() {
MutableDocument<Node, Element, Text> doc = getDoc(
"<x id=\"x\">hello</x><y id=\"y\"><yy id=\"y\">blah</yy>yeah</y><z>final</z>");
Element root = doc.getDocumentElement();
Node x = root.getFirstChild();
Node z = root.getLastChild();
Node y = z.getPreviousSibling();
int firstLoc = doc.getLocation(x);
assertSame(firstLoc, DocHelper.findLocationById(doc, "x"));
assertSame(x, DocHelper.findElementById(doc, "x"));
assertSame(firstLoc + 7, DocHelper.findLocationById(doc, "y"));
assertSame(y, DocHelper.findElementById(doc, "y"));
assertSame(-1, DocHelper.findLocationById(doc, "a"));
assertSame(null, DocHelper.findElementById(doc, "a"));
}
public void testFindByIdFromElement() {
MutableDocument<Node, Element, Text> doc = getDoc(
"<x id=\"x\">hello</x>" +
"<aroundy><y id=\"y\"><yy id=\"y\">blah</yy>yeah</y></aroundy>" +
"<z>final</z>");
Element root = doc.getDocumentElement();
Node x = root.getFirstChild();
Node z = root.getLastChild();
Node aroundy = z.getPreviousSibling();
Node y = aroundy.getFirstChild();
Node yy = y.getFirstChild();
assertSame(null, DocHelper.findElementById(doc, x.asElement(), "y"));
assertSame(y, DocHelper.findElementById(doc, aroundy.asElement(), "y"));
assertSame(y, DocHelper.findElementById(doc, y.asElement(), "y"));
assertSame(null, DocHelper.findElementById(doc, z.asElement(), "y"));
assertSame(yy, DocHelper.findElementById(doc, yy.asElement(), "y"));
}
public void testMatchingElement() {
MutableDocument<Node, Element, Text> doc = getDoc("<x/>hello");
Node n = doc.getDocumentElement().getFirstChild();
assertFalse(DocHelper.isMatchingElement(doc, n.getNextSibling(), "x"));
assertFalse(DocHelper.isMatchingElement(doc, n, "y"));
assertTrue(DocHelper.isMatchingElement(doc, n, "x"));
}
final DocPredicate IS_X = new DocPredicate() {
@Override
public <N, E extends N, T extends N> boolean apply(ReadableDocument<N, E, T> doc, N node) {
return DocHelper.isMatchingElement(doc, node, "x");
}
};
public void testJumpOutJumpsReturnsNullWithNoMatch() {
MutableDocument<Node, Element, Text> doc = getDoc("<w><y><z>abc</z>def</y>ghi</w>hello");
Element z = DocHelper.getElementWithTagName(doc, "z");
assertNull(DocHelper.jumpOut(doc, Point.start(doc, z), IS_X));
doc = getDoc("<x><y><z>abc</z>def</y>ghi</x>hello");
Element x = DocHelper.getElementWithTagName(doc, "x");
assertNull(DocHelper.jumpOut(doc, Point.before(doc, x), IS_X));
assertNull(DocHelper.jumpOut(doc, Point.after(doc, x), IS_X));
assertNull(DocHelper.jumpOut(doc, Point.inText(x.getNextSibling(), 2), IS_X));
}
public void testJumpOutJumpsOutRightwards() {
MutableDocument<Node, Element, Text> doc = getDoc("<x><y><z>abc</z>def</y>ghi</x>hello");
Element x = DocHelper.getElementWithTagName(doc, "x");
Element y = DocHelper.getElementWithTagName(doc, "y");
Element z = DocHelper.getElementWithTagName(doc, "z");
Point<Node> afterY = Point.after(doc, y);
assertEquals(afterY, DocHelper.jumpOut(doc, Point.inText(z.getFirstChild(), 1), IS_X));
assertEquals(afterY, DocHelper.jumpOut(doc, Point.start(doc, z), IS_X));
assertEquals(afterY, DocHelper.jumpOut(doc, Point.start(doc, y), IS_X));
assertEquals(afterY, DocHelper.jumpOut(doc, Point.<Node>end(y), IS_X));
assertSame(afterY, DocHelper.jumpOut(doc, afterY, IS_X));
}
public void testJumpOutPreservesIdentityWherePossible() {
MutableDocument<Node, Element, Text> doc = getDoc("<x/>hello");
Node x = doc.getDocumentElement().getFirstChild();
Point<Node> afterX = Point.after(doc, x);
Point<Node> inText = Point.inText(x.getNextSibling(), 2);
Point<Node> inX = Point.end(x);
assertSame(afterX, DocHelper.jumpOut(doc, afterX, DocHelper.ROOT_PREDICATE));
assertSame(inText, DocHelper.jumpOut(doc, inText, DocHelper.ROOT_PREDICATE));
assertSame(inX, DocHelper.jumpOut(doc, inX, IS_X));
}
public void testExplicitCreateFailsOnOldDocument() {
MutableDocument<Node, Element, Text> doc = getDoc("");
DocHelper.createFirstTopLevelElement(doc, "foo");
}
public void testExpectedGetSucceedsOnOldEmptyDocument() {
MutableDocument<Node, Element, Text> doc = getDoc("");
try {
Element top = DocHelper.expectAndGetFirstTopLevelElement(doc, "foo");
fail("this test is not expected to work with new ops.");
} catch (IllegalArgumentException ex) {
// Success
}
}
public void testGetOrCreateSucceedsOnOldEmptyDocument() {
MutableDocument<Node, Element, Text> doc = getDoc("");
Element top = DocHelper.getOrCreateFirstTopLevelElement(doc, "foo");
assertEquals(doc.getFirstChild(doc.getDocumentElement()), top);
}
private MutableDocument<Node, Element, Text> getDoc(String innerXml) {
return ContextProviders.createTestPojoContext(innerXml, null, null, null,
DocumentSchema.NO_SCHEMA_CONSTRAINTS).document();
}
/** Tests for the isAncestor helper method. */
public void testIsAncestor() {
// build a simple tree
// _1
// 0_/ 3
// \_2_/
// \4
ReadableWDocument<Node, Element, Text> doc = DocProviders.POJO.parse(
"<A><B/><C><D/><E/></C></A>");
Node[] nodes = new Node[5];
nodes[0] = doc.getDocumentElement().getFirstChild();
nodes[1] = nodes[0].getFirstChild();
nodes[2] = nodes[1].getNextSibling();
nodes[3] = nodes[2].getFirstChild();
nodes[4] = nodes[3].getNextSibling();
// check each pair:
for (int i = 0; i < nodes.length; i++) {
for (int j = 0; j < nodes.length; j++) {
// find results:
boolean resultExclusive = DocHelper.isAncestor(doc, nodes[i], nodes[j], false);
boolean resultInclusive = DocHelper.isAncestor(doc, nodes[i], nodes[j], true);
// calculate manually:
boolean isParentExclusive = false;
if (i == 0 || i == 2) {
isParentExclusive = (j > i); // 0 and 2 are parents of everything lower.
}
boolean isParentInclusive = isParentExclusive || (i == j);
// verify:
assertEquals(isParentExclusive, resultExclusive);
assertEquals(isParentInclusive, resultInclusive);
}
}
// final check for null:
assertFalse(DocHelper.isAncestor(doc, nodes[0], null, true));
assertFalse(DocHelper.isAncestor(doc, nodes[0], null, false));
}
}