001: /*
002: * Copyright (C) Chaperon. All rights reserved.
003: * -------------------------------------------------------------------------
004: * This software is published under the terms of the Apache Software License
005: * version 1.1, a copy of which has been included with this distribution in
006: * the LICENSE file.
007: */
008:
009: package net.sourceforge.chaperon.process;
010:
011: import net.sourceforge.chaperon.common.Decoder;
012: import net.sourceforge.chaperon.common.IntegerList;
013:
014: import org.apache.commons.logging.Log;
015:
016: import org.xml.sax.Attributes;
017: import org.xml.sax.ContentHandler;
018: import org.xml.sax.Locator;
019: import org.xml.sax.SAXException;
020: import org.xml.sax.ext.LexicalHandler;
021: import org.xml.sax.helpers.AttributesImpl;
022: import org.xml.sax.helpers.LocatorImpl;
023:
024: import java.util.Stack;
025:
026: /**
027: * This class represents a simulation of a pushdown automata using the parser automaton class.
028: *
029: * @author <a href="mailto:stephan@apache.org">Stephan Michels </a>
030: * @version CVS $Id: ParserProcessor.java,v 1.28 2004/01/08 11:30:52 benedikta Exp $
031: */
032: public class ParserProcessor implements ContentHandler, LexicalHandler {
033: /** Namespace for the generated SAX events. */
034: public static final String NS_OUTPUT = "http://chaperon.sourceforge.net/schema/syntaxtree/1.0";
035: public static final String OUTPUT = "output";
036: public static final String ERROR = "error";
037: private boolean flatten = false;
038: private boolean localizable = false;
039: private String source;
040: private int lineNumber;
041: private int columnNumber;
042: private ContentHandler contentHandler = null;
043: private LexicalHandler lexicalHandler = null;
044: private Locator locator = null;
045: private LocatorImpl locatorImpl = null;
046: private static final int STATE_OUTSIDE = 0;
047: private static final int STATE_INSIDE = 1;
048: private static final int STATE_LEXEME = 2;
049: private static final int STATE_GROUP = 3;
050: private static final int STATE_ERROR = 4;
051: private int state = STATE_OUTSIDE;
052: private ParserAutomaton automaton;
053: private IntegerList statestack = new IntegerList();
054: private Stack treestack = new Stack();
055: private Log log;
056: private StringBuffer lineSnippet = new StringBuffer();
057: private boolean unrecoverable = false;
058: private ParseException exception = null;
059:
060: /**
061: * Create a new parser processor.
062: */
063: public ParserProcessor() {
064: }
065:
066: /**
067: * Create a new parser processor.
068: *
069: * @param automaton Parser automaton, which the processor should ues.
070: * @param handler Handler, which should receives the parser events.
071: * @param log Log, which should used.
072: */
073: public ParserProcessor(ParserAutomaton automaton, Log log) {
074: this .automaton = automaton;
075: this .log = log;
076: }
077:
078: /**
079: * Set the parser automaton for the processor.
080: *
081: * @param automaton Parser automaton.
082: */
083: public void setParserAutomaton(ParserAutomaton automaton) {
084: this .automaton = automaton;
085: }
086:
087: /**
088: * Set the <code>ContentHandler</code> that will receive XML data.
089: */
090: public void setContentHandler(ContentHandler handler) {
091: this .contentHandler = handler;
092: }
093:
094: /**
095: * Set the <code>LexicalHandler</code> that will receive XML data.
096: */
097: public void setLexicalHandler(LexicalHandler handler) {
098: this .lexicalHandler = handler;
099: }
100:
101: /**
102: * Provide processor with a log.
103: *
104: * @param log The log.
105: */
106: public void setLog(Log log) {
107: this .log = log;
108: }
109:
110: /**
111: * If <code>true</code>, the line and column number information are let in the XML output for
112: * each token.
113: *
114: * @param localizable If the XML may be localizable.
115: */
116: public void setLocalizable(boolean localizable) {
117: this .localizable = localizable;
118: }
119:
120: /**
121: * If the adapter should produce a more flatten XML hirachy, which means elements which the same
122: * name will be collapsed
123: *
124: * @param flatten True, if a more flatten hirachy should be produced.
125: */
126: public void setFlatten(boolean flatten) {
127: this .flatten = flatten;
128: }
129:
130: /**
131: * Receive an object for locating the origin of SAX document events.
132: */
133: public void setDocumentLocator(Locator locator) {
134: this .locator = locator;
135: this .locatorImpl = null;
136: if (locator != null) {
137: this .locatorImpl = new LocatorImpl(locator);
138: contentHandler.setDocumentLocator(locatorImpl);
139: }
140: }
141:
142: /**
143: * Receive notification of the beginning of a document.
144: */
145: public void startDocument() throws SAXException {
146: contentHandler.startDocument();
147: state = STATE_OUTSIDE;
148: }
149:
150: /**
151: * Receive notification of the end of a document.
152: */
153: public void endDocument() throws SAXException {
154: if (state == STATE_OUTSIDE)
155: contentHandler.endDocument();
156: }
157:
158: /**
159: * Receive notification of the beginning of an element.
160: */
161: public void startElement(String namespaceURI, String localName,
162: String qName, Attributes atts) throws SAXException {
163: if (state == STATE_OUTSIDE) {
164: if ((namespaceURI != null)
165: && (namespaceURI.equals(LexicalProcessor.NS_OUTPUT))
166: && (localName.equals(LexicalProcessor.OUTPUT))) {
167: handleStartDocument();
168:
169: state = STATE_INSIDE;
170:
171: if (atts.getValue("source") != null)
172: source = atts.getValue("source");
173: else if (locator != null)
174: source = locator.getSystemId();
175: else
176: source = "unknown";
177: } else
178: contentHandler.startElement(namespaceURI, localName,
179: qName, atts);
180: } else if (state == STATE_INSIDE) {
181: if ((namespaceURI != null)
182: && (namespaceURI.equals(LexicalProcessor.NS_OUTPUT))
183: && (localName.equals(LexicalProcessor.LEXEME))) {
184: if (atts.getValue("column") != null)
185: columnNumber = Integer.parseInt(atts
186: .getValue("column"));
187: else if (locator != null)
188: columnNumber = locator.getColumnNumber();
189: else
190: columnNumber = 1;
191:
192: if (atts.getValue("line") != null)
193: lineNumber = Integer
194: .parseInt(atts.getValue("line"));
195: else if (locator != null)
196: lineNumber = locator.getLineNumber();
197: else
198: lineNumber = 1;
199:
200: handleLexeme(atts.getValue("symbol"), atts
201: .getValue("text"));
202: state = STATE_LEXEME;
203: } else if ((namespaceURI != null)
204: && (namespaceURI.equals(LexicalProcessor.NS_OUTPUT))
205: && (localName.equals(ERROR))) {
206: if (atts.getValue("column") != null)
207: columnNumber = Integer.parseInt(atts
208: .getValue("column"));
209: else if (locator != null)
210: columnNumber = locator.getColumnNumber();
211: else
212: columnNumber = 1;
213:
214: if (atts.getValue("line") != null)
215: lineNumber = Integer
216: .parseInt(atts.getValue("line"));
217: else if (locator != null)
218: lineNumber = locator.getLineNumber();
219: else
220: lineNumber = 1;
221:
222: handleLexeme("error", atts.getValue("text"));
223: state = STATE_ERROR;
224: } else
225: throw new SAXException("Unexpected start element.");
226: } else if (state == STATE_LEXEME) {
227: if ((namespaceURI != null)
228: && (namespaceURI.equals(LexicalProcessor.NS_OUTPUT))
229: && (localName.equals(LexicalProcessor.GROUP)))
230: state = STATE_GROUP;
231: else
232: throw new SAXException("Unexpected start element.");
233: } else if ((state == STATE_ERROR) || (state == STATE_GROUP))
234: throw new SAXException("Unexpected start element.");
235: }
236:
237: /**
238: * Receive notification of the end of an element.
239: */
240: public void endElement(String namespaceURI, String localName,
241: String qName) throws SAXException {
242: if (state == STATE_OUTSIDE)
243: contentHandler.endElement(namespaceURI, localName, qName);
244: else if (state == STATE_INSIDE) {
245: if ((namespaceURI != null)
246: && (namespaceURI.equals(LexicalProcessor.NS_OUTPUT))
247: && (localName.equals(LexicalProcessor.OUTPUT))) {
248: contentHandler.startPrefixMapping("", NS_OUTPUT);
249: contentHandler.startElement(NS_OUTPUT, OUTPUT, OUTPUT,
250: new AttributesImpl());
251:
252: handleEndDocument();
253:
254: contentHandler.endElement(NS_OUTPUT, OUTPUT, OUTPUT);
255: contentHandler.endPrefixMapping("");
256:
257: state = STATE_OUTSIDE;
258: } else
259: throw new SAXException("Unexpected end element.");
260: } else if ((state == STATE_LEXEME) || (state == STATE_ERROR))
261: state = STATE_INSIDE;
262: else if (state == STATE_GROUP)
263: state = STATE_LEXEME;
264: }
265:
266: /**
267: * Receive notification of character data.
268: */
269: public void characters(char[] ch, int start, int length)
270: throws SAXException {
271: if (state == STATE_OUTSIDE)
272: contentHandler.characters(ch, start, length);
273: }
274:
275: /**
276: * Receive notification of ignorable whitespace in element content.
277: */
278: public void ignorableWhitespace(char[] ch, int start, int length)
279: throws SAXException {
280: if (state == STATE_OUTSIDE)
281: contentHandler.ignorableWhitespace(ch, start, length);
282: }
283:
284: /**
285: * Begin the scope of a prefix-URI Namespace mapping.
286: */
287: public void startPrefixMapping(String prefix, String uri)
288: throws SAXException {
289: contentHandler.startPrefixMapping(prefix, uri);
290: }
291:
292: /**
293: * End the scope of a prefix-URI mapping.
294: */
295: public void endPrefixMapping(String prefix) throws SAXException {
296: contentHandler.endPrefixMapping(prefix);
297: }
298:
299: /**
300: * Receive notification of a processing instruction.
301: */
302: public void processingInstruction(String target, String data)
303: throws SAXException {
304: if (locatorImpl != null) {
305: locatorImpl.setLineNumber(locator.getLineNumber());
306: locatorImpl.setColumnNumber(locator.getColumnNumber());
307: }
308:
309: if (state == STATE_OUTSIDE)
310: contentHandler.processingInstruction(target, data);
311: }
312:
313: /**
314: * Receive notification of a skipped entity.
315: */
316: public void skippedEntity(String name) throws SAXException {
317: if (locatorImpl != null) {
318: locatorImpl.setLineNumber(locator.getLineNumber());
319: locatorImpl.setColumnNumber(locator.getColumnNumber());
320: }
321:
322: if (state == STATE_OUTSIDE)
323: contentHandler.skippedEntity(name);
324: }
325:
326: /**
327: * Report the start of DTD declarations, if any.
328: */
329: public void startDTD(String name, String publicId, String systemId)
330: throws SAXException {
331: if (lexicalHandler != null)
332: lexicalHandler.startDTD(name, publicId, systemId);
333: }
334:
335: /**
336: * Report the end of DTD declarations.
337: */
338: public void endDTD() throws SAXException {
339: if (lexicalHandler != null)
340: lexicalHandler.endDTD();
341: }
342:
343: /**
344: * Report the beginning of an entity.
345: */
346: public void startEntity(String name) throws SAXException {
347: if (lexicalHandler != null)
348: lexicalHandler.startEntity(name);
349: }
350:
351: /**
352: * Report the end of an entity.
353: */
354: public void endEntity(String name) throws SAXException {
355: if (lexicalHandler != null)
356: lexicalHandler.endEntity(name);
357: }
358:
359: /**
360: * Report the start of a CDATA section.
361: */
362: public void startCDATA() throws SAXException {
363: if (lexicalHandler != null)
364: lexicalHandler.startCDATA();
365: }
366:
367: /**
368: * Report the end of a CDATA section.
369: */
370: public void endCDATA() throws SAXException {
371: if (lexicalHandler != null)
372: lexicalHandler.endCDATA();
373: }
374:
375: /**
376: * Report an XML comment anywhere in the document.
377: */
378: public void comment(char[] ch, int start, int len)
379: throws SAXException {
380: if (lexicalHandler != null)
381: lexicalHandler.comment(ch, start, len);
382: }
383:
384: private String getLocation() {
385: if (locator == null)
386: return "unknown";
387:
388: return locator.getSystemId() + ":" + locator.getLineNumber()
389: + ":" + locator.getColumnNumber();
390: }
391:
392: /**
393: * Receives the notification, that the lexical processor starts reading a new document.
394: *
395: * @throws Exception If a exception occurs.
396: */
397: private void handleStartDocument() {
398: statestack.clear();
399: statestack.push(0); // First state is zero
400:
401: treestack.clear();
402:
403: lineSnippet = new StringBuffer();
404: unrecoverable = false;
405: exception = null;
406: }
407:
408: /**
409: * Receives the notification, that the lexical processor has recognized a lexeme.
410: *
411: * @param symbol Symbol of the lexeme.
412: * @param text Recognized text.
413: *
414: * @throws Exception If a exception occurs.
415: */
416: private void handleLexeme(String symbolname, String text)
417: throws SAXException {
418: if (unrecoverable)
419: return;
420:
421: int symbol = -1; // Index of the token from the input
422:
423: for (int i = 0; (i < automaton.getTerminalCount())
424: && (symbol == -1); i++)
425: if (automaton.getTerminal(i).equals(symbolname))
426: symbol = i;
427:
428: int state = statestack.peek();
429:
430: if (symbol == -1) {
431: for (int i = 0; (i < automaton.getTerminalCount())
432: && (symbol == -1); i++)
433: if (automaton.getTerminal(i).equals("error"))
434: symbol = i;
435:
436: if (symbol == -1) {
437: if ((log != null) && (log.isDebugEnabled()))
438: log.debug("State " + state + " unexpected token "
439: + Decoder.toString(text) + "(" + symbolname
440: + ")");
441:
442: StringBuffer message = new StringBuffer();
443:
444: message.append("Unexpected token ");
445: message.append(symbolname);
446: message.append("[\"");
447: message.append(text);
448: message.append("\"], expected tokens: ");
449: for (symbol = 0; symbol < automaton.getTerminalCount(); symbol++)
450: if (!automaton.isErrorAction(state, symbol)) {
451: if (symbol > 0)
452: message.append(", ");
453:
454: message.append(automaton.getTerminal(symbol));
455: }
456:
457: unrecoverable = true;
458: exception = new ParseException(message.toString(),
459: symbolname, text, lineSnippet.toString(),
460: source, lineNumber, columnNumber);
461:
462: return;
463: } else
464: symbolname = "error";
465: }
466:
467: /* ============================ Reduce =================================== */
468: while (automaton.isReduceAction(state, symbol)) {
469: int production = automaton.getReduceProduction(state,
470: symbol);
471:
472: if ((log != null) && (log.isDebugEnabled()))
473: log.debug("State "
474: + state
475: + " reduce "
476: + automaton.getNonterminal(automaton
477: .getProductionSymbol(production))
478: + " (" + production + ")");
479:
480: ProductionNode productionnode = new ProductionNode(
481: automaton.getNonterminal(automaton
482: .getProductionSymbol(production)));
483:
484: TreeNode node = null;
485: for (int i = 0; i < automaton
486: .getProductionLength(production); i++) {
487: statestack.pop();
488: productionnode.insert((node = (TreeNode) treestack
489: .pop()));
490: }
491:
492: if (node != null) {
493: productionnode.linenumber = node.linenumber;
494: productionnode.columnnumber = node.columnnumber;
495: }
496:
497: treestack.push(productionnode);
498:
499: statestack.push(automaton.getTransition(statestack.peek(),
500: automaton.getProductionSymbol(production)));
501:
502: state = statestack.peek();
503: }
504:
505: /* ================================== Error =================================== */
506: if (automaton.isErrorAction(state, symbol)) {
507: if ((log != null) && (log.isDebugEnabled()))
508: log.debug("State " + state + " error token "
509: + Decoder.toString(text) + "(" + symbolname
510: + ")");
511:
512: StringBuffer errortext = new StringBuffer();
513:
514: // Remove states from stack, until error token is found
515: while ((statestack.getCount() > 1)
516: && (((automaton.isErrorAction(state, symbol)) && (automaton
517: .getErrorTransition(state, symbol) == 0)) && (!automaton
518: .isShiftAction(state, symbol)))) {
519: statestack.pop();
520:
521: TreeNode node = (TreeNode) treestack.pop();
522: errortext.insert(0, node.getText());
523: state = statestack.peek();
524: }
525:
526: if (((!symbolname.equals("error")) && (!automaton
527: .isErrorAction(state, symbol)))
528: || ((symbolname.equals("error")) && (!automaton
529: .isShiftAction(state, symbol))))
530: throw new SAXException("Couldn't accept input "
531: + symbolname + "[" + Decoder.toString(text)
532: + "] at " + getLocation());
533:
534: if (automaton.isErrorAction(state, symbol))
535: statestack.push(automaton.getErrorTransition(state,
536: symbol));
537: else
538: statestack.push(automaton.getShiftTransition(state,
539: symbol));
540:
541: state = statestack.peek();
542:
543: if (automaton.isErrorAction(state, symbol))
544: errortext.append(text);
545:
546: // push error token on top of the stack
547: TokenNode tokennode = new TokenNode("error", errortext
548: .toString());
549:
550: if (locator != null) {
551: tokennode.linenumber = lineNumber;
552: tokennode.columnnumber = columnNumber;
553: }
554:
555: treestack.push(tokennode);
556:
557: if (automaton.isErrorAction(state, symbol))
558: return;
559: }
560:
561: /* ============================ Reduce =================================== */
562: while (automaton.isReduceAction(state, symbol)) {
563: int production = automaton.getReduceProduction(state,
564: symbol);
565:
566: if ((log != null) && (log.isDebugEnabled()))
567: log.debug("State "
568: + state
569: + " reduce "
570: + automaton.getNonterminal(automaton
571: .getProductionSymbol(production))
572: + " (" + production + ")");
573:
574: ProductionNode productionnode = new ProductionNode(
575: automaton.getNonterminal(automaton
576: .getProductionSymbol(production)));
577:
578: TreeNode node = null;
579: for (int i = 0; i < automaton
580: .getProductionLength(production); i++) {
581: statestack.pop();
582: productionnode.insert((node = (TreeNode) treestack
583: .pop()));
584: }
585:
586: if (node != null) {
587: productionnode.linenumber = node.linenumber;
588: productionnode.columnnumber = node.columnnumber;
589: }
590:
591: treestack.push(productionnode);
592:
593: statestack.push(automaton.getTransition(statestack.peek(),
594: automaton.getProductionSymbol(production)));
595:
596: state = statestack.peek();
597: }
598:
599: /* ==================================== Shift =================================== */
600: if (automaton.isShiftAction(state, symbol)) {
601: if ((log != null) && (log.isDebugEnabled()))
602: log.debug("State " + state + " shift token "
603: + symbolname + " (" + symbol + ")");
604:
605: statestack
606: .push(automaton.getShiftTransition(state, symbol));
607:
608: TokenNode tokennode = new TokenNode(symbolname, text);
609:
610: if (locator != null) {
611: tokennode.linenumber = lineNumber;
612: tokennode.columnnumber = columnNumber;
613: }
614:
615: treestack.push(tokennode);
616:
617: if ((text.lastIndexOf("\n") >= 0)
618: || (text.lastIndexOf("\r") >= 0)) {
619: lineSnippet = new StringBuffer();
620: lineSnippet.append(text.substring(Math.max(text
621: .lastIndexOf("\n"), text.lastIndexOf("\r"))));
622: } else
623: lineSnippet.append(text);
624: }
625: }
626:
627: /**
628: * Receives the notification, that the lexical processor accepted the complete document, and
629: * stops with reading.
630: *
631: * @throws Exception If a exception occurs.
632: */
633: private void handleEndDocument() throws SAXException {
634: if (unrecoverable) {
635: fireException();
636: return;
637: }
638:
639: int state = statestack.peek();
640:
641: /* ============================ Error =================================== */
642: if (automaton.isErrorAction(state)) {
643: if ((log != null) && (log.isDebugEnabled()))
644: log.debug("State " + state
645: + " error unexpected end of file");
646:
647: StringBuffer errortext = new StringBuffer();
648:
649: // Remove states from stack, until error token is found
650: while ((statestack.getCount() > 1)
651: && (automaton.getErrorTransition(state) == 0)) {
652: statestack.pop();
653:
654: TreeNode node = (TreeNode) treestack.pop();
655: errortext.insert(0, node.getText());
656: state = statestack.peek();
657: }
658:
659: // push error token on top of the stack
660: statestack.push(automaton.getErrorTransition(state));
661:
662: state = statestack.peek();
663:
664: // if not transition possible, then ignore terminal by error token
665: if (automaton.isErrorAction(state))
666: throw new SAXException(
667: "Couldn't accept end of document at "
668: + getLocation());
669:
670: TokenNode tokennode = new TokenNode("error", errortext
671: .toString());
672:
673: if (locator != null) {
674: tokennode.linenumber = lineNumber;
675: tokennode.columnnumber = columnNumber;
676: }
677:
678: treestack.push(tokennode);
679: }
680:
681: /* ============================ Reduce & Accept =================================== */
682: while (automaton.isReduceAction(state)
683: || automaton.isAcceptAction(state)) {
684: int production = automaton.getReduceProduction(state);
685:
686: if ((log != null) && (log.isDebugEnabled()))
687: log.debug("State "
688: + state
689: + " reduce "
690: + automaton.getNonterminal(automaton
691: .getProductionSymbol(production))
692: + " (" + production + ")");
693:
694: ProductionNode productionnode = new ProductionNode(
695: automaton.getNonterminal(automaton
696: .getProductionSymbol(production)));
697:
698: TreeNode node = null;
699: for (int i = 0; i < automaton
700: .getProductionLength(production); i++) {
701: statestack.pop();
702: productionnode.insert((node = (TreeNode) treestack
703: .pop()));
704: }
705:
706: if (node != null) {
707: productionnode.linenumber = node.linenumber;
708: productionnode.columnnumber = node.columnnumber;
709: }
710:
711: treestack.push(productionnode);
712:
713: /* ================================== Accept =================================== */
714: if ((automaton.isAcceptAction(state))
715: && (statestack.getCount() == 1)) {
716: if ((log != null) && (log.isDebugEnabled()))
717: log.debug("State " + state + " accept");
718:
719: if (locatorImpl != null)
720: locatorImpl.setSystemId(source);
721:
722: fireEvents(productionnode);
723: return;
724: } else
725: statestack.push(automaton.getTransition(statestack
726: .peek(), automaton
727: .getProductionSymbol(production)));
728:
729: state = statestack.peek();
730: }
731:
732: if ((automaton.isErrorAction(state))
733: && (statestack.getCount() > 1)) {
734: if ((log != null) && (log.isDebugEnabled()))
735: log.debug("State " + state
736: + " error unexpected end of file");
737:
738: StringBuffer message = new StringBuffer();
739:
740: message.append("Unexpected end of file, expected tokens: ");
741: for (int i = 0; i < automaton.getTerminalCount(); i++)
742: if (!automaton.isErrorAction(state, i)) {
743: message.append(automaton.getTerminal(i));
744: message.append(" ");
745: }
746:
747: exception = new ParseException(message.toString(), "", "",
748: lineSnippet.toString(), source, lineNumber,
749: columnNumber);
750:
751: fireException();
752: }
753: }
754:
755: /**
756: * Fire the SAX events by traverseing the hirachy.
757: *
758: * @param node Current node.
759: */
760: private void fireEvents(TreeNode node) throws SAXException {
761: Stack stack = new Stack();
762:
763: ProductionNode previous = null;
764: TreeNode next = node;
765: do {
766: while (next != null) {
767: stack.push(next);
768:
769: if (locatorImpl != null) {
770: locatorImpl.setLineNumber(next.linenumber);
771: locatorImpl.setColumnNumber(next.columnnumber);
772: }
773:
774: if ((!flatten) || (previous == null)
775: || (!previous.symbol.equals(next.symbol))) {
776: AttributesImpl atts = new AttributesImpl();
777: if (localizable) {
778: atts.addAttribute("", "line", "line", "CDATA",
779: String.valueOf(next.linenumber));
780: atts.addAttribute("", "column", "column",
781: "CDATA", String
782: .valueOf(next.columnnumber));
783: }
784:
785: contentHandler.startElement(NS_OUTPUT, next.symbol,
786: next.symbol, atts);
787: }
788:
789: if (next instanceof ProductionNode) {
790: ProductionNode production = (ProductionNode) next;
791: previous = production;
792: next = production.firstchild;
793: } else {
794: TokenNode token = (TokenNode) next;
795: contentHandler.characters(token.text.toCharArray(),
796: 0, token.text.length());
797: next = null;
798: }
799: }
800:
801: next = (TreeNode) stack.pop();
802: previous = stack.isEmpty() ? null : (ProductionNode) stack
803: .peek();
804:
805: if (locatorImpl != null) {
806: locatorImpl.setLineNumber(next.linenumber);
807: locatorImpl.setColumnNumber(next.columnnumber);
808: }
809:
810: if ((!flatten) || (previous == null)
811: || (!previous.symbol.equals(next.symbol)))
812: contentHandler.endElement(NS_OUTPUT, next.symbol,
813: next.symbol);
814:
815: next = next.nextsibling;
816: } while (!stack.isEmpty());
817: }
818:
819: private void fireException() throws SAXException {
820: AttributesImpl atts = new AttributesImpl();
821: atts.addAttribute("", "symbol", "symbol", "CDATA", exception
822: .getSymbol());
823: atts.addAttribute("", "text", "text", "CDATA", exception
824: .getText());
825: atts.addAttribute("", "line-snippet", "line-snippet", "CDATA",
826: exception.getLineSnippet());
827: atts.addAttribute("", "localized", "localized", "CDATA", String
828: .valueOf(exception.isLocalized()));
829: atts.addAttribute("", "line-number", "line-number", "CDATA",
830: String.valueOf(exception.getLineNumber()));
831: atts.addAttribute("", "column-number", "column-number",
832: "CDATA", String.valueOf(exception.getColumnNumber()));
833: contentHandler.startElement(NS_OUTPUT, "exception",
834: "exception", atts);
835: contentHandler.endElement(NS_OUTPUT, "exception", "exception");
836: }
837:
838: private abstract class TreeNode {
839: public String symbol = null;
840: public int linenumber = 1;
841: public int columnnumber = 1;
842: public TreeNode previoussibling = null;
843: public TreeNode nextsibling = null;
844:
845: public abstract String getText();
846:
847: public String toString() {
848: return symbol;
849: }
850: }
851:
852: private class TokenNode extends TreeNode {
853: public TokenNode(String symbol, String text) {
854: this .symbol = symbol;
855: this .text = text;
856: }
857:
858: public String text = null;
859:
860: public String getText() {
861: return text
862: + ((nextsibling != null) ? nextsibling.getText()
863: : "");
864: }
865: }
866:
867: private class ProductionNode extends TreeNode {
868: public ProductionNode(String symbol) {
869: this .symbol = symbol;
870: }
871:
872: public TreeNode firstchild = null;
873: public TreeNode lastchild = null;
874:
875: public void insert(TreeNode node) {
876: if (firstchild == null) {
877: firstchild = node;
878: lastchild = node;
879: } else {
880: firstchild.previoussibling = node;
881: node.nextsibling = firstchild;
882: firstchild = node;
883: }
884: }
885:
886: public void insertChilds(ProductionNode production) {
887: if (firstchild == null) {
888: firstchild = production.firstchild;
889: lastchild = production.lastchild;
890: } else {
891: firstchild.previoussibling = production.lastchild;
892: production.lastchild.nextsibling = firstchild;
893: firstchild = production.firstchild;
894: }
895: }
896:
897: public String getText() {
898: return ((firstchild != null) ? firstchild.getText() : "")
899: + ((nextsibling != null) ? nextsibling.getText()
900: : "");
901: }
902: }
903: }
|