/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.jsp.ast;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.pmd.util.StringUtil;
/**
* Utility class to keep track of unclosed tags. The mechanism is rather simple.
* If a end tag (</x>) is encountered, it will iterate through the open
* tag list and it will mark the first tag named 'x' as closed. If other tags
* have been opened after 'x' ( <x> <y> <z> </x>) it
* will mark y and z as unclosed.
*
* @author Victor Bucutea
*
*/
public class OpenTagRegister {
private List<ASTElement> tagList = new ArrayList<>();
public void openTag(ASTElement elm) {
if (elm == null || StringUtil.isEmpty(elm.getName())) {
throw new IllegalStateException("Tried to open a tag with empty name");
}
tagList.add(elm);
}
/**
*
* @param closingTagName
* @return true if a matching tag was found. False if no tag with this name
* was ever opened ( or registered )
*/
public boolean closeTag(String closingTagName) {
if (StringUtil.isEmpty(closingTagName)) {
throw new IllegalStateException("Tried to close a tag with empty name");
}
int lastRegisteredTagIdx = tagList.size() - 1;
/*
* iterate from top to bottom and look for the last tag with the same
* name as element
*/
boolean matchingTagFound = false;
List<ASTElement> processedElmnts = new ArrayList<>();
for (int i = lastRegisteredTagIdx; i >= 0; i--) {
ASTElement parent = tagList.get(i);
String parentName = parent.getName();
processedElmnts.add(parent);
if (parentName.equals(closingTagName)) {
// mark this tag as being closed
parent.setUnclosed(false);
// tag has children it cannot be empty
parent.setEmpty(false);
matchingTagFound = true;
break;
} else {
// only mark as unclosed if tag is not
// empty (e.g. <tag/> is empty and properly closed)
if (!parent.isEmpty()) {
parent.setUnclosed(true);
}
parent.setEmpty(true);
}
}
/*
* remove all processed tags. We should look for rogue tags which have
* no start (unopened tags) e.g. " <a> <b> <b> </z> </a>" if "</z>" has
* no open tag in the list (and in the whole document) we will consider
* </a> as the closing tag for <a>.If on the other hand tags are
* interleaved: <x> <a> <b> <b> </x> </a> then we will consider </x> the
* closing tag of <x> and </a> a rogue tag or the closing tag of a
* potentially open <a> parent tag ( but not the one after the <x> )
*/
if (matchingTagFound) {
tagList.removeAll(processedElmnts);
}
return matchingTagFound;
}
public void closeTag(ASTElement z) {
closeTag(z.getName());
}
}