/**
* 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.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);
}
}