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 (</x>) 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' ( <x> <y> <z> </x>) 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  }