001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.om.AttributeCollectionImpl;
004: import net.sf.saxon.om.NamespaceConstant;
005: import net.sf.saxon.trans.DynamicError;
006: import net.sf.saxon.trans.SaxonErrorCode;
007: import net.sf.saxon.trans.XPathException;
008: import net.sf.saxon.type.SchemaException;
009: import net.sf.saxon.value.Whitespace;
010: import org.xml.sax.ContentHandler;
011: import org.xml.sax.Locator;
012: import org.xml.sax.SAXException;
013: import org.xml.sax.ext.LexicalHandler;
014:
015: import javax.xml.transform.Result;
016: import java.util.Properties;
017: import java.util.Stack;
018:
019: /**
020: * A ContentHandlerProxy is an Emitter that filters data before passing it to an
021: * underlying SAX2 ContentHandler. Relevant events (notably comments) can also be
022: * fed to a LexicalHandler.
023: * <p/>
024: * Note that in general the output passed to an Emitter
025: * corresponds to an External General Parsed Entity. A SAX2 ContentHandler only expects
026: * to deal with well-formed XML documents, so we only pass it the contents of the first
027: * element encountered, unless the saxon:require-well-formed output property is set to "no".
028: * </p><p>
029: * This ContentHandlerProxy provides no access to type information. For a ContentHandler that
030: * makes type information available, see {@link net.sf.saxon.dom.TypedContentHandler}
031: */
032:
033: public class ContentHandlerProxy extends Emitter implements Locator {
034: protected ContentHandler handler;
035: protected LexicalHandler lexicalHandler;
036: private LocationProvider locationProvider;
037: private int depth = 0;
038: private boolean requireWellFormed = false;
039: private boolean undeclareNamespaces = false;
040: private Stack elementStack = new Stack();
041: private Stack namespaceStack = new Stack();
042: protected AttributeCollectionImpl pendingAttributes;
043: private int pendingElement = -1;
044: private int currentLocationId;
045:
046: private static final String marker = "##";
047:
048: /**
049: * Set the underlying content handler. This call is mandatory before using the Emitter.
050: */
051:
052: public void setUnderlyingContentHandler(ContentHandler handler) {
053: this .handler = handler;
054: if (handler instanceof LexicalHandler) {
055: this .lexicalHandler = (LexicalHandler) handler;
056: }
057: }
058:
059: /**
060: * Get the underlying content handler
061: */
062:
063: public ContentHandler getUnderlyingContentHandler() {
064: return handler;
065: }
066:
067: /**
068: * Set the Lexical Handler to be used. If called, this must be called AFTER
069: * setUnderlyingContentHandler()
070: */
071:
072: public void setLexicalHandler(LexicalHandler handler) {
073: this .lexicalHandler = handler;
074: }
075:
076: /**
077: * Set the pipeline configuration
078: */
079:
080: public void setPipelineConfiguration(PipelineConfiguration config) {
081: super .setPipelineConfiguration(config);
082: this .locationProvider = config.getLocationProvider();
083: }
084:
085: /**
086: * Set the output details.
087: */
088:
089: public void setOutputProperties(Properties details)
090: throws XPathException {
091: String prop = details
092: .getProperty(SaxonOutputKeys.REQUIRE_WELL_FORMED);
093: if (prop != null) {
094: requireWellFormed = prop.equals("yes");
095: }
096: prop = details.getProperty(SaxonOutputKeys.UNDECLARE_PREFIXES);
097: if (prop != null) {
098: undeclareNamespaces = prop.equals("yes");
099: }
100: super .setOutputProperties(details);
101: }
102:
103: /**
104: * Determine whether the content handler can handle a stream of events that is merely
105: * well-balanced, or whether it can only handle a well-formed sequence.
106: */
107:
108: public boolean isRequireWellFormed() {
109: return requireWellFormed;
110: }
111:
112: /**
113: * Indicate whether the content handler can handle a stream of events that is merely
114: * well-balanced, or whether it can only handle a well-formed sequence.
115: */
116:
117: public void setRequireWellFormed(boolean wellFormed) {
118: requireWellFormed = wellFormed;
119: }
120:
121: /**
122: * Determine whether namespace undeclaration events (for a non-null prefix) should be notified.
123: * The default is no, because some ContentHandlers (e.g. JDOM) can't cope with them.
124: *
125: * @return true if namespace undeclarations (xmlns:p="") are output
126: */
127:
128: public boolean isUndeclareNamespaces() {
129: return undeclareNamespaces;
130: }
131:
132: /**
133: * Determine whether namespace undeclaration events (for a non-null prefix) should be notified.
134: * The default is no, because some ContentHandlers (e.g. JDOM) can't cope with them.
135: *
136: * @param undeclareNamespaces true if namespace undeclarations (xmlns:p="") are to be output
137: */
138:
139: public void setUndeclareNamespaces(boolean undeclareNamespaces) {
140: this .undeclareNamespaces = undeclareNamespaces;
141: }
142:
143: /**
144: * Start of document
145: */
146:
147: public void open() throws XPathException {
148: pendingAttributes = new AttributeCollectionImpl(
149: getPipelineConfiguration().getConfiguration()
150: .getNamePool());
151: if (handler == null) {
152: throw new DynamicError(
153: "ContentHandlerProxy.startDocument(): no underlying handler provided");
154: }
155: try {
156: locationProvider = getPipelineConfiguration()
157: .getLocationProvider();
158: pendingAttributes.setLocationProvider(locationProvider);
159: handler.setDocumentLocator(this );
160: handler.startDocument();
161: } catch (SAXException err) {
162: throw new DynamicError(err);
163: }
164: depth = 0;
165: }
166:
167: /**
168: * End of document
169: */
170:
171: public void close() throws XPathException {
172: try {
173: handler.endDocument();
174: } catch (SAXException err) {
175: throw new DynamicError(err);
176: }
177: }
178:
179: /**
180: * Start of a document node.
181: */
182:
183: public void startDocument(int properties) throws XPathException {
184: }
185:
186: /**
187: * Notify the end of a document node
188: */
189:
190: public void endDocument() throws XPathException {
191: }
192:
193: /**
194: * Notify the start of an element
195: */
196:
197: public void startElement(int nameCode, int typeCode,
198: int locationId, int properties) throws XPathException {
199: depth++;
200: if (depth <= 0 && requireWellFormed) {
201: notifyNotWellFormed();
202: }
203: pendingElement = nameCode;
204: currentLocationId = locationId;
205: namespaceStack.push(marker);
206: }
207:
208: /**
209: * Notify a namespace. Namespaces are notified <b>after</b> the startElement event, and before
210: * any children for the element.
211: */
212:
213: public void namespace(int namespaceCode, int properties)
214: throws XPathException {
215: if (namespaceCode == NamespaceConstant.XML_NAMESPACE_CODE) {
216: return;
217: }
218: String prefix = namePool
219: .getPrefixFromNamespaceCode(namespaceCode);
220: String uri = namePool.getURIFromNamespaceCode(namespaceCode);
221: if ((!undeclareNamespaces) && "".equals(uri)
222: && !("".equals(prefix))) {
223: return;
224: }
225: try {
226: handler.startPrefixMapping(prefix, uri);
227: namespaceStack.push(prefix);
228: } catch (SAXException err) {
229: throw new DynamicError(err);
230: }
231: }
232:
233: /**
234: * Notify an attribute. Attributes are notified after the startElement event, and before any
235: * children.
236: */
237:
238: public void attribute(int nameCode, int typeCode,
239: CharSequence value, int locationId, int properties)
240: throws XPathException {
241: int index = pendingAttributes
242: .getIndexByFingerprint(nameCode & 0xfffff);
243: if (index < 0) {
244: pendingAttributes.addAttribute(nameCode, typeCode, value
245: .toString(), locationId, properties);
246: } else {
247: pendingAttributes.setAttribute(index, nameCode, typeCode,
248: value.toString(), locationId, properties);
249: }
250: }
251:
252: /**
253: * Notify the start of the content, that is, the completion of all attributes and namespaces.
254: * Note that the initial receiver of output from XSLT instructions will not receive this event,
255: * it has to detect it itself. Note that this event is reported for every element even if it has
256: * no attributes, no namespaces, and no content.
257: */
258:
259: public void startContent() throws XPathException {
260: try {
261: if (depth > 0 || !requireWellFormed) {
262: String uri = namePool.getURI(pendingElement);
263: String localName = namePool
264: .getLocalName(pendingElement);
265: String qname = namePool.getDisplayName(pendingElement);
266:
267: handler.startElement(uri, localName, qname,
268: pendingAttributes);
269:
270: elementStack.push(uri);
271: elementStack.push(localName);
272: elementStack.push(qname);
273:
274: pendingAttributes.clear();
275: pendingElement = -1;
276: }
277: } catch (SAXException err) {
278: Exception nested = err.getException();
279: if (nested instanceof XPathException) {
280: throw (XPathException) nested;
281: } else if (nested instanceof SchemaException) {
282: throw new DynamicError(nested);
283: } else {
284: throw new DynamicError(err);
285: }
286: }
287: }
288:
289: /**
290: * End of element
291: */
292:
293: public void endElement() throws XPathException {
294: if (depth > 0) {
295: try {
296: String qname = (String) elementStack.pop();
297: String localName = (String) elementStack.pop();
298: String uri = (String) elementStack.pop();
299: handler.endElement(uri, localName, qname);
300: } catch (SAXException err) {
301: throw new DynamicError(err);
302: }
303: }
304:
305: while (true) {
306: String prefix = (String) namespaceStack.pop();
307: if (prefix.equals(marker)) {
308: break;
309: }
310: try {
311: handler.endPrefixMapping(prefix);
312: } catch (SAXException err) {
313: throw new DynamicError(err);
314: }
315: }
316: depth--;
317: // if this was the outermost element, and well formed output is required
318: // then no further elements will be processed
319: if (requireWellFormed && depth <= 0) {
320: depth = Integer.MIN_VALUE; // crude but effective
321: }
322:
323: }
324:
325: /**
326: * Character data
327: */
328:
329: public void characters(CharSequence chars, int locationId,
330: int properties) throws XPathException {
331: currentLocationId = locationId;
332: boolean disable = ((properties & ReceiverOptions.DISABLE_ESCAPING) != 0);
333: if (disable) {
334: setEscaping(false);
335: }
336: try {
337: if (depth <= 0 && requireWellFormed) {
338: if (Whitespace.isWhite(chars)) {
339: // ignore top-level white space
340: } else {
341: notifyNotWellFormed();
342: }
343: } else {
344: handler.characters(chars.toString().toCharArray(), 0,
345: chars.length());
346: }
347: } catch (SAXException err) {
348: throw new DynamicError(err);
349: }
350: if (disable) {
351: setEscaping(true);
352: }
353: }
354:
355: /**
356: * The following function is called when it is found that the output is not a well-formed document.
357: * Unless the ContentHandler accepts "balanced content", this is a fatal error.
358: */
359:
360: protected void notifyNotWellFormed() throws XPathException {
361: DynamicError err = new DynamicError(
362: "The result tree cannot be supplied to the ContentHandler because it is not well-formed XML");
363: err.setErrorCode(SaxonErrorCode.SXCH0002);
364: throw err;
365: }
366:
367: /**
368: * Processing Instruction
369: */
370:
371: public void processingInstruction(String target, CharSequence data,
372: int locationId, int properties) throws XPathException {
373: currentLocationId = locationId;
374: try {
375: handler.processingInstruction(target, data.toString());
376: } catch (SAXException err) {
377: throw new DynamicError(err);
378: }
379: }
380:
381: /**
382: * Output a comment. Passes it on to the ContentHandler provided that the ContentHandler
383: * is also a SAX2 LexicalHandler.
384: */
385:
386: public void comment(CharSequence chars, int locationId,
387: int properties) throws XPathException {
388: currentLocationId = locationId;
389: try {
390: if (lexicalHandler != null) {
391: lexicalHandler.comment(chars.toString().toCharArray(),
392: 0, chars.length());
393: }
394: } catch (SAXException err) {
395: throw new DynamicError(err);
396: }
397: }
398:
399: /**
400: * Switch escaping on or off. This is called when the XSLT disable-output-escaping attribute
401: * is used to switch escaping on or off. It is not called for other sections of output (e.g.
402: * element names) where escaping is inappropriate. The action, as defined in JAXP 1.1, is
403: * to notify the request to the Content Handler using a processing instruction.
404: */
405:
406: private void setEscaping(boolean escaping) {
407: try {
408: handler.processingInstruction(
409: (escaping ? Result.PI_ENABLE_OUTPUT_ESCAPING
410: : PI_DISABLE_OUTPUT_ESCAPING), "");
411: } catch (SAXException err) {
412: }
413: }
414:
415: ////////////////////////////////////////////////////////////////////
416: // Implementation of Locator interface
417: ////////////////////////////////////////////////////////////////////
418:
419: /**
420: * Get the Public ID
421: * @return null (always)
422: */
423:
424: public String getPublicId() {
425: return null;
426: }
427:
428: /**
429: * Get the System ID
430: * @return the system ID giving the location of the most recent event notified
431: */
432:
433: public String getSystemId() {
434: return locationProvider.getSystemId(currentLocationId);
435: }
436:
437: /**
438: * Get the line number
439: * @return the line number giving the location of the most recent event notified
440: */
441:
442: public int getLineNumber() {
443: return locationProvider.getLineNumber(currentLocationId);
444: }
445:
446: /**
447: * Get the column number
448: * @return -1 (always)
449: */
450:
451: public int getColumnNumber() {
452: return -1;
453: }
454:
455: }
456:
457: //
458: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
459: // you may not use this file except in compliance with the License. You may obtain a copy of the
460: // License at http://www.mozilla.org/MPL/
461: //
462: // Software distributed under the License is distributed on an "AS IS" basis,
463: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
464: // See the License for the specific language governing rights and limitations under the License.
465: //
466: // The Original Code is: all this file.
467: //
468: // The Initial Developer of the Original Code is Michael H. Kay.
469: //
470: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
471: //
472: // Contributor(s): none.
473: //
|