// Copyright (c) 2003-2014, Jodd Team (jodd.org). All Rights Reserved.
package jodd.decora.parser;
import jodd.decora.DecoraException;
import jodd.lagarto.EmptyTagVisitor;
import jodd.lagarto.Tag;
import jodd.lagarto.TagType;
import java.util.ArrayList;
/**
* Visitor that detect Decora tags in the decorator.
* Therefore, it determines Decora tags position inside decorator file.
* <p>
* If decorator content is static, array of Decora tags can be cached
* and {@link jodd.decora.parser.DecoraTag#duplicate() duplicated} to
* skip parsing decorator again.
*/
public class DecoratorTagVisitor extends EmptyTagVisitor {
protected ArrayList<DecoraTag> decoraTags = new ArrayList<DecoraTag>();
/**
* Returns an array of founded Decora tags.
*/
public DecoraTag[] getDecoraTags() {
return decoraTags.toArray(new DecoraTag[decoraTags.size()]);
}
protected String decoraTagName;
protected String decoraIdName;
protected int decoraTagStart;
protected int decoraTagEnd;
protected int decoraTagDefaultValueStart;
protected int decoraTagDefaultValueEnd;
protected String closingTagName;
protected int closingTagDeepLevel;
@Override
public void tag(Tag tag) {
String tagName = tag.getName().toString();
if (tagName.startsWith("decora:") ) {
onDecoraTag(tag);
return;
}
if (tag.getType().isStartingTag()) {
CharSequence id = tag.getId();
if (id != null && id.toString().startsWith("decora-")) {
onIdAttrStart(tag);
}
} else {
// close tag
if (tagName.equals(closingTagName) && closingTagDeepLevel == tag.getDeepLevel()) {
onIdAttrEnd(tag);
}
}
}
// ---------------------------------------------------------------- handlers
/**
* Handle Decora tags.
*/
protected void onDecoraTag(Tag tag) {
String tagName = tag.getName().toString();
if (tag.getType() == TagType.SELF_CLOSING) {
checkNestedDecoraTags();
decoraTagName = tagName.substring(7);
decoraTagStart = tag.getTagPosition();
decoraTagEnd = tag.getTagPosition() + tag.getTagLength();
defineDecoraTag();
return;
}
if (tag.getType() == TagType.START) {
checkNestedDecoraTags();
decoraTagName = tagName.substring(7);
decoraTagStart = tag.getTagPosition();
decoraTagDefaultValueStart = tag.getTagPosition() + tag.getTagLength();
return;
}
// closed tag type
decoraTagEnd = tag.getTagPosition() + tag.getTagLength();
decoraTagDefaultValueEnd = tag.getTagPosition();
defineDecoraTag();
}
/**
* Handle open and empty ID attribute tags.
*/
protected void onIdAttrStart(Tag tag) {
String id = tag.getId().toString().substring(7);
String tagName;
String idName;
int dashIndex = id.indexOf('-');
if (dashIndex == -1) {
tagName = id;
idName = null;
} else {
tagName = id.substring(0, dashIndex);
idName = id.substring(dashIndex + 1);
}
if (tag.getType() == TagType.SELF_CLOSING) {
checkNestedDecoraTags();
decoraTagName = tagName;
decoraIdName = idName;
decoraTagStart = tag.getTagPosition();
decoraTagEnd = tag.getTagPosition() + tag.getTagLength();
defineDecoraTag();
return;
}
if (tag.getType() == TagType.START) {
checkNestedDecoraTags();
decoraTagName = tagName;
decoraIdName = idName;
decoraTagStart = tag.getTagPosition();
decoraTagDefaultValueStart = tag.getTagPosition() + tag.getTagLength();
closingTagName = tag.getName().toString();
closingTagDeepLevel = tag.getDeepLevel();
}
}
protected void onIdAttrEnd(Tag tag) {
decoraTagEnd = tag.getTagPosition() + tag.getTagLength();
decoraTagDefaultValueEnd = tag.getTagPosition();
defineDecoraTag();
}
// ---------------------------------------------------------------- define
/**
* Defines Decora tag position inside decorator content.
* Resets current Decora tag tracking.
*/
protected void defineDecoraTag() {
DecoraTag decoraTag =
decoraTagDefaultValueStart == 0 ?
new DecoraTag(decoraTagName, decoraIdName, decoraTagStart, decoraTagEnd) :
new DecoraTag(
decoraTagName, decoraIdName,
decoraTagStart, decoraTagEnd,
decoraTagDefaultValueStart, decoraTagDefaultValueEnd - decoraTagDefaultValueStart);
decoraTags.add(decoraTag);
decoraTagName = null;
decoraIdName = null;
closingTagName = null;
decoraTagDefaultValueStart = 0;
}
// ---------------------------------------------------------------- tools
/**
* Check if decora tag is currently defined and throws an exception
* on nested tags.
*/
protected void checkNestedDecoraTags() {
if (decoraTagName != null) {
throw new DecoraException("Nested Decora tags not allowed");
}
}
}