package com.google.dart.tools.ui.web.html;
import com.google.dart.tools.core.html.HtmlParser;
import com.google.dart.tools.core.html.XmlDocument;
import com.google.dart.tools.core.html.XmlNode;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.IPartitionTokenScanner;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class HtmlCustomPartitionScanner implements IPartitionTokenScanner {
private static class HtmlToken {
private IToken token;
private HtmlToken next;
private int offset;
private int length;
public HtmlToken() {
}
public HtmlToken(IToken token, int offset, int length) {
this.token = token;
this.offset = offset;
this.length = length;
}
@Override
public String toString() {
return "[" + token.getData() + "," + offset + "," + length + "]";
}
}
private static IToken DEFAULT_TOKEN = new Token(null);
private static IToken COMMENT_TOKEN = new Token(HtmlEditor.HTML_COMMENT_PARTITION);
private static IToken BRACKET_TOKEN = new Token(HtmlEditor.HTML_BRACKET_PARTITION);
private static IToken STYLE_TOKEN = new Token(HtmlEditor.HTML_STYLE_PARTITION);
private static IToken CODE_TOKEN = new Token(HtmlEditor.HTML_CODE_PARTITION);
private HtmlToken root;
private HtmlToken current;
public HtmlCustomPartitionScanner() {
}
@Override
public int getTokenLength() {
return current.length;
}
@Override
public int getTokenOffset() {
return current.offset;
}
@Override
public IToken nextToken() {
if (current == null) {
current = root;
} else {
current = current.next;
}
return current.token;
}
@Override
public void setPartialRange(IDocument document, int offset, int length, String contentType,
int partitionOffset) {
setRange(document, partitionOffset, length + offset - partitionOffset);
}
@Override
public void setRange(IDocument document, int offset, int length) {
current = null;
if (offset != 0 || length != document.getLength()) {
root = trimTokenData(parse(document.get(), offset), offset, length);
} else {
root = parse(document.get(), 0);
}
//print(root);
}
private HtmlToken convertToLinkedList(List<HtmlToken> tokens, int docLength) {
Collections.sort(tokens, new Comparator<HtmlToken>() {
@Override
public int compare(HtmlToken one, HtmlToken two) {
return one.offset - two.offset;
}
});
// set up the next pointers and add any necessary default tokens
HtmlToken fakeHead = new HtmlToken();
HtmlToken current = fakeHead;
int lastEmitted = 0;
for (HtmlToken token : tokens) {
if (lastEmitted < token.offset) {
if (token.offset - lastEmitted > 0) {
current.next = new HtmlToken(DEFAULT_TOKEN, lastEmitted, token.offset - lastEmitted);
current = current.next;
}
lastEmitted = token.offset;
}
current.next = token;
current = token;
lastEmitted = token.offset + token.length;
}
if (lastEmitted < docLength) {
if (docLength - lastEmitted > 0) {
current.next = new HtmlToken(DEFAULT_TOKEN, lastEmitted, docLength - lastEmitted);
current = current.next;
}
lastEmitted = current.offset;
}
current.next = new HtmlToken(Token.EOF, docLength, 0);
return fakeHead.next;
}
private HtmlToken convertToTokens(XmlDocument document, int length) {
List<HtmlToken> tokens = createTokensFrom(document);
return convertToLinkedList(tokens, length);
}
private List<HtmlToken> createTokensFrom(XmlDocument document) {
List<HtmlToken> tokens = new ArrayList<HtmlToken>();
for (XmlNode node : document.getChildren()) {
createTokensFrom(node, tokens);
}
return tokens;
}
private void createTokensFrom(XmlNode node, List<HtmlToken> tokens) {
boolean recurse = true;
if (node.isComment()) {
tokens.add(new HtmlToken(
COMMENT_TOKEN,
node.getStartToken().getLocation(),
node.getEndOffset() - node.getStartOffset()));
} else {
tokens.add(new HtmlToken(BRACKET_TOKEN, node.getStartOffset(), node.getEndOffset()
- node.getStartOffset()));
if (node.getEndNode() != null) {
XmlNode endNode = node.getEndNode();
tokens.add(new HtmlToken(BRACKET_TOKEN, endNode.getStartOffset(), endNode.getEndOffset()
- endNode.getStartOffset()));
if ("style".equals(node.getLabel())) {
recurse = false;
tokens.add(new HtmlToken(STYLE_TOKEN, node.getEndOffset(), endNode.getStartOffset()
- node.getEndOffset()));
} else if ("script".equals(node.getLabel())) {
recurse = false;
tokens.add(new HtmlToken(CODE_TOKEN, node.getEndOffset(), endNode.getStartOffset()
- node.getEndOffset()));
}
}
}
if (recurse) {
for (XmlNode n : node.getChildren()) {
createTokensFrom(n, tokens);
}
}
}
private HtmlToken parse(String str, int offset) {
HtmlParser parser = new HtmlParser(str);
return convertToTokens(parser.parse(), str.length());
}
@SuppressWarnings("unused")
private void print(HtmlToken token) {
while (token != null) {
System.out.println(token);
token = token.next;
}
}
/**
* Adjust the linked list of tokens so that only those that encompass the given range of
* characters will be returned.
*
* @param offset the offset of the first character to be included in a token
* @param length the number of characters to be included in tokens
*/
private HtmlToken trimTokenData(HtmlToken start, int offset, int length) {
// Skip over any tokens that should not be returned. currentToken is assumed to be the fake
// token created before the first real token.
HtmlToken nextToken = start;
while (nextToken.next != null && nextToken != nextToken.next && nextToken.next.offset <= offset) {
nextToken = nextToken.next;
}
start = nextToken;
// Trim the tail of the list to cover only the requested length.
int totalLength = nextToken.length - (offset - nextToken.offset);
while (nextToken.next != null && totalLength < length) {
nextToken = nextToken.next;
totalLength += nextToken.length;
}
if (totalLength > length) {
HtmlToken lastToken = nextToken.next;
while (lastToken.next != null) {
lastToken = lastToken.next;
}
nextToken.next = lastToken;
}
return start;
}
}