KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > taglibs > standard > tlv > JstlXmlTLV


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.apache.taglibs.standard.tlv;
18
19 import java.util.Set JavaDoc;
20 import java.util.Stack JavaDoc;
21
22 import javax.servlet.jsp.tagext.PageData JavaDoc;
23 import javax.servlet.jsp.tagext.ValidationMessage JavaDoc;
24
25 import org.apache.taglibs.standard.resources.Resources;
26 import org.xml.sax.Attributes JavaDoc;
27 import org.xml.sax.helpers.DefaultHandler JavaDoc;
28
29 /**
30  * <p>A SAX-based TagLibraryValidator for the JSTL XML library.
31  * Currently implements the following checks:</p>
32  *
33  * <ul>
34  * <li>Expression syntax validation.
35  * <li>Choose / when / otherwise constraints</li>
36  * <li>Tag bodies that must either be empty or non-empty given
37  * particular attributes.</li>
38  * <li>Other minor constraints.</li>
39  * </ul>
40  *
41  * @author Shawn Bayern
42  */

43 public class JstlXmlTLV extends JstlBaseTLV {
44
45     //*********************************************************************
46
// Implementation Overview
47

48     /*
49      * We essentially just run the page through a SAX parser, handling
50      * the callbacks that interest us. We collapse <jsp:text> elements
51      * into the text they contain, since this simplifies processing
52      * somewhat. Even a quick glance at the implementation shows its
53      * necessary, tree-oriented nature: multiple Stacks, an understanding
54      * of 'depth', and so on all are important as we recover necessary
55      * state upon each callback. This TLV demonstrates various techniques,
56      * from the general "how do I use a SAX parser for a TLV?" to
57      * "how do I read my init parameters and then validate?" But also,
58      * the specific SAX methodology was kept as general as possible to
59      * allow for experimentation and flexibility.
60      *
61      * Much of the code and structure is duplicated from JstlCoreTLV.
62      * An effort has been made to re-use code where unambiguously useful.
63      * However, splitting logic among parent/child classes isn't
64      * necessarily the cleanest approach when writing a parser like the
65      * one we need. I'd like to reorganize this somewhat, but it's not
66      * a priority.
67      */

68
69
70     //*********************************************************************
71
// Constants
72

73     // tag names
74
private final String JavaDoc CHOOSE = "choose";
75     private final String JavaDoc WHEN = "when";
76     private final String JavaDoc OTHERWISE = "otherwise";
77     private final String JavaDoc PARSE = "parse";
78     private final String JavaDoc PARAM = "param";
79     private final String JavaDoc TRANSFORM = "transform";
80     private final String JavaDoc JSP_TEXT = "jsp:text";
81
82     // attribute names
83
private final String JavaDoc VALUE = "value";
84     private final String JavaDoc SOURCE = "xml";
85
86
87     //*********************************************************************
88
// set its type and delegate validation to super-class
89
public ValidationMessage JavaDoc[] validate(
90         String JavaDoc prefix, String JavaDoc uri, PageData JavaDoc page) {
91     return super.validate( TYPE_XML, prefix, uri, page );
92     }
93
94
95     //*********************************************************************
96
// Contract fulfillment
97

98     protected DefaultHandler JavaDoc getHandler() {
99     return new Handler JavaDoc();
100     }
101
102
103     //*********************************************************************
104
// SAX event handler
105

106     /** The handler that provides the base of our implementation. */
107     private class Handler extends DefaultHandler JavaDoc {
108
109     // parser state
110
private int depth = 0;
111     private Stack JavaDoc chooseDepths = new Stack JavaDoc();
112     private Stack JavaDoc chooseHasOtherwise = new Stack JavaDoc();
113         private Stack JavaDoc chooseHasWhen = new Stack JavaDoc();
114     private String JavaDoc lastElementName = null;
115     private boolean bodyNecessary = false;
116     private boolean bodyIllegal = false;
117     private Stack JavaDoc transformWithSource = new Stack JavaDoc();
118
119     // process under the existing context (state), then modify it
120
public void startElement(
121             String JavaDoc ns, String JavaDoc ln, String JavaDoc qn, Attributes JavaDoc a) {
122
123             // substitute our own parsed 'ln' if it's not provided
124
if (ln == null)
125                 ln = getLocalPart(qn);
126
127         // for simplicity, we can ignore <jsp:text> for our purposes
128
// (don't bother distinguishing between it and its characters)
129
if (qn.equals(JSP_TEXT))
130         return;
131
132         // check body-related constraint
133
if (bodyIllegal)
134         fail(Resources.getMessage("TLV_ILLEGAL_BODY", lastElementName));
135
136             // validate expression syntax if we need to
137
Set JavaDoc expAtts;
138             if (qn.startsWith(prefix + ":")
139                     && (expAtts = (Set JavaDoc) config.get(ln)) != null) {
140                 for (int i = 0; i < a.getLength(); i++) {
141                     String JavaDoc attName = a.getLocalName(i);
142                     if (expAtts.contains(attName)) {
143                         String JavaDoc vMsg =
144                             validateExpression(
145                                 ln,
146                                 attName,
147                                 a.getValue(i));
148                         if (vMsg != null)
149                             fail(vMsg);
150                     }
151                 }
152             }
153
154             // validate attributes
155
if (qn.startsWith(prefix + ":") && !hasNoInvalidScope(a))
156                 fail(Resources.getMessage("TLV_INVALID_ATTRIBUTE",
157                     SCOPE, qn, a.getValue(SCOPE)));
158         if (qn.startsWith(prefix + ":") && hasEmptyVar(a))
159         fail(Resources.getMessage("TLV_EMPTY_VAR", qn));
160             if (qn.startsWith(prefix + ":") && hasDanglingScope(a))
161                 fail(Resources.getMessage("TLV_DANGLING_SCOPE", qn));
162
163         // check invariants for <choose>
164
if (chooseChild()) {
165                 // mark <choose> for the first the first <when>
166
if (isXmlTag(ns, ln, WHEN)) {
167                     chooseHasWhen.pop();
168                     chooseHasWhen.push(Boolean.TRUE);
169                 }
170
171         // ensure <choose> has the right children
172
if(!isXmlTag(ns, ln, WHEN) && !isXmlTag(ns, ln, OTHERWISE)) {
173             fail(Resources.getMessage("TLV_ILLEGAL_CHILD_TAG",
174             prefix, CHOOSE, qn));
175         }
176
177         // make sure <otherwise> is the last tag
178
if (((Boolean JavaDoc) chooseHasOtherwise.peek()).booleanValue()) {
179            fail(Resources.getMessage("TLV_ILLEGAL_ORDER",
180             qn, prefix, OTHERWISE, CHOOSE));
181         }
182         if (isXmlTag(ns, ln, OTHERWISE)) {
183             chooseHasOtherwise.pop();
184             chooseHasOtherwise.push(Boolean.TRUE);
185         }
186
187         }
188
189         // Specific check, directly inside <transform source="...">
190
if (!transformWithSource.empty() &&
191             topDepth(transformWithSource) == (depth - 1)) {
192         // only allow <param>
193
if (!isXmlTag(ns, ln, PARAM))
194             fail(Resources.getMessage("TLV_ILLEGAL_BODY",
195             prefix + ":" + TRANSFORM));
196
197         // thus, if we get the opportunity to hit depth++,
198
// we know we've got a <param> subtag
199
}
200
201         // now, modify state
202

203         // we're a choose, so record new choose-specific state
204
if (isXmlTag(ns, ln, CHOOSE)) {
205         chooseDepths.push(new Integer JavaDoc(depth));
206                 chooseHasWhen.push(Boolean.FALSE);
207         chooseHasOtherwise.push(Boolean.FALSE);
208         }
209
210         // set up a check against illegal attribute/body combinations
211
bodyIllegal = false;
212         bodyNecessary = false;
213         if (isXmlTag(ns, ln, PARSE)) {
214         if (hasAttribute(a, SOURCE))
215             bodyIllegal = true;
216         } else if (isXmlTag(ns, ln, PARAM)) {
217         if (hasAttribute(a, VALUE))
218             bodyIllegal = true;
219         else
220             bodyNecessary = true;
221         } else if (isXmlTag(ns, ln, TRANSFORM)) {
222         if (hasAttribute(a, SOURCE))
223             transformWithSource.push(new Integer JavaDoc(depth));
224         }
225
226         // record the most recent tag (for error reporting)
227
lastElementName = qn;
228             lastElementId = a.getValue("http://java.sun.com/JSP/Page", "id");
229
230         // we're a new element, so increase depth
231
depth++;
232     }
233
234     public void characters(char[] ch, int start, int length) {
235
236         bodyNecessary = false; // body is no longer necessary!
237

238         // ignore strings that are just whitespace
239
String JavaDoc s = new String JavaDoc(ch, start, length).trim();
240         if (s.equals(""))
241         return;
242
243         // check and update body-related constraints
244
if (bodyIllegal)
245         fail(Resources.getMessage("TLV_ILLEGAL_BODY", lastElementName));
246
247         // make sure <choose> has no non-whitespace text
248
if (chooseChild()) {
249         String JavaDoc msg =
250             Resources.getMessage("TLV_ILLEGAL_TEXT_BODY",
251             prefix, CHOOSE,
252             (s.length() < 7 ? s : s.substring(0,7)));
253         fail(msg);
254         }
255
256             // Specific check, directly inside <transform source="...">
257
if (!transformWithSource.empty()
258             && topDepth(transformWithSource) == (depth - 1)) {
259                 fail(Resources.getMessage("TLV_ILLEGAL_BODY",
260                     prefix + ":" + TRANSFORM));
261             }
262     }
263
264     public void endElement(String JavaDoc ns, String JavaDoc ln, String JavaDoc qn) {
265
266         // consistently, we ignore JSP_TEXT
267
if (qn.equals(JSP_TEXT))
268         return;
269
270         // handle body-related invariant
271
if (bodyNecessary)
272         fail(Resources.getMessage("TLV_MISSING_BODY",
273             lastElementName));
274         bodyIllegal = false; // reset: we've left the tag
275

276         // update <choose>-related state
277
if (isXmlTag(ns, ln, CHOOSE)) {
278                 Boolean JavaDoc b = (Boolean JavaDoc) chooseHasWhen.pop();
279                 if (!b.booleanValue())
280                     fail(Resources.getMessage("TLV_PARENT_WITHOUT_SUBTAG",
281                         CHOOSE, WHEN));
282         chooseDepths.pop();
283         chooseHasOtherwise.pop();
284         }
285
286         // update <transform source="...">-related state
287
if (!transformWithSource.empty()
288             && topDepth(transformWithSource) == (depth - 1))
289         transformWithSource.pop();
290
291         // update our depth
292
depth--;
293     }
294
295     // are we directly under a <choose>?
296
private boolean chooseChild() {
297         return (!chooseDepths.empty()
298         && (depth - 1) == ((Integer JavaDoc) chooseDepths.peek()).intValue());
299     }
300
301         // returns the top int depth (peeked at) from a Stack of Integer
302
private int topDepth(Stack JavaDoc s) {
303             return ((Integer JavaDoc) s.peek()).intValue();
304         }
305     }
306 }
307
Popular Tags