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 }