001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2012-05-05 01:42:47 +0000 (Sat, 05 May 2012) $' 
007 * '$Revision: 29810 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.sdm.spa;
031
032import java.io.StringWriter;
033import java.io.Writer;
034
035import org.apache.xml.serialize.OutputFormat;
036import org.apache.xml.serialize.SerializerFactory;
037import org.apache.xml.serialize.XMLSerializer;
038import org.apache.xpath.XPathAPI;
039import org.w3c.dom.Document;
040import org.w3c.dom.Element;
041import org.w3c.dom.Node;
042import org.w3c.dom.NodeList;
043
044import ptolemy.actor.TypedIOPort;
045import ptolemy.actor.lib.Transformer;
046import ptolemy.actor.parameters.PortParameter;
047import ptolemy.data.ArrayToken;
048import ptolemy.data.StringToken;
049import ptolemy.data.XMLToken;
050import ptolemy.data.type.ArrayType;
051import ptolemy.data.type.BaseType;
052import ptolemy.kernel.CompositeEntity;
053import ptolemy.kernel.util.IllegalActionException;
054import ptolemy.kernel.util.NameDuplicationException;
055
056//////////////////////////////////////////////////////////////////////////
057//// XPath
058/**
059 * XPath selects XML nodes based on the XPath syntax.
060 * 
061 * This actor takes in an XPath string and an XMLToken, and it returns an array
062 * of XMLTokens.
063 * 
064 * @author xiaowen
065 * @version $Id: XPath.java 29810 2012-05-05 01:42:47Z crawl $
066 */
067
068public class XPath extends Transformer {
069
070        /**
071         * Construct an XPath actor with the given container and name.
072         * 
073         * @param container
074         *            The container.
075         * @param name
076         *            The name of this actor.
077         * @exception IllegalActionException
078         *                If the actor cannot be contained by the proposed
079         *                container.
080         * @exception NameDuplicationException
081         *                If the container already has an actor with this name.
082         */
083        public XPath(CompositeEntity container, String name)
084                        throws NameDuplicationException, IllegalActionException {
085
086                super(container, name);
087
088                portXPath = new PortParameter(this, "xpath");
089
090                all = new TypedIOPort(this, "all", false, true);
091
092                input.setTypeEquals(BaseType.XMLTOKEN);
093                output.setTypeEquals(new ArrayType(BaseType.XMLTOKEN));
094                all.setTypeEquals(new ArrayType(BaseType.STRING));
095                portXPath.setTypeEquals(BaseType.STRING);
096
097                _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" "
098                                + "width=\"60\" height=\"20\" " + "style=\"fill:white\"/>\n"
099                                + "</svg>\n");
100
101        }
102
103        // /////////////////////////////////////////////////////////////////
104        // // ports and parameters ////
105
106        /** The XPath expression. */
107        public PortParameter portXPath;
108
109        /** The text resulting from applying the XPath expression. */
110        public TypedIOPort all;
111
112        // /////////////////////////////////////////////////////////////////
113        // // public methods ////
114
115
116        /**
117         * Take in an XMLToken and the XPath expression, and return an ArrayToken
118         * containing XMLTokens representing the result of selecting nodes using the
119         * XPath expression.
120         * 
121         * @exception IllegalActionException
122         *                If it can't select nodes using the XPath expression or if
123         *                it's unable to create the resulting XMLTokens.
124         */
125        public void fire() throws IllegalActionException {
126                super.fire();
127
128                // get inputs
129                XMLToken tokenXml = (XMLToken) input.get(0);
130                portXPath.update();
131                String strXPath = ((StringToken) portXPath.getToken()).stringValue();
132                _debug("The XPath expression is: " + strXPath);
133
134        XMLToken xmlTokens[] = null;
135        StringToken stringTokens[] = null;
136
137                // run XPath on it
138                Document document = tokenXml.getDomTree();
139                if(document == null) {
140                    throw new IllegalActionException(this, "Input has no XML: " + tokenXml);
141                }
142                
143                synchronized(document) {
144                
145                Element element = document.getDocumentElement();
146                if(element == null) {
147                    throw new IllegalActionException(this, "No document element in: " + tokenXml);
148                }
149                Node nodeRoot = element.getFirstChild();
150                NodeList nodeHits = null;
151    
152                try {
153                        nodeHits = XPathAPI.selectNodeList(nodeRoot, strXPath);
154                } catch (javax.xml.transform.TransformerException e) {
155                        throw new IllegalActionException(this, "XPath: could not select nodes.");
156                } catch(NullPointerException e2) {
157                    throw new IllegalActionException(this, "Error selecting node list for: " + tokenXml);
158                }
159    
160                // format the results
161                int numXMLTokens = 0;
162                xmlTokens = new XMLToken[nodeHits.getLength()];
163                stringTokens = new StringToken[nodeHits.getLength()];
164    
165                SerializerFactory serializerFactory = SerializerFactory
166                                .getSerializerFactory("xml");
167                OutputFormat outputFormat = new OutputFormat();
168                outputFormat.setOmitXMLDeclaration(true);
169                XMLSerializer xmlSerializer = (XMLSerializer) serializerFactory
170                                .makeSerializer(outputFormat);
171    
172                for (int i = 0; i < nodeHits.getLength(); i++) {
173                        Writer stringWriter = new StringWriter();
174                        xmlSerializer.setOutputCharStream(stringWriter);
175    
176                        Node node = nodeHits.item(i);
177    
178                        if (node.getNodeType() == Node.ELEMENT_NODE) {
179                                numXMLTokens++;
180                                try {
181                                        xmlSerializer.serialize((Element) node);
182                                } catch (java.io.IOException e) {
183                                        throw new IllegalActionException(this,
184                                                        "XPath: java.io.IOException ...");
185                                }
186    
187                                String xmlStr = stringWriter.toString();
188    
189                                try {
190                                        xmlTokens[i] = new XMLToken(xmlStr);
191                                } catch (java.lang.Exception e) {
192                                        throw new IllegalActionException(this,
193                                                        "XPath: unable to create XMLToken with string: "
194                                                                        + stringWriter.toString());
195                                }
196    
197                                stringTokens[i] = new StringToken(xmlStr);
198                        } else if (node.getNodeType() == Node.ATTRIBUTE_NODE
199                                        || node.getNodeType() == Node.TEXT_NODE) {
200                                stringTokens[i] = new StringToken(node.getNodeValue());
201                        }
202                }
203    
204                // If there were no results, then send out a dummy xml token.
205                // This is really a hack. The ideal solution for this actor would be
206                // able to send out an empty ArrayToken.
207                if (numXMLTokens < xmlTokens.length) {
208                        xmlTokens = new XMLToken[1];
209                        try {
210                                xmlTokens[0] = new XMLToken("<dummy/>");
211                        } catch (java.lang.Exception e) {
212                                // well this really shouldn't happen.
213                                throw new IllegalActionException(this,
214                                                "XPath: '<dummy/>' apparently isn't valid XML");
215                        }
216                }
217
218                }
219                
220                // send out the results
221                if(xmlTokens.length == 0) {
222                    output.send(0, new ArrayToken(BaseType.XMLTOKEN));
223            } else {
224                output.send(0, new ArrayToken(xmlTokens));
225            }
226
227                // if no results, send empty array
228                if (stringTokens.length == 0) {
229                        all.broadcast(new ArrayToken(BaseType.STRING));
230                } else {
231                        all.broadcast(new ArrayToken(stringTokens));
232                }
233        }
234
235}