001/* An actor that read an XSLT file and apply it to its input.
002
003@Copyright (c) 2003-2014 The Regents of the University of California.
004All rights reserved.
005
006Permission is hereby granted, without written agreement and without
007license or royalty fees, to use, copy, modify, and distribute this
008software and its documentation for any purpose, provided that the
009above copyright notice and the following two paragraphs appear in all
010copies of this software.
011
012IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016SUCH DAMAGE.
017
018THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023ENHANCEMENTS, OR MODIFICATIONS.
024
025PT_COPYRIGHT_VERSION 2
026COPYRIGHTENDKEY
027 */
028package ptolemy.actor.lib.xslt;
029
030import java.io.BufferedReader;
031import java.io.ByteArrayOutputStream;
032import java.io.IOException;
033import java.util.Iterator;
034
035import javax.xml.transform.TransformerException;
036
037import org.w3c.dom.Document;
038
039import ptolemy.actor.lib.Transformer;
040import ptolemy.actor.parameters.PortParameter;
041import ptolemy.data.RecordToken;
042import ptolemy.data.StringToken;
043import ptolemy.data.Token;
044import ptolemy.data.XMLToken;
045import ptolemy.data.expr.FileParameter;
046import ptolemy.data.type.BaseType;
047import ptolemy.data.type.RecordType;
048import ptolemy.data.type.Type;
049import ptolemy.kernel.CompositeEntity;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.NameDuplicationException;
052import ptolemy.kernel.util.Workspace;
053
054///////////////////////////////////////////////////////////////////
055//// XSLTTransformer
056
057/**
058   This actor reads an XSLT file and apply it to a dom tree. The file or
059   URL is specified using any form acceptable to the FileParameter class.
060
061   <p>Currently, this actor requires the
062   <a href="http://saxon.sourceforge.net/">Saxon</a> XSLT processor
063   so as to ensure reproducible results.  This restriction may
064   be relaxed in later versions of this actor.
065
066   <p>FIXME: what should the type of the input/output ports be???.
067
068   @see ptolemy.actor.lib.javasound.AudioReader
069   @author  Yang Zhao, Christopher Hylands Brooks
070   @version $Id$
071   @since Ptolemy II 4.0
072   @Pt.ProposedRating Red (liuj)
073   @Pt.AcceptedRating Red (liuj)
074 */
075public class XSLTransformer extends Transformer {
076    /** Construct an actor with the given container and name.
077     *  @param container The container.
078     *  @param name The name of this actor.
079     *  @exception IllegalActionException If the actor cannot be contained
080     *   by the proposed container.
081     *  @exception NameDuplicationException If the container already has an
082     *   actor with this name.
083     */
084    public XSLTransformer(CompositeEntity container, String name)
085            throws IllegalActionException, NameDuplicationException {
086        super(container, name);
087
088        // Set the type of the input port.
089        //input.setMultiport(true);
090        input.setTypeEquals(BaseType.XMLTOKEN);
091
092        // Set the type of the output port.
093        //output.setMultiport(true);
094        output.setTypeEquals(BaseType.STRING);
095
096        styleSheetParameters = new PortParameter(this, "styleSheetParameters");
097        styleSheetParameters
098                .setTypeAtMost(new RecordType(new String[0], new Type[0]));
099        styleSheetParameters.setExpression("emptyRecord()");
100        styleSheetFile = new FileParameter(this, "styleSheetFile");
101
102    }
103
104    ///////////////////////////////////////////////////////////////////
105    ////                     ports and parameters                  ////
106
107    /** The file name or URL from which to read.  This is a string with
108     *  any form accepted by FileParameter.
109     *  @see FileParameter
110     */
111    public FileParameter styleSheetFile;
112
113    /** The parameters to be used in the stylesheet. This is a record
114     *  that defaults to "emptyRecord()", an expression language command
115     *  that returns an empty record.
116     *  For example, if the parameter used in the style sheet is named
117     *  <i>a</i> with type <i>int</i>, then the styleSheetParameters has
118     *  type <i>{a = int}</i>. If the style sheet has multiple parameters,
119     *  then each of them is represented as a field of the record.
120     */
121    public PortParameter styleSheetParameters;
122
123    ///////////////////////////////////////////////////////////////////
124    ////                         public methods                    ////
125
126    /** Clone the actor into the specified workspace. This calls the
127     *  base class and then set the filename public member.
128     *  @param workspace The workspace for the new object.
129     *  @return A new actor.
130     *  @exception CloneNotSupportedException If a derived class contains
131     *   an attribute that cannot be cloned.
132     */
133    @Override
134    public Object clone(Workspace workspace) throws CloneNotSupportedException {
135        XSLTransformer newObject = (XSLTransformer) super.clone(workspace);
136        newObject.input.setTypeEquals(BaseType.XMLTOKEN);
137        newObject.output.setTypeEquals(BaseType.STRING);
138        return newObject;
139    }
140
141    /** Consume an XMLToken from the input and apply the XSL transform
142     *  specified by the styleSheetFile parameter.  If the styleSheetFile parameter
143     *  does not name a file, then the input is copied to the output
144     *  without modification.
145     *  @exception IllegalActionException If the parent class throws it
146     */
147    @Override
148    public void fire() throws IllegalActionException {
149        super.fire();
150        styleSheetParameters.update();
151        ByteArrayOutputStream out = new ByteArrayOutputStream();
152        javax.xml.transform.Result result = new javax.xml.transform.stream.StreamResult(
153                out);
154
155        if (_debugging) {
156            _debug("--- open an output stream for the result. \n");
157        }
158
159        if (_transformer != null) {
160            RecordToken parameters = (RecordToken) styleSheetParameters
161                    .getToken();
162            if (parameters != null) {
163                Iterator labels = parameters.labelSet().iterator();
164
165                while (labels.hasNext()) {
166                    String name = (String) labels.next();
167                    Token token = parameters.get(name);
168                    if (token instanceof StringToken) {
169                        StringToken s = (StringToken) token;
170                        _transformer.setParameter(name, s.stringValue());
171                    } else {
172                        _transformer.setParameter(name, token.toString());
173                    }
174                }
175            }
176            for (int i = 0; i < input.getWidth(); i++) {
177                if (input.hasToken(i)) {
178                    XMLToken in = (XMLToken) input.get(i);
179                    Document doc = in.getDomTree();
180
181                    try {
182                        javax.xml.transform.Source xmlSource = new javax.xml.transform.dom.DOMSource(
183                                doc);
184                        _transformer.transform(xmlSource, result);
185
186                        if (_debugging) {
187                            _debug("--- transform the xmlSource: "
188                                    + in.toString() + "\n");
189                        }
190
191                        if (out != null) {
192                            if (_debugging) {
193                                _debug("--- moml change request string: "
194                                        + out.toString() + "\n");
195                            }
196
197                            StringToken outputToken = new StringToken(
198                                    out.toString());
199                            output.broadcast(outputToken);
200
201                            if (_debugging) {
202                                _debug("--- change request string token "
203                                        + "send out. \n");
204                            }
205                        }
206                    } catch (TransformerException ex) {
207                        throw new IllegalActionException(this, ex,
208                                "Failed  to process '" + in + "'");
209                    }
210
211                    try {
212                        out.flush();
213                        out.close();
214                    } catch (IOException ex) {
215                        throw new IllegalActionException(this, ex,
216                                "Failed  to close or flush '" + out + "'");
217                    }
218                }
219            }
220        } else {
221            // If there is no transformer, then output the xml string.
222            for (int i = 0; i < input.getWidth(); i++) {
223                if (input.hasToken(i)) {
224                    XMLToken in = (XMLToken) input.get(i);
225                    output.broadcast(new StringToken(in.toString()));
226                }
227            }
228        }
229    }
230
231    /** Open the XSL file named by the styleSheetFile parameter and
232     *  set up the transformer.
233     *  @exception IllegalActionException If the TransformFactory
234     *  class name does not start with net.sf.saxon.
235     */
236    @Override
237    public void initialize() throws IllegalActionException {
238        super.initialize();
239        _xsltSource = null;
240        _transformer = null;
241
242        try {
243            BufferedReader reader;
244
245            //          Ignore if the styleSheetFile is blank.
246            if (styleSheetFile.getExpression().trim().equals("")) {
247                reader = null;
248            } else {
249                reader = styleSheetFile.openForReading();
250            }
251
252            if (reader != null) {
253                _xsltSource = new javax.xml.transform.stream.StreamSource(
254                        reader);
255            } else {
256                _xsltSource = null;
257            }
258
259            if (_debugging) {
260                _debug("processing xsltSource change in " + getFullName());
261            }
262
263            if (_xsltSource != null) {
264                _transformerFactory = javax.xml.transform.TransformerFactory
265                        .newInstance();
266
267                /* if (!_transformerFactory.getClass().getName().startsWith(
268                   "net.sf.saxon")) {
269                   throw new IllegalActionException(
270                   this,
271                   "The XSLTransformer actor works best\nwith "
272                   + "saxon7.jar.\n"
273                   + "The transformerFactory was '"
274                   + _transformerFactory.getClass().getName()
275                   + "'.\nIf saxon7.jar was in the classpath, then "
276                   + "it should have\nstarted with "
277                   + "\"net.sf.saxon\".\n"
278                   + "If this actor does not use "
279                   + "saxon, then the results will be "
280                   + "different between\nruns that "
281                   + "use saxon and runs that "
282                   + "do not.\nDetails:\n"
283                   + "This actor uses "
284                   + "javax.xml.transform.TransformerFactory.\nThe "
285                   + "concrete TransformerFactory class can be "
286                   + "adjusted by\nsetting the "
287                   + "javax.xml.transform.TransformerFactory "
288                   + "property or by\nreading in a jar file that "
289                   + "has the appropriate\nService Provider set.\n"
290                   + "(For details about Jar Service Providers,\nsee "
291                   + "http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html\n"
292                   + "The saxon7.jar file includes a\n"
293                   + "META-INF/services/javax.xml.transform.TransformerFactory "
294                   + "\nfile that sets the TransformerFactory "
295                   + "class name start with 'net.sf.saxon'.");
296                   }*/
297
298                _transformer = _transformerFactory.newTransformer(_xsltSource);
299
300                if (_debugging) {
301                    _debug("1 processing xsltSource change in "
302                            + getFullName());
303                }
304            } else {
305                _transformer = null;
306            }
307        } catch (Throwable throwable) {
308            throw new IllegalActionException(this, throwable,
309                    "Failed to initialize.");
310        }
311    }
312
313    ///////////////////////////////////////////////////////////////////
314    ////                         private members                   ////
315    private javax.xml.transform.Source _xsltSource;
316
317    private javax.xml.transform.TransformerFactory _transformerFactory;
318
319    private javax.xml.transform.Transformer _transformer;
320}