// Copyright 2008 Google Inc. All Rights Reserved.
package org.waveprotocol.wave.model.document.raw;
import org.waveprotocol.wave.model.document.indexed.SimpleXmlParser;
import org.waveprotocol.wave.model.document.indexed.SimpleXmlParser.ItemType;
import org.waveprotocol.wave.model.document.util.Point;
/**
* Parses a string into a RawDocument
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public class RawDocumentParserWithSelection<N, E extends N, T extends N,
D extends RawDocument<N, E, T>> {
private final RawDocument.Factory<D> factory;
/**
*/
public interface SelectionParsingListener<N> {
/**
*/
void onStartSelection(Point<N> start);
/**
*/
void onEndSelection(Point<N> end);
}
/**
* Creates a parser that uses a builder.
*
* @param factory factory to use when parsing
* @return a new parser.
*/
public static <N, E extends N, T extends N, D extends RawDocument<N, E, T>>
RawDocumentParserWithSelection<N, E, T, D> create(RawDocument.Factory<D> factory) {
return new RawDocumentParserWithSelection<N, E, T, D>(factory);
}
private RawDocumentParserWithSelection(RawDocument.Factory<D> factory) {
this.factory = factory;
}
/**
* @param xmlString
* @return parsed string
*/
public RawDocument<N, E, T> parse(String xmlString) {
return parseIntoDocument(xmlString, null);
}
/**
* @param xmlString
* @param selectionListener
* @return new document
*/
public D parseIntoDocument(
String xmlString, SelectionParsingListener<N> selectionListener) {
SimpleXmlParser parser = new SimpleXmlParser(xmlString);
while (parser.getCurrentType() != ItemType.START_ELEMENT) {
parser.next();
}
D doc = factory.create(parser.getTagName(), parser.getAttributes());
parseChildren(parser, selectionListener, doc, doc.getDocumentElement());
return doc;
}
@SuppressWarnings("cast") // TODO(danilatos): Figure out how to avoid cast to Point<N>
private E parseElement(SimpleXmlParser parser, SelectionParsingListener<N> selectionListener,
D doc, E parent, N nodeAfter) {
return parseChildren(parser, selectionListener, doc,
doc.createElement(parser.getTagName(), parser.getAttributes(), parent, nodeAfter));
}
private E parseChildren(SimpleXmlParser parser, SelectionParsingListener<N> selectionListener,
D doc, E element) {
int start = -1;
int end = -1;
boolean startBefore = false;
boolean endBefore = false;
N startNodeAfter = null;
N endNodeAfter = null;
N recentChild = null;
parser.next();
while (true) {
ItemType type = parser.getCurrentType();
if (type == ItemType.END_ELEMENT) {
break;
}
switch (type) {
case START_ELEMENT:
recentChild = parseElement(parser, selectionListener, doc, element, null);
break;
case TEXT:
String text = parser.getText();
if (selectionListener != null) {
start = text.indexOf('|');
end = start;
if (start >= 0) {
text = removeChar(text, start);
} else {
start = text.indexOf('[');
if (start >= 0) {
text = removeChar(text, start);
}
end = text.indexOf(']');
if (end >= 0) {
text = removeChar(text, end);
}
}
assert end == -1 || end >= start;
}
if (text.length() > 0) {
recentChild = doc.createTextNode(text, element, null);
if (start >= 0) {
selectionListener.onStartSelection(Point.inText(recentChild, start));
}
if (end >= 0) {
selectionListener.onEndSelection(Point.inText(recentChild, end));
}
} else {
if (start >= 0) {
startBefore = true;
}
if (end >= 0) {
endBefore = true;
}
}
parser.next();
break;
}
if (startBefore && start == -1) {
startNodeAfter = recentChild;
startBefore = false;
}
if (endBefore && end == -1) {
endNodeAfter = recentChild;
endBefore = false;
}
start = -1;
end = -1;
}
if (startBefore || startNodeAfter != null) {
selectionListener.onStartSelection(Point.<N>inElement(element, startNodeAfter));
}
if (endBefore || endNodeAfter != null) {
selectionListener.onEndSelection(Point.<N>inElement(element, endNodeAfter));
}
parser.next();
return element;
}
private static String removeChar(String str, int pos) {
return str.substring(0, pos) + str.substring(pos + 1);
}
}