001/* An actor that combines multiple XML files into one.
002
003@Copyright (c) 2007-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 ptolemy.actor.lib.Transformer;
031import ptolemy.actor.parameters.PortParameter;
032import ptolemy.data.ArrayToken;
033import ptolemy.data.StringToken;
034import ptolemy.data.Token;
035import ptolemy.data.XMLToken;
036import ptolemy.data.expr.StringParameter;
037import ptolemy.data.type.ArrayType;
038import ptolemy.data.type.BaseType;
039import ptolemy.kernel.CompositeEntity;
040import ptolemy.kernel.util.IllegalActionException;
041import ptolemy.kernel.util.InternalErrorException;
042import ptolemy.kernel.util.NameDuplicationException;
043
044///////////////////////////////////////////////////////////////////
045
046/**
047   Combine multiple XMLTokens into one XMLToken.
048
049   <p>The actor reads in multiple arrays of XML Tokens from the
050   <i>input</i> port.  It also takes a port parameter,
051   <i>template</i>, that specifies how the XML tokens will be
052   combined.  The template is of the form:
053
054   <pre>
055   &lt;?xml version=&quot;1.0&quot; standalone=&quot;no&quot;?&gt;
056   &lt;Node&gt;
057   $inputi,j
058   &lt;/Node&gt;
059   </pre>
060
061   The template is a XML Token with $input as a delimiter for where
062   the input XML tokens should be placed, <code>i</code> specifies
063   which array (i.e. which channel) and <code>j</code> specifies which
064   XML Token in the array.  Setting <code>j</code> equal to
065   <code>n</code> will insert (in order) all XML tokens in that
066   particular array into the template file.  If <code>i</code> or
067   <code>j</code> are out of bounds, <code>$inputi,j</code> will not
068   be replaced.  It also takes in a string parameter,
069   <i>headerParameter</i>, which is the header used for the output XML
070   token.  A XML Token with the delimiters replaced with the
071   appropriate XML Token is sent to the <i>output</i> port.  No
072   changes are made to the input XML Tokens besides removing the
073   header and DTD.
074
075   @author Christine Avanessians, Edward Lee, Thomas Feng
076   @version $Id$
077   @since Ptolemy II 6.1
078   @Pt.ProposedRating Red (cavaness)
079   @Pt.AcceptedRating Red (cavaness)
080 */
081
082public class XMLInclusion extends Transformer {
083
084    /** Construct an actor with the given container and name.
085     *  @param container The container.
086     *  @param name The name of this actor.
087     *  @exception IllegalActionException If the actor cannot be contained
088     *   by the proposed container.
089     *  @exception NameDuplicationException If the container already has an
090     *   actor with this name.
091     */
092    public XMLInclusion(CompositeEntity container, String name)
093            throws IllegalActionException, NameDuplicationException {
094        super(container, name);
095
096        // Set the type of the input port.
097        // Input port is a multiport.
098        input.setTypeEquals(new ArrayType(BaseType.XMLTOKEN));
099        input.setMultiport(true);
100
101        // FIXME: what is the initial value of this parameter?
102        template = new PortParameter(this, "template");
103        template.setStringMode(true);
104
105        headerParameter = new StringParameter(this, "headerParameter");
106        headerParameter
107                .setExpression("<?xml version=\"1.0\" standalone=\"no\"?>");
108
109        // Set the type of the output port.
110        output.setTypeEquals(BaseType.XMLTOKEN);
111    }
112
113    ///////////////////////////////////////////////////////////////////
114    ////                     ports and parameters                  ////
115
116    /** The template that specifies how the XML tokens will be combined.
117     *  The type of this parameter is not defined, though it is in string mode.
118     *  The initial value is not defined.
119     */
120    public PortParameter template;
121
122    // FIXME: change the name from headerParameter to header.
123    // We already know this is a parameter, so remove that from the name.
124
125    /** The xml header.  This parameter is a string with an initial value of
126     *  <code>&lt;?xml version="1.0" standalone="no"?&gt;</code>.
127     */
128    public StringParameter headerParameter;
129
130    ///////////////////////////////////////////////////////////////////
131    ////                         public methods                    ////
132
133    /** Read multiple arrays of XMLTokens from the input and combine
134     *  them according to the specified template.  If the template
135     *  contains invalid delimiters, then return the template file with
136     *  the valid ones replaced and the invalid ones unmodified.
137     *  @exception IllegalActionException If thrown by the parent class,
138     *  while reading a parameter or while reading the input.
139     */
140    @Override
141    public void fire() throws IllegalActionException {
142        super.fire();
143        template.update();
144        String outputString = removeHeader(template.getToken());
145        String all = "";
146        for (int j = 0; j < input.getWidth(); j++) {
147            ArrayToken a = (ArrayToken) input.get(j);
148
149            // FIXME: use StringBuffer instead of concatenating a String.
150            String allInArray = "";
151            int i;
152            for (i = 0; i < a.length(); i++) {
153                String elemInArray = removeHeader(a.getElement(i));
154                if (i == 0) {
155                    allInArray = allInArray.concat(elemInArray);
156                } else {
157                    allInArray = allInArray.concat('\n' + elemInArray);
158                }
159                String elemTag = "$input" + Integer.toString(j) + ','
160                        + Integer.toString(i);
161                outputString = outputString.replace(elemTag, elemInArray);
162            }
163            String arrayTag = "$input" + Integer.toString(j) + ",n";
164            outputString = outputString.replace(arrayTag, allInArray);
165            if (j == 0) {
166                all = all.concat(allInArray);
167            } else {
168                all = all.concat('\n' + allInArray);
169            }
170        }
171        outputString = outputString.replace("$inputn", all);
172        String ADDheader = headerParameter.stringValue() + "\n";
173        ADDheader = ADDheader.concat(outputString);
174        try {
175            XMLToken out = new XMLToken(ADDheader);
176            output.broadcast(out);
177        } catch (Exception e) {
178            // FIXME: throw an exception that uses "this" so we
179            // know in which actor the error is located
180            throw new InternalErrorException(e);
181        }
182    }
183
184    // FIXME: insert private comment header, see Ramp
185
186    //Removes XML header and DTD if there is one
187    private String removeHeader(Token T) {
188        String str = "";
189        if (T instanceof StringToken) {
190            str = ((StringToken) T).stringValue();
191        } else if (T instanceof XMLToken) {
192            str = T.toString();
193        } else {
194            // FIXME, use this when throwing exceptions
195            throw new InternalErrorException("The token should be either "
196                    + "of type StringToken, or of type XMLToken.");
197        }
198        String s = str.trim();
199        int i = 0;
200        if (s.startsWith("<?xml")) {//removes header
201            i = 1;
202            s = loopThroughHeaders(s);
203        }
204        String s2 = s.trim();
205        if (s2.startsWith("<!DOCTYPE")) {//removes DTD
206            i = 2;
207            s2 = loopThroughHeaders(s2);
208        }
209        if (i == 0) { // in order to not remove the white spaces that trim removes
210            return str;
211        } else if (i == 1) {
212            return s;
213        } else {
214            return s2;
215        }
216    }
217
218    private String loopThroughHeaders(String s) {
219        boolean inQuote = false;
220        int pos = 0;
221        while (pos < s.length() && (inQuote || s.charAt(pos) != '>')) {
222            if (s.charAt(pos) == '\"') {
223                inQuote = !inQuote;
224            }
225            pos++;
226        }
227        if (pos < s.length()) {
228            s = s.substring(pos + 1);
229        }
230        if (s.charAt(0) == '\n') {
231            s = s.substring(1);
232        }
233        return s;
234    }
235}