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