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<>();
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  
31  		tagList.add(elm);
32  	}
33  
34  	/**
35  	 * 
36  	 * @param closingTagName
37  	 * @return true if a matching tag was found. False if no tag with this name
38  	 * was ever opened ( or registered )
39  	 */
40  	public boolean closeTag(String closingTagName) {
41  		if (StringUtil.isEmpty(closingTagName)) {
42  			throw new IllegalStateException(
43  					"Tried to close a tag with empty name");
44  		}
45  
46  		int lastRegisteredTagIdx = tagList.size() - 1;
47  		/*
48  		 * iterate from top to bottom and look for the last tag with the same
49  		 * name as element
50  		 */
51  		boolean matchingTagFound = false;
52  		List<ASTElement> processedElmnts = new ArrayList<>();
53  		for (int i = lastRegisteredTagIdx; i >= 0; i--) {
54  			ASTElement parent = tagList.get(i);
55  			String parentName = parent.getName();
56  
57  			processedElmnts.add(parent);
58  			if (parentName.equals(closingTagName)) {
59  				// mark this tag as being closed
60  				parent.setUnclosed(false);
61  				// tag has children it cannot be empty
62  				parent.setEmpty(false);
63  				matchingTagFound = true;
64  				break;
65  			} else {
66  				// only mark as unclosed if tag is not 
67  				// empty (e.g. <tag/> is empty and properly closed)
68  				if ( !parent.isEmpty()) {
69  					parent.setUnclosed(true);
70  				}
71  				
72  				parent.setEmpty(true);
73  			}
74  		}
75  
76  		/*
77  		 * remove all processed tags. We should look for rogue tags which have
78  		 * no start (unopened tags) e.g. " <a> <b> <b> </z> </a>" if "</z>" has
79  		 * no open tag in the list (and in the whole document) we will consider
80  		 * </a> as the closing tag for <a>.If on the other hand tags are
81  		 * interleaved: <x> <a> <b> <b> </x> </a> then we will consider </x> the
82  		 * closing tag of <x> and </a> a rogue tag or the closing tag of a
83  		 * potentially open <a> parent tag ( but not the one after the <x> )
84  		 */
85  		if (matchingTagFound) {
86  			tagList.removeAll(processedElmnts);
87  		}
88  		
89  		return matchingTagFound;
90  	}
91  
92  	public void closeTag(ASTElement z) {
93  		closeTag(z.getName());
94  	}
95  }