/**
* Copyright 2008 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.model.document.raw;
import org.waveprotocol.wave.model.document.parser.ItemType;
import org.waveprotocol.wave.model.document.parser.XmlParseException;
import org.waveprotocol.wave.model.document.parser.XmlParserFactory;
import org.waveprotocol.wave.model.document.parser.SafeXmlPullParser;
import org.waveprotocol.wave.model.document.util.Point;
import org.waveprotocol.wave.model.util.CollectionUtils;
/**
* 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) {
SafeXmlPullParser parser;
try {
parser = XmlParserFactory.buffered(xmlString);
} catch (XmlParseException e) {
throw new RuntimeException("Cannot parse xml: " + xmlString, e);
}
while (parser.getCurrentType() != ItemType.START_ELEMENT) {
parser.next();
}
D doc = factory.create(parser.getTagName(), CollectionUtils.newJavaMap(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(SafeXmlPullParser parser, SelectionParsingListener<N> selectionListener,
D doc, E parent, N nodeAfter) {
return parseChildren(parser, selectionListener, doc,
doc.createElement(parser.getTagName(),
CollectionUtils.newJavaMap(parser.getAttributes()), parent, nodeAfter));
}
private E parseChildren(SafeXmlPullParser 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);
}
}