001/* A validating parser.
002
003 Copyright (c) 2010-2015 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.configs.test;
029
030import java.io.StringReader;
031
032import javax.xml.parsers.SAXParser;
033import javax.xml.parsers.SAXParserFactory;
034
035import org.xml.sax.Attributes;
036import org.xml.sax.EntityResolver;
037import org.xml.sax.ErrorHandler;
038import org.xml.sax.InputSource;
039import org.xml.sax.Locator;
040import org.xml.sax.SAXException;
041import org.xml.sax.SAXParseException;
042import org.xml.sax.XMLReader;
043import org.xml.sax.helpers.DefaultHandler;
044
045import ptolemy.moml.MoMLParser;
046import ptolemy.util.FileUtilities;
047
048/**
049 A validating XML parser.
050 <p>Note that MoML files are not always valid XML because of the
051 &lt;configure&gt; tag, so that tag should be removed.</p>
052
053 @author Christopher Brooks
054 @version $Id$
055 @since Ptolemy II 10.0
056 @Pt.ProposedRating Red (cxh)
057 @Pt.AcceptedRating Red (cxh)
058 */
059public class ValidatingXMLParser extends DefaultHandler {
060    /** Parse an XML parser using a validating parser.
061     *  The DTD http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd
062     *  is replaced with the value of MoMLParser.MoML_1.dtd.
063     *  @param args A single element array that names the xml file to
064     *  be parsed.  The file should have
065     *  &lt;configure&gt;...&lt;configure&gt; removed.  One workaround
066     *  is to remove the configure tags with a script before
067     *  validating, see $PTII/ptolemy/moml/test/removeconfigure.
068     *  See also <a href="http://ptolemy.eecs.berkeley.edu/ptolemyII/ptIIfaq.htm#MoML">http://ptolemy.eecs.berkeley.edu/ptolemyII/ptIIfaq.htm#MoML</a>
069     */
070    public static void main(String[] args) {
071        try {
072            parse(args[0]);
073        } catch (Throwable throwable) {
074            throwable.printStackTrace();
075            System.exit(1);
076        }
077        System.exit(0);
078    }
079
080    /** Parse an XML parser using a validating parser.
081     *  The DTD http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd
082     *  is replaced with the value of MoMLParser.MoML_1.dtd.
083     *  @param fileName  The xml file to be parsed.  The file should
084     *  have &lt;configure&gt;...&lt;configure&gt; removed.
085     *  @exception Throwable If the XML is invalid XML.
086     */
087    public static void parse(String fileName) throws Throwable {
088        DefaultHandler handler = new ValidatingXMLParser();
089
090        // Use the validating parser
091        SAXParserFactory factory = SAXParserFactory.newInstance();
092        factory.setValidating(true);
093        try {
094            SAXParser saxParser = factory.newSAXParser();
095            // Instead of using saxParser.parser, we create our own
096            // entity resolver so that we do not beat up the ptolemy
097            // web server.
098            //saxParser.parse(new File(fileName), handler);
099
100            XMLReader xmlReader = saxParser.getXMLReader();
101            // Use error handlers so that if parsing fails, we throw
102            // an exception that can be caught by the test, which will
103            // show which .xml file is failing.  Validating the
104            // accessors required this.
105            xmlReader.setErrorHandler(new ErrorHandler() {
106                @Override
107                public void fatalError(SAXParseException exception)
108                        throws SAXException {
109                    System.err.println("fatalError: " + exception);
110                    throw exception;
111                }
112
113                @Override
114                public void error(SAXParseException exception)
115                        throws SAXException {
116                    System.err.println("error: " + exception);
117                    throw exception;
118                }
119
120                @Override
121                public void warning(SAXParseException exception)
122                        throws SAXException {
123                    System.err.println("warning: " + exception);
124                    throw exception;
125                }
126            });
127            MoMLEntityResolver resolver = new MoMLEntityResolver();
128            xmlReader.setEntityResolver(resolver);
129            xmlReader.setContentHandler(handler);
130            xmlReader.parse(new InputSource(fileName));
131        } catch (SAXParseException ex) {
132            System.out.println("\n Error parsing line " + ex.getLineNumber()
133                    + ", uri " + ex.getSystemId());
134            System.out.println("   " + ex.getMessage());
135            throw ex;
136        }
137    }
138
139    /** Resolve an entity by checking for
140     *  http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd
141     *  and, if found, return the value of MoMLParser.MoML_1.dtd.
142     */
143    public static class MoMLEntityResolver implements EntityResolver {
144        /** Resolve the entity.
145         *  @param publicID Ignored.
146         *  @param systemID The systemID.
147         *  @return If systemID equals
148         *  http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd then
149         *  return an InputSource based on the value of
150         *  MoMLParser.MoML_DTD_1, If the systemID equals
151         *  https://accessors.org/Accessor_1.dtd, then return the
152         *  contents of
153         *  $CLASSPATH/org/terraswarm/accessor/accessors/web/Accessor_1.dtd
154         *  otherwise return null.
155         *  @exception SAXException If the MoML DTD cannot be created.
156         */
157        @Override
158        public InputSource resolveEntity(String publicID, String systemID)
159                throws SAXException {
160            if (systemID.equals(
161                    "http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd")) {
162                InputSource source = new InputSource(
163                        new StringReader(MoMLParser.MoML_DTD_1));
164                source.setPublicId(publicID);
165                source.setSystemId(systemID);
166                return source;
167            } else if (publicID.equals("-//TerraSwarm//DTD Accessor 1//EN")
168                    && systemID
169                            .equals("https://accessors.org/Accessor_1.dtd")) {
170                try {
171                    // See also org/terraswarm/accessor/JSAccessor.java
172                    String dtd = FileUtilities.getFileAsString(
173                            "$CLASSPATH/org/terraswarm/accessor/accessors/web/Accessor_1.dtd");
174                    InputSource source = new InputSource(new StringReader(dtd));
175                    source.setPublicId(publicID);
176                    source.setSystemId(systemID);
177                    return source;
178                } catch (Exception ex) {
179                    throw new SAXException(
180                            "Failed to read Accessor_1.dtd from local file system",
181                            ex);
182                }
183            }
184            return null;
185        }
186    }
187
188    //===========================================================
189    // SAX DocumentHandler methods
190    //===========================================================
191    @Override
192    public void setDocumentLocator(Locator l) {
193    }
194
195    @Override
196    public void startDocument() throws SAXException {
197    }
198
199    @Override
200    public void endDocument() throws SAXException {
201    }
202
203    @Override
204    public void startElement(String namespaceURI, String sName, // simple name
205            String qName, // qualified name
206            Attributes attrs) throws SAXException {
207    }
208
209    @Override
210    public void endElement(String namespaceURI, String sName, // simple name
211            String qName // qualified name
212    ) throws SAXException {
213    }
214
215    @Override
216    public void characters(char[] buf, int offset, int len)
217            throws SAXException {
218    }
219
220    @Override
221    public void ignorableWhitespace(char[] buf, int offset, int len)
222            throws SAXException {
223    }
224
225    @Override
226    public void processingInstruction(String target, String data)
227            throws SAXException {
228    }
229
230    @Override
231    public void error(SAXParseException exception) throws SAXParseException {
232        warning(exception);
233        throw exception;
234    }
235
236    @Override
237    public void warning(SAXParseException exception) throws SAXParseException {
238        System.out.println("Warning: line " + exception.getLineNumber()
239                + ", uri " + exception.getSystemId());
240        System.out.println("   " + exception.getMessage());
241    }
242}