001: /*
002: * Copyright 2001-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: AbstractTranslet.java,v 1.58 2005/07/11 13:18:28 zongaro Exp $
018: */
019:
020: package org.apache.xalan.xsltc.runtime;
021:
022: import java.io.File;
023: import java.io.FileWriter;
024: import java.text.DecimalFormat;
025: import java.text.DecimalFormatSymbols;
026: import java.util.ArrayList;
027: import java.util.Enumeration;
028: import java.util.Vector;
029: import javax.xml.transform.Templates;
030: import javax.xml.parsers.DocumentBuilderFactory;
031: import org.w3c.dom.Document;
032: import org.w3c.dom.DOMImplementation;
033: import javax.xml.parsers.ParserConfigurationException;
034:
035: import org.apache.xml.dtm.DTM;
036:
037: import org.apache.xalan.xsltc.DOM;
038: import org.apache.xalan.xsltc.DOMCache;
039: import org.apache.xalan.xsltc.DOMEnhancedForDTM;
040: import org.apache.xalan.xsltc.Translet;
041: import org.apache.xalan.xsltc.TransletException;
042: import org.apache.xalan.xsltc.dom.DOMAdapter;
043: import org.apache.xalan.xsltc.dom.KeyIndex;
044: import org.apache.xalan.xsltc.runtime.output.TransletOutputHandlerFactory;
045: import org.apache.xml.dtm.DTMAxisIterator;
046: import org.apache.xml.serializer.SerializationHandler;
047:
048: /**
049: * @author Jacek Ambroziak
050: * @author Santiago Pericas-Geertsen
051: * @author Morten Jorgensen
052: * @author G. Todd Miller
053: * @author John Howard, JohnH@schemasoft.com
054: */
055: public abstract class AbstractTranslet implements Translet {
056:
057: // These attributes are extracted from the xsl:output element. They also
058: // appear as fields (with the same type, only public) in Output.java
059: public String _version = "1.0";
060: public String _method = null;
061: public String _encoding = "UTF-8";
062: public boolean _omitHeader = false;
063: public String _standalone = null;
064: public String _doctypePublic = null;
065: public String _doctypeSystem = null;
066: public boolean _indent = false;
067: public String _mediaType = null;
068: public Vector _cdata = null;
069: public int _indentamount = -1;
070:
071: public static final int FIRST_TRANSLET_VERSION = 100;
072: public static final int VER_SPLIT_NAMES_ARRAY = 101;
073: public static final int CURRENT_TRANSLET_VERSION = VER_SPLIT_NAMES_ARRAY;
074:
075: // Initialize Translet version field to base value. A class that extends
076: // AbstractTranslet may override this value to a more recent translet
077: // version; if it doesn't override the value (because it was compiled
078: // before the notion of a translet version was introduced, it will get
079: // this default value).
080: protected int transletVersion = FIRST_TRANSLET_VERSION;
081:
082: // DOM/translet handshaking - the arrays are set by the compiled translet
083: protected String[] namesArray;
084: protected String[] urisArray;
085: protected int[] typesArray;
086: protected String[] namespaceArray;
087:
088: // The Templates object that is used to create this Translet instance
089: protected Templates _templates = null;
090:
091: // Boolean flag to indicate whether this translet has id functions.
092: protected boolean _hasIdCall = false;
093:
094: // TODO - these should only be instanciated when needed
095: protected StringValueHandler stringValueHandler = new StringValueHandler();
096:
097: // Use one empty string instead of constantly instanciating String("");
098: private final static String EMPTYSTRING = "";
099:
100: // This is the name of the index used for ID attributes
101: private final static String ID_INDEX_NAME = "##id";
102:
103: /************************************************************************
104: * Debugging
105: ************************************************************************/
106: public void printInternalState() {
107: System.out.println("-------------------------------------");
108: System.out.println("AbstractTranslet this = " + this );
109: System.out.println("pbase = " + pbase);
110: System.out.println("vframe = " + pframe);
111: System.out
112: .println("paramsStack.size() = " + paramsStack.size());
113: System.out.println("namesArray.size = " + namesArray.length);
114: System.out.println("namespaceArray.size = "
115: + namespaceArray.length);
116: System.out.println("");
117: System.out.println("Total memory = "
118: + Runtime.getRuntime().totalMemory());
119: }
120:
121: /**
122: * Wrap the initial input DOM in a dom adapter. This adapter is wrapped in
123: * a DOM multiplexer if the document() function is used (handled by compiled
124: * code in the translet - see compiler/Stylesheet.compileTransform()).
125: */
126: public final DOMAdapter makeDOMAdapter(DOM dom)
127: throws TransletException {
128: return new DOMAdapter(dom, namesArray, urisArray, typesArray,
129: namespaceArray);
130: }
131:
132: /************************************************************************
133: * Parameter handling
134: ************************************************************************/
135:
136: // Parameter's stack: <tt>pbase</tt> and <tt>pframe</tt> are used
137: // to denote the current parameter frame.
138: protected int pbase = 0, pframe = 0;
139: protected ArrayList paramsStack = new ArrayList();
140:
141: /**
142: * Push a new parameter frame.
143: */
144: public final void pushParamFrame() {
145: paramsStack.add(pframe, new Integer(pbase));
146: pbase = ++pframe;
147: }
148:
149: /**
150: * Pop the topmost parameter frame.
151: */
152: public final void popParamFrame() {
153: if (pbase > 0) {
154: final int oldpbase = ((Integer) paramsStack.get(--pbase))
155: .intValue();
156: for (int i = pframe - 1; i >= pbase; i--) {
157: paramsStack.remove(i);
158: }
159: pframe = pbase;
160: pbase = oldpbase;
161: }
162: }
163:
164: /**
165: * Add a new global parameter if not already in the current frame.
166: * To setParameters of the form {http://foo.bar}xyz
167: * This needs to get mapped to an instance variable in the class
168: * The mapping created so that
169: * the global variables in the generated class become
170: * http$colon$$flash$$flash$foo$dot$bar$colon$xyz
171: */
172: public final Object addParameter(String name, Object value) {
173: name = BasisLibrary.mapQNameToJavaName(name);
174: return addParameter(name, value, false);
175: }
176:
177: /**
178: * Add a new global or local parameter if not already in the current frame.
179: * The 'isDefault' parameter is set to true if the value passed is the
180: * default value from the <xsl:parameter> element's select attribute or
181: * element body.
182: */
183: public final Object addParameter(String name, Object value,
184: boolean isDefault) {
185: // Local parameters need to be re-evaluated for each iteration
186: for (int i = pframe - 1; i >= pbase; i--) {
187: final Parameter param = (Parameter) paramsStack.get(i);
188:
189: if (param._name.equals(name)) {
190: // Only overwrite if current value is the default value and
191: // the new value is _NOT_ the default value.
192: if (param._isDefault || !isDefault) {
193: param._value = value;
194: param._isDefault = isDefault;
195: return value;
196: }
197: return param._value;
198: }
199: }
200:
201: // Add new parameter to parameter stack
202: paramsStack
203: .add(pframe++, new Parameter(name, value, isDefault));
204: return value;
205: }
206:
207: /**
208: * Clears the parameter stack.
209: */
210: public void clearParameters() {
211: pbase = pframe = 0;
212: paramsStack.clear();
213: }
214:
215: /**
216: * Get the value of a parameter from the current frame or
217: * <tt>null</tt> if undefined.
218: */
219: public final Object getParameter(String name) {
220:
221: name = BasisLibrary.mapQNameToJavaName(name);
222:
223: for (int i = pframe - 1; i >= pbase; i--) {
224: final Parameter param = (Parameter) paramsStack.get(i);
225: if (param._name.equals(name))
226: return param._value;
227: }
228: return null;
229: }
230:
231: /************************************************************************
232: * Message handling - implementation of <xsl:message>
233: ************************************************************************/
234:
235: // Holds the translet's message handler - used for <xsl:message>.
236: // The deault message handler dumps a string stdout, but anything can be
237: // used, such as a dialog box for applets, etc.
238: private MessageHandler _msgHandler = null;
239:
240: /**
241: * Set the translet's message handler - must implement MessageHandler
242: */
243: public final void setMessageHandler(MessageHandler handler) {
244: _msgHandler = handler;
245: }
246:
247: /**
248: * Pass a message to the message handler - used by Message class.
249: */
250: public final void displayMessage(String msg) {
251: if (_msgHandler == null) {
252: System.err.println(msg);
253: } else {
254: _msgHandler.displayMessage(msg);
255: }
256: }
257:
258: /************************************************************************
259: * Decimal number format symbol handling
260: ************************************************************************/
261:
262: // Contains decimal number formatting symbols used by FormatNumberCall
263: public Hashtable _formatSymbols = null;
264:
265: /**
266: * Adds a DecimalFormat object to the _formatSymbols hashtable.
267: * The entry is created with the input DecimalFormatSymbols.
268: */
269: public void addDecimalFormat(String name,
270: DecimalFormatSymbols symbols) {
271: // Instanciate hashtable for formatting symbols if needed
272: if (_formatSymbols == null)
273: _formatSymbols = new Hashtable();
274:
275: // The name cannot be null - use empty string instead
276: if (name == null)
277: name = EMPTYSTRING;
278:
279: // Construct a DecimalFormat object containing the symbols we got
280: final DecimalFormat df = new DecimalFormat();
281: if (symbols != null) {
282: df.setDecimalFormatSymbols(symbols);
283: }
284: _formatSymbols.put(name, df);
285: }
286:
287: /**
288: * Retrieves a named DecimalFormat object from _formatSymbols hashtable.
289: */
290: public final DecimalFormat getDecimalFormat(String name) {
291:
292: if (_formatSymbols != null) {
293: // The name cannot be null - use empty string instead
294: if (name == null)
295: name = EMPTYSTRING;
296:
297: DecimalFormat df = (DecimalFormat) _formatSymbols.get(name);
298: if (df == null)
299: df = (DecimalFormat) _formatSymbols.get(EMPTYSTRING);
300: return df;
301: }
302: return (null);
303: }
304:
305: /**
306: * Give the translet an opportunity to perform a prepass on the document
307: * to extract any information that it can store in an optimized form.
308: *
309: * Currently, it only extracts information about attributes of type ID.
310: */
311: public final void prepassDocument(DOM document) {
312: setIndexSize(document.getSize());
313: buildIDIndex(document);
314: }
315:
316: /**
317: * Leverages the Key Class to implement the XSLT id() function.
318: * buildIdIndex creates the index (##id) that Key Class uses.
319: * The index contains the element node index (int) and Id value (String).
320: */
321: private final void buildIDIndex(DOM document) {
322:
323: if (document instanceof DOMEnhancedForDTM) {
324: DOMEnhancedForDTM enhancedDOM = (DOMEnhancedForDTM) document;
325:
326: // If the input source is DOMSource, the KeyIndex table is not
327: // built at this time. It will be built later by the lookupId()
328: // and containsId() methods of the KeyIndex class.
329: if (enhancedDOM.hasDOMSource()) {
330: buildKeyIndex(ID_INDEX_NAME, document);
331: return;
332: } else {
333: final Hashtable elementsByID = enhancedDOM
334: .getElementsWithIDs();
335:
336: if (elementsByID == null) {
337: return;
338: }
339:
340: // Given a Hashtable of DTM nodes indexed by ID attribute values,
341: // loop through the table copying information to a KeyIndex
342: // for the mapping from ID attribute value to DTM node
343: final Enumeration idValues = elementsByID.keys();
344: boolean hasIDValues = false;
345:
346: while (idValues.hasMoreElements()) {
347: final Object idValue = idValues.nextElement();
348: final int element = ((Integer) elementsByID
349: .get(idValue)).intValue();
350:
351: buildKeyIndex(ID_INDEX_NAME, element, idValue);
352: hasIDValues = true;
353: }
354:
355: if (hasIDValues) {
356: setKeyIndexDom(ID_INDEX_NAME, document);
357: }
358: }
359: }
360: }
361:
362: /**
363: * After constructing the translet object, this method must be called to
364: * perform any version-specific post-initialization that's required.
365: */
366: public final void postInitialization() {
367: // If the version of the translet had just one namesArray, split
368: // it into multiple fields.
369: if (transletVersion < VER_SPLIT_NAMES_ARRAY) {
370: int arraySize = namesArray.length;
371: String[] newURIsArray = new String[arraySize];
372: String[] newNamesArray = new String[arraySize];
373: int[] newTypesArray = new int[arraySize];
374:
375: for (int i = 0; i < arraySize; i++) {
376: String name = namesArray[i];
377: int colonIndex = name.lastIndexOf(':');
378: int lNameStartIdx = colonIndex + 1;
379:
380: if (colonIndex > -1) {
381: newURIsArray[i] = name.substring(0, colonIndex);
382: }
383:
384: // Distinguish attribute and element names. Attribute has
385: // @ before local part of name.
386: if (name.charAt(lNameStartIdx) == '@') {
387: lNameStartIdx++;
388: newTypesArray[i] = DTM.ATTRIBUTE_NODE;
389: } else if (name.charAt(lNameStartIdx) == '?') {
390: lNameStartIdx++;
391: newTypesArray[i] = DTM.NAMESPACE_NODE;
392: } else {
393: newTypesArray[i] = DTM.ELEMENT_NODE;
394: }
395: newNamesArray[i] = (lNameStartIdx == 0) ? name : name
396: .substring(lNameStartIdx);
397: }
398:
399: namesArray = newNamesArray;
400: urisArray = newURIsArray;
401: typesArray = newTypesArray;
402: }
403:
404: // Was translet compiled using a more recent version of the XSLTC
405: // compiler than is known by the AbstractTranslet class? If, so
406: // and we've made it this far (which is doubtful), we should give up.
407: if (transletVersion > CURRENT_TRANSLET_VERSION) {
408: BasisLibrary.runTimeError(
409: BasisLibrary.UNKNOWN_TRANSLET_VERSION_ERR, this
410: .getClass().getName());
411: }
412: }
413:
414: /************************************************************************
415: * Index(es) for <xsl:key> / key() / id()
416: ************************************************************************/
417:
418: // Container for all indexes for xsl:key elements
419: private Hashtable _keyIndexes = null;
420: private KeyIndex _emptyKeyIndex = null;
421: private int _indexSize = 0;
422:
423: /**
424: * This method is used to pass the largest DOM size to the translet.
425: * Needed to make sure that the translet can index the whole DOM.
426: */
427: public void setIndexSize(int size) {
428: if (size > _indexSize)
429: _indexSize = size;
430: }
431:
432: /**
433: * Creates a KeyIndex object of the desired size - don't want to resize!!!
434: */
435: public KeyIndex createKeyIndex() {
436: return (new KeyIndex(_indexSize));
437: }
438:
439: /**
440: * Adds a value to a key/id index
441: * @param name is the name of the index (the key or ##id)
442: * @param node is the node id of the node to insert
443: * @param value is the value that will look up the node in the given index
444: */
445: public void buildKeyIndex(String name, int node, Object value) {
446: if (_keyIndexes == null)
447: _keyIndexes = new Hashtable();
448:
449: KeyIndex index = (KeyIndex) _keyIndexes.get(name);
450: if (index == null) {
451: _keyIndexes.put(name, index = new KeyIndex(_indexSize));
452: }
453: index.add(value, node);
454: }
455:
456: /**
457: * Create an empty KeyIndex in the DOM case
458: * @param name is the name of the index (the key or ##id)
459: * @param dom is the DOM
460: */
461: public void buildKeyIndex(String name, DOM dom) {
462: if (_keyIndexes == null)
463: _keyIndexes = new Hashtable();
464:
465: KeyIndex index = (KeyIndex) _keyIndexes.get(name);
466: if (index == null) {
467: _keyIndexes.put(name, index = new KeyIndex(_indexSize));
468: }
469: index.setDom(dom);
470: }
471:
472: /**
473: * Returns the index for a given key (or id).
474: * The index implements our internal iterator interface
475: */
476: public KeyIndex getKeyIndex(String name) {
477: // Return an empty key index iterator if none are defined
478: if (_keyIndexes == null) {
479: return (_emptyKeyIndex != null) ? _emptyKeyIndex
480: : (_emptyKeyIndex = new KeyIndex(1));
481: }
482:
483: // Look up the requested key index
484: final KeyIndex index = (KeyIndex) _keyIndexes.get(name);
485:
486: // Return an empty key index iterator if the requested index not found
487: if (index == null) {
488: return (_emptyKeyIndex != null) ? _emptyKeyIndex
489: : (_emptyKeyIndex = new KeyIndex(1));
490: }
491:
492: return (index);
493: }
494:
495: /**
496: * This method builds key indexes - it is overridden in the compiled
497: * translet in cases where the <xsl:key> element is used
498: */
499: public void buildKeys(DOM document, DTMAxisIterator iterator,
500: SerializationHandler handler, int root)
501: throws TransletException {
502:
503: }
504:
505: /**
506: * This method builds key indexes - it is overridden in the compiled
507: * translet in cases where the <xsl:key> element is used
508: */
509: public void setKeyIndexDom(String name, DOM document) {
510: getKeyIndex(name).setDom(document);
511:
512: }
513:
514: /************************************************************************
515: * DOM cache handling
516: ************************************************************************/
517:
518: // Hold the DOM cache (if any) used with this translet
519: private DOMCache _domCache = null;
520:
521: /**
522: * Sets the DOM cache used for additional documents loaded using the
523: * document() function.
524: */
525: public void setDOMCache(DOMCache cache) {
526: _domCache = cache;
527: }
528:
529: /**
530: * Returns the DOM cache used for this translet. Used by the LoadDocument
531: * class (if present) when the document() function is used.
532: */
533: public DOMCache getDOMCache() {
534: return (_domCache);
535: }
536:
537: /************************************************************************
538: * Multiple output document extension.
539: * See compiler/TransletOutput for actual implementation.
540: ************************************************************************/
541:
542: public SerializationHandler openOutputHandler(String filename,
543: boolean append) throws TransletException {
544: try {
545: final TransletOutputHandlerFactory factory = TransletOutputHandlerFactory
546: .newInstance();
547:
548: String dirStr = new File(filename).getParent();
549: if ((null != dirStr) && (dirStr.length() > 0)) {
550: File dir = new File(dirStr);
551: dir.mkdirs();
552: }
553:
554: factory.setEncoding(_encoding);
555: factory.setOutputMethod(_method);
556: factory.setWriter(new FileWriter(filename, append));
557: factory.setOutputType(TransletOutputHandlerFactory.STREAM);
558:
559: final SerializationHandler handler = factory
560: .getSerializationHandler();
561:
562: transferOutputSettings(handler);
563: handler.startDocument();
564: return handler;
565: } catch (Exception e) {
566: throw new TransletException(e);
567: }
568: }
569:
570: public SerializationHandler openOutputHandler(String filename)
571: throws TransletException {
572: return openOutputHandler(filename, false);
573: }
574:
575: public void closeOutputHandler(SerializationHandler handler) {
576: try {
577: handler.endDocument();
578: handler.close();
579: } catch (Exception e) {
580: // what can you do?
581: }
582: }
583:
584: /************************************************************************
585: * Native API transformation methods - _NOT_ JAXP/TrAX
586: ************************************************************************/
587:
588: /**
589: * Main transform() method - this is overridden by the compiled translet
590: */
591: public abstract void transform(DOM document,
592: DTMAxisIterator iterator, SerializationHandler handler)
593: throws TransletException;
594:
595: /**
596: * Calls transform() with a given output handler
597: */
598: public final void transform(DOM document,
599: SerializationHandler handler) throws TransletException {
600: try {
601: transform(document, document.getIterator(), handler);
602: } finally {
603: _keyIndexes = null;
604: }
605: }
606:
607: /**
608: * Used by some compiled code as a shortcut for passing strings to the
609: * output handler
610: */
611: public final void characters(final String string,
612: SerializationHandler handler) throws TransletException {
613: if (string != null) {
614: //final int length = string.length();
615: try {
616: handler.characters(string);
617: } catch (Exception e) {
618: throw new TransletException(e);
619: }
620: }
621: }
622:
623: /**
624: * Add's a name of an element whose text contents should be output as CDATA
625: */
626: public void addCdataElement(String name) {
627: if (_cdata == null) {
628: _cdata = new Vector();
629: }
630:
631: int lastColon = name.lastIndexOf(':');
632:
633: if (lastColon > 0) {
634: String uri = name.substring(0, lastColon);
635: String localName = name.substring(lastColon + 1);
636: _cdata.addElement(uri);
637: _cdata.addElement(localName);
638: } else {
639: _cdata.addElement(null);
640: _cdata.addElement(name);
641: }
642: }
643:
644: /**
645: * Transfer the output settings to the output post-processor
646: */
647: protected void transferOutputSettings(SerializationHandler handler) {
648: if (_method != null) {
649: if (_method.equals("xml")) {
650: if (_standalone != null) {
651: handler.setStandalone(_standalone);
652: }
653: if (_omitHeader) {
654: handler.setOmitXMLDeclaration(true);
655: }
656: handler.setCdataSectionElements(_cdata);
657: if (_version != null) {
658: handler.setVersion(_version);
659: }
660: handler.setIndent(_indent);
661: handler.setIndentAmount(_indentamount);
662: if (_doctypeSystem != null) {
663: handler.setDoctype(_doctypeSystem, _doctypePublic);
664: }
665: } else if (_method.equals("html")) {
666: handler.setIndent(_indent);
667: handler.setDoctype(_doctypeSystem, _doctypePublic);
668: if (_mediaType != null) {
669: handler.setMediaType(_mediaType);
670: }
671: }
672: } else {
673: handler.setCdataSectionElements(_cdata);
674: if (_version != null) {
675: handler.setVersion(_version);
676: }
677: if (_standalone != null) {
678: handler.setStandalone(_standalone);
679: }
680: if (_omitHeader) {
681: handler.setOmitXMLDeclaration(true);
682: }
683: handler.setIndent(_indent);
684: handler.setDoctype(_doctypeSystem, _doctypePublic);
685: }
686: }
687:
688: private Hashtable _auxClasses = null;
689:
690: public void addAuxiliaryClass(Class auxClass) {
691: if (_auxClasses == null)
692: _auxClasses = new Hashtable();
693: _auxClasses.put(auxClass.getName(), auxClass);
694: }
695:
696: public void setAuxiliaryClasses(Hashtable auxClasses) {
697: _auxClasses = auxClasses;
698: }
699:
700: public Class getAuxiliaryClass(String className) {
701: if (_auxClasses == null)
702: return null;
703: return ((Class) _auxClasses.get(className));
704: }
705:
706: // GTM added (see pg 110)
707: public String[] getNamesArray() {
708: return namesArray;
709: }
710:
711: public String[] getUrisArray() {
712: return urisArray;
713: }
714:
715: public int[] getTypesArray() {
716: return typesArray;
717: }
718:
719: public String[] getNamespaceArray() {
720: return namespaceArray;
721: }
722:
723: public boolean hasIdCall() {
724: return _hasIdCall;
725: }
726:
727: public Templates getTemplates() {
728: return _templates;
729: }
730:
731: public void setTemplates(Templates templates) {
732: _templates = templates;
733: }
734:
735: /************************************************************************
736: * DOMImplementation caching for basis library
737: ************************************************************************/
738: protected DOMImplementation _domImplementation = null;
739:
740: public Document newDocument(String uri, String qname)
741: throws ParserConfigurationException {
742: if (_domImplementation == null) {
743: _domImplementation = DocumentBuilderFactory.newInstance()
744: .newDocumentBuilder().getDOMImplementation();
745: }
746: return _domImplementation.createDocument(uri, qname, null);
747: }
748: }
|