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 }