View Javadoc

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