/**
* Copyright 2009 Google Inc.
*
* 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.waveprotocol.wave.client.editor.extract;
import org.waveprotocol.wave.model.document.ReadableWDocument;
import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema.PermittedCharacters;
import org.waveprotocol.wave.model.document.util.DocHelper;
import org.waveprotocol.wave.model.document.util.Point;
import org.waveprotocol.wave.model.document.util.Range;
import org.waveprotocol.wave.model.document.util.XmlStringBuilder;
import org.waveprotocol.wave.model.util.CollectionUtils;
/**
* Extracts a subtree from the document that contains all nodes where the
* start/end tags or characters lie within a given range.
*
*
* @param <N>
* @param <E>
* @param <T>
*/
public class SubTreeXmlRenderer<N, E extends N, T extends N> {
private final ReadableWDocument<N, E, T> doc;
public SubTreeXmlRenderer(ReadableWDocument<N, E, T> doc) {
this.doc = doc;
}
/**
* Renders the range between the given points as an xml string.
*
* @param start
* @param end
*/
public XmlStringBuilder renderRange(Point<N> start, Point<N> end) {
N nearestCommonAncestor =
DocHelper.nearestCommonAncestor(doc, start.getCanonicalNode(), end.getCanonicalNode());
Range inclusion = new Range(doc.getLocation(start), doc.getLocation(end));
XmlStringBuilder builder =
XmlStringBuilder.createEmptyWithCharConstraints(PermittedCharacters.BLIP_TEXT);
E asElement = doc.asElement(nearestCommonAncestor);
if (asElement != null) {
for (N child = doc.getFirstChild(asElement);
child != null; child = doc.getNextSibling(child)) {
builder.append(augmentBuilder(child, inclusion));
}
if (asElement != doc.getDocumentElement()
&& shouldInclude(inclusion, getNodeRange(asElement))) {
builder.wrap(doc.getTagName(asElement),
CollectionUtils.adaptStringMap(doc.getAttributes(asElement)));
}
} else {
T asText = doc.asText(nearestCommonAncestor);
int tStart = doc.getLocation(asText);
String substring =
doc.getData(asText).substring(inclusion.getStart() - tStart, inclusion.getEnd() - tStart);
builder.appendText(substring);
}
return builder;
}
private XmlStringBuilder augmentBuilder(N node, Range inclusion) {
Range nodeRange = getNodeRange(node);
XmlStringBuilder builder = XmlStringBuilder.createEmpty();
if (!shouldInclude(inclusion, nodeRange)) {
return builder;
}
E asElement = doc.asElement(node);
if (doc.asElement(node) != null) {
for (N child = doc.getFirstChild(node); child != null; child = doc.getNextSibling(child)) {
builder.append(augmentBuilder(child, inclusion));
}
builder.wrap(doc.getTagName(asElement),
CollectionUtils.adaptStringMap(doc.getAttributes(asElement)));
} else {
T asText = doc.asText(node);
int tStart = doc.getLocation(asText);
String data = doc.getData(asText);
int start = Math.max(0, inclusion.getStart() - tStart);
int end = Math.min(data.length(), inclusion.getEnd() - tStart);
builder.appendText(data.substring(start, end));
}
return builder;
}
private boolean shouldInclude(Range inclusion, Range nodeRange) {
if (nodeRange.getStart() < inclusion.getStart() && nodeRange.getEnd() > inclusion.getEnd()) {
return false;
}
if (nodeRange.getEnd() <= inclusion.getStart() || nodeRange.getStart() >= inclusion.getEnd()) {
return false;
}
return true;
}
private int getNodeEnd(N node) {
int end;
N nextSibling = doc.getNextSibling(node);
if (nextSibling != null) {
return doc.getLocation(nextSibling);
} else {
E parent = doc.getParentElement(node);
return parent == doc.getDocumentElement() ? doc.size() : getNodeEnd(parent) - 1;
}
}
private Range getNodeRange(N node) {
assert node != null && node != doc.getDocumentElement() :
"Node cannot be null or the document element";
Range r = new Range(doc.getLocation(node), getNodeEnd(node));
return r;
}
}