while (firstNonSlash < selectorSpecStrLen && selectorSpecStr.charAt(firstNonSlash) == '/') {
firstNonSlash++;
}
if (firstNonSlash >= selectorSpecStrLen) {
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": '/' should be followed by " +
"further selector specification");
}
final int selEnd = selectorSpecStr.substring(firstNonSlash).indexOf('/');
if (selEnd != -1) {
final String tail = selectorSpecStr.substring(firstNonSlash).substring(selEnd);
selectorSpecStr = selectorSpecStr.substring(0, firstNonSlash + selEnd);
this.next = new DOMSelector(tail, false);
} else {
this.next = null;
}
final Matcher matcher = selectorPattern.matcher(selectorSpecStr);
if (!matcher.matches()) {
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": selector does not match selector syntax: " +
"((/|//)?selector)?([@attrib=\"value\" (and @attrib2=\"value\")?])?([index])?");
}
final String rootGroup = matcher.group(1);
final String selectorNameGroup = matcher.group(2);
final String modifiersGroup = matcher.group(3);
if (rootGroup == null) {
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": selector does not match selector syntax: " +
"((/|//)?selector)?([@attrib=\"value\" (and @attrib2=\"value\")?])?([index])?");
}
if ("//".equals(rootGroup)) {
this.descendMoreThanOneLevel = true;
} else if ("/".equals(rootGroup)) {
this.descendMoreThanOneLevel = false;
} else {
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": selector does not match selector syntax: " +
"((/|//)?selector)?([@attrib=\"value\" (and @attrib2=\"value\")?])?([index])?");
}
if (selectorNameGroup == null) {
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": selector does not match selector syntax: " +
"((/|//)?selector)?([@attrib=\"value\" (and @attrib2=\"value\")?])?([index])?");
}
/*
* Process path: extract id, class, reference modifiers...
*/
String path = selectorNameGroup;
final int idModifierPos = path.indexOf(ID_MODIFIER_SEPARATOR);
final int classModifierPos = path.indexOf(CLASS_MODIFIER_SEPARATOR);
final int referenceModifierPos = path.indexOf(REFERENCE_MODIFIER_SEPARATOR);
if (idModifierPos != -1) {
if (classModifierPos != -1 || referenceModifierPos != -1) {
throw new TemplateProcessingException(
"More than one modifier (id, class, reference) have been specified at " +
"DOM selector expression \"" + this.selectorExpression + "\", which is forbidden.");
}
this.selectorPathIdModifier = path.substring(idModifierPos + ID_MODIFIER_SEPARATOR.length());
path = path.substring(0, idModifierPos);
if (StringUtils.isEmptyOrWhitespace(this.selectorPathIdModifier)) {
throw new TemplateProcessingException(
"Empty id modifier in DOM selector expression " +
"\"" + this.selectorExpression + "\", which is forbidden.");
}
} else {
this.selectorPathIdModifier = null;
}
if (classModifierPos != -1) {
if (idModifierPos != -1 || referenceModifierPos != -1) {
throw new TemplateProcessingException(
"More than one modifier (id, class, reference) have been specified at " +
"DOM selector expression \"" + this.selectorExpression + "\", which is forbidden.");
}
this.selectorPathClassModifier = path.substring(classModifierPos + CLASS_MODIFIER_SEPARATOR.length());
path = path.substring(0, classModifierPos);
if (StringUtils.isEmptyOrWhitespace(this.selectorPathClassModifier)) {
throw new TemplateProcessingException(
"Empty id modifier in DOM selector expression " +
"\"" + this.selectorExpression + "\", which is forbidden.");
}
} else {
this.selectorPathClassModifier = null;
}
if (referenceModifierPos != -1) {
if (idModifierPos != -1 || classModifierPos != -1) {
throw new TemplateProcessingException(
"More than one modifier (id, class, reference) have been specified at " +
"DOM selector expression \"" + this.selectorExpression + "\", which is forbidden.");
}
this.selectorPathReferenceModifier = path.substring(referenceModifierPos + REFERENCE_MODIFIER_SEPARATOR.length());
path = path.substring(0, referenceModifierPos);
if (StringUtils.isEmptyOrWhitespace(this.selectorPathReferenceModifier)) {
throw new TemplateProcessingException(
"Empty id modifier in DOM selector expression " +
"\"" + this.selectorExpression + "\", which is forbidden.");
}
} else {
this.selectorPathReferenceModifier = null;
}
this.selectorPath = path;
// We use element normalization because path is made up of element names
this.normalizedSelectorPath = Element.normalizeElementName(this.selectorPath);
this.text = TEXT_SELECTOR.equals(this.normalizedSelectorPath);
/*
* Process classifiers: attributes and index.
*/
if (modifiersGroup != null) {
/*
* A selector level can include two types of filters between [...], in this order:
* * 1. Attribute based: [@a='X' and @b='Y'], any number of them: [@a='X'][@b='Y']...
* * 2. Index based: [23]
*/
String remainingModifiers = modifiersGroup;
while (remainingModifiers != null) {
// This pattern is made to be recursive, acting group 2 as the recursion tail
final Matcher modifiersMatcher = modifiersPattern.matcher(remainingModifiers);
if (!modifiersMatcher.matches()) {
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": selector does not match selector syntax: " +
"((/|//)?selector)?([@attrib=\"value\" (and @attrib2=\"value\")?])?([index])?");
}
final String currentModifier = modifiersMatcher.group(1);
remainingModifiers = modifiersMatcher.group(2);
final Integer modifierAsIndex = parseIndex(currentModifier);
if (modifierAsIndex != null) {
this.index = modifierAsIndex;
if (remainingModifiers != null) {
// If this is an index, it must be the last modifier!
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": selector does not match selector syntax: " +
"((/|//)?selector)?([@attrib=\"value\" (and @attrib2=\"value\")?])?([index])?");
}
} else {
// Modifier is not an index
final List<AttributeCondition> attribs = parseAttributes(selectorExpression, currentModifier);
if (attribs == null) {
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": selector does not match selector syntax: " +
"(/|//)(selector)([@attrib=\"value\" (and @attrib2=\"value\")?])?([index])?");
}
if (this.attributes == null) {
// This is done to save an object. The method that creates the "attribs" list is completely
// under our control, so there should be no problem.
this.attributes = attribs;
} else {
this.attributes.addAll(attribs);
}
}
}
if (this.descendMoreThanOneLevel && this.index != null) {
throw new TemplateProcessingException(
"Invalid syntax in DOM selector \"" + selectorExpression + "\": index cannot be specified on a \"descend any levels\" selector (//).");
}
}