001: package com.sun.xml.xsom.impl.parser.state;
002:
003: import java.text.MessageFormat;
004: import java.util.ArrayList;
005: import java.util.Stack;
006: import java.util.StringTokenizer;
007:
008: import org.xml.sax.Attributes;
009: import org.xml.sax.ContentHandler;
010: import org.xml.sax.Locator;
011: import org.xml.sax.SAXException;
012: import org.xml.sax.SAXParseException;
013:
014: /**
015: * Runtime Engine for RELAXNGCC execution.
016: *
017: * This class has the following functionalities:
018: *
019: * <ol>
020: * <li>Managing a stack of NGCCHandler objects and
021: * switching between them appropriately.
022: *
023: * <li>Keep track of all Attributes.
024: *
025: * <li>manage mapping between namespace URIs and prefixes.
026: *
027: * <li>TODO: provide support for interleaving.
028: *
029: * @version $Id: NGCCRuntime.java,v 1.15 2002/09/29 02:55:48 okajima Exp $
030: * @author Kohsuke Kawaguchi (kk@kohsuke.org)
031: */
032: public class NGCCRuntime implements ContentHandler, NGCCEventSource {
033:
034: public NGCCRuntime() {
035: reset();
036: }
037:
038: /**
039: * Sets the root handler, which will be used to parse the
040: * root element.
041: * <p>
042: * This method can be called right after the object is created
043: * or the reset method is called. You can't replace the root
044: * handler while parsing is in progress.
045: * <p>
046: * Usually a generated class that corresponds to the <start>
047: * pattern will be used as the root handler, but any NGCCHandler
048: * can be a root handler.
049: *
050: * @exception IllegalStateException
051: * If this method is called but it doesn't satisfy the
052: * pre-condition stated above.
053: */
054: public void setRootHandler(NGCCHandler rootHandler) {
055: if (currentHandler != null)
056: throw new IllegalStateException();
057: currentHandler = rootHandler;
058: }
059:
060: /**
061: * Cleans up all the data structure so that the object can be reused later.
062: * Normally, applications do not need to call this method directly,
063: *
064: * as the runtime resets itself after the endDocument method.
065: */
066: public void reset() {
067: attStack.clear();
068: currentAtts = null;
069: currentHandler = null;
070: indent = 0;
071: locator = null;
072: namespaces.clear();
073: needIndent = true;
074: redirect = null;
075: redirectionDepth = 0;
076: text = new StringBuffer();
077:
078: // add a dummy attributes at the bottom as a "centinel."
079: attStack.push(new AttributesImpl());
080: }
081:
082: // current content handler can be acccessed via set/getContentHandler.
083:
084: private Locator locator;
085:
086: public void setDocumentLocator(Locator _loc) {
087: this .locator = _loc;
088: }
089:
090: /**
091: * Gets the source location of the current event.
092: *
093: * <p>
094: * One can call this method from RelaxNGCC handlers to access
095: * the line number information. Note that to
096: */
097: public Locator getLocator() {
098: return locator;
099: }
100:
101: /** stack of {@link Attributes}. */
102: private final Stack attStack = new Stack();
103: /** current attributes set. always equal to attStack.peek() */
104: private AttributesImpl currentAtts;
105:
106: /**
107: * Attributes that belong to the current element.
108: * <p>
109: * It's generally not recommended for applications to use
110: * this method. RelaxNGCC internally removes processed attributes,
111: * so this doesn't correctly reflect all the attributes an element
112: * carries.
113: */
114: public Attributes getCurrentAttributes() {
115: return currentAtts;
116: }
117:
118: /** accumulated text. */
119: private StringBuffer text = new StringBuffer();
120:
121: /** The current NGCCHandler. Always equals to handlerStack.peek() */
122: private NGCCEventReceiver currentHandler;
123:
124: public int replace(NGCCEventReceiver o, NGCCEventReceiver n) {
125: if (o != currentHandler)
126: throw new IllegalStateException(); // bug of RelaxNGCC
127: currentHandler = n;
128:
129: return 0; // we only have one thread.
130: }
131:
132: /**
133: * Processes buffered text.
134: *
135: * This method will be called by the start/endElement event to process
136: * buffered text as a text event.
137: *
138: * <p>
139: * Whitespace handling is a tricky business. Consider the following
140: * schema fragment:
141: *
142: * <xmp>
143: * <element name="foo">
144: * <choice>
145: * <element name="bar"><empty/></element>
146: * <text/>
147: * </choice>
148: * </element>
149: * </xmp>
150: *
151: * Assume we hit the following instance:
152: * <xmp>
153: * <foo> <bar/></foo>
154: * </xmp>
155: *
156: * Then this first space needs to be ignored (for otherwise, we will
157: * end up treating this space as the match to <text/> and won't
158: * be able to process <bar>.)
159: *
160: * Now assume the following instance:
161: * <xmp>
162: * <foo/>
163: * </xmp>
164: *
165: * This time, we need to treat this empty string as a text, for
166: * otherwise we won't be able to accept this instance.
167: *
168: * <p>
169: * This is very difficult to solve in general, but one seemingly
170: * easy solution is to use the type of next event. If a text is
171: * followed by a start tag, it follows from the constraint on
172: * RELAX NG that that text must be either whitespaces or a match
173: * to <text/>.
174: *
175: * <p>
176: * On the contrary, if a text is followed by a end tag, then it
177: * cannot be whitespace unless the content model can accept empty,
178: * in which case sending a text event will be harmlessly ignored
179: * by the NGCCHandler.
180: *
181: * <p>
182: * Thus this method take one parameter, which controls the
183: * behavior of this method.
184: *
185: * <p>
186: * TODO: according to the constraint of RELAX NG, if characters
187: * follow an end tag, then they must be either whitespaces or
188: * must match to <text/>.
189: *
190: * @param possiblyWhitespace
191: * True if the buffered character can be ignorabale. False if
192: * it needs to be consumed.
193: */
194: private void processPendingText(boolean ignorable)
195: throws SAXException {
196: if (ignorable && text.toString().trim().length() == 0)
197: ; // ignore. See the above javadoc comment for the description
198: else
199: currentHandler.text(text.toString()); // otherwise consume this token
200:
201: // truncate StringBuffer, but avoid excessive allocation.
202: if (text.length() > 1024)
203: text = new StringBuffer();
204: else
205: text.setLength(0);
206: }
207:
208: public void processList(String str) throws SAXException {
209: StringTokenizer t = new StringTokenizer(str, " \t\r\n");
210: while (t.hasMoreTokens())
211: currentHandler.text(t.nextToken());
212: }
213:
214: public void startElement(String uri, String localname,
215: String qname, Attributes atts) throws SAXException {
216:
217: if (redirect != null) {
218: redirect.startElement(uri, localname, qname, atts);
219: redirectionDepth++;
220: } else {
221: processPendingText(true);
222: // System.out.println("startElement:"+localname+"->"+_attrStack.size());
223: currentHandler.enterElement(uri, localname, qname, atts);
224: }
225: }
226:
227: /**
228: * Called by the generated handler code when an enter element
229: * event is consumed.
230: *
231: * <p>
232: * Pushes a new attribute set.
233: *
234: * <p>
235: * Note that attributes are NOT pushed at the startElement method,
236: * because the processing of the enterElement event can trigger
237: * other attribute events and etc.
238: * <p>
239: * This method will be called from one of handlers when it truely
240: * consumes the enterElement event.
241: */
242: public void onEnterElementConsumed(String uri, String localName,
243: String qname, Attributes atts) throws SAXException {
244: attStack.push(currentAtts = new AttributesImpl(atts));
245: nsEffectiveStack.push(new Integer(nsEffectivePtr));
246: nsEffectivePtr = namespaces.size();
247: }
248:
249: public void onLeaveElementConsumed(String uri, String localName,
250: String qname) throws SAXException {
251: attStack.pop();
252: if (attStack.isEmpty())
253: currentAtts = null;
254: else
255: currentAtts = (AttributesImpl) attStack.peek();
256: nsEffectivePtr = ((Integer) nsEffectiveStack.pop()).intValue();
257: }
258:
259: public void endElement(String uri, String localname, String qname)
260: throws SAXException {
261:
262: if (redirect != null) {
263: redirect.endElement(uri, localname, qname);
264: redirectionDepth--;
265:
266: if (redirectionDepth != 0)
267: return;
268:
269: // finished redirection.
270: for (int i = 0; i < namespaces.size(); i += 2)
271: redirect.endPrefixMapping((String) namespaces.get(i));
272: redirect.endDocument();
273:
274: redirect = null;
275: // then process this element normally
276: }
277:
278: processPendingText(false);
279:
280: currentHandler.leaveElement(uri, localname, qname);
281: // System.out.println("endElement:"+localname);
282: }
283:
284: public void characters(char[] ch, int start, int length)
285: throws SAXException {
286: if (redirect != null)
287: redirect.characters(ch, start, length);
288: else
289: text.append(ch, start, length);
290: }
291:
292: public void ignorableWhitespace(char[] ch, int start, int length)
293: throws SAXException {
294: if (redirect != null)
295: redirect.ignorableWhitespace(ch, start, length);
296: else
297: text.append(ch, start, length);
298: }
299:
300: public int getAttributeIndex(String uri, String localname) {
301: return currentAtts.getIndex(uri, localname);
302: }
303:
304: public void consumeAttribute(int index) throws SAXException {
305: final String uri = currentAtts.getURI(index);
306: final String local = currentAtts.getLocalName(index);
307: final String qname = currentAtts.getQName(index);
308: final String value = currentAtts.getValue(index);
309: currentAtts.removeAttribute(index);
310:
311: currentHandler.enterAttribute(uri, local, qname);
312: currentHandler.text(value);
313: currentHandler.leaveAttribute(uri, local, qname);
314: }
315:
316: public void startPrefixMapping(String prefix, String uri)
317: throws SAXException {
318: if (redirect != null)
319: redirect.startPrefixMapping(prefix, uri);
320: else {
321: namespaces.add(prefix);
322: namespaces.add(uri);
323: }
324: }
325:
326: public void endPrefixMapping(String prefix) throws SAXException {
327: if (redirect != null)
328: redirect.endPrefixMapping(prefix);
329: else {
330: namespaces.remove(namespaces.size() - 1);
331: namespaces.remove(namespaces.size() - 1);
332: }
333: }
334:
335: public void skippedEntity(String name) throws SAXException {
336: if (redirect != null)
337: redirect.skippedEntity(name);
338: }
339:
340: public void processingInstruction(String target, String data)
341: throws SAXException {
342: if (redirect != null)
343: redirect.processingInstruction(target, data);
344: }
345:
346: /** Impossible token. This value can never be a valid XML name. */
347: static final String IMPOSSIBLE = "\u0000";
348:
349: public void endDocument() throws SAXException {
350: // consume the special "end document" token so that all the handlers
351: // currently at the stack will revert to their respective parents.
352: //
353: // this is necessary to handle a grammar like
354: // <start><ref name="X"/></start>
355: // <define name="X">
356: // <element name="root"><empty/></element>
357: // </define>
358: //
359: // With this grammar, when the endElement event is consumed, two handlers
360: // are on the stack (because a child object won't revert to its parent
361: // unless it sees a next event.)
362:
363: // pass around an "impossible" token.
364: currentHandler.leaveElement(IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE);
365:
366: reset();
367: }
368:
369: public void startDocument() {
370: }
371:
372: //
373: //
374: // event dispatching methods
375: //
376: //
377:
378: public void sendEnterAttribute(int threadId, String uri,
379: String local, String qname) throws SAXException {
380:
381: currentHandler.enterAttribute(uri, local, qname);
382: }
383:
384: public void sendEnterElement(int threadId, String uri,
385: String local, String qname, Attributes atts)
386: throws SAXException {
387:
388: currentHandler.enterElement(uri, local, qname, atts);
389: }
390:
391: public void sendLeaveAttribute(int threadId, String uri,
392: String local, String qname) throws SAXException {
393:
394: currentHandler.leaveAttribute(uri, local, qname);
395: }
396:
397: public void sendLeaveElement(int threadId, String uri,
398: String local, String qname) throws SAXException {
399:
400: currentHandler.leaveElement(uri, local, qname);
401: }
402:
403: public void sendText(int threadId, String value)
404: throws SAXException {
405: currentHandler.text(value);
406: }
407:
408: //
409: //
410: // redirection of SAX2 events.
411: //
412: //
413: /** When redirecting a sub-tree, this value will be non-null. */
414: private ContentHandler redirect = null;
415:
416: /**
417: * Counts the depth of the elements when we are re-directing
418: * a sub-tree to another ContentHandler.
419: */
420: private int redirectionDepth = 0;
421:
422: /**
423: * This method can be called only from the enterElement handler.
424: * The sub-tree rooted at the new element will be redirected
425: * to the specified ContentHandler.
426: *
427: * <p>
428: * Currently active NGCCHandler will only receive the leaveElement
429: * event of the newly started element.
430: *
431: * @param uri,local,qname
432: * Parameters passed to the enter element event. Used to
433: * simulate the startElement event for the new ContentHandler.
434: */
435: public void redirectSubtree(ContentHandler child, String uri,
436: String local, String qname) throws SAXException {
437:
438: redirect = child;
439: redirect.setDocumentLocator(locator);
440: redirect.startDocument();
441:
442: // TODO: when a prefix is re-bound to something else,
443: // the following code is potentially dangerous. It should be
444: // modified to report active bindings only.
445: for (int i = 0; i < namespaces.size(); i += 2)
446: redirect.startPrefixMapping((String) namespaces.get(i),
447: (String) namespaces.get(i + 1));
448:
449: redirect.startElement(uri, local, qname, currentAtts);
450: redirectionDepth = 1;
451: }
452:
453: //
454: //
455: // validation context implementation
456: //
457: //
458: /** in-scope namespace mapping.
459: * namespaces[2n ] := prefix
460: * namespaces[2n+1] := namespace URI */
461: private final ArrayList namespaces = new ArrayList();
462: /**
463: * Index on the namespaces array, which points to
464: * the top of the effective bindings. Because of the
465: * timing difference between the startPrefixMapping method
466: * and the execution of the corresponding actions,
467: * this value can be different from <code>namespaces.size()</code>.
468: * <p>
469: * For example, consider the following schema:
470: * <pre><xmp>
471: * <oneOrMore>
472: * <element name="foo"><empty/></element>
473: * </oneOrMore>
474: * code fragment X
475: * <element name="bob"/>
476: * </xmp></pre>
477: * Code fragment X is executed after we see a startElement event,
478: * but at this time the namespaces variable already include new
479: * namespace bindings declared on "bob".
480: */
481: private int nsEffectivePtr = 0;
482:
483: /**
484: * Stack to preserve old nsEffectivePtr values.
485: */
486: private final Stack nsEffectiveStack = new Stack();
487:
488: public String resolveNamespacePrefix(String prefix) {
489: for (int i = nsEffectivePtr - 2; i >= 0; i -= 2)
490: if (namespaces.get(i).equals(prefix))
491: return (String) namespaces.get(i + 1);
492:
493: // no binding was found.
494: if (prefix.equals(""))
495: return ""; // return the default no-namespace
496: if (prefix.equals("xml")) // pre-defined xml prefix
497: return "http://www.w3.org/XML/1998/namespace";
498: else
499: return null; // prefix undefined
500: }
501:
502: // error reporting
503: protected void unexpectedX(String token) throws SAXException {
504: throw new SAXParseException(MessageFormat.format(
505: "Unexpected {0} appears at line {1} column {2}",
506: new Object[] { token,
507: new Integer(getLocator().getLineNumber()),
508: new Integer(getLocator().getColumnNumber()) }),
509: getLocator());
510: }
511:
512: //
513: //
514: // trace functions
515: //
516: //
517: private int indent = 0;
518: private boolean needIndent = true;
519:
520: private void printIndent() {
521: for (int i = 0; i < indent; i++)
522: System.out.print(" ");
523: }
524:
525: public void trace(String s) {
526: if (needIndent) {
527: needIndent = false;
528: printIndent();
529: }
530: System.out.print(s);
531: }
532:
533: public void traceln(String s) {
534: trace(s);
535: trace("\n");
536: needIndent = true;
537: }
538: }
|