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
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<ASTElement>();
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 }