001/* A parser for PlotML (Plot Markup Language) supporting Plot commands.
002
003 Copyright (c) 1998-2016 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.plot.plotml;
029
030import com.microstar.xml.XmlException;
031
032// Ptolemy imports.
033import ptolemy.plot.PlotInterface;
034
035///////////////////////////////////////////////////////////////////
036//// PlotMLParser
037
038/**
039 This class constructs a plot from specifications
040 in PlotML (Plot Markup Language), which is an XML language.
041 This class supports extends the base class to
042 support the subset that applies to the Plot class.
043 It ignores unrecognized elements in the DTD.
044 The class contains an instance of the Microstar Ælfred XML
045 parser and implements callback methods to interpret the parsed XML.
046 The way to use this class is to construct it with a reference to
047 a Plot object and then call its parse() method.
048
049 @author Edward A. Lee
050 @version $Id$
051 @since Ptolemy II 0.4
052 @Pt.ProposedRating Yellow (eal)
053 @Pt.AcceptedRating Red (cxh)
054 */
055public class PlotMLParser extends PlotBoxMLParser {
056    /** Construct an parser to parse commands for the specified plot object.
057     *  @param plot The plot object to which to apply the commands.
058     */
059    public PlotMLParser(PlotInterface plot) {
060        super(plot);
061    }
062
063    /** Protected constructor allows derived classes to set _plot
064     *  differently.
065     */
066    protected PlotMLParser() {
067    }
068
069    ///////////////////////////////////////////////////////////////////
070    ////                         public methods                    ////
071
072    /** End an element. This method
073     *  calls the appropriate Plot methods.
074     *  Ælfred will call this method at the end of each element
075     *  (including EMPTY elements).
076     *  @param elementName The element type name.
077     *  @exception Exception If thrown by the superclass or while calling
078     *  setConnected().
079     */
080    @Override
081    public void endElement(String elementName) throws Exception {
082        super.endElement(elementName);
083
084        if (elementName.equals("dataset")) {
085            // Reset the default, in case it was changed for this dataset.
086            ((PlotInterface) _plot).setConnected(_connected);
087        }
088    }
089
090    /** Start a document.  This method is called just before the parser
091     *  attempts to read the first entity (the root of the document).
092     *  It is guaranteed that this will be the first method called.
093     */
094    @Override
095    public void startDocument() {
096        super.startDocument();
097        _currentDataset = -1;
098        _currentPointCount = 0.0;
099    }
100
101    /** Start an element.
102     *  This is called at the beginning of each XML
103     *  element.  By the time it is called, all of the attributes
104     *  for the element will already have been reported using the
105     *  attribute() method.  Unrecognized elements are ignored.
106     *  @param elementName The element type name.
107     *  @exception XmlException If the element produces an error
108     *   in constructing the model.
109     */
110    @Override
111    public void startElement(String elementName) throws XmlException {
112        try {
113            // NOTE: The elements are alphabetical below...
114            if (elementName.equals("barGraph")) {
115                String widthSpec = (String) _attributes.get("width");
116                String offsetSpec = (String) _attributes.get("offset");
117
118                // NOTE: If only one of these is given, then the other
119                // is ignored.
120                if (widthSpec == null || offsetSpec == null) {
121                    ((PlotInterface) _plot).setBars(true);
122                } else {
123                    double width = Double.valueOf(widthSpec).doubleValue();
124                    double offset = Double.valueOf(offsetSpec).doubleValue();
125                    ((PlotInterface) _plot).setBars(width, offset);
126                }
127            } else if (elementName.equals("dataset")) {
128                String name = (String) _attributes.get("name");
129
130                if (!((PlotInterface) _plot).getReuseDatasets() || name == null
131                        || _currentDataset < 0) {
132                    // reuseDatasets was not present or if it was,
133                    // the current dataset does not have a name
134                    // or we have not yet seen a dataset.
135                    _currentDataset++;
136                    _currentPointCount = 0.0;
137                } else {
138                    // reuseDatasets was set to true and name is not null.
139                    int possibleDataset = ((PlotInterface) _plot)
140                            .getLegendDataset(name);
141
142                    if (possibleDataset != -1) {
143                        _currentDataset = possibleDataset;
144                    } else {
145                        // Did not yet have a dataset with that name.
146                        _currentDataset++;
147                        _currentPointCount = 0.0;
148                    }
149                }
150
151                if (name != null) {
152                    ((PlotInterface) _plot).addLegend(_currentDataset, name);
153                }
154
155                String connected = (String) _attributes.get("connected");
156
157                if (connected != null) {
158                    if (connected.equals("no")) {
159                        ((PlotInterface) _plot).setConnected(false,
160                                _currentDataset);
161                    } else {
162                        ((PlotInterface) _plot).setConnected(true,
163                                _currentDataset);
164                    }
165                }
166
167                String lineStyle = (String) _attributes.get("lineStyle");
168                if (lineStyle != null) {
169                    ((PlotInterface) _plot).setLineStyle(lineStyle,
170                            _currentDataset);
171                }
172
173                String marks = (String) _attributes.get("marks");
174
175                if (marks != null) {
176                    ((PlotInterface) _plot).setMarksStyle(marks,
177                            _currentDataset);
178                }
179
180                String stems = (String) _attributes.get("stems");
181
182                if (stems != null) {
183                    if (stems.equals("yes")) {
184                        ((PlotInterface) _plot).setImpulses(true,
185                                _currentDataset);
186                    } else {
187                        ((PlotInterface) _plot).setImpulses(false,
188                                _currentDataset);
189                    }
190                }
191            } else if (elementName.equals("default")) {
192                String connected = (String) _attributes.get("connected");
193
194                if (connected.equals("yes")) {
195                    ((PlotInterface) _plot).setConnected(true);
196                    _connected = true;
197                } else {
198                    ((PlotInterface) _plot).setConnected(false);
199                    _connected = false;
200                }
201
202                String lineStyles = (String) _attributes.get("lineStyles");
203
204                if (lineStyles != null) {
205                    if (lineStyles.equals("yes")) {
206                        ((PlotInterface) _plot).setLineStyles(true);
207                    } else {
208                        ((PlotInterface) _plot).setLineStyles(false);
209                    }
210                }
211
212                String marks = (String) _attributes.get("marks");
213
214                if (marks != null) {
215                    ((PlotInterface) _plot).setMarksStyle(marks);
216                }
217
218                String stems = (String) _attributes.get("stems");
219
220                if (stems.equals("no")) {
221                    ((PlotInterface) _plot).setImpulses(false);
222                } else {
223                    ((PlotInterface) _plot).setImpulses(true);
224                }
225            } else if (elementName.equals("m")) {
226                _addPoint(false, elementName);
227            } else if (elementName.equals("move")) {
228                _addPoint(false, elementName);
229            } else if (elementName.equals("p")) {
230                _addPoint(true, elementName);
231            } else if (elementName.equals("point")) {
232                _addPoint(true, elementName);
233            } else if (elementName.equals("reuseDatasets")) {
234                ((PlotInterface) _plot).setReuseDatasets(true);
235            } else {
236                super.startElement(elementName);
237            }
238        } catch (Exception ex) {
239            if (ex instanceof XmlException) {
240                throw (XmlException) ex;
241            } else {
242                // FIXME: Temporary for debugging.
243                System.err.println(ex.toString());
244                ex.printStackTrace();
245
246                String msg = "XML element \"" + elementName
247                        + "\" triggers exception:\n  " + ex.toString();
248                throw new XmlException(msg, _currentExternalEntity(),
249                        _parser.getLineNumber(), _parser.getColumnNumber());
250            }
251        }
252
253        // NOTE: if super is called, this gets done twice.
254        // Any way to avoid it?
255        _attributes.clear();
256    }
257
258    ///////////////////////////////////////////////////////////////////
259    ////                         protected members                 ////
260
261    /** The default connected state. */
262    protected boolean _connected = true;
263
264    /** The current dataset number in a "dataset" element. */
265    protected int _currentDataset = -1;
266
267    /** A count within the current dataset, in case no x value is given. */
268    protected double _currentPointCount = 0.0;
269
270    ///////////////////////////////////////////////////////////////////
271    ////                         protected methods                 ////
272
273    /** Add a point based on the current attributes.
274     *  If the first argument is true, connect it to the previous point.
275     *  The second argument is the element name, used for error reporting.
276     *  @param connected If true, connect to the previous point.
277     *  @param element The name of the element.
278     *  @exception Exception If there is a problem adding the point.
279     */
280    protected void _addPoint(boolean connected, String element)
281            throws Exception {
282        String xSpec = (String) _attributes.get("x");
283        double x;
284
285        if (xSpec == null) {
286            // No x value given.  Use _currentPointCount.
287            x = _currentPointCount;
288            _currentPointCount += 1.0;
289        } else {
290            // NOTE: We use Double.parseDouble() here, which breaks
291            // Java 1.1 compatibility, but means we don't allocate a Double.
292            //x = (Double.valueOf(xSpec)).doubleValue();
293            x = Double.parseDouble(xSpec);
294        }
295
296        String ySpec = (String) _attributes.get("y");
297        _checkForNull(ySpec, "No y value for element \"" + element + "\"");
298
299        // NOTE: We use Double.parseDouble() here, which breaks
300        // Java 1.1 compatibility, but means we don't allocate a Double.
301        //double y = (Double.valueOf(ySpec)).doubleValue();
302        double y = Double.parseDouble(ySpec);
303
304        String lowSpec = (String) _attributes.get("lowErrorBar");
305        String highSpec = (String) _attributes.get("highErrorBar");
306
307        if (lowSpec == null && highSpec == null) {
308            ((PlotInterface) _plot).addPoint(_currentDataset, x, y, connected);
309        } else {
310            double low;
311            double high;
312
313            if (lowSpec != null) {
314                low = Double.valueOf(lowSpec).doubleValue();
315            } else {
316                low = x;
317            }
318
319            if (highSpec != null) {
320                high = Double.valueOf(highSpec).doubleValue();
321            } else {
322                high = x;
323            }
324
325            ((PlotInterface) _plot).addPointWithErrorBars(_currentDataset, x, y,
326                    null, low, high, connected);
327        }
328    }
329}