11.3. Checking Syntax with TagLibraryValidator

Your tag handlers (i.e., classes that implement the Tag interface, usually by extending TagSupport or BodyTagSupport) can handle some simple usage errors at request time. For instance, if your tag has an attribute that expects an integer, the associated set Xxx method should convert the incoming String to an int by putting the call to Integer.parseInt within a try/catch block to handle situations where a malformed value is supplied. In addition to these request time checks, the system will automatically do simple syntax checking at page translation time based on information supplied in the TLD file. For example, if you misspell an attribute or omit one that is listed as required, the system will flag these errors when the JSP page is translated into a servlet.

Both of these approaches are fine for simple cases; neither is sufficient for complicated situations. For example, suppose the TLD file lists two attributes as optional, but it doesn’t make sense to use one without the other. How do you enforce that both are supplied or both are omitted? In principle, your tag handler could enforce these restrictions, but doing so would make the code significantly more complicated. Besides, errors of this type should be recognized at page translation time; if you can catch them at page translation time, why slow down request time execution by checking for them? Other syntax errors simply cannot be detected by tag handlers. For example, suppose that a certain tag must always contain one of two possible subelements or cannot contain a certain subelement. The nested tags can discover their enclosing tags with findAncestorWithClass (see Section 3.7), but how does the enclosing tag enforce which nested tags it contains?

JSP 1.1 provides a TagExtraInfo class that lets you perform some of these checks. However, TagExtraInfo is difficult to use and limited in capability. So, JSP 1.2 introduced a new class called TagLibraryValidator that lets you perform arbitrary page translation-time syntax checks. This class has a validate method indirectly giving you an InputStream that lets you read the entire JSP page (represented in XML format), so you have full access to all available translation-time information about the page. In most cases you don’t read the input directly from the input stream. Instead, you use an XML-based API like SAX, DOM, or JDOM to look at the various XML elements (start tags, end tags, tag attributes, and tag bodies) of the file. In such a situation, the HTML content is represented as the body of a jsp:text element and is usually ignored by the validator.

Using a validator consists of the following steps.

1.
Create a subclass of TagLibraryValidator. Note that TagLibraryValidator is in the javax.servlet.jsp.tagext package.

2.
Override the validate method. This method takes three arguments: two strings giving the prefix and uri declared in the taglib directive of the JSP page using the tag library, and a PageData object. Call getInputStream on the PageData object to obtain a stream that lets you read the XML representation of the JSP page. This stream is typically passed to an XML parser, and your validate method deals with the XML elements, not the raw characters. If the syntax checks out properly, return null from validate. If there are errors, return an array of ValidationMessage objects, each of which is built by calling the ValidationMessage constructor with the ID of the tag (usually null since servers are not required to give IDs to tags) and a String describing the problem. Here is a simplified example:

public ValidationMessage[] validate(String prefix, 
                                    String uri, 
                                    PageData page) {
  InputStream stream = page.getInputStream(); 
  BookOrder[] orders = findBookOrders(stream); 
  for(int i=0; i<orders.length; i++) {
    String title = orders[i].getTitle(); 
    int numOrdered = orders[i].getNumOrdered(); 
    if (title.equals("More Servlets and JavaServer Pages") && 
        (numOrdered < 100)) {
      String message = "Too few copies of MSAJSP ordered!"; 
      ValidationMessage[] errors = 
        { new ValidationMessage(null, message) }; 
      return(errors); 
    } 
  } 
  return(null); 
} 

3.
Optionally override other methods. You read validator initialization parameters (supplied in the TLD file with the init-param subelement of validator) with getInitParameters. You can set initialization parameters with setInitParameters. If you store persistent values in fields of your validator, use release to reset the fields—validator instances, like tag instances, can be reused by servers.

4.
Declare the validator in the TLD file. Use the validator element with a validator-class subelement and optionally one or more init-param subelements and a description element. The validator element goes after description but before listener and tag in the TLD file. Here is a simplified example:

<?xml version="1.0" encoding="ISO-8859-1" ?> 
<!DOCTYPE ...> 
<taglib> 
  <tlib-version>...</tlib-version> 
  <jsp-version>1.2</jsp-version> 
  <short-name>...</short-name> 
  <description>...</description> 
  <validator>
								<validator-class>
								somePackage.SomeValidatorClass
								</validator-class>
								</validator> 
  <tag>...</tag> 
</taglib> 

5.
Try JSP pages that use the tag library. First, deploy your Web application. Second, see if you get errors when the JSP pages are translated. If you use load-on-startup (see Section 5.5, “ Initializing and Preloading Servlets and JSP Pages ”), the JSP page is translated into a servlet when the server is loaded. Otherwise, it is translated the first time it is accessed. If the validate method returns null, no action is taken. If validate returns a nonempty array, the server makes the error messages available in some server-specific manner.

Remember, however, that each JSP page is only translated into a servlet once. Unless the JSP page is modified, it doesn’t get retranslated even if the server is restarted. This means that, during development, you have to be sure to modify your JSP pages whenever you want to test a change in your validator. It is also possible to delete the servlet that resulted from the JSP page, but that servlet is stored in a server-specific location. I usually just add and then delete a space in the JSP page, then redeploy it.

Core Warning

Tag library validators are only triggered when the associated JSP pages are translated into servlets. So, if you modify a validator, be sure to modify and redeploy the JSP pages that it applies to.


Example: Tracing the Tag Structure

To become acquainted with validators, let’s make a validator that simply discovers all of the start and end tags, tag attributes, and tag bodies. It will print a summary to standard output and always return null to indicate that no syntax errors were found. I’ll show a more interesting validator in the next subsection.

Even this simple task would be a lot of work if we had to parse the JSP page ourselves. Fortunately, since the page is represented in XML, we can use an XML parser and one of the standardized APIs like SAX, DOM, or JDOM. I’ll use the Apache Xerces parser and the SAX API in this example. I’ll also use the Java API for XML Parsing (JAXP) so that I can switch from the Apache parser to another SAX-compliant parser by changing only a single property value. If you aren’t familiar with SAX and JAXP, Section 11.4 summarizes their use.

Accomplishing this task involves the following steps.

1.
Creation of a subclass of TagLibraryValidator. Listing 11.12 shows a class called SAXValidator that extends TagLibraryValidator.

2.
Overriding of the validate method. I take the third argument to validate (the PageData object), call getInputStream, and pass that to the SAX InputSource constructor. I then tell SAX to parse the JSP document using that InputSource and a handler called PrintHandler (Listing 11.13). This handler simply prints to standard output the start tags (with attributes), the end tags, and the first word of each tag body. Again, see Section 11.4 if you are unfamiliar with the SAX API.

3.
Declaration of the validator in the TLD file. Listing 11.14 shows an updated version of the TLD file for the session-counting example, with a validator entry added.

4.
Try JSP pages that use the tag library. Listing 11.15 shows the standard output that results when index.jsp (shown earlier in Listing 11.8 and Figures 11-1 and 11-2) is accessed.

Listing 11.12. SAXValidator.java
import javax.servlet.jsp.tagext.*; 
import javax.xml.parsers.*; 
import org.xml.sax.*; 
import org.xml.sax.helpers.*; 

/** A "validator" that really just prints out an outline 
 *  of the JSP page (in its XML representation). The 
 *  validate method always returns null, so the page is 
 *  always considered valid. 
 */ 

public class SAXValidator extends TagLibraryValidator {
  /** Print an outline of the XML representation of 
   *  the JSP page. 
   */ 
  public ValidationMessage[] validate(String prefix,
							String uri,
							PageData page) {
    String jaxpPropertyName = 
      "javax.xml.parsers.SAXParserFactory"; 
    // Pass the parser factory in on the command line with 
    // -D to override the use of the Apache parser. 
    if (System.getProperty(jaxpPropertyName) == null) {
      String apacheXercesPropertyValue = 
        "org.apache.xerces.jaxp.SAXParserFactoryImpl"; 
      System.setProperty(jaxpPropertyName, 
                         apacheXercesPropertyValue); 
    } 
    DefaultHandler handler = new PrintHandler(); 
    SAXParserFactory factory = SAXParserFactory.newInstance(); 
    try {
      SAXParser parser = factory.newSAXParser(); 
      InputSource source = 
        new InputSource(page.getInputStream()); 
      parser.parse(source, handler); 
    } catch(Exception e) {
      String errorMessage = 
        "SAX parse error: " + e; 
      System.err.println(errorMessage); 
      e.printStackTrace(); 
    } 
    return(null); 
  } 
} 

Listing 11.13. PrintHandler.java
package moreservlets; 

import org.xml.sax.*; 
import org.xml.sax.helpers.*; 
import java.util.StringTokenizer; 

/** A SAX handler that prints out the start tags, end tags, 
 *  and first word of tag body. Indents two spaces 
 *  for each nesting level. 
 */ 

public class PrintHandler extends DefaultHandler {
  private int indentation = 0; 

  /** When you see a start tag, print it out and then increase 
   *  indentation by two spaces. If the element has 
   *  attributes, place them in parens after the element name. 
   */ 

  public void startElement(String namespaceUri, 
                           String localName, 
                           String qualifiedName, 
                           Attributes attributes) 
      throws SAXException {
    indent(indentation); 
    System.out.print("<" + qualifiedName); 
    int numAttributes = attributes.getLength(); 
    // For <someTag> just print out "<someTag>". But for 
    // <someTag att1="Val1" att2="Val2"> (or variations 
    // that have extra white space), print out 
    // <someTag att1="Val1" att2="Val2">. 
    if (numAttributes > 0) {
      for(int i=0; i<numAttributes; i++) {
        System.out.print(" "); 
        System.out.print(attributes.getQName(i) + "="" + 
                         attributes.getValue(i) + """); 
      } 
    } 
    System.out.println(">"); 
    indentation = indentation + 2; 
  } 

  /** When you see the end tag, print it out and decrease 
   *  indentation level by 2. 
   */ 

  public void endElement(String namespaceUri, 
                         String localName, 
                         String qualifiedName) 
      throws SAXException {
    indentation = indentation - 2; 
    indent(indentation); 
    System.out.println("</" + qualifiedName + ">"); 
  } 

  /** Print out the first word of each tag body. */ 

  public void characters(char[] chars, 
                         int startIndex, 
                         int length) {
    String data = new String(chars, startIndex, length); 
    // White space makes up default StringTokenizer delimiters 
    StringTokenizer tok = new StringTokenizer(data); 
    if (tok.hasMoreTokens()) {
      indent(indentation); 
      System.out.print(tok.nextToken()); 
      if (tok.hasMoreTokens()) {
        System.out.println("..."); 
      } else {
        System.out.println(); 
      } 
    } 
  } 

  private void indent(int indentation) {
    for(int i=0; i<indentation; i++) {
      System.out.print(" "); 
    } 
  } 
} 

Listing 11.14. session-count-taglib-0.9-beta.tld (Updated)
<?xml version="1.0" encoding="ISO-8859-1" ?> 
<!DOCTYPE taglib 
 PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" 
 "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> 

<taglib> 
  <tlib-version>0.9</tlib-version> 
  <jsp-version>1.2</jsp-version> 
  <short-name>company-name-tags</short-name> 
  <description> 
    A tag library that lets you print out the number of 
    sessions currently in memory and/or a warning about 
    the session count exceeding the limit. 

    The tlib-version number and the TLD filename are intended 
    to suggest that this tag library is in development. In 
    such a situation, you want to use the web.xml taglib 
    element to define an alias for the TLD filename. You 
    would want to do so even if you weren't trying 
    to accommodate Tomcat 4.0, which only reads listener 
    declarations from TLD files that are declared that way. 
  </description> 

  <!-- Declare a validator to do translation-time checking 
       of custom tag syntax. --> 
  <validator>
							<validator-class>moreservlets.SAXValidator</validator-class>
							</validator> 

  <!-- Register the listener that records the counts. --> 
  <listener> 
    <listener-class> 
      moreservlets.listeners.ActiveSessionCounter 
    </listener-class> 
  </listener> 

  <!-- Define a tag that prints out the session count. --> 
  <tag> 
    <name>sessionCount</name> 
    <tag-class> 
      moreservlets.tags.SessionCountTag 
    </tag-class> 
    <body-content>empty</body-content> 
    <description>The number of active sessions.</description> 
  </tag> 
  <!-- Define a tag that prints out an optional 
       session-count warning. --> 
  <tag> 
    <name>sessionCountWarning</name> 
    <tag-class> 
      moreservlets.tags.SessionCountWarningTag 
    </tag-class> 
    <body-content>empty</body-content> 
    <description> 
      If the number of sessions exceeds the limit, 
      this prints a warning. Otherwise, it does nothing. 
    </description> 
  </tag> 
</taglib> 

Listing 11.15. Validator output (for index.jsp — Listing 11.8)
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" 
          version="1.2" 
          xmlns:counts="/session-count-taglib.tld"> 
  <jsp:text> 
    <!DOCTYPE... 
  </jsp:text> 
  <jsp:text> 
    Number... 
  </jsp:text> 
  <counts:sessionCount>
							</counts:sessionCount> 
  <jsp:text> 
  </jsp:text> 
  <counts:sessionCountWarning>
							</counts:sessionCountWarning> 
  <jsp:text> 
    </BODY>... 
  </jsp:text> 
</jsp:root> 

Example: Enforcing Tag Nesting Order

The previous subsection showed how to make a simple validator that uses SAX to discover the various tags in the page. Now let’s do something with those tags. Listings 11.16 and 11.17 show two simple tags: OuterTag and InnerTag. OuterTag should not contain other OuterTag instances, and InnerTag should only appear within OuterTag. Developing a validator to enforce these restrictions involves the following steps.

1.
Creation of a subclass of TagLibraryValidator. Listing 11.18 shows a class called NestingValidator that extends TagLibraryValidator.

2.
Overriding of the validate method. I take the third argument to validate (the PageData object), call getInputStream, and pass that to the SAX InputSource constructor. I then tell SAX to parse the JSP document using that InputSource and a handler called NestingHandler (Listing 11.19). The NestingHandler class throws an exception in two situations: if it finds the outer tag when an existing outer tag instance is open and if it finds the inner tag when an existing outer tag instance is not open. The main validator returns null if the handler throws no exceptions. If the handler throws an exception, a 1-element ValidationMessage array is returned that contains a ValidationMessage describing the error.

3.
Declaration of the validator in the TLD file. Listing 11.20 shows a TLD file that gives tag names to the two tag handlers and declares the validator.

4.
Try JSP pages that use the tag library. Listings 11.21 and 11.22 show two JSP pages that correctly follow the rules that the outer tag cannot be nested and that the inner tag must appear directly or indirectly within the outer tag. Figures 11-4 and 11-5 show the results— the validator does not affect the output in any way. Listing 11.23 shows a JSP page that incorrectly attempts to use the inner tag when it is not nested within the outer tag. Figures 11-6 and 11-7 show the results in Tomcat 4.0 and ServletExec 4.1, respectively—the normal output is blocked since the JSP page was not successfully translated into a servlet. Listing 11.24 shows a JSP page that incorrectly attempts to nest the outer tag. Figure 11-8 shows the result in Tomcat 4.0.

Figure 11-4. Result of nesting-test1.jsp. Tags are nested properly, so output is normal.


Figure 11-5. Result of nesting-test2.jsp. Tags are nested properly, so output is normal.


Figure 11-6. Result of nesting-test3.jsp. Tags are nested improperly, so normal output is prevented—at page translation time, the normal servlet is replaced by one that gives an error message. This output is from Tomcat 4.0.


Figure 11-7. The result of nesting-test3.jsp when accessed on ServletExec 4.1.


Figure 11-8. Result of nesting-test4.jsp. Tags are nested improperly, so normal output is prevented—at page translation time, the normal servlet is replaced by one that gives an error message. This output is from Tomcat 4.0.


Listing 11.16. OuterTag.java
package moreservlets.tags; 

import javax.servlet.*; 
import javax.servlet.jsp.*; 
import javax.servlet.jsp.tagext.*; 
import java.io.*; 

/** Prints out a simple message. A TagLibraryValidator will 
 *  enforce a nesting order for tags associated with this class. 
 */ 

public class OuterTag extends TagSupport {
  public int doStartTag() {
    try {
      JspWriter out = pageContext.getOut(); 
      out.print("OuterTag"); 
    } catch(IOException ioe) {
      System.out.println("Error printing OuterTag."); 
    } 
    return(EVAL_BODY_INCLUDE); 
  } 
} 

Listing 11.17. InnerTag.java
package moreservlets.tags; 

import javax.servlet.*; 
import javax.servlet.jsp.*; 
import javax.servlet.jsp.tagext.*; 
import java.io.*; 

/** Prints out a simple message. A TagLibraryValidator will 
 *  enforce a nesting order for tags associated with this class. 
 */ 
public class InnerTag extends TagSupport {
  public int doStartTag() {
    try {
      JspWriter out = pageContext.getOut(); 
      out.print("InnerTag"); 
    } catch(IOException ioe) {
      System.out.println("Error printing InnerTag."); 
    } 
    return(EVAL_BODY_INCLUDE); 
  } 
} 

Listing 11.18. NestingValidator.java
package moreservlets; 

import javax.servlet.jsp.tagext.*; 
import javax.xml.parsers.*; 
import org.xml.sax.*; 
import org.xml.sax.helpers.*; 

/** A validator that verifies that tags follow 
 *  proper nesting order. 
 */ 

public class NestingValidator extends TagLibraryValidator {

  public ValidationMessage[] validate(String prefix,
							String uri,
							PageData page) {
    String jaxpPropertyName = 
      "javax.xml.parsers.SAXParserFactory"; 
    // Pass the parser factory in on the command line with 
    // -D to override the use of the Apache parser. 
    if (System.getProperty(jaxpPropertyName) == null) {
      String apacheXercesPropertyValue = 
        "org.apache.xerces.jaxp.SAXParserFactoryImpl"; 
      System.setProperty(jaxpPropertyName, 
                         apacheXercesPropertyValue); 
    } 
    DefaultHandler handler = new NestingHandler(); 
    SAXParserFactory factory = SAXParserFactory.newInstance(); 
    try {
      SAXParser parser = factory.newSAXParser(); 
      InputSource source = 
        new InputSource(page.getInputStream()); 
      parser.parse(source, handler); 
      return(null); 
    } catch(Exception e) {
      String errorMessage = e.getMessage(); 
      // The first argument to the ValidationMessage 
      // constructor can be a tag ID. Since tag IDs 
      // are not universally supported, use null for 
      // portability. The important part is the second 
      // argument: the error message. 
      ValidationMessage[] messages =
							{ new ValidationMessage(null, errorMessage) };
							return(messages); 
    } 
  } 
} 

Listing 11.19. NestingHandler.java
package moreservlets; 

import org.xml.sax.*; 
import org.xml.sax.helpers.*; 
import java.util.StringTokenizer; 

/** A SAX handler that returns an exception if either of 
 *  the following two situations occurs: 
 *  <UL> 
 *    <LI>The designated outer tag is directly or indirectly 
 *        nested within the outer tag (i.e., itself). 
 *    <LI>The designated inner tag is <I>not</I> directly 
 *        or indirectly nested within the outer tag. 
 *  </UL> 
 */ 

public class NestingHandler extends DefaultHandler {
  private String outerTagName = "outerTag"; 
  private String innerTagName = "innerTag"; 
  private boolean inOuterTag = false; 

  public void startElement(String namespaceUri, 
                           String localName, 
                           String qualifiedName, 
                           Attributes attributes) 
      throws SAXException {
    String tagName = mainTagName(qualifiedName); 
    if (tagName.equals(outerTagName)) {
							if (inOuterTag) {
							throw new SAXException("
Cannot nest " + outerTagName);
							}
							inOuterTag = true;
							} else if (tagName.equals(innerTagName) && !inOuterTag) {
							throw new SAXException("
" + innerTagName +
							" can only appear within " +
							outerTagName);
							} 
  } 

  public void endElement(String namespaceUri, 
                         String localName, 
                         String qualifiedName) 
      throws SAXException {
    String tagName = mainTagName(qualifiedName); 
    if (tagName.equals(outerTagName)) {
							inOuterTag = false;
							} 
  } 
  private String mainTagName(String qualifiedName) {
    StringTokenizer tok = 
      new StringTokenizer(qualifiedName, ":"); 
    tok.nextToken(); 
    return(tok.nextToken()); 
  } 
} 

Listing 11.20. nested-tag-taglib.tld
<?xml version="1.0" encoding="ISO-8859-1" ?> 
<!DOCTYPE taglib 
 PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" 
 "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> 

<taglib> 
  <tlib-version>1.0</tlib-version> 
  <jsp-version>1.2</jsp-version> 
  <short-name>nested-tags</short-name> 
  <description> 
    A tag library that has two tags: outerTag and innerTag. 
    A TagLibraryValidator will enforce the following 
    nesting rules: 
     1) innerTag can only appear inside outerTag. It can 
        be nested, however. 
     2) outerTag cannot be nested within other outerTag 
        instances. 
  </description> 

  <!-- Declare a validator to do translation-time checking 
       of custom tag syntax. --> 
  <validator>
							<validator-class>
							moreservlets.NestingValidator
							</validator-class>
							</validator> 

  <!-- Define the outerTag tag. --> 
  <tag> 
    <name>outerTag</name> 
    <tag-class> 
      moreservlets.tags.OuterTag 
    </tag-class> 
    <body-content>JSP</body-content> 
    <description> 
      A simple tag: cannot be nested within other outerTag 
      instances. 
    </description> 
  </tag> 

  <!-- Define the innerTag tag. --> 
  <tag> 
    <name>innerTag</name> 
    <tag-class> 
      moreservlets.tags.InnerTag 
    </tag-class> 
    <body-content>JSP</body-content> 
    <description> 
      A simple tag: can only appear within outerTag. 
    </description> 
  </tag> 
</taglib> 

Listing 11.21. nesting-test1.jsp (Proper tag nesting)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<HTML> 
<HEAD> 
<TITLE>Nested Tags: Test 1</TITLE> 
<LINK REL=STYLESHEET 
      HREF="styles.css" 
      TYPE="text/css"> 
</HEAD> 

<BODY> 
<TABLE BORDER=5 ALIGN="CENTER"> 
  <TR><TH CLASS="TITLE"> 
      Nested Tags: Test 1 
</TABLE> 

<%@ taglib uri="/WEB-INF/nested-tag-taglib.tld" prefix="test" %> 
<PRE> 
<test:outerTag>
							<test:innerTag/>
							<test:innerTag/>
							<test:innerTag/>
							</test:outerTag>
							<test:outerTag/> 
</PRE> 

</BODY> 
</HTML> 

Listing 11.22. nesting-test2.jsp (Proper tag nesting)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<HTML> 
<HEAD> 
<TITLE>Nested Tags: Test 2</TITLE> 
<LINK REL=STYLESHEET 
      HREF="styles.css" 
      TYPE="text/css"> 
</HEAD> 

<BODY> 
<TABLE BORDER=5 ALIGN="CENTER"> 
  <TR><TH CLASS="TITLE"> 
      Nested Tags: Test 2 
</TABLE> 

<%@ taglib uri="/WEB-INF/nested-tag-taglib.tld" prefix="test" %> 

<PRE> 
<test:outerTag>
							<test:innerTag>
							<test:innerTag/>
							</test:innerTag>
							<test:innerTag>
							<test:innerTag>
							<test:innerTag/>
							</test:innerTag>
							</test:innerTag>
							</test:outerTag>
							<test:outerTag/> 
</PRE> 

</BODY> 
</HTML> 

Listing 11.23. nesting-test3.jsp (Improper tag nesting)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<HTML> 
<HEAD> 
<TITLE>Nested Tags: Test 3</TITLE> 
<LINK REL=STYLESHEET 
      HREF="styles.css" 
      TYPE="text/css"> 
</HEAD> 

<BODY> 
<TABLE BORDER=5 ALIGN="CENTER"> 
  <TR><TH CLASS="TITLE"> 
      Nested Tags: Test 3 
</TABLE> 

<%@ taglib uri="/WEB-INF/nested-tag-taglib.tld" prefix="test" %> 

<PRE> 
<test:innerTag/> 
</PRE> 

</BODY> 
</HTML> 

Listing 11.24. nesting-test4.jsp (Improper tag nesting)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<HTML> 
<HEAD> 
<TITLE>Nested Tags: Test 4</TITLE> 
<LINK REL=STYLESHEET 
      HREF="styles.css" 
      TYPE="text/css"> 
</HEAD> 

<BODY> 
<TABLE BORDER=5 ALIGN="CENTER"> 
  <TR><TH CLASS="TITLE"> 
      Nested Tags: Test 4 
</TABLE> 

<%@ taglib uri="/WEB-INF/nested-tag-taglib.tld" prefix="test" %> 
<PRE> 
<test:outerTag>
							<test:outerTag/>
							</test:outerTag> 
</PRE> 

</BODY> 
</HTML> 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.31.163