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 <?xml version="1.0" standalone="no"?> 056 <Node> 057 $inputi,j 058 </Node> 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><?xml version="1.0" standalone="no"?></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}