001: /* *****************************************************************************
002: * Parser.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2006 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.compiler;
011:
012: import java.io.*;
013: import java.lang.reflect.Method;
014: import java.lang.reflect.InvocationTargetException;
015: import java.util.*;
016:
017: import org.xml.sax.*;
018: import org.xml.sax.Locator;
019: import org.xml.sax.ext.DeclHandler;
020: import org.xml.sax.ext.LexicalHandler;
021: import org.xml.sax.helpers.LocatorImpl;
022: import org.xml.sax.helpers.AttributesImpl;
023: import org.xml.sax.helpers.XMLFilterImpl;
024:
025: import org.jdom.*;
026: import org.jdom.Element;
027:
028: /**
029:
030: Generate SAX events from a Laszlo Parser JDOM tree, with filename
031: and linenumber info updated from the ElementWithLocationInfo
032: elements.
033:
034: <p>
035: This does the bare minimum needed to feed the RELAXNG
036: validator. It is not namespace aware.
037:
038: */
039: public class SourceLocatorSAXOutputter extends XMLFilterImpl {
040: public static final String SOURCEINFO_ATTRIBUTE_NAME = "_lzc_meta_sourceLocation";
041:
042: private LocatorImpl locator = new LocatorImpl();
043:
044: /** registered <code>ContentHandler</code> */
045: private ContentHandler contentHandler;
046:
047: /** registered <code>ErrorHandler</code> */
048: private ErrorHandler errorHandler;
049:
050: private boolean writeMetaData = false;
051:
052: /**
053: * <p>
054: * This will create a <code>SAXOutputter</code> without any
055: * registered handler. The application is then responsible for
056: * registering them using the <code>setXxxHandler()</code> methods.
057: * </p>
058: */
059: public SourceLocatorSAXOutputter() {
060: }
061:
062: /**
063: * <p>
064: * This will create a <code>SAXOutputter</code> with the
065: * specified <code>ContentHandler</code>.
066: * </p>
067: *
068: * @param contentHandler contains <code>ContentHandler</code>
069: * callback methods
070: */
071: public SourceLocatorSAXOutputter(ContentHandler contentHandler) {
072: setContentHandler(contentHandler);
073: }
074:
075: /**
076: * <p>
077: * This will set the <code>ContentHandler</code>.
078: * </p>
079: *
080: * @param contentHandler contains <code>ContentHandler</code>
081: * callback methods.
082: */
083: public void setContentHandler(ContentHandler contentHandler) {
084: this .contentHandler = contentHandler;
085: }
086:
087: /**
088: * <p>
089: * Returns the registered <code>ContentHandler</code>.
090: * </p>
091: *
092: * @return the current <code>ContentHandler</code> or
093: * <code>null</code> if none was registered.
094: */
095: public ContentHandler getContentHandler() {
096: return this .contentHandler;
097: }
098:
099: /**
100: * <p>
101: * This will set the <code>ErrorHandler</code>.
102: * </p>
103: *
104: * @param errorHandler contains <code>ErrorHandler</code> callback methods.
105: */
106: public void setErrorHandler(ErrorHandler errorHandler) {
107: this .errorHandler = errorHandler;
108: }
109:
110: /**
111: * <p>
112: * Return the registered <code>ErrorHandler</code>.
113: * </p>
114: *
115: * @return the current <code>ErrorHandler</code> or
116: * <code>null</code> if none was registered.
117: */
118: public ErrorHandler getErrorHandler() {
119: return this .errorHandler;
120: }
121:
122: /**
123: * <p>
124: * This will set the state of a SAX feature. We don't support any options right now.
125: */
126: public void setFeature(String name, boolean value)
127: throws SAXNotRecognizedException, SAXNotSupportedException {
128: // No options supported
129: }
130:
131: /**
132: * This will look up the value of a SAX feature.
133: */
134: public boolean getFeature(String name)
135: throws SAXNotRecognizedException, SAXNotSupportedException {
136: // we don't support any options
137: return false;
138: }
139:
140: /**
141: */
142: public void setProperty(String name, Object value)
143: throws SAXNotRecognizedException, SAXNotSupportedException {
144: // nothing to see here
145: }
146:
147: /**
148: * <p>
149: * This will look up the value of a SAX property.
150: * </p>
151: *
152: */
153: public Object getProperty(String name)
154: throws SAXNotRecognizedException, SAXNotSupportedException {
155: // nothing to see here
156: return null;
157: }
158:
159: public void setWriteMetaData(boolean writeMetaData) {
160: this .writeMetaData = writeMetaData;
161: }
162:
163: /**
164: * <p>
165: * This will output the <code>JDOM Document</code>, firing off the
166: * SAX events that have been registered.
167: * </p>
168: *
169: * @param document <code>JDOM Document</code> to output.
170: */
171: public void output(Document document) throws JDOMException {
172: if (document == null) {
173: return;
174: }
175:
176: // contentHandler.setDocumentLocator()
177: documentLocator(document);
178:
179: // contentHandler.startDocument()
180: _startDocument();
181:
182: // Fire DTD events
183: //dtdEvents(document);
184:
185: // Handle root element, as well as any root level
186: // processing instructions and CDATA sections
187: Iterator i = document.getContent().iterator();
188: while (i.hasNext()) {
189: Object obj = i.next();
190: if (obj instanceof Element) {
191: // process root element and its content
192: element(document.getRootElement());
193: } else if (obj instanceof ProcessingInstruction) {
194: // contentHandler.processingInstruction()
195: processingInstruction((ProcessingInstruction) obj);
196: } else if (obj instanceof CDATA) {
197: // contentHandler.characters()
198: characters(((CDATA) obj).getText());
199: }
200: }
201:
202: // contentHandler.endDocument()
203: _endDocument();
204: }
205:
206: /**
207: * Copy source linenumber information from the Elements to
208: * make them available to the SAX API.
209: *
210: *
211: * @param document JDOM <code>Document</code>. */
212: private void documentLocator(Document document) {
213: String publicID = null;
214: String systemID = null;
215: DocType docType = document.getDocType();
216: if (docType != null) {
217: publicID = docType.getPublicID();
218: systemID = docType.getSystemID();
219: }
220:
221: locator.setPublicId(publicID);
222: locator.setSystemId(systemID);
223: locator.setLineNumber(1);
224: locator.setColumnNumber(1);
225:
226: contentHandler.setDocumentLocator((Locator) locator);
227: }
228:
229: /**
230: * <p>
231: * This method is always the second method of all callbacks in
232: * all handlers to be invoked (setDocumentLocator is always first).
233: * </p>
234: */
235: private void _startDocument() throws JDOMException {
236: try {
237: contentHandler.startDocument();
238: //contentHandler.startPrefixMapping("ps",
239: //"http://www.psol.com/2001/08/dw/tip");
240: //attributes.addAttribute("","ps","xmlns:ps","CDATA",
241: //"http://www.psol.com/2001/08/dw/tip"); }
242: } catch (SAXException se) {
243: throw new JDOMException("Exception in startDocument", se);
244: }
245: }
246:
247: /**
248: * <p>
249: * Always the last method of all callbacks in all handlers
250: * to be invoked.
251: * </p>
252: */
253: private void _endDocument() throws JDOMException {
254: try {
255: contentHandler.endDocument();
256: } catch (SAXException se) {
257: throw new JDOMException("Exception in endDocument", se);
258: }
259: }
260:
261: /**
262: * <p>
263: * This will invoke the <code>ContentHandler.processingInstruction</code>
264: * callback when a processing instruction is encountered.
265: * </p>
266: *
267: * @param pi <code>ProcessingInstruction</code> containing target and data.
268: */
269: private void processingInstruction(ProcessingInstruction pi)
270: throws JDOMException {
271: if (pi != null) {
272: String target = pi.getTarget();
273: String data = pi.getData();
274: try {
275: contentHandler.processingInstruction(target, data);
276: } catch (SAXException se) {
277: throw new JDOMException(
278: "Exception in processingInstruction", se);
279: }
280: }
281: }
282:
283: /**
284: * <p>
285: * This will recursively invoke all of the callbacks for a particular
286: * element.
287: * </p>
288: *
289: * @param element <code>Element</code> used in callbacks.
290: */
291: private void element(Element element) throws JDOMException {
292:
293: // Update the document locator
294: Integer lineNumber = Parser.getSourceLocation(
295: (ElementWithLocationInfo) element, Parser.LINENO);
296: Integer colNumber = Parser.getSourceLocation(
297: (ElementWithLocationInfo) element, Parser.COLNO);
298:
299: locator.setSystemId(Parser.getSourcePathname(element));
300: locator.setPublicId(Parser.getSourceMessagePathname(element));
301: locator.setLineNumber(lineNumber.intValue());
302: locator.setColumnNumber(colNumber.intValue());
303: contentHandler.setDocumentLocator((Locator) locator);
304:
305: // +++ Check if we need to send a new documentLocator event if
306: // we changed these values?
307:
308: // contentHandler.startElement()
309: startElement(element);
310:
311: // handle content in the element
312: elementContent(element);
313:
314: // contentHandler.endElement()
315: endElement(element);
316:
317: }
318:
319: /**
320: * <p>
321: * This will invoke the <code>startElement</code> callback
322: * in the <code>ContentHandler</code>.
323: * </p>
324: *
325: * @param element <code>Element</code> used in callbacks.
326: */
327: private void startElement(Element element) throws JDOMException {
328: String namespaceURI = element.getNamespaceURI();
329: String localName = element.getName();
330: String rawName = element.getQualifiedName();
331:
332: AttributesImpl atts = new AttributesImpl();
333: List attributes = element.getAttributes();
334: Iterator i = attributes.iterator();
335: while (i.hasNext()) {
336: Attribute a = (Attribute) i.next();
337: atts.addAttribute(a.getNamespaceURI(), a.getName(), a
338: .getQualifiedName(), getAttributeTypeName(a
339: .getAttributeType()), a.getValue());
340: }
341: if (this .writeMetaData) {
342: // TODO [2003-4-30 ows]: Use a namespace for LZX
343: // metasource information, instead of an obfuscated name.
344: atts.addAttribute("", SOURCEINFO_ATTRIBUTE_NAME,
345: SOURCEINFO_ATTRIBUTE_NAME, "CDATA",
346: ((ElementWithLocationInfo) element)
347: .getSourceLocator().toString());
348: }
349:
350: try {
351: contentHandler.startElement(namespaceURI, localName,
352: rawName, atts);
353: } catch (SAXException se) {
354: throw new JDOMException("Exception in startElement", se);
355: }
356: }
357:
358: /**
359: * <p>
360: * This will invoke the <code>endElement</code> callback
361: * in the <code>ContentHandler</code>.
362: * </p>
363: *
364: * @param element <code>Element</code> used in callbacks.
365: */
366: private void endElement(Element element) throws JDOMException {
367: String namespaceURI = element.getNamespaceURI();
368: String localName = element.getName();
369: String rawName = element.getQualifiedName();
370:
371: try {
372: contentHandler.endElement(namespaceURI, localName, rawName);
373: } catch (SAXException se) {
374: throw new JDOMException(
375: /* (non-Javadoc)
376: * @i18n.test
377: * @org-mes="Exception in endElement"
378: */
379: org.openlaszlo.i18n.LaszloMessages.getMessage(
380: SourceLocatorSAXOutputter.class.getName(),
381: "051018-392"), se);
382: }
383: }
384:
385: /**
386: * <p>
387: * This will invoke the callbacks for the content of an element.
388: * </p>
389: *
390: * @param element <code>Element</code> used in callbacks.
391: */
392: private void elementContent(Element element) throws JDOMException {
393: List eltContent = element.getContent();
394:
395: boolean empty = eltContent.size() == 0;
396: boolean stringOnly = !empty && eltContent.size() == 1
397: && eltContent.get(0) instanceof Text;
398:
399: if (stringOnly) {
400: // contentHandler.characters()
401: characters(element.getText());
402: } else {
403: Object content = null;
404: for (int i = 0, size = eltContent.size(); i < size; i++) {
405: content = eltContent.get(i);
406: if (content instanceof Element) {
407: element((Element) content);
408: } else if (content instanceof Text) {
409: // contentHandler.characters()
410: characters(((Text) content).getText());
411: } else if (content instanceof CDATA) {
412: // contentHandler.characters()
413: characters(((CDATA) content).getText());
414: } else if (content instanceof ProcessingInstruction) {
415: // contentHandler.processingInstruction()
416: processingInstruction((ProcessingInstruction) content);
417: }
418: }
419: }
420: }
421:
422: /**
423: * <p>
424: * This will be called for each chunk of character data encountered.
425: * </p>
426: *
427: * @param elementText all text in an element, including whitespace.
428: */
429: private void characters(String elementText) throws JDOMException {
430: char[] c = elementText.toCharArray();
431: try {
432: contentHandler.characters(c, 0, c.length);
433: } catch (SAXException se) {
434: throw new JDOMException(
435: /* (non-Javadoc)
436: * @i18n.test
437: * @org-mes="Exception in characters"
438: */
439: org.openlaszlo.i18n.LaszloMessages.getMessage(
440: SourceLocatorSAXOutputter.class.getName(),
441: "051018-460"), se);
442: }
443: }
444:
445: /**
446: * Array to map JDOM attribute type (as entry index) to SAX
447: * attribute type names.
448: */
449: private static final String[] attrTypeToNameMap = new String[] {
450: "CDATA", // Attribute.UNDEFINED_ATTRIBUTE, as per SAX 2.0 spec.
451: "CDATA", // Attribute.CDATA_ATTRIBUTE
452: "ID", // Attribute.ID_ATTRIBUTE
453: "IDREF", // Attribute.IDREF_ATTRIBUTE
454: "IDREFS", // Attribute.IDREFS_ATTRIBUTE
455: "ENTITY", // Attribute.ENTITY_ATTRIBUTE
456: "ENTITIES", // Attribute.ENTITIES_ATTRIBUTE
457: "NMTOKEN", // Attribute.NMTOKEN_ATTRIBUTE
458: "NMTOKENS", // Attribute.NMTOKENS_ATTRIBUTE
459: "NOTATION", // Attribute.NOTATION_ATTRIBUTE
460: "NMTOKEN", // Attribute.ENUMERATED_ATTRIBUTE, as per SAX 2.0 spec.
461: };
462:
463: /**
464: * <p>
465: * Returns the SAX 2.0 attribute type string from the type of
466: * a JDOM Attribute.
467: * </p>
468: *
469: * @param type <code>int</code> the type of the JDOM attribute.
470: *
471: * @return <code>String</code> the SAX 2.0 attribute type string.
472: *
473: * @see org.jdom.Attribute#getAttributeType
474: * @see org.xml.sax.Attributes#getType
475: */
476: private String getAttributeTypeName(int type) {
477: if ((type < 0) || (type >= attrTypeToNameMap.length)) {
478: type = Attribute.UNDECLARED_TYPE;
479: }
480: return attrTypeToNameMap[type];
481: }
482:
483: }
|