KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > compiler > ViewSchema


1 /* ****************************************************************************
2  * ViewSchema.java
3  * ****************************************************************************/

4
5 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
6 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
7 * Use is subject to license terms. *
8 * J_LZ_COPYRIGHT_END *********************************************************/

9
10 package org.openlaszlo.compiler;
11 import java.io.*;
12 import java.util.*;
13 import org.apache.oro.text.regex.*;
14 import org.jdom.Document;
15 import org.jdom.Attribute;
16 import org.jdom.Element;
17 import org.jdom.Namespace;
18 import org.jdom.output.XMLOutputter;
19 import org.jdom.input.SAXBuilder;
20 import org.jdom.JDOMException;
21 import org.openlaszlo.xml.internal.Schema;
22 import org.openlaszlo.xml.internal.XMLUtils;
23 import org.openlaszlo.utils.ChainedException;
24 import org.openlaszlo.server.*;
25
26 /** A schema that describes a Laszlo XML file. */
27 public class ViewSchema extends Schema {
28     private static final Set sInputTextElements = new HashSet();
29     private static final Set sHTMLContentElements = new HashSet();
30
31     /** The location of the base Laszlo RELAXNG schema */
32     private final String JavaDoc SCHEMA_PATH = LPS.HOME() + File.separator +
33         "WEB-INF" + File.separator +
34         "lps" + File.separator +
35         "schema" + File.separator + "lzx.rng";
36
37     private Document schemaDOM = null;
38
39     private static Document sCachedSchemaDOM;
40     private static long sCachedSchemaLastModified;
41     
42     /** Default table of attribute name -> typecode */
43     private static final Map sAttributeTypes = new HashMap();
44
45     /** Mapping of RNG type names -> LPS Types */
46     private static final Map sRNGtoLPSTypeMap = new HashMap();
47
48     /** {String} */
49     private static final Set sMouseEventAttributes;
50     
51     /** Maps a class (name) to its ClassModel. Holds info about
52      * attribute/types for each class, as well as pointer to the
53      * superclass if any.
54      */

55     private final Map mClassMap = new HashMap();
56
57     /** Type of script expressions. */
58     public static final Type EXPRESSION_TYPE = newType("expression");
59     public static final Type REFERENCE_TYPE = newType("reference");
60     /** Type of event bodies. */
61     public static final Type EVENT_TYPE = newType("script");
62
63     /** Type of attribute setter function */
64     public static final Type SETTER_TYPE = newType("setter");
65
66     /** Type of tokens. */
67     public static final Type TOKEN_TYPE = newType("token");
68     public static final Type COLOR_TYPE = newType("color");
69     public static final Type NUMBER_EXPRESSION_TYPE = newType("numberExpression");
70     public static final Type SIZE_EXPRESSION_TYPE = newType("sizeExpression");
71     public static final Type CSS_TYPE = newType("css");
72     public static final Type INHERITABLE_BOOLEAN_TYPE = newType("inheritableBoolean");
73     
74     static {
75         sHTMLContentElements.add("text");
76         sInputTextElements.add("inputtext");
77
78         // Define mapping from RNG Schema types to LPS types
79
sRNGtoLPSTypeMap.put("ID", ID_TYPE);
80         sRNGtoLPSTypeMap.put("anyURI", STRING_TYPE);
81         sRNGtoLPSTypeMap.put("boolean", EXPRESSION_TYPE);
82         sRNGtoLPSTypeMap.put("booleanLiteral", EXPRESSION_TYPE);
83         sRNGtoLPSTypeMap.put("inheritableBooleanLiteral", INHERITABLE_BOOLEAN_TYPE);
84         sRNGtoLPSTypeMap.put("color", COLOR_TYPE);
85         sRNGtoLPSTypeMap.put("colorLiteral", COLOR_TYPE);
86         sRNGtoLPSTypeMap.put("css", CSS_TYPE);
87         sRNGtoLPSTypeMap.put("double", NUMBER_TYPE);
88         sRNGtoLPSTypeMap.put("enumeration", STRING_TYPE);
89         sRNGtoLPSTypeMap.put("expression", EXPRESSION_TYPE);
90         sRNGtoLPSTypeMap.put("float", NUMBER_TYPE);
91         sRNGtoLPSTypeMap.put("integer", NUMBER_TYPE);
92         sRNGtoLPSTypeMap.put("number", NUMBER_TYPE);
93         sRNGtoLPSTypeMap.put("numberLiteral", NUMBER_TYPE);
94         sRNGtoLPSTypeMap.put("numberExpression", NUMBER_EXPRESSION_TYPE);
95         sRNGtoLPSTypeMap.put("propertyPath", STRING_TYPE);
96         sRNGtoLPSTypeMap.put("reference", REFERENCE_TYPE);
97         sRNGtoLPSTypeMap.put("script", EVENT_TYPE);
98         sRNGtoLPSTypeMap.put("size", SIZE_EXPRESSION_TYPE);
99         sRNGtoLPSTypeMap.put("sizeLiteral", SIZE_EXPRESSION_TYPE);
100         sRNGtoLPSTypeMap.put("sizeExpression", SIZE_EXPRESSION_TYPE);
101         sRNGtoLPSTypeMap.put("string", STRING_TYPE);
102         sRNGtoLPSTypeMap.put("token", TOKEN_TYPE);
103         sRNGtoLPSTypeMap.put("opacity", NUMBER_TYPE);
104
105         // from http://www.w3.org/TR/REC-html40/interact/scripts.html
106
String JavaDoc[] mouseEventAttributes = {
107             "onclick", "ondblclick", "onmousedown", "onmouseup", "onmouseover",
108             "onmousemove", "onmouseout"};
109         String JavaDoc[] eventAttributes = {
110             "onkeypress", "onstart" , "onstop",
111             "onfocus", "onblur",
112             "onkeydown", "onkeyup", "onsubmit", "onreset", "onselect",
113             "onchange" , "oninit", "onerror", "ondata", "ontimeout",
114             "oncommand" , "onapply" , "onremove"};
115         setAttributeTypes(mouseEventAttributes, EVENT_TYPE);
116         setAttributeTypes(eventAttributes, EVENT_TYPE);
117         sMouseEventAttributes = new HashSet(Arrays.asList(mouseEventAttributes));
118     }
119
120     private static final String JavaDoc AUTOINCLUDES_PROPERTY_FILE =
121         LPS.getMiscDirectory() + File.separator +
122         "lzx-autoincludes.properties";
123     public static final Properties sAutoincludes = new Properties();
124     
125     static {
126         try {
127             InputStream is = new FileInputStream(AUTOINCLUDES_PROPERTY_FILE);
128             try {
129                 sAutoincludes.load(is);
130             } finally {
131                 is.close();
132             }
133         } catch (java.io.IOException JavaDoc e) {
134             throw new ChainedException(e);
135         }
136     }
137     
138     /** Set the attributes to the type.
139      * @param attributes a list of attributes
140      * @param type a type
141      */

142     private static void setAttributeTypes(String JavaDoc[] attributes, Type type) {
143         for (int i = 0; i < attributes.length; i++) {
144             sAttributeTypes.put(attributes[i].intern(), type);
145         }
146     }
147
148     public AttributeSpec findSimilarAttribute (String JavaDoc className, String JavaDoc attributeName) {
149         ClassModel info = getClassModel(className);
150
151         if (info != null) {
152             return info.findSimilarAttribute(attributeName);
153         } else {
154             // Check other classes....
155
return null;
156         }
157     }
158
159     /** Set the attribute to the given type, for a specific element */
160     public void setAttributeType (Element elt, String JavaDoc classname, String JavaDoc attrName, AttributeSpec attrspec) {
161         ClassModel classModel = getClassModel(classname);
162         if (classModel == null) {
163             throw new RuntimeException JavaDoc("undefined class: " + classname);
164         }
165         if (classModel.attributeSpecs.get(attrName) != null) {
166             throw new CompilationError("duplicate definition of attribute " + classname + "." + attrName, elt);
167         }
168         classModel.attributeSpecs.put(attrName, attrspec);
169
170         if (attrName.equals("text")) {
171             classModel.supportsTextAttribute = true;
172         }
173     }
174
175     public void addMethodDeclaration (Element elt, String JavaDoc classname, String JavaDoc methodName, String JavaDoc arglist) {
176         ClassModel classModel = getClassModel(classname);
177         if (classModel == null) {
178             throw new RuntimeException JavaDoc("undefined class: " + classname);
179         }
180         if (classModel.methods.get(methodName) != null) {
181             throw new CompilationError("duplicate definition of method " + classname + "." + methodName, elt);
182         }
183         classModel.methods.put(methodName, arglist);
184     }
185
186     public String JavaDoc getSuperclassName(String JavaDoc className) {
187         ClassModel model = getClassModel(className);
188         if (model == null)
189             return null;
190         return model.getSuperclassName();
191     }
192     
193     /**
194      * @return the base class of a user defined class
195      */

196     public String JavaDoc getBaseClassname(String JavaDoc className) {
197         String JavaDoc ancestor = getSuperclassName(className);
198         String JavaDoc superclass = ancestor;
199
200         while (ancestor != null) {
201             if (ancestor.equals(className)) {
202                 throw new CompilationError(
203                     "recursive class definition on "+className);
204             }
205             superclass = ancestor;
206             ancestor = getSuperclassName(ancestor);
207         }
208         return superclass;
209     }
210
211     /** Does this class or its ancestors have this attribute declared for it? */
212     AttributeSpec getClassAttribute (String JavaDoc classname, String JavaDoc attrName) {
213         // OK, walk up the superclasses, checking for existence of this attribute
214
ClassModel info = getClassModel(classname);
215         if (info == null) {
216             return null;
217         } else {
218             return info.getAttribute(attrName);
219         }
220     }
221
222     /**
223      * Add in the XML to the Schema class definition MYCLASS,
224      * to add this list of attribute declarations.
225      *
226      * @param sourceElement the user's LZX source file element that holds class LZX definition
227      * @param classname the class we are defining
228      * @param classDef the RNG class declaration
229      * @param attributeDefs list of AttributeSpec attribute info to add to the Schema
230      *
231      * This is confusing because we are modifying the RNG XML tree,
232      * not the actual lzx source code tree. So it is confusing to see
233      * JDOM Elements floating around and not be able to tell whether
234      * they pertain to the LZX source or the RNG tree. Only
235      * <i>sourceElement</i> is from the lzx source, we have it here so
236      * we can identify the source file and line # in case of an error.
237      */

238     void addAttributeDefs (Element sourceElement, String JavaDoc classname,
239                            Element classDef, List attributeDefs)
240     {
241         /*
242           <element>
243             CHILDREN
244           </element>
245
246           ==>
247
248           <element>
249             NEWATTRS
250             CHILDREN
251           </element>
252         */

253         if (!attributeDefs.isEmpty()) {
254             Namespace ns = classDef.getNamespace();
255             List attrList = new ArrayList();
256             for (Iterator iter = attributeDefs.iterator(); iter.hasNext();) {
257                 AttributeSpec attr = (AttributeSpec) iter.next();
258
259                 // If this attribute does not already occur someplace
260
// in an ancestor, then let's add it to the schema, so
261
// that the validation phase will know about it.
262
//
263
// We don't want to splice this attribute into the
264
// schema if it already is present in an ancestor,
265
// because that causes a fatal error ("duplicate
266
// attribute") when parsing the schema.
267

268                 //System.out.println("getClassAttribute( "+ classname+", "+attr.name+ ") = " + classInheritsAttribute(classname, attr.name));
269
if (getClassAttribute(classname, attr.name) == null) {
270                     // Splice some XML into the Schema element
271
Element newAttr = new Element("attribute", ns);
272                     newAttr.setAttribute("name", attr.name);
273                     /* Datatype needs to be a ref to a laszlo type
274                        definition, so we generate this RELAX XML for
275                        the attribute:
276                        
277                        <optional>
278                          <attribute name="rotation" a:defaultValue="0">
279                            <ref name="numberExpression"/>
280                          </attribute>
281                        </optional>
282
283                        except for the base RELAX type 'string', for
284                        which there is no ref, we generate this:
285
286                        <optional>
287                          <attribute name="foo">
288                            <data type="string"/>
289                          </attribute>
290                        </optional>
291                     */

292                     String JavaDoc attrTypeName = attr.type.toString();
293                     Element datatype;
294                     if (attrTypeName.equals("string") || attrTypeName.equals("boolean")) {
295                         datatype = new Element("data", ns);
296                         datatype.setAttribute("type", attrTypeName);
297                     } else {
298                         datatype = new Element("ref", ns);
299                         datatype.setAttribute("name", attrTypeName);
300                     }
301                     // assemble the <attribute> node
302
newAttr.addContent(datatype);
303                     // Wrap it in an <optional>
304
Element optional = new Element("optional", ns);
305                     optional.addContent(newAttr);
306                     // If it's an <attribute name="text" type="text">, add this
307
// instead:
308
// <ref name="textAttributes"/>
309
if (attr.contentType != attr.NO_CONTENT) {
310                         optional = new Element("ref", ns);
311                         if (attr.contentType == attr.TEXT_CONTENT) {
312                             optional.setAttribute("name", "textContent");
313                         } else if (attr.contentType == attr.HTML_CONTENT) {
314                             optional.setAttribute("name", "htmlContent");
315                         } else {
316                             throw new RuntimeException JavaDoc("unknown content type");
317                         }
318                     }
319                     attrList.add(optional);
320                 } else {
321                     // Check that the overriding type is the same as the superclass' type
322
Type parentType = getAttributeType(classname, attr.name);
323
324                     if (parentType != attr.type) {
325                         throw new CompilationError(sourceElement, attr.name, new Throwable JavaDoc("In class '" + classname + "' attribute '"+ attr.name + "' with type '"+attr.type.toString() +
326                                                    "' is overriding superclass attribute with same name but different type: "+
327                                                    parentType.toString()));
328                     }
329                 }
330
331                 // Update the in-memory attribute type table
332
setAttributeType(sourceElement, classname, attr.name, attr);
333             }
334
335             if (!attrList.isEmpty()) {
336                 // Now splice the attribute list to the start of the class declaration node
337
// Remove the original children of the class node
338
List children = new ArrayList();
339                 for (Iterator iter = classDef.getChildren().iterator(); iter.hasNext();) {
340                     Element child = (Element) iter.next();
341                     children.add(child);
342                     // The only way to detach the child from the
343
// parent is to use the iterator.remove() method
344
// otherwise you get a ConcurrentModificationException
345
iter.remove();
346                 }
347                 // Add in attributes
348
classDef.setContent(attrList);
349                 // Then add back the old children
350
for (Iterator iter = children.iterator(); iter.hasNext();) {
351                     Element child = (Element) iter.next();
352                     classDef.addContent(child);
353                 }
354             }
355         }
356     }
357
358     /**
359      * Add a new element to the attribute type map.
360      *
361      * Modifies the in-core schema DOM tree as well, to clone the superclass node.
362      *
363      * @param element the element to add to the map
364      * @param superclass an element to inherit attribute to type info from. May be null.
365      * @param attributeDefs list of attribute name/type defs
366      */

367     public void addElement (Element elt, String JavaDoc className,
368                             String JavaDoc superclassName, List attributeDefs)
369     {
370         ClassModel superclass = getClassModel(superclassName);
371
372         if (superclass == null) {
373             throw new CompilationError("undefined superclass " + superclassName + " for class "+className);
374         }
375
376         if (mClassMap.get(className) != null) {
377             String JavaDoc builtin = "builtin ";
378             String JavaDoc also = "";
379             Element other = getClassModel(className).definition;
380             if (other != null) {
381                 builtin = "";
382                 also = "; also defined at " + Parser.getSourceMessagePathname(other) + ":" + Parser.getSourceLocation(other, Parser.LINENO);
383             }
384             throw new CompilationError("duplicate class definitions for " + builtin + className + also, elt);
385         }
386         ClassModel info = new ClassModel(className, superclass, this, elt);
387         mClassMap.put(className, info);
388
389         if (sInputTextElements.contains(superclassName)) {
390             info.isInputText = true;
391             info.hasInputText = true;
392         } else {
393             info.isInputText = superclass.isInputText;
394             info.hasInputText = superclass.hasInputText;
395         }
396
397         info.supportsTextAttribute = superclass.supportsTextAttribute;
398
399
400         // Modify the RELAX schema DOM to add this element. We find the superclass and
401
// clone it, and modify its tag name.
402

403         /*
404           Say we are defining a new class "myclass extends somesuperclass":
405
406           We look for some XML in the Schema which defines the
407           "somesuperclass" class, which will look like
408             <element name="somesuperclass"> .... </element>
409           or else
410             <element>
411               <choice>
412                 <name>...</name>.
413                 <name>somesuperclass</name>
414               </choice>
415             </element>
416
417           We then need to clone the superclass node, change its name
418           to "myclass", and replace the original superclass node with
419           a CHOICE of
420             <choice>
421               <..orginal superclass node..>
422               <..our new class node..>
423             </choice>
424
425           This involves detach()'ing the superclass node, cloning it,
426           and then making the new choice node and adding it back into
427           the parent.
428         */

429
430         // Find the superclass definition
431
Element superclassDef = findSchemaClassDef(superclassName, schemaDOM.getRootElement());
432         if (superclassDef == null) {
433             throw new CompilationError("No definition for superclass "+superclassName+" found in the schema");
434         }
435
436         Namespace ns = superclassDef.getNamespace();
437         // Clone the superclass's schema definition
438
Element myclassDef = (Element) superclassDef.clone();
439
440         // Update the map of classnames to defining Elements
441
classElementTable.put(className, myclassDef);
442
443         // Add in the attribute declarations
444
addAttributeDefs(elt, className, myclassDef, attributeDefs);
445         // Change "name" attritbute (if there is one) to className.
446
// If there's no "name" attribute, we look for the CHOICE
447
// child node that has NAME children and remove it.
448

449         // Change the name to our new classname
450
Attribute nameAttr = myclassDef.getAttribute("name");
451         if (nameAttr == null) {
452             // OK, it's not of the form <element name="classname">, so
453
// it is of the form <element>
454
// <choice><name>classname</name></choice>...</element>.
455
//
456
// We need to remove the "choice" child and add a "name"
457
// attribute
458

459             // +++ We don't necessarily want to remove the first
460
// 'choice' node, we want to remove the one which has
461
// 'name' children.
462

463             for (Iterator iter = myclassDef.getChildren("choice", ns).iterator(); iter.hasNext();) {
464                 Element choice = (Element) iter.next();
465                 List names = choice.getChildren("name", ns);
466                 if (!names.isEmpty()) {
467                     // it contains one or more <name> elements, so
468
// remove this whole 'choice' node from its parent
469
iter.remove();
470                     break;
471                 }
472             }
473         }
474
475         myclassDef.setAttribute("name", className);
476
477         /* Now detach the superclass node and put in it's place
478              <choice>
479                <SUPERCLASSNODE>
480                <MYNEWCLASSNODE>
481              </choice>
482         */

483
484         Element parent = superclassDef.getParent();
485         if (parent.getName().equals("choice"))
486             parent.addContent(myclassDef);
487         else {
488             superclassDef.detach();
489             Element newChoice = new Element("choice", ns);
490             newChoice.addContent(superclassDef);
491             newChoice.addContent(myclassDef);
492             parent.addContent(newChoice);
493         }
494
495         /*
496           if (definition != null) {
497           // Look through the definition for children who are or
498           // have text elements, and set info.hasInputText
499           // accordingly
500           info.hasInputText = info.hasInputText || (new Object() {
501           boolean isOrHasChildren(Element elt) {
502           if (isInputText(elt.getName()) || hasInputText(elt.getName())) {
503           return true;
504           }
505           for (Iterator iter = elt.getChildren().iterator(); iter.hasNext(); ) {
506           Element child = (Element) iter.getNext();
507           return isOrHasChildren(child);
508           }
509           }
510           }).isOrHasChildren(definition);
511
512           }*/

513         /* <class ...>
514            <view>
515            <text/>
516            </view>
517            </class>
518         */

519     }
520
521     /**
522        Walk the Schema DOM looking for an element which defines CLASSNAME.
523        <p>
524        This
525        can actually occur in the schema as either
526        <pre>
527        &lt;element name="classname"&gt;
528        or
529        &lt;element&gt; &lt;choice&gt; ... ... &lt;name&gt;classname&lt;/name&gt; &lt;/choice&gt;
530        </pre>
531
532        <p>
533        Uses the classElementTable to speed up the lookup.
534     */

535     
536     Element findSchemaClassDef (String JavaDoc classname, Element elt) {
537
538         // Check the index
539
Element classdef = (Element) classElementTable.get(classname);
540         if (classdef != null) {
541             return classdef;
542         }
543
544         if (elt.getName().equals("element")) {
545             Namespace ns = elt.getNamespace();
546             // Is this an element named "classname"?
547
String JavaDoc name = elt.getAttributeValue("name");
548             if (name != null && name.equals(classname)) {
549                 // cache this
550
classElementTable.put(classname, elt);
551                 return elt;
552             } else if (name == null) {
553                 // This is a structure of the form
554
// <element><choice><name>xxxx</name>...? Look for
555
// immediate "choice" child, and look in its "name"
556
// children
557
for (Iterator iter = elt.getChildren("choice", ns).iterator(); iter.hasNext();) {
558                     Element choice = (Element) iter.next();
559                     for (Iterator iter2 = choice.getChildren("name", ns).iterator(); iter2.hasNext();) {
560                         Element child = (Element) iter2.next();
561                         if (child.getTextTrim().equals(classname)) {
562                             classElementTable.put(classname, elt);
563                             return elt;
564                         }
565                     }
566                 }
567             }
568         }
569
570         // class def not found here; descend the tree to search for it
571
for (Iterator iter = elt.getChildren().iterator(); iter.hasNext(); ) {
572             Element child = (Element) iter.next();
573             Element result = findSchemaClassDef(classname, child);
574             if (result != null) {
575                 return result;
576             }
577         }
578
579         return (Element) null;
580     }
581
582     public Type getTypeForName(String JavaDoc name) {
583         if (name.equals("text") ||
584             name.equals("html"))
585             name = "string";
586         if (sRNGtoLPSTypeMap.containsKey(name))
587             return (Type) sRNGtoLPSTypeMap.get(name);
588         return super.getTypeForName(name);
589     }
590
591     /** Maps definition name to list of DOM Elements in the parsed RNG schema DOM */
592     private final Map referenceTable = new HashMap();
593     /** List of all <ELEMENT> tags in the schema DOM. */
594     private final List schemaElementTable = new ArrayList();
595
596     /** Table which maps class names to their definitions in the
597      * schema DOM, used by findSchemaClassDef() */

598     private final Map classElementTable = new HashMap();
599
600
601     /** Construct a table of <definition> elements from the RNG schema, to make
602      * efficient lookup possible when following <ref> tags.
603      */

604     private void buildReferenceTable (Element elt) {
605         // If it's a <define name="xxx"> add an entry in the referenceTable
606
String JavaDoc eltname = elt.getName();
607         if (eltname.equals("define")) {
608             String JavaDoc name = elt.getAttributeValue("name");
609             List elts = (List) referenceTable.get(name);
610             if (elts == null) {
611                 elts = new ArrayList();
612                 referenceTable.put(name, elts);
613             }
614             elts.add(elt);
615             //System.err.println("[adding ref "+name+" " + elt.getAttribute("name")+"]");
616
} else if (eltname.equals("element")) {
617             schemaElementTable.add(elt);
618         }
619
620         for (Iterator iter = elt.getChildren().iterator(); iter.hasNext(); ) {
621             Element child = (Element) iter.next();
622             buildReferenceTable(child);
623         }
624     }
625
626     /** Adds a ClassModel entry into the class table for CLASSNAME. */
627     private void makeNewStaticClass (String JavaDoc classname) {
628         ClassModel info = new ClassModel(classname, this);
629         if (sInputTextElements.contains(classname)) {
630             info.isInputText = true;
631             info.hasInputText = true;
632         }
633         if (mClassMap.get(classname) == null) {
634             mClassMap.put(classname, info);
635         } else {
636             // <font> seems to come out twice when we parse the
637
// schema, so this check doesn't really work.
638

639             // throw new CompilationError("makeNewStaticClass: duplicate definition for " + classname);
640
}
641
642     }
643
644
645     /**
646        Walks the schema DOM searching for all <element> tags. For each named <element>, parse out the
647        attribute declarations for it and add them to the in-core attribute-type table.
648        <p>
649        Definitions of elements can actually occur in the schema as either
650        <pre>
651        &lt;element name="classname"&gt;
652        or
653        &lt;element&gt; &lt;choice&gt; ... ... &lt;name&gt;classname&lt;/name&gt; &lt;/choice&gt;
654        </pre>
655        <p>
656
657        In the second case above, where a set of element names is declared, we
658        set the attribute types once for each element (class) name.
659
660        <p>
661
662        The reason we need this method is that when a user defines a new class
663        which has one or more attributes, we need to learn if each attribute has
664        been declared by a superclass. That info is available for user-defined
665        classes, in the mClassMap table, but it's not present for 'primitive'
666        system types that are implicitly built into the base schema RNG file. So
667        here's where we parse that info from the base schema.
668
669        <p>
670
671        The algorithm is as follows: buildReferenceTable() is called once at
672        schema load time to initialize a table of <definition> elements by name,
673        for efficient following of <ref> tags.
674
675        <p>
676
677        Make a pass descending from root:
678        <ul>
679        <li>When you get to an <element>, set that to the current class name, and keep adding attributes
680        as you find them, following references. When you hit a "element", change class name.
681        <p>
682        <li>Mark elements as having been visited to avoid re-traversing them.
683        </ul>
684     */

685     
686     public void parseSchemaAttributes () {
687         for (Iterator eltIter = schemaElementTable.iterator(); eltIter.hasNext();) {
688             Element elt = (Element) eltIter.next();
689
690             // We are starting on a known element tag; what class names does it define?
691
List classnames = new ArrayList();
692             Namespace ns = elt.getNamespace();
693             String JavaDoc name = elt.getAttributeValue("name");
694             // Is this tag of the form <element name="foo"> or
695
// <element><choice><name>foo</name><name>bar</name>... ?
696
if (name != null) {
697                 classnames.add(name);
698                 //System.err.println("parseSchemaAttributes("+name+")");
699
makeNewStaticClass(name);
700             } else if (name == null) {
701                 // This is a structure of the form
702
// <element><choice><name>xxxx</name>...? Look for
703
// immediate "choice" child, and look in it's "name"
704
// children
705
for (Iterator iter = elt.getChildren("choice", ns).iterator(); iter.hasNext();) {
706                     Element choice = (Element) iter.next();
707                     for (Iterator iter2 = choice.getChildren("name", ns).iterator(); iter2.hasNext();) {
708                         Element child = (Element) iter2.next();
709                         name = child.getTextTrim();
710                         // Add this name to the list of classnames that this element defines
711
classnames.add(name);
712                         //System.err.println("parseSchemaAttributes("+name+")");
713
makeNewStaticClass(name);
714                     }
715                 }
716             }
717             // OK we're defining some named element, lets follow the
718
// tree down to the next element
719
if (!classnames.isEmpty()) {
720                 //System.out.println("calling parseSchemaAttributes:: "+classnames);
721
// Now descend the tree looking for <attribute> tags, and following <refs>
722
for (Iterator iter = elt.getChildren().iterator(); iter.hasNext(); ) {
723                     Element child = (Element) iter.next();
724                     parseSchemaAttributes(child, classnames);
725                 }
726             }
727         }
728     }
729
730     /**
731        This descends the schema DOM collecting <attribute> declaration
732        info, but stops the descent wherever another <element> tag is
733        found.
734
735        <ref> tags are followed via the referenceTable map.
736     */

737
738     public void parseSchemaAttributes (Element elt, List classnames) {
739         // Stop if we get to an <element>
740
if (elt.getName().equals("element")) {
741             return;
742         } else if (elt.getName().equals("attribute")) {
743             String JavaDoc attrName = elt.getAttributeValue("name");
744             if (attrName == null) {
745                 //throw new RuntimeException("parsing choice attribute names in the schema is unimplemented");
746
// There is actually one unnamed attribute, the "anyName" attribute for datacontent.
747
} else {
748                 //System.err.print("parsing attribute "+classnames+"."+attrName);
749

750                 String JavaDoc rngTypeName;
751
752                 /*
753                   We will see one of these three types of attribute elements:
754
755                   <attribute name="width" a:defaultValue="800">
756                     <ref name="size"/>
757                   </attribute>
758
759                   <attribute name="title" a:defaultValue="Laszlo Presentation Server">
760                     <a:documentation>The string that is used in the browser window.</a:documentation>
761                     <data type="string" datatypeLibrary=""/>
762                   </attribute>
763
764                   <attribute name="fontstyle" a:defaultValue="">
765                     <a:documentation>The default font style for views in this application.</a:documentation>
766                     <choice>
767                       <value>bold</value>
768                       <value>italic</value>
769                       <value>bold italic</value>
770                       <value>plain</value>
771                       <value/>
772                     </choice>
773                   </attribute>
774
775                   // or this degenerate case from menuitem
776                   <attribute name="type">
777                      <value>separator</value>
778                   </attribute>
779                 */

780                 Namespace ns = elt.getNamespace();
781
782                 Element ref = elt.getChild("ref", ns);
783                 if (ref != null) {
784                     rngTypeName = ref.getAttributeValue("name");
785                 } else {
786                     Element data = elt.getChild("data", ns);
787                     if (data != null) {
788                         rngTypeName = data.getAttributeValue("type");
789                     } else {
790                         // If it's a <choice> or <value>, then it's a token
791
rngTypeName = "token";
792                     }
793                 }
794
795                 // Set the attribute type
796
Type attrType = (Type) sRNGtoLPSTypeMap.get(rngTypeName);
797
798                 if (attrType == null) {
799                     throw new RuntimeException JavaDoc("error parsing RNG schema: unknown attribute type name "+ rngTypeName +
800                                                " for attribute "+attrName);
801                 }
802
803                 AttributeSpec attrspec = new AttributeSpec(attrName, attrType, null, null);
804                 //System.err.println(" ==> "+attrType);
805

806                 // Define this attribute on all classnames it applies to
807
Iterator citer = classnames.iterator();
808                 while (citer.hasNext()) {
809                     String JavaDoc cname = (String JavaDoc) citer.next();
810                     //System.err.println("adding attribute type from schema: elt(class) "+cname+": "+attrName);
811

812                     // TODO: [2003-02-04 hqm] There is a special case
813
// for <splash> elements; the schema contains a
814
// special <view> declaration which differs from
815
// the normal one, in that "x" and "y" attributes
816
// are numeric constants rather than expressions.
817

818                     // To deal with this, we check if this element has
819
// a parent of <splash> and if so we just discard
820
// the attribute info. SplashCompiler will have a
821
// special case to quote values for x and y
822
// attributes.
823

824                     boolean ignore = false;
825                     // search up the parents for a "splash" element
826
if (cname.equals("view")) {
827                         Element p = elt;
828                         while (p != null) {
829                             p = p.getParent();
830                             if (p == null) {
831                                 break;
832                             }
833                             if ((p.getName().equals("element")) && ("splash".equals(p.getAttributeValue("name")))) {
834                                 ignore = true;
835                                 break;
836                             }
837                         }
838                     }
839
840                     // Look up the ClassModel in the class info table
841
ClassModel classModel = getClassModel(cname);
842                     if (classModel == null) {
843                         throw new RuntimeException JavaDoc("parseSchemaAttributes: undefined class: " + cname);
844                     }
845                     if (ignore) {
846                         //System.err.println("ignoring splash child attribute "+attrName+" "+attrType);
847
} else {
848                         classModel.attributeSpecs.put(attrName, attrspec);
849                     }
850                 }
851             }
852         } else if (elt.getName().equals("ref")) {
853             // follow references
854
String JavaDoc refName = elt.getAttributeValue("name");
855             // The list of all elements that define this reference. A
856
// reference can be split over several different elements
857
// in the schema. See "viewContentElements" for example.
858
List refElements = (List) referenceTable.get(refName);
859             if (refElements == null || refElements.isEmpty()) {
860                 throw new RuntimeException JavaDoc("error parsing schema: could not find definition for reference "+refName+" on elt" + elt);
861             }
862             Iterator riter = refElements.iterator();
863             while (riter.hasNext()) {
864                 Element ref = (Element) riter.next();
865                 if (ref == null) {
866                     throw new RuntimeException JavaDoc("error parsing schema: could not find definition for reference "+refName+" on elt" + elt);
867                 }
868                 parseSchemaAttributes(ref, classnames);
869             }
870         }
871
872         // Descend the tree
873
for (Iterator iter = elt.getChildren().iterator(); iter.hasNext(); ) {
874             Element child = (Element) iter.next();
875             parseSchemaAttributes(child, classnames);
876         }
877     }
878
879     static Type getAttributeType(String JavaDoc attrName) {
880         return (Type) sAttributeTypes.get(attrName);
881     }
882     
883     /**
884      * Returns a value representing the type of an attribute within an
885      * XML element. Unknown attributes have Expression type.
886      *
887      * @param e an Element
888      * @param name an attribute name
889      * @return a value represting the type of the attribute's
890      */

891     public Type getAttributeType(Element e, String JavaDoc attrName) {
892         return getAttributeType(e.getName(), attrName);
893     }
894
895     /**
896      * Returns a value representing the type of an attribute within an
897      * XML element. Unknown attributes have Expression type.
898      *
899      * @param e an Element name
900      * @param name an attribute name
901      * @param throwException if no explicit type is found, throw UnknownAttributeException
902      * @return a value represting the type of the attribute's
903      */

904     public Type getAttributeType(String JavaDoc elt, String JavaDoc attrName)
905         throws UnknownAttributeException
906     {
907         String JavaDoc elementName = elt.intern();
908         // +++ This special-case stuff will go away when the RELAX schema
909
// is automatically translated to ViewSchema initialization
910
// code.
911

912         if (elementName.equals("canvas")) {
913             // override NUMBER_EXPRESSION_TYPE, on view
914
if (attrName.equals("width") || attrName.equals("height")) {
915                 return NUMBER_TYPE;
916             }
917         }
918
919         Type type = null;
920
921         // Look up attribute in type map for this element
922
ClassModel classModel = getClassModel(elementName);
923         
924         if (classModel != null) {
925             try {
926                 type = classModel.getAttributeTypeOrException(attrName);
927             } catch (UnknownAttributeException e) {
928                 e.setName(attrName);
929                 e.setElementName(elt);
930                 throw e;
931             }
932         } else {
933             type = getAttributeType(attrName);
934             if (type == null) {
935                 throw new UnknownAttributeException(elt, attrName);
936                 //type = EXPRESSION_TYPE;
937
}
938         }
939         return type;
940     }
941
942     boolean isMouseEventAttribute(String JavaDoc name) {
943         return sMouseEventAttributes.contains(name);
944     }
945
946     ClassModel getClassModel (String JavaDoc elementName) {
947         return (ClassModel) mClassMap.get(elementName);
948     }
949     
950     public void loadSchema() throws JDOMException {
951         String JavaDoc schemaPath = SCHEMA_PATH;
952         // Load the schema if it hasn't been.
953
// Reload it if it's been touched, to make it easier for developers.
954
while (sCachedSchemaDOM == null ||
955                new File(schemaPath).lastModified() != sCachedSchemaLastModified) {
956             // using 'while' and reading the timestamp *first* avoids a
957
// race condition --- although since this doesn't happen in
958
// production code, this isn't critical
959
sCachedSchemaLastModified = new File(schemaPath).lastModified();
960             // Without this, parsing fails when LPS is installed
961
// in a directory with a space in the name.
962
String JavaDoc schemaURI = "file:///" + schemaPath;
963             sCachedSchemaDOM = new SAXBuilder().build(schemaURI);
964         }
965         schemaDOM = (Document) sCachedSchemaDOM.clone();
966         buildReferenceTable(schemaDOM.getRootElement());
967         // Parse out the attribute types of built-in defs
968
parseSchemaAttributes();
969     }
970     
971     public Document getSchemaDOM() throws JDOMException {
972         if (schemaDOM == null) {
973             loadSchema();
974         }
975         return schemaDOM;
976     }
977
978     /** @return true if this element is an input text field */
979     boolean isInputTextElement(Element e) {
980         String JavaDoc classname = e.getName();
981         ClassModel info = getClassModel(classname);
982         if (info != null) {
983             return info.isInputText;
984         }
985         return sInputTextElements.contains(classname);
986     }
987
988     boolean supportsTextAttribute(Element e) {
989         String JavaDoc classname = e.getName();
990         ClassModel info = getClassModel(classname);
991         if (info != null) {
992             return info.supportsTextAttribute;
993         } else {
994             return false;
995         }
996     }
997
998
999     /** @return true if this element content is interpreted as text */
1000    boolean hasTextContent(Element e) {
1001        // input text elements can have text
1002
return isInputTextElement(e) || supportsTextAttribute(e);
1003    }
1004
1005    /** @return true if this element's content is HTML. */
1006    boolean hasHTMLContent(Element e) {
1007        String JavaDoc name = e.getName().intern();
1008        // TBD: return sHTMLContentElements.contains(name). Currently
1009
// uses a blacklist instead of a whitelist because the parser
1010
// doesn't tell the schema about user-defined classes.
1011
// XXX Since any view can have a text body, this implementation
1012
// is actually correct. That is, blacklist rather than whitelist
1013
// is the way to go here.
1014
return name != "class" && name != "method"
1015            && name != "property" && name != "script"
1016            && name != "attribute"
1017            && !isHTMLElement(e) && !isInputTextElement(e);
1018    }
1019
1020    /** @return true if this element is an HTML element, that should
1021     * be included in the text content of an element that tests true
1022     * with hasHTMLContent. */

1023    static boolean isHTMLElement(Element e) {
1024        String JavaDoc name = e.getName();
1025        return name.equals("a")
1026            || name.equals("b")
1027            || name.equals("img")
1028            || name.equals("br")
1029            || name.equals("font")
1030            || name.equals("i")
1031            || name.equals("p")
1032            || name.equals("pre")
1033            || name.equals("u");
1034    }
1035
1036
1037    /* Constants for parsing CSS colors. */
1038    static final PatternMatcher sMatcher = new Perl5Matcher();
1039    static final Pattern sRGBPattern;
1040    static final Pattern sHex3Pattern;
1041    static final Pattern sHex6Pattern;
1042    static final HashMap sColorValues = new HashMap();
1043    static {
1044        try {
1045            Perl5Compiler compiler = new Perl5Compiler();
1046            String JavaDoc s = "\\s*(-?\\d+(?:(?:.\\d*)%)?)\\s*"; // component
1047
String JavaDoc hexDigit = "[0-9a-fA-F]";
1048            String JavaDoc hexByte = hexDigit + hexDigit;
1049            sRGBPattern = compiler.compile(
1050                "\\s*rgb\\("+s+","+s+","+s+"\\)\\s*");
1051            sHex3Pattern = compiler.compile(
1052                "\\s*#\\s*(" + hexDigit + hexDigit + hexDigit + ")\\s*");
1053            sHex6Pattern = compiler.compile(
1054                "\\s*#\\s*(" + hexByte + hexByte + hexByte + ")\\s*");
1055        } catch (MalformedPatternException e) {
1056            throw new ChainedException(e);
1057        }
1058
1059        String JavaDoc[] colorNameValues = {
1060            "black", "000000", "green", "008000",
1061            "silver", "C0C0C0", "lime", "00FF00",
1062            "gray", "808080", "olive", "808000",
1063            "white", "FFFFFF", "yellow", "FFFF00",
1064            "maroon", "800000", "navy", "000080",
1065            "red", "FF0000", "blue", "0000FF",
1066            "purple", "800080", "teal", "008080",
1067            "fuchsia", "FF00FF", "aqua", "00FFFF"};
1068        for (int i = 0; i < colorNameValues.length; ) {
1069            String JavaDoc name = colorNameValues[i++];
1070            String JavaDoc value = colorNameValues[i++];
1071            sColorValues.put(name, new Integer JavaDoc(Integer.parseInt(value, 16)));
1072        }
1073    }
1074
1075    static class ColorFormatException extends RuntimeException JavaDoc {
1076        ColorFormatException(String JavaDoc message) {
1077            super(message);
1078        }
1079    }
1080     
1081    /** Parse according to http://www.w3.org/TR/2001/WD-css3-color-20010305,
1082     * but also allow 0xXXXXXX */

1083    public static int parseColor(String JavaDoc str) {
1084        {
1085            Object JavaDoc value = sColorValues.get(str);
1086            if (value != null) {
1087                return ((Integer JavaDoc) value).intValue();
1088            }
1089        }
1090        if (str.startsWith("0x")) {
1091            try {
1092                return Integer.parseInt(str.substring(2), 16);
1093            } catch (java.lang.NumberFormatException JavaDoc e) {
1094                // fall through
1095
}
1096        }
1097        if (sMatcher.matches(str, sHex3Pattern)) {
1098            int r1g1b1 = Integer.parseInt(sMatcher.getMatch().group(1), 16);
1099            int r = (r1g1b1 >> 8) * 17;
1100            int g = ((r1g1b1 >> 4) & 0xf) * 17;
1101            int b = (r1g1b1 & 0xf) * 17;
1102            return (r << 16) + (g << 8) + b;
1103        }
1104        if (sMatcher.matches(str, sHex6Pattern)) {
1105            return Integer.parseInt(sMatcher.getMatch().group(1), 16);
1106        }
1107        if (sMatcher.matches(str, sRGBPattern)) {
1108            int v = 0;
1109            for (int i = 0; i < 3; i++) {
1110                String JavaDoc s = sMatcher.getMatch().group(i+1);
1111                int c;
1112                if (s.charAt(s.length()-1) == '%') {
1113                    s = s.substring(0, s.length()-1);
1114                    float f = Float.parseFloat(s);
1115                    c = (int) f * 255 / 100;
1116                } else {
1117                    c = Integer.parseInt(s);
1118                }
1119                if (c < 0) c = 0;
1120                if (c > 255) c = 255;
1121                v = (v << 8) | c;
1122            }
1123            return v;
1124        }
1125        throw new ColorFormatException(str);
1126    }
1127}
1128
Popular Tags