extract node iterators; separate merging of nodes with text

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/MergeNodesWithTextIterator.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/MergeNodesWithTextIterator.java
new file mode 100644
index 0000000..a8f6dc9
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/MergeNodesWithTextIterator.java
@@ -0,0 +1,123 @@
+package org.eclipse.vex.core.internal.dom;

+

+import java.util.Iterator;

+import java.util.NoSuchElementException;

+

+import org.eclipse.core.runtime.Assert;

+

+public class MergeNodesWithTextIterator implements Iterator<Node> {

+

+	private final Parent parent;

+	private final Iterator<Node> nodes;

+	private final Content content;

+	private final ContentRange contentRange;

+

+	private int textCursor;

+	private Node currentChild;

+	private ContentRange nextTextGap;

+

+	public MergeNodesWithTextIterator(final Parent parent, final Iterable<Node> nodes, final Content content, final ContentRange contentRange) {

+		this.parent = parent;

+		this.nodes = nodes.iterator();

+		this.content = content;

+		this.contentRange = contentRange.intersection(parent.getRange());

+		initialize();

+	}

+

+	private void initialize() {

+		currentChild = null;

+		nextTextGap = contentRange;

+		textCursor = contentRange.getStartOffset();

+		nextStep();

+	}

+

+	private void nextStep() {

+		while (nodes.hasNext()) {

+			currentChild = nodes.next();

+			if (!currentChild.isAssociated()) {

+				nextTextGap = contentRange;

+				return;

+			} else if (currentChild.isInRange(contentRange)) {

+				nextTextGap = currentChild.getRange();

+				textCursor = findNextTextStart(textCursor, nextTextGap.getStartOffset());

+				return;

+			} else if (contentRange.contains(currentChild.getStartOffset())) {

+				nextTextGap = contentRange.intersection(currentChild.getRange());

+				textCursor = findNextTextStart(textCursor, nextTextGap.getStartOffset());

+				currentChild = null; // we can bail out here because we are behind the trimmed range now

+				return;

+			} else if (contentRange.contains(currentChild.getEndOffset())) {

+				textCursor = currentChild.getEndOffset() + 1;

+			}

+		}

+

+		currentChild = null;

+		nextTextGap = new ContentRange(contentRange.getEndOffset(), contentRange.getEndOffset());

+		textCursor = findNextTextStart(textCursor, contentRange.getEndOffset());

+	}

+

+	private int findNextTextStart(int currentOffset, final int maximumOffset) {

+		while (currentOffset < maximumOffset && content.isTagMarker(currentOffset)) {

+			currentOffset++;

+		}

+		return currentOffset;

+	}

+

+	private int findNextTextEnd(int currentOffset, final int minimumOffset) {

+		while (currentOffset > minimumOffset && content.isTagMarker(currentOffset)) {

+			currentOffset--;

+		}

+		return currentOffset;

+	}

+

+	public boolean hasNext() {

+		return hasMoreChildrenInRange() || hasMoreText();

+	}

+

+	private boolean hasMoreChildrenInRange() {

+		return currentChild != null;

+	}

+

+	private boolean hasMoreText() {

+		return textCursor < nextTextGap.getStartOffset();

+	}

+

+	public Node next() {

+		if (!hasNext()) {

+			throw new NoSuchElementException();

+		}

+

+		if (currentChild != null && !currentChild.isAssociated()) {

+			return nextChild();

+		}

+

+		final int textStart = findNextTextStart(textCursor, nextTextGap.getStartOffset());

+		final int textEnd = findNextTextEnd(nextTextGap.getStartOffset(), textStart);

+		textCursor = nextTextGap.getEndOffset() + 1;

+

+		if (textStart < textEnd) {

+			return nextText(textStart, textEnd);

+		}

+		if (textStart == textEnd && !content.isTagMarker(textStart)) {

+			return nextText(textStart, textEnd);

+		}

+

+		Assert.isNotNull(currentChild, "No text and no node make Vex go crazy!");

+

+		return nextChild();

+	}

+

+	private Node nextChild() {

+		final Node child = currentChild;

+		nextStep();

+		return child;

+	}

+

+	private Node nextText(final int textStart, final int textEnd) {

+		return new Text(parent, content, new ContentRange(textStart, textEnd));

+	}

+

+	public void remove() {

+		throw new UnsupportedOperationException("Cannot remove node.");

+	}

+}
\ No newline at end of file
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/NodesInContentRangeIterator.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/NodesInContentRangeIterator.java
new file mode 100644
index 0000000..ebd6acc
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/NodesInContentRangeIterator.java
@@ -0,0 +1,48 @@
+package org.eclipse.vex.core.internal.dom;

+

+import java.util.Iterator;

+import java.util.NoSuchElementException;

+

+public class NodesInContentRangeIterator implements Iterator<Node> {

+

+	private final Iterator<Node> nodes;

+	private final ContentRange contentRange;

+

+	private Node currentNode;

+

+	public NodesInContentRangeIterator(final Iterable<Node> nodes, final ContentRange contentRange) {

+		this.contentRange = contentRange;

+		this.nodes = nodes.iterator();

+		nextStep();

+	}

+

+	private void nextStep() {

+		while (nodes.hasNext()) {

+			currentNode = nodes.next();

+			if (!currentNode.isAssociated()) {

+				return;

+			}

+			if (contentRange.contains(currentNode.getRange())) {

+				return;

+			}

+		}

+		currentNode = null;

+	}

+

+	public boolean hasNext() {

+		return currentNode != null;

+	}

+

+	public Node next() {

+		if (!hasNext()) {

+			throw new NoSuchElementException();

+		}

+		final Node next = currentNode;

+		nextStep();

+		return next;

+	}

+

+	public void remove() {

+		throw new UnsupportedOperationException("Cannot remove node.");

+	}

+}
\ No newline at end of file
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Parent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Parent.java
index 37aa332..4af572b 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Parent.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/dom/Parent.java
@@ -14,7 +14,6 @@
 import java.util.ArrayList;

 import java.util.Iterator;

 import java.util.List;

-import java.util.NoSuchElementException;

 

 import org.eclipse.core.runtime.Assert;

 

@@ -91,7 +90,10 @@
 		return new Axis(this) {

 			@Override

 			public Iterator<Node> iterator(final Node sourceNode, final Axis axis) {

-				return new ChildrenAndText(getContentRange(), shouldIncludeText());

+				if (shouldIncludeText()) {

+					return new MergeNodesWithTextIterator(Parent.this, children, getContent(), getContentRange());

+				}

+				return new NodesInContentRangeIterator(children, getContentRange());

 			}

 		};

 	}

@@ -126,121 +128,4 @@
 		return this;

 	}

 

-	private class ChildrenAndText implements Iterator<Node> {

-

-		private final ContentRange trimmedRange;

-		private final boolean includeText;

-		private final Iterator<Node> childIterator;

-

-		private int textCursor;

-		private Node currentChild;

-		private ContentRange nextTextGap;

-

-		public ChildrenAndText(final ContentRange range, final boolean includeText) {

-			this.includeText = includeText;

-			trimmedRange = range.intersection(getRange());

-			childIterator = children.iterator();

-			initialize();

-		}

-

-		private void initialize() {

-			currentChild = null;

-			nextTextGap = trimmedRange;

-			textCursor = trimmedRange.getStartOffset();

-			nextStep();

-		}

-

-		private void nextStep() {

-			while (childIterator.hasNext()) {

-				currentChild = childIterator.next();

-				if (!currentChild.isAssociated()) {

-					nextTextGap = trimmedRange;

-					return;

-				} else if (currentChild.isInRange(trimmedRange)) {

-					nextTextGap = currentChild.getRange();

-					textCursor = findNextTextStart(textCursor, nextTextGap.getStartOffset());

-					return;

-				} else if (trimmedRange.contains(currentChild.getStartOffset())) {

-					nextTextGap = trimmedRange.intersection(currentChild.getRange());

-					textCursor = findNextTextStart(textCursor, nextTextGap.getStartOffset());

-					currentChild = null; // we can bail out here because we are behind the trimmed range now

-					return;

-				} else if (trimmedRange.contains(currentChild.getEndOffset())) {

-					textCursor = currentChild.getEndOffset() + 1;

-				}

-			}

-

-			currentChild = null;

-			nextTextGap = new ContentRange(trimmedRange.getEndOffset(), trimmedRange.getEndOffset());

-			textCursor = findNextTextStart(textCursor, trimmedRange.getEndOffset());

-		}

-

-		private int findNextTextStart(int currentOffset, final int maximumOffset) {

-			while (currentOffset < maximumOffset && getContent().isTagMarker(currentOffset)) {

-				currentOffset++;

-			}

-			return currentOffset;

-		}

-

-		private int findNextTextEnd(int currentOffset, final int minimumOffset) {

-			while (currentOffset > minimumOffset && getContent().isTagMarker(currentOffset)) {

-				currentOffset--;

-			}

-			return currentOffset;

-		}

-

-		public boolean hasNext() {

-			return hasMoreChildrenInRange() || hasMoreText();

-		}

-

-		private boolean hasMoreChildrenInRange() {

-			return currentChild != null;

-		}

-

-		private boolean hasMoreText() {

-			return includeText && textCursor < nextTextGap.getStartOffset();

-		}

-

-		public Node next() {

-			if (!hasNext()) {

-				throw new NoSuchElementException();

-			}

-

-			if (currentChild != null && !currentChild.isAssociated()) {

-				return nextChild();

-			}

-

-			if (includeText) {

-				final int textStart = findNextTextStart(textCursor, nextTextGap.getStartOffset());

-				final int textEnd = findNextTextEnd(nextTextGap.getStartOffset(), textStart);

-				textCursor = nextTextGap.getEndOffset() + 1;

-

-				if (textStart < textEnd) {

-					return nextText(textStart, textEnd);

-				}

-				if (textStart == textEnd && !getContent().isTagMarker(textStart)) {

-					return nextText(textStart, textEnd);

-				}

-			}

-

-			Assert.isNotNull(currentChild, "No text and no child makes Homer go crazy!");

-

-			return nextChild();

-		}

-

-		private Node nextChild() {

-			final Node child = currentChild;

-			nextStep();

-			return child;

-		}

-

-		private Node nextText(final int textStart, final int textEnd) {

-			return new Text(Parent.this, getContent(), new ContentRange(textStart, textEnd));

-		}

-

-		public void remove() {

-			throw new UnsupportedOperationException("Cannot remove children.");

-		}

-	}

-

 }