View Javadoc

    1   package net.sourceforge.pmd.lang.jsp.ast;
    2
    3   import java.util.ArrayList;
    4   import java.util.List;
    5
    6   import net.sourceforge.pmd.util.StringUtil;
    7
    8   /**
    9    * Utility class to keep track of unclosed tags. The mechanism is rather simple.
    10   * If a end tag (&lt;/x&gt;) is encountered, it will iterate through the open
    11   * tag list and it will mark the first tag named 'x' as closed. If other tags
    12   * have been opened after 'x' ( &lt;x&gt; &lt;y&gt; &lt;z&gt; &lt;/x&gt;) it
    13   * will mark y and z as unclosed.
    14   *
    15   * @author Victor Bucutea
    16   *
    17   */
    18  public class OpenTagRegister {
    19
    20  	private List<ASTElement> tagList = new ArrayList<ASTElement>();
    21
    22  	public void openTag(ASTElement elm) {
    23  		if (elm == null || StringUtil.isEmpty(elm.getName()))
    24  			throw new IllegalStateException(
    25  					"Tried to open a tag with empty name");
    26
    27  		tagList.add(elm);
    28  	}
    29
    30  	/**
    31  	 *
    32  	 * @param closingTagName
    33  	 * @return true if a matching tag was found. False if no tag with this name
    34  	 * was ever opened ( or registered )
    35  	 */
    36  	public boolean closeTag(String closingTagName) {
    37  		if (StringUtil.isEmpty(closingTagName))
    38  			throw new IllegalStateException(
    39  					"Tried to close a tag with empty name");
    40
    41  		int lastRegisteredTagIdx = tagList.size() - 1;
    42  		/*
    43  		 * iterate from top to bottom and look for the last tag with the same
    44  		 * name as element
    45  		 */
    46  		boolean matchingTagFound = false;
    47  		List<ASTElement> processedElmnts = new ArrayList<ASTElement>();
    48  		for (int i = lastRegisteredTagIdx; i >= 0; i--) {
    49  			ASTElement parent = tagList.get(i);
    50  			String parentName = parent.getName();
    51
    52  			processedElmnts.add(parent);
    53  			if (parentName.equals(closingTagName)) {
    54  				// mark this tag as being closed
    55  				parent.setUnclosed(false);
    56  				// tag has children it cannot be empty
    57  				parent.setEmpty(false);
    58  				matchingTagFound = true;
    59  				break;
    60  			} else {
    61  				// only mark as unclosed if tag is not
    62  				// empty (e.g. <tag/> is empty and properly closed)
    63  				if ( !parent.isEmpty()) {
    64  					parent.setUnclosed(true);
    65  				}
    66
    67  				parent.setEmpty(true);
    68  			}
    69  		}
    70
    71  		/*
    72  		 * remove all processed tags. We should look for rogue tags which have
    73  		 * no start (unopened tags) e.g. " <a> <b> <b> </z> </a>" if "</z>" has
    74  		 * no open tag in the list (and in the whole document) we will consider
    75  		 * </a> as the closing tag for <a>.If on the other hand tags are
    76  		 * interleaved: <x> <a> <b> <b> </x> </a> then we will consider </x> the
    77  		 * closing tag of <x> and </a> a rogue tag or the closing tag of a
    78  		 * potentially open <a> parent tag ( but not the one after the <x> )
    79  		 */
    80  		if (matchingTagFound) {
    81  			tagList.removeAll(processedElmnts);
    82  		}
    83
    84  		return matchingTagFound;
    85  	}
    86
    87  	public void closeTag(ASTElement z) {
    88  		closeTag(z.getName());
    89  	}
    90  }