0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.cocoon.transformation;
0018:
0019: import org.apache.avalon.framework.activity.Disposable;
0020: import org.apache.avalon.framework.configuration.Configurable;
0021: import org.apache.avalon.framework.configuration.Configuration;
0022: import org.apache.avalon.framework.configuration.ConfigurationException;
0023: import org.apache.avalon.framework.parameters.Parameters;
0024: import org.apache.avalon.framework.service.ServiceException;
0025: import org.apache.avalon.framework.service.ServiceManager;
0026: import org.apache.avalon.framework.service.Serviceable;
0027:
0028: import org.apache.cocoon.ProcessingException;
0029: import org.apache.cocoon.environment.Context;
0030: import org.apache.cocoon.environment.ObjectModelHelper;
0031: import org.apache.cocoon.environment.Request;
0032: import org.apache.cocoon.environment.Response;
0033: import org.apache.cocoon.environment.SourceResolver;
0034: import org.apache.cocoon.transformation.helpers.ParametersRecorder;
0035: import org.apache.cocoon.transformation.helpers.TextRecorder;
0036: import org.apache.cocoon.util.ClassUtils;
0037: import org.apache.cocoon.util.TraxErrorHandler;
0038: import org.apache.cocoon.xml.AttributesImpl;
0039: import org.apache.cocoon.xml.ImmutableAttributesImpl;
0040: import org.apache.cocoon.xml.IncludeXMLConsumer;
0041: import org.apache.cocoon.xml.SaxBuffer;
0042: import org.apache.cocoon.xml.XMLConsumer;
0043: import org.apache.cocoon.xml.XMLUtils;
0044: import org.apache.cocoon.xml.dom.DOMBuilder;
0045:
0046: import org.apache.excalibur.source.SourceParameters;
0047: import org.apache.excalibur.xml.sax.XMLizable;
0048: import org.w3c.dom.Document;
0049: import org.w3c.dom.DocumentFragment;
0050: import org.w3c.dom.Node;
0051: import org.xml.sax.Attributes;
0052: import org.xml.sax.ContentHandler;
0053: import org.xml.sax.Locator;
0054: import org.xml.sax.SAXException;
0055: import org.xml.sax.ext.LexicalHandler;
0056:
0057: import javax.xml.transform.TransformerFactory;
0058: import javax.xml.transform.sax.SAXTransformerFactory;
0059: import java.io.IOException;
0060: import java.util.ArrayList;
0061: import java.util.Iterator;
0062: import java.util.List;
0063: import java.util.Map;
0064: import java.util.Properties;
0065: import java.util.Stack;
0066:
0067: /**
0068: * This class is the basis for all transformers. It provides various useful
0069: * methods and hooks for implementing own custom transformers.
0070: *
0071: * <p>The basic behaviour of each transformer consists of the following four
0072: * parts:</p>
0073: * <ul>
0074: * <li>Listen for specific events with a given namespace</li>
0075: * <li>Collect information via these events</li>
0076: * <li>Process the information</li>
0077: * <li>Create new events from the processed information</li>
0078: * </ul>
0079: *
0080: * <p>For all these four purposes the AbstractSAXTransformer offers some
0081: * powerful methods and hooks:</p>
0082: *
0083: * <h3>Namespace handling</h3>
0084: * By setting the instance variable namespaceURI to the namespace the
0085: * events are filtered and only events with this namespace are send to
0086: * the two hooks: <code>startTransformingElement</code> and
0087: * <code>endTransformingElement</code>. It is possible to override the default
0088: * namespace for the transformer by specifying the parameter "namespaceURI"
0089: * in the pipeline. This avoids possible namespace collisions.
0090: *
0091: * <h3>Recording of information</h3>
0092: * There are several methods for recording information, e.g. startRecording(),
0093: * startTextRecording() etc. These methods collect information from the xml
0094: * stream for further processing.
0095: *
0096: * <h3>Creating new events</h3>
0097: * New events can be easily created with the <code>sendEvents()</code>
0098: * method, the <code>sendStartElementEvent()</code> methods, the
0099: * <code>sendEndElementEvent()</code> method or the
0100: * <code>sendTextEvent()</code> method.
0101: *
0102: * <h3>Initialization</h3>
0103: * Before the document is processed the <code>setupTransforming</code> hook
0104: * is invoked.
0105: *
0106: * @author <a href="mailto:cziegeler@s-und-n.de">Carsten Ziegeler</a>
0107: * @version $Id: AbstractSAXTransformer.java 433543 2006-08-22 06:22:54Z crossley $
0108: */
0109: public abstract class AbstractSAXTransformer extends
0110: AbstractTransformer implements Serviceable, Configurable,
0111: Disposable {
0112:
0113: /**
0114: * Empty immutable attributes (for performance). Use them
0115: * whenever creating an element with no attributes.
0116: */
0117: protected static final Attributes EMPTY_ATTRIBUTES = XMLUtils.EMPTY_ATTRIBUTES;
0118:
0119: /**
0120: * The trax <code>TransformerFactory</code> used by this transformer.
0121: */
0122: private SAXTransformerFactory tfactory;
0123:
0124: /**
0125: * Controlls SAX event handling.
0126: * If set to true all whitespace events are ignored.
0127: */
0128: protected boolean ignoreWhitespaces;
0129:
0130: /**
0131: * Controlls SAX event handling.
0132: * If set to true all characters events containing only whitespaces
0133: * are ignored.
0134: */
0135: protected boolean ignoreEmptyCharacters;
0136:
0137: /**
0138: * Controlls SAX event handling.
0139: * If this is incremented all events are not forwarded to the next
0140: * pipeline component, but the hooks are still called.
0141: */
0142: protected int ignoreEventsCount;
0143:
0144: /**
0145: * Controlls SAX event handling.
0146: * If this is greater than zero, the hooks are not called. Attention,
0147: * make sure, that you decrement this counter properly as your hooks are
0148: * not called anymore!
0149: */
0150: protected int ignoreHooksCount;
0151:
0152: /**
0153: * The namespace used by the transformer for the SAX events filtering.
0154: * This either equals to the {@link #defaultNamespaceURI} or to the value
0155: * set by the <code>namespaceURI</code> sitemap parameter for the pipeline.
0156: * Must never be null.
0157: */
0158: protected String namespaceURI;
0159:
0160: /**
0161: * This is the default namespace used by the transformer.
0162: * Implementations should set its value in the constructor.
0163: * Must never be null.
0164: */
0165: protected String defaultNamespaceURI;
0166:
0167: /**
0168: * A stack for collecting information.
0169: * The stack is important for collection information especially when
0170: * the tags can be nested.
0171: */
0172: protected final Stack stack = new Stack();
0173:
0174: /**
0175: * The stack of current used recorders
0176: */
0177: protected final Stack recorderStack = new Stack();
0178:
0179: /**
0180: * The current Request object
0181: */
0182: protected Request request;
0183:
0184: /**
0185: * The current Response object
0186: */
0187: protected Response response;
0188:
0189: /**
0190: * The current Context object
0191: */
0192: protected Context context;
0193:
0194: /**
0195: * The current objectModel of the environment
0196: */
0197: protected Map objectModel;
0198:
0199: /**
0200: * The parameters specified in the sitemap
0201: */
0202: protected Parameters parameters;
0203:
0204: /**
0205: * The source attribute specified in the sitemap
0206: */
0207: protected String source;
0208:
0209: /**
0210: * The Avalon ServiceManager for getting Components
0211: */
0212: protected ServiceManager manager;
0213:
0214: /**
0215: * The SourceResolver for this request
0216: */
0217: protected SourceResolver resolver;
0218:
0219: /**
0220: * Are we already initialized for the current request?
0221: */
0222: private boolean isInitialized;
0223:
0224: /**
0225: * Empty attributes (for performance). This can be used
0226: * do create own attributes, but make sure to clean them
0227: * afterwords.
0228: * @deprecated Use {@link AbstractSAXTransformer#EMPTY_ATTRIBUTES}.
0229: */
0230: protected Attributes emptyAttributes = EMPTY_ATTRIBUTES;
0231:
0232: /**
0233: * The namespaces and their prefixes
0234: */
0235: private final List namespaces = new ArrayList(5);
0236:
0237: /**
0238: * The current prefix for our namespace
0239: */
0240: private String ourPrefix;
0241:
0242: //
0243: // Lifecycle
0244: //
0245:
0246: /* (non-Javadoc)
0247: * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
0248: */
0249: public void service(ServiceManager manager) throws ServiceException {
0250: this .manager = manager;
0251: }
0252:
0253: /* (non-Javadoc)
0254: * @see Configurable#configure(Configuration)
0255: */
0256: public void configure(Configuration configuration)
0257: throws ConfigurationException {
0258: String tFactoryClass = configuration.getChild(
0259: "transformer-factory").getValue(null);
0260: if (tFactoryClass != null) {
0261: try {
0262: this .tfactory = (SAXTransformerFactory) ClassUtils
0263: .newInstance(tFactoryClass);
0264: if (getLogger().isDebugEnabled()) {
0265: getLogger().debug(
0266: "Using transformer factory "
0267: + tFactoryClass);
0268: }
0269: } catch (Exception e) {
0270: throw new ConfigurationException(
0271: "Cannot load transformer factory "
0272: + tFactoryClass, e);
0273: }
0274: } else {
0275: // Standard TrAX behaviour
0276: this .tfactory = (SAXTransformerFactory) TransformerFactory
0277: .newInstance();
0278: }
0279: tfactory.setErrorListener(new TraxErrorHandler(getLogger()));
0280: }
0281:
0282: /* (non-Javadoc)
0283: * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(SourceResolver, Map, String, Parameters)
0284: */
0285: public void setup(SourceResolver resolver, Map objectModel,
0286: String src, Parameters params) throws ProcessingException,
0287: SAXException, IOException {
0288:
0289: if (getLogger().isDebugEnabled()) {
0290: getLogger().debug(
0291: "Setup resolver=" + resolver + ", objectModel="
0292: + objectModel + ", src=" + src
0293: + ", parameters=" + params);
0294: }
0295:
0296: // defaultNamespaceURI should never be null
0297: if (this .defaultNamespaceURI == null) {
0298: this .defaultNamespaceURI = "";
0299: }
0300: this .objectModel = objectModel;
0301:
0302: this .request = ObjectModelHelper.getRequest(objectModel);
0303: this .response = ObjectModelHelper.getResponse(objectModel);
0304: this .context = ObjectModelHelper.getContext(objectModel);
0305: this .resolver = resolver;
0306: this .parameters = params;
0307: this .source = src;
0308: this .isInitialized = false;
0309:
0310: // get the current namespace
0311: this .namespaceURI = params.getParameter("namespaceURI",
0312: this .defaultNamespaceURI);
0313:
0314: this .ignoreHooksCount = 0;
0315: this .ignoreEventsCount = 0;
0316: this .ignoreWhitespaces = true;
0317: this .ignoreEmptyCharacters = false;
0318: }
0319:
0320: /* (non-Javadoc)
0321: * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
0322: */
0323: public void recycle() {
0324: this .namespaceURI = null;
0325: this .objectModel = null;
0326: this .request = null;
0327: this .response = null;
0328: this .context = null;
0329: this .resolver = null;
0330: this .stack.clear();
0331: this .recorderStack.clear();
0332: this .parameters = null;
0333: this .source = null;
0334: this .namespaces.clear();
0335: this .ourPrefix = null;
0336:
0337: super .recycle();
0338: }
0339:
0340: public void dispose() {
0341: this .manager = null;
0342: }
0343:
0344: //
0345: // SAX ContentHandler methods
0346: //
0347:
0348: /**
0349: * Process the SAX event.
0350: * @see ContentHandler#setDocumentLocator
0351: */
0352: public void setDocumentLocator(Locator locator) {
0353: if (this .ignoreEventsCount == 0) {
0354: super .setDocumentLocator(locator);
0355: }
0356: }
0357:
0358: /**
0359: * Process the SAX event. A new document is processed. The hook method
0360: * {@link #setupTransforming} is invoked.
0361: * @see ContentHandler#startDocument
0362: */
0363: public void startDocument() throws SAXException {
0364: if (!this .isInitialized) {
0365: try {
0366: setupTransforming();
0367: } catch (ProcessingException e) {
0368: throw new SAXException("ProcessingException: " + e, e);
0369: } catch (IOException e) {
0370: throw new SAXException("IOException: " + e, e);
0371: }
0372: this .isInitialized = true;
0373: }
0374:
0375: if (this .ignoreEventsCount == 0) {
0376: super .startDocument();
0377: }
0378: }
0379:
0380: /**
0381: * Process the SAX event. The processing of the document is finished.
0382: * @see org.xml.sax.ContentHandler#endDocument
0383: */
0384: public void endDocument() throws SAXException {
0385: if (this .ignoreEventsCount == 0) {
0386: super .endDocument();
0387: }
0388: }
0389:
0390: /**
0391: * Process the SAX event.
0392: * @see org.xml.sax.ContentHandler#startPrefixMapping
0393: */
0394: public void startPrefixMapping(String prefix, String uri)
0395: throws SAXException {
0396: if (prefix != null) {
0397: this .namespaces.add(new String[] { prefix, uri });
0398: }
0399: if (namespaceURI.equals(uri)) {
0400: this .ourPrefix = prefix;
0401: }
0402: if (this .ignoreEventsCount == 0) {
0403: super .startPrefixMapping(prefix, uri);
0404: }
0405: }
0406:
0407: /**
0408: * Process the SAX event.
0409: * @see org.xml.sax.ContentHandler#endPrefixMapping
0410: */
0411: public void endPrefixMapping(String prefix) throws SAXException {
0412:
0413: if (prefix != null) {
0414: // Find and remove the namespace prefix
0415: boolean found = false;
0416: for (int i = this .namespaces.size() - 1; i >= 0; i--) {
0417: final String[] prefixAndUri = (String[]) this .namespaces
0418: .get(i);
0419: if (prefixAndUri[0].equals(prefix)) {
0420: this .namespaces.remove(i);
0421: found = true;
0422: break;
0423: }
0424: }
0425: if (!found) {
0426: throw new SAXException("Namespace for prefix '"
0427: + prefix + "' not found.");
0428: }
0429:
0430: if (prefix.equals(this .ourPrefix)) {
0431: // Reset our current prefix
0432: this .ourPrefix = null;
0433:
0434: // Now search if we have a different prefix for our namespace
0435: for (int i = this .namespaces.size() - 1; i >= 0; i--) {
0436: final String[] prefixAndUri = (String[]) this .namespaces
0437: .get(i);
0438: if (namespaceURI.equals(prefixAndUri[1])) {
0439: this .ourPrefix = prefixAndUri[0];
0440: break;
0441: }
0442: }
0443: }
0444: }
0445:
0446: if (this .ignoreEventsCount == 0) {
0447: super .endPrefixMapping(prefix);
0448: }
0449: }
0450:
0451: /**
0452: * Process the SAX event. The namespace of the event is checked.
0453: * If it is the defined namespace for this transformer,
0454: * the {@link #startTransformingElement} hook is called.
0455: * @see org.xml.sax.ContentHandler#startElement
0456: */
0457: public void startElement(String uri, String name, String raw,
0458: Attributes attr) throws SAXException {
0459: if (namespaceURI.equals(uri) && ignoreHooksCount == 0) {
0460: // this is our namespace:
0461: try {
0462: startTransformingElement(uri, name, raw, attr);
0463: } catch (ProcessingException e) {
0464: throw new SAXException("ProcessingException: " + e, e);
0465: } catch (IOException e) {
0466: throw new SAXException(
0467: "IOException occured during processing: " + e,
0468: e);
0469: }
0470: } else {
0471: if (ignoreEventsCount == 0) {
0472: super .startElement(uri, name, raw, attr);
0473: }
0474: }
0475: }
0476:
0477: /**
0478: * Process the SAX event. The namespace of the event is checked.
0479: * If it is the defined namespace for this transformer,
0480: * the {@link #endTransformingElement} hook is called.
0481: * @see org.xml.sax.ContentHandler#endElement
0482: */
0483: public void endElement(String uri, String name, String raw)
0484: throws SAXException {
0485: if (namespaceURI.equals(uri) && this .ignoreHooksCount == 0) {
0486: // this is our namespace:
0487: try {
0488: endTransformingElement(uri, name, raw);
0489: } catch (ProcessingException e) {
0490: throw new SAXException("ProcessingException: " + e, e);
0491: } catch (IOException e) {
0492: throw new SAXException(
0493: "IOException occured during processing: " + e,
0494: e);
0495: }
0496: } else {
0497: if (ignoreEventsCount == 0) {
0498: super .endElement(uri, name, raw);
0499: }
0500: }
0501: }
0502:
0503: /**
0504: * Process the SAX event.
0505: * @see org.xml.sax.ContentHandler#characters
0506: */
0507: public void characters(char[] p0, int p1, int p2)
0508: throws SAXException {
0509: if (this .ignoreEventsCount == 0) {
0510: if (this .ignoreEmptyCharacters) {
0511: String value = new String(p0, p1, p2);
0512: if (value.trim().length() > 0) {
0513: super .characters(p0, p1, p2);
0514: }
0515: } else {
0516: super .characters(p0, p1, p2);
0517: }
0518: }
0519: }
0520:
0521: /**
0522: * Process the SAX event.
0523: * @see org.xml.sax.ContentHandler#ignorableWhitespace
0524: */
0525: public void ignorableWhitespace(char[] p0, int p1, int p2)
0526: throws SAXException {
0527: if (ignoreWhitespaces == false && ignoreEventsCount == 0) {
0528: super .ignorableWhitespace(p0, p1, p2);
0529: }
0530: }
0531:
0532: /**
0533: * Process the SAX event.
0534: * @see ContentHandler#processingInstruction
0535: */
0536: public void processingInstruction(String target, String data)
0537: throws SAXException {
0538: if (this .ignoreEventsCount == 0) {
0539: super .processingInstruction(target, data);
0540: }
0541: }
0542:
0543: /**
0544: * Process the SAX event.
0545: * @see ContentHandler#skippedEntity
0546: */
0547: public void skippedEntity(String name) throws SAXException {
0548: if (this .ignoreEventsCount == 0) {
0549: super .skippedEntity(name);
0550: }
0551: }
0552:
0553: //
0554: // SAX LexicalHandler methods
0555: //
0556:
0557: /**
0558: * @see LexicalHandler#startDTD
0559: */
0560: public void startDTD(String name, String public_id, String system_id)
0561: throws SAXException {
0562: if (this .ignoreEventsCount == 0) {
0563: super .startDTD(name, public_id, system_id);
0564: }
0565: }
0566:
0567: /**
0568: * @see LexicalHandler#endDTD
0569: */
0570: public void endDTD() throws SAXException {
0571: if (this .ignoreEventsCount == 0) {
0572: super .endDTD();
0573: }
0574: }
0575:
0576: /**
0577: * @see LexicalHandler#startEntity
0578: */
0579: public void startEntity(String name) throws SAXException {
0580: if (this .ignoreEventsCount == 0) {
0581: super .startEntity(name);
0582: }
0583: }
0584:
0585: /**
0586: * @see LexicalHandler#endEntity
0587: */
0588: public void endEntity(String name) throws SAXException {
0589: if (this .ignoreEventsCount == 0) {
0590: super .endEntity(name);
0591: }
0592: }
0593:
0594: /**
0595: * @see LexicalHandler#startCDATA
0596: */
0597: public void startCDATA() throws SAXException {
0598: if (this .ignoreEventsCount == 0) {
0599: super .startCDATA();
0600: }
0601: }
0602:
0603: /**
0604: * @see LexicalHandler#endCDATA
0605: */
0606: public void endCDATA() throws SAXException {
0607: if (this .ignoreEventsCount == 0) {
0608: super .endCDATA();
0609: }
0610: }
0611:
0612: /**
0613: * @see LexicalHandler#comment
0614: */
0615: public void comment(char ary[], int start, int length)
0616: throws SAXException {
0617: if (this .ignoreEventsCount == 0) {
0618: super .comment(ary, start, length);
0619: }
0620: }
0621:
0622: /*
0623: * Recording of events.
0624: * With this method all events are not forwarded to the next component in the pipeline.
0625: * They are recorded to create a document fragment.
0626: */
0627:
0628: private LexicalHandler originalLexicalHandler;
0629: private ContentHandler originalContentHandler;
0630:
0631: /**
0632: * Add a new recorder to the recording chain.
0633: * Do not invoke this method directly.
0634: */
0635: protected void addRecorder(XMLConsumer recorder) {
0636: if (this .recorderStack.empty()) {
0637: // redirect if first (top) recorder
0638: this .originalLexicalHandler = this .lexicalHandler;
0639: this .originalContentHandler = this .contentHandler;
0640: }
0641: setContentHandler(recorder);
0642: setLexicalHandler(recorder);
0643: this .recorderStack.push(recorder);
0644: }
0645:
0646: /**
0647: * Remove a recorder from the recording chain.
0648: * Do not invoke this method directly.
0649: */
0650: protected Object removeRecorder() {
0651: Object recorder = this .recorderStack.pop();
0652: if (this .recorderStack.empty() == true) {
0653: // undo redirect if no recorder any more
0654: setContentHandler(originalContentHandler);
0655: setLexicalHandler(originalLexicalHandler);
0656: this .originalLexicalHandler = null;
0657: this .originalContentHandler = null;
0658: } else {
0659: XMLConsumer next = (XMLConsumer) recorderStack.peek();
0660: setContentHandler(next);
0661: setLexicalHandler(next);
0662: }
0663:
0664: return recorder;
0665: }
0666:
0667: /**
0668: * Start recording of SAX events.
0669: * All incoming events are recorded and not forwarded. The resulting
0670: * XMLizable can be obtained by the matching {@link #endSAXRecording} call.
0671: * @since 2.1.5
0672: */
0673: public void startSAXRecording() throws SAXException {
0674: addRecorder(new SaxBuffer());
0675: sendStartPrefixMapping();
0676: }
0677:
0678: /**
0679: * Stop recording of SAX events.
0680: * This method returns the resulting XMLizable.
0681: * @since 2.1.5
0682: */
0683: public XMLizable endSAXRecording() throws SAXException {
0684: sendEndPrefixMapping();
0685: return (XMLizable) removeRecorder();
0686: }
0687:
0688: /**
0689: * Start recording of a text.
0690: * No events forwarded, and all characters events
0691: * are collected into a string.
0692: */
0693: public void startTextRecording() throws SAXException {
0694: if (getLogger().isDebugEnabled()) {
0695: getLogger().debug("Start text recording");
0696: }
0697: addRecorder(new TextRecorder());
0698: sendStartPrefixMapping();
0699: }
0700:
0701: /**
0702: * Stop recording of text and return the recorded information.
0703: * @return The String, trimmed.
0704: */
0705: public String endTextRecording() throws SAXException {
0706: sendEndPrefixMapping();
0707:
0708: TextRecorder recorder = (TextRecorder) removeRecorder();
0709: String text = recorder.getText();
0710: if (getLogger().isDebugEnabled()) {
0711: getLogger().debug("End text recording. Text=" + text);
0712: }
0713: return text;
0714: }
0715:
0716: /**
0717: * Start recording of serialized xml
0718: * All events are converted to an xml string which can be retrieved by
0719: * endSerializedXMLRecording.
0720: * @param format The format for the serialized output. If <CODE>null</CODE>
0721: * is specified, the default format is used.
0722: */
0723: public void startSerializedXMLRecording(Properties format)
0724: throws SAXException {
0725: if (getLogger().isDebugEnabled()) {
0726: getLogger().debug(
0727: "Start serialized XML recording. Format=" + format);
0728: }
0729: this .stack.push(format == null ? XMLUtils
0730: .createPropertiesForXML(false) : format);
0731: startSAXRecording();
0732: }
0733:
0734: /**
0735: * Return the serialized xml string.
0736: * @return A string containing the recorded xml information, formatted by
0737: * the properties passed to the corresponding startSerializedXMLRecording().
0738: */
0739: public String endSerializedXMLRecording() throws SAXException,
0740: ProcessingException {
0741: XMLizable xml = endSAXRecording();
0742: String text = XMLUtils.serialize(xml, (Properties) this .stack
0743: .pop());
0744: if (getLogger().isDebugEnabled()) {
0745: getLogger().debug(
0746: "End serialized XML recording. XML=" + text);
0747: }
0748: return text;
0749: }
0750:
0751: /**
0752: * Start recording of parameters.
0753: * All events are not forwarded and the incoming xml is converted to
0754: * parameters. Each toplevel node is a parameter and its text subnodes
0755: * form the value.
0756: * The Parameters can eiter be retrieved by endParametersRecording().
0757: */
0758: public void startParametersRecording() throws SAXException {
0759: if (getLogger().isDebugEnabled()) {
0760: getLogger().debug("Start parameters recording");
0761: }
0762: addRecorder(new ParametersRecorder());
0763: sendStartPrefixMapping();
0764: }
0765:
0766: /**
0767: * End recording of parameters
0768: * If source is null a new parameters object is created, otherwise
0769: * the parameters are added to this object.
0770: * @param source An optional parameters object.
0771: * @return The object containing all parameters.
0772: */
0773: public SourceParameters endParametersRecording(Parameters source)
0774: throws SAXException {
0775: sendEndPrefixMapping();
0776:
0777: ParametersRecorder recorder = (ParametersRecorder) this
0778: .removeRecorder();
0779: SourceParameters parameters = recorder.getParameters(source);
0780: if (getLogger().isDebugEnabled()) {
0781: getLogger().debug(
0782: "End parameters recording. Parameters="
0783: + parameters);
0784: }
0785: return parameters;
0786: }
0787:
0788: /**
0789: * End recording of parameters
0790: * If source is null a new parameters object is created, otherwise
0791: * the parameters are added to this object.
0792: * @param source An optional parameters object.
0793: * @return The object containing all parameters.
0794: */
0795: public SourceParameters endParametersRecording(
0796: SourceParameters source) throws SAXException {
0797: sendEndPrefixMapping();
0798:
0799: ParametersRecorder recorder = (ParametersRecorder) removeRecorder();
0800: SourceParameters parameters = recorder.getParameters(source);
0801: if (getLogger().isDebugEnabled()) {
0802: getLogger().debug(
0803: "End parameters recording. Parameters="
0804: + parameters);
0805: }
0806: return parameters;
0807: }
0808:
0809: /**
0810: * Start DOM DocumentFragment recording.
0811: * All incoming events are recorded and not forwarded. The resulting
0812: * DocumentFragment can be obtained by the matching {@link #endRecording} call.
0813: */
0814: public void startRecording() throws SAXException {
0815: if (getLogger().isDebugEnabled()) {
0816: getLogger().debug("Start recording");
0817: }
0818: DOMBuilder builder = new DOMBuilder(this .tfactory);
0819: addRecorder(builder);
0820: builder.startDocument();
0821: builder.startElement("", "cocoon", "cocoon", EMPTY_ATTRIBUTES);
0822: sendStartPrefixMapping();
0823: }
0824:
0825: /**
0826: * Stop DOM DocumentFragment recording.
0827: * This method returns the resulting DocumentFragment, normalized.
0828: */
0829: public DocumentFragment endRecording() throws SAXException {
0830: sendEndPrefixMapping();
0831:
0832: DOMBuilder builder = (DOMBuilder) removeRecorder();
0833: builder.endElement("", "cocoon", "cocoon");
0834: builder.endDocument();
0835:
0836: // Create Document Fragment
0837: final Document doc = builder.getDocument();
0838: final DocumentFragment fragment = doc.createDocumentFragment();
0839: final Node root = doc.getDocumentElement();
0840:
0841: // Remove empty text nodes and collapse neighbouring text nodes
0842: root.normalize();
0843:
0844: // Move all nodes into the fragment
0845: boolean space = true;
0846: while (root.hasChildNodes()) {
0847: Node child = root.getFirstChild();
0848: root.removeChild(child);
0849:
0850: // Leave out leading whitespace nodes
0851: // FIXME: Why leading spaces are trimmed at all? Why not trailing spaces?
0852: if (space && child.getNodeType() == Node.TEXT_NODE
0853: && child.getNodeValue().trim().length() == 0) {
0854: continue;
0855: }
0856: space = false;
0857:
0858: fragment.appendChild(child);
0859: }
0860:
0861: if (getLogger().isDebugEnabled()) {
0862: Object serializedXML = null;
0863: try {
0864: serializedXML = fragment == null ? "null" : XMLUtils
0865: .serializeNode(fragment, XMLUtils
0866: .createPropertiesForXML(false));
0867: } catch (ProcessingException ignore) {
0868: serializedXML = fragment;
0869: }
0870: getLogger().debug(
0871: "End recording. Fragment=" + serializedXML);
0872: }
0873:
0874: return fragment;
0875: }
0876:
0877: //
0878: // Hooks
0879: //
0880:
0881: /**
0882: * Setup the transformation of an xml document.
0883: * This method is called just before the transformation (sending of sax events)
0884: * starts. It should be used to initialize setup parameter depending on the
0885: * object modell.
0886: */
0887: public void setupTransforming() throws IOException,
0888: ProcessingException, SAXException {
0889: if (getLogger().isDebugEnabled()) {
0890: getLogger().debug("setupTransforming");
0891: }
0892: this .stack.clear();
0893: this .recorderStack.clear();
0894: this .ignoreWhitespaces = true;
0895: this .ignoreEmptyCharacters = false;
0896: }
0897:
0898: /**
0899: * Start processing elements of our namespace.
0900: * This hook is invoked for each sax event with our namespace.
0901: * @param uri The namespace of the element.
0902: * @param name The local name of the element.
0903: * @param raw The qualified name of the element.
0904: * @param attr The attributes of the element.
0905: */
0906: public void startTransformingElement(String uri, String name,
0907: String raw, Attributes attr) throws ProcessingException,
0908: IOException, SAXException {
0909: if (this .ignoreEventsCount == 0) {
0910: super .startElement(uri, name, raw, attr);
0911: }
0912: }
0913:
0914: /**
0915: * Start processing elements of our namespace.
0916: * This hook is invoked for each sax event with our namespace.
0917: * @param uri The namespace of the element.
0918: * @param name The local name of the element.
0919: * @param raw The qualified name of the element.
0920: */
0921: public void endTransformingElement(String uri, String name,
0922: String raw) throws ProcessingException, IOException,
0923: SAXException {
0924: if (this .ignoreEventsCount == 0) {
0925: super .endElement(uri, name, raw);
0926: }
0927: }
0928:
0929: /**
0930: * Send SAX events to the next pipeline component.
0931: * The characters event for the given text is send to the next
0932: * component in the current pipeline.
0933: * @param text The string containing the information.
0934: */
0935: public void sendTextEvent(String text) throws SAXException {
0936: characters(text.toCharArray(), 0, text.length());
0937: }
0938:
0939: /**
0940: * Send SAX events to the next pipeline component.
0941: * The startElement event for the given element is send
0942: * to the next component in the current pipeline.
0943: * The element has no namespace and no attributes
0944: * @param localname The name of the event.
0945: */
0946: public void sendStartElementEvent(String localname)
0947: throws SAXException {
0948: startElement("", localname, localname, EMPTY_ATTRIBUTES);
0949: }
0950:
0951: /**
0952: * Send SAX events to the next pipeline component.
0953: * The startElement event for the given element is send
0954: * to the next component in the current pipeline.
0955: * The element has the namespace of the transformer,
0956: * but not attributes
0957: * @param localname The name of the event.
0958: */
0959: public void sendStartElementEventNS(String localname)
0960: throws SAXException {
0961: startElement(this .namespaceURI, localname, this .ourPrefix + ':'
0962: + localname, EMPTY_ATTRIBUTES);
0963: }
0964:
0965: /**
0966: * Send SAX events to the next pipeline component.
0967: * The startElement event for the given element is send
0968: * to the next component in the current pipeline.
0969: * The element has no namespace.
0970: * @param localname The name of the event.
0971: * @param attr The Attributes of the element
0972: */
0973: public void sendStartElementEvent(String localname, Attributes attr)
0974: throws SAXException {
0975: startElement("", localname, localname, attr);
0976: }
0977:
0978: /**
0979: * Send SAX events to the next pipeline component.
0980: * The startElement event for the given element is send
0981: * to the next component in the current pipeline.
0982: * The element has the namespace of the transformer.
0983: * @param localname The name of the event.
0984: * @param attr The Attributes of the element
0985: */
0986: public void sendStartElementEventNS(String localname,
0987: Attributes attr) throws SAXException {
0988: startElement(this .namespaceURI, localname, this .ourPrefix + ':'
0989: + localname, attr);
0990: }
0991:
0992: /**
0993: * Send SAX events to the next pipeline component.
0994: * The endElement event for the given element is send
0995: * to the next component in the current pipeline.
0996: * The element has no namespace.
0997: * @param localname The name of the event.
0998: */
0999: public void sendEndElementEvent(String localname)
1000: throws SAXException {
1001: endElement("", localname, localname);
1002: }
1003:
1004: /**
1005: * Send SAX events to the next pipeline component.
1006: * The endElement event for the given element is send
1007: * to the next component in the current pipeline.
1008: * The element has the namespace of the transformer.
1009: * @param localname The name of the event.
1010: */
1011: public void sendEndElementEventNS(String localname)
1012: throws SAXException {
1013: endElement(this .namespaceURI, localname, this .ourPrefix + ':'
1014: + localname);
1015: }
1016:
1017: /**
1018: * Send SAX events to the next pipeline component.
1019: * The node is parsed and the events are send to
1020: * the next component in the pipeline.
1021: * @param node The tree to be included.
1022: */
1023: public void sendEvents(Node node) throws SAXException {
1024: IncludeXMLConsumer.includeNode(node, this , this );
1025: }
1026:
1027: /**
1028: * Send SAX events for the <code>SourceParameters</code>.
1029: * For each parametername/value pair an element is
1030: * created with the name of the parameter and the content
1031: * of this element is the value.
1032: */
1033: public void sendParametersEvents(SourceParameters pars)
1034: throws SAXException {
1035:
1036: if (pars != null) {
1037: Iterator names = pars.getParameterNames();
1038: while (names.hasNext()) {
1039: final String currentName = (String) names.next();
1040: Iterator values = pars.getParameterValues(currentName);
1041: while (values.hasNext()) {
1042: final String currentValue = (String) values.next();
1043: sendStartElementEvent(currentName);
1044: sendTextEvent(currentValue);
1045: sendEndElementEvent(currentName);
1046: }
1047: }
1048: }
1049: }
1050:
1051: /**
1052: * Send all start prefix mapping events to the current content handler
1053: */
1054: protected void sendStartPrefixMapping() throws SAXException {
1055: final int l = this .namespaces.size();
1056: for (int i = 0; i < l; i++) {
1057: String[] prefixAndUri = (String[]) this .namespaces.get(i);
1058: super .contentHandler.startPrefixMapping(prefixAndUri[0],
1059: prefixAndUri[1]);
1060: }
1061: }
1062:
1063: /**
1064: * Send all end prefix mapping events to the current content handler
1065: */
1066: protected void sendEndPrefixMapping() throws SAXException {
1067: final int l = this .namespaces.size();
1068: for (int i = 0; i < l; i++) {
1069: String[] prefixAndUri = (String[]) this .namespaces.get(i);
1070: super .contentHandler.endPrefixMapping(prefixAndUri[0]);
1071: }
1072: }
1073:
1074: /**
1075: * Find prefix mapping for the given namespace URI.
1076: * @return Prefix mapping or null if no prefix defined
1077: */
1078: protected String findPrefixMapping(String uri) {
1079: final int l = this .namespaces.size();
1080: for (int i = 0; i < l; i++) {
1081: String[] prefixAndUri = (String[]) this .namespaces.get(i);
1082: if (prefixAndUri[1].equals(uri)) {
1083: return prefixAndUri[0];
1084: }
1085: }
1086:
1087: return null;
1088: }
1089:
1090: /**
1091: * Helper method to get a modifiable attribute set.
1092: */
1093: protected AttributesImpl getMutableAttributes(Attributes a) {
1094: if (a instanceof AttributesImpl
1095: && !(a instanceof ImmutableAttributesImpl)) {
1096: return (AttributesImpl) a;
1097: }
1098: return new AttributesImpl(a);
1099: }
1100: }
|