1 // Copyright (C) 2004, 2015 Christian Jaekl
3 package net.jaekl.qd.xml;
5 import java.io.ByteArrayInputStream;
6 import java.io.IOException;
7 import java.util.ArrayList;
8 import java.util.Locale;
10 import org.junit.Assert;
12 import org.junit.Test;
13 import org.xml.sax.Attributes;
14 import org.xml.sax.InputSource;
15 import org.xml.sax.SAXException;
16 import org.xml.sax.XMLReader;
17 import org.xml.sax.helpers.XMLReaderFactory;
19 public class ParseResultTest {
20 // Some samples of XML that we're going to (try to) parse\
21 private static final String MINIMAL_XML =
23 private static final String MINIMAL_XML_WITH_PROLOGUE =
24 "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root/>";
25 private static final String XML_WITH_MINOR_CONTENT =
26 "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root><One/></Root>";
27 private static final String ROOT_INSIDE_SECONDARY_ELEMENT =
28 "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Secondary><Root><One/></Root></Secondary>";
29 private static final String PROLOGUE_AND_SECONDARY_ELEMENT =
30 "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Secondary/>";
31 private static final String SIMPLE_INTERNAL_TAGS =
32 "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root><One/><Two>content of two</Two><Three>3</Three></Root>";
33 private static final String ROUTE_SUMMARY_FOR_STOP =
34 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
35 + "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
36 + "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"
37 + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
39 + " <GetRouteSummaryForStopResponse xmlns=\"http://octranspo.com\">\n"
40 + " <GetRouteSummaryForStopResult>\n"
41 + " <StopNo xmlns=\"http://tempuri.org/\">1234</StopNo>\n"
43 + " xmlns=\"http://tempuri.org/\">ONE-TWO-THREE-FOUR</StopDescription>\n"
44 + " <Error xmlns=\"http://tempuri.org/\"/>\n"
45 + " <Routes xmlns=\"http://tempuri.org/\">\n"
47 + " <RouteNo>123</RouteNo>\n"
48 + " <DirectionID>0</DirectionID>\n"
49 + " <Direction>NORTH</Direction>\n"
50 + " <RouteHeading>First Mall</RouteHeading>\n"
53 + " <RouteNo>123</RouteNo>\n"
54 + " <DirectionID>1</DirectionID>\n"
55 + " <Direction>SOUTH</Direction>\n"
56 + " <RouteHeading>Second Mall</RouteHeading>\n"
59 + " </GetRouteSummaryForStopResult>\n"
60 + " </GetRouteSummaryForStopResponse>\n"
62 + "</soap:Envelope>\n";
63 private static final String NEXT_TRIPS_FOR_STOP =
64 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
65 + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
66 + "<soap:Body><GetRouteSummaryForStopResponse xmlns=\"http://octranspo.com\">"
67 + "<GetRouteSummaryForStopResult>"
68 + "<StopNo xmlns=\"http://tempuri.org/\">2438</StopNo>"
69 + "<StopDescription xmlns=\"http://tempuri.org/\">BRONSON SUNNYSIDE</StopDescription>"
70 + "<Error xmlns=\"http://tempuri.org/\"/><Routes xmlns=\"http://tempuri.org/\">"
71 + "<Route><RouteNo>4</RouteNo><DirectionID>1</DirectionID><Direction>Northbound</Direction>"
72 + "<RouteHeading>Rideau C / Ctr Rideau</RouteHeading>"
74 + "<Trip ghost=\"false\"><TripDestination>Rideau Centre / Centre Rideau</TripDestination><TripStartTime>19:00</TripStartTime>"
75 + "<AdjustedScheduleTime>16</AdjustedScheduleTime><AdjustmentAge>0.45</AdjustmentAge><LastTripOfSchedule/>"
76 + "<BusType>4LB - IN</BusType><Latitude>45.408957</Latitude><Longitude>-75.664125</Longitude>"
77 + "<GPSSpeed>66.4</GPSSpeed></Trip>"
78 + "<Trip ghost=\"true\"><TripDestination>Rideau Centre / Centre Rideau</TripDestination>"
79 + "<TripStartTime>19:30</TripStartTime><AdjustedScheduleTime>40</AdjustedScheduleTime><AdjustmentAge>-1</AdjustmentAge>"
80 + "<LastTripOfSchedule/><BusType>4LB - IN</BusType><Latitude/><Longitude/><GPSSpeed/></Trip>"
81 + "<Trip ghost=\"true\"><TripDestination>Rideau Centre / Centre Rideau</TripDestination><TripStartTime>20:00</TripStartTime>"
82 + "<AdjustedScheduleTime>70</AdjustedScheduleTime><AdjustmentAge>-1</AdjustmentAge><LastTripOfSchedule/>"
83 + "<BusType>4LB - IN</BusType><Latitude/><Longitude/><GPSSpeed/></Trip>"
84 + "</Trips></Route></Routes></GetRouteSummaryForStopResult></GetRouteSummaryForStopResponse>"
85 + "</soap:Body></soap:Envelope>";
87 // Do the least possible parsing: check for the <Root/> element only.
88 public static class MinimalParse extends ParseResult {
89 private static final String[] INTERNAL = {};
90 private static final Object[][] EXTERNAL = {} ;
92 public MinimalParse() {
93 super("Root", INTERNAL, EXTERNAL);
97 public void endContents(String uri, String localName, String qName,
98 String chars) throws XmlParseException
100 Assert.fail("Should not have any contents to end.");
104 public void endExternal(String uri, String localName, String qName)
105 throws XmlParseException
107 Assert.fail("Should not have any external tags to end.");
111 // Check that we can parse a minimal document without errors.
112 // Because there's no content being parsed (beyond the root element), there is
113 // no "correct" behaviour to assert. The test is to confirm that we
114 // don't do anything incorrect--no calls to endContent() nor endExternal(),
115 // and no exceptions thrown along the way.
117 public void test_withMinimalParse() throws IOException, SAXException {
118 MinimalParse mp = new MinimalParse();
119 ByteArrayInputStream bais = null;
123 MINIMAL_XML_WITH_PROLOGUE,
124 XML_WITH_MINOR_CONTENT,
125 ROOT_INSIDE_SECONDARY_ELEMENT
128 for (String datum : data) {
130 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
131 XMLReader reader = XMLReaderFactory.createXMLReader();
132 ParseHandler ph = new ParseHandler(mp);
133 reader.setContentHandler(ph);
134 reader.parse(new InputSource(bais));
144 // If we parse something that doesn't have the expected root element, we should generate an exception
146 public void test_minimalParseWithMismatchedRootElement() throws IOException {
147 MinimalParse mp = new MinimalParse();
148 ByteArrayInputStream bais = null;
150 String[] data = { PROLOGUE_AND_SECONDARY_ELEMENT };
152 for (String datum : data) {
154 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
155 XMLReader reader = XMLReaderFactory.createXMLReader();
156 ParseHandler ph = new ParseHandler(mp);
157 reader.setContentHandler(ph);
158 reader.parse(new InputSource(bais));
159 Assert.fail("Should have thrown an exception.");
161 catch ( SAXException se ) {
162 Throwable cause = se.getCause();
163 Assert.assertNotNull(cause);
164 Assert.assertTrue(cause instanceof MissingInfoException);
165 MissingInfoException mie = (MissingInfoException) cause;
166 Assert.assertEquals("Root", mie.getTagName());
176 // Do the some simple parsing: <Root/> and some subtags that are processed internally
177 public static class SimpleParse extends ParseResult {
178 private static final String ONE = "One";
179 private static final String TWO = "Two";
180 private static final String THREE = "Three";
182 private static final String[] INTERNAL = {ONE, TWO, THREE};
183 private static final Object[][] EXTERNAL = {} ;
189 public SimpleParse() {
190 super("Root", INTERNAL, EXTERNAL);
192 m_one = m_two = m_three = null;
195 public String getOne() { return m_one; }
196 public String getTwo() { return m_two; }
197 public String getThree() { return m_three; }
200 public void endContents(String uri, String localName, String qName,
201 String chars) throws XmlParseException
203 if (localName.equals(ONE)) {
206 else if (localName.equals(TWO)) {
209 else if (localName.equals(THREE)) {
215 public void endExternal(String uri, String localName, String qName)
216 throws XmlParseException
218 Assert.fail("Should not have any external tags to end.");
222 // Parse some XML containing subtags that are handled internally by SimpleParse
224 public void test_parseWithInternalSubtags() throws IOException, SAXException
226 SimpleParse sp = new SimpleParse();
227 ByteArrayInputStream bais = null;
233 for (String datum : data) {
235 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
236 XMLReader reader = XMLReaderFactory.createXMLReader();
237 ParseHandler ph = new ParseHandler(sp);
238 reader.setContentHandler(ph);
239 reader.parse(new InputSource(bais));
241 Assert.assertEquals("", sp.getOne());
242 Assert.assertEquals("content of two", sp.getTwo());
243 Assert.assertEquals("3", sp.getThree());
253 // Parse sub-tags, handling some internally and some externally
254 public static class RouteSummaryParse extends ParseResult {
255 private static final String STOP_NO = "StopNo";
256 private static final String STOP_DESCR = "StopDescription";
257 private static final String ERROR = "Error";
258 private static final String ROUTES = "Routes";
259 private static final String ROUTE = "Route";
261 private static final String[] INTERNAL = {STOP_NO, STOP_DESCR, ERROR, ROUTES};
262 private static final Object[][] EXTERNAL = { {ROUTE, RouteParse.class} };
264 // Data gleaned from parsing
268 ArrayList<RouteParse> m_routes;
270 public RouteSummaryParse() {
271 super("GetRouteSummaryForStopResult", INTERNAL, EXTERNAL);
274 m_stopDescr = m_error = null;
275 m_routes = new ArrayList<RouteParse>();
278 public int getStopNo() { return m_stopNo; }
279 public String getStopDescription() { return m_stopDescr; }
280 public String getError() { return m_error; }
281 public int getNumRoutes() { return m_routes.size(); }
282 public RouteParse getRoute(int idx) { return m_routes.get(idx); }
285 public void endContents(String uri, String localName, String qName, String chars) throws XmlParseException
287 if (localName.equals(STOP_NO)) {
288 m_stopNo = Integer.parseInt(chars);
290 else if (localName.equals(STOP_DESCR)) {
293 else if (localName.equals(ERROR)) {
299 public void endExternal(String uri, String localName, String qName)
300 throws XmlParseException
302 if (localName.equals(ROUTE)) {
303 ParseResult[] collected = collectParsedChildren(RouteParse.class);
304 for (ParseResult pr : collected) {
305 Assert.assertTrue(pr instanceof RouteParse);
306 m_routes.add((RouteParse)pr);
311 public static class RouteParse extends ParseResult {
312 private static final String ROUTE = "Route";
313 private static final String ROUTE_NO = "RouteNo";
314 private static final String DIR_ID = "DirectionID";
315 private static final String DIR = "Direction";
316 private static final String HEADING = "RouteHeading";
317 private static final String TRIPS = "Trips";
318 private static final String TRIP = "Trip";
320 private static final String[] INTERNAL = {ROUTE_NO, DIR_ID, DIR, HEADING, TRIPS};
321 private static final Object[][] EXTERNAL = { {TRIP, TripParse.class} };
323 // Data gleaned from parsing
328 ArrayList<TripParse> m_trips;
330 public RouteParse() {
331 super(ROUTE, INTERNAL, EXTERNAL);
333 m_routeNo = m_dirID = 0;
334 m_dir = m_heading = null;
335 m_trips = new ArrayList<TripParse>();
338 public int getRouteNo() { return m_routeNo; }
339 public int getDirectionID() { return m_dirID; }
340 public String getDirection() { return m_dir; }
341 public String getHeading() { return m_heading; }
342 public int getNumTrips() { return m_trips.size(); }
343 public TripParse getTrip(int idx) { return m_trips.get(idx); }
346 public void endContents(String uri, String localName, String qName,
347 String chars) throws XmlParseException
349 if (localName.equals(ROUTE_NO)) {
350 m_routeNo = Integer.parseInt(chars);
352 else if (localName.equals(DIR_ID)) {
353 m_dirID = Integer.parseInt(chars);
355 else if (localName.equals(DIR)) {
358 else if (localName.equals(HEADING)) {
364 public void endExternal(String uri, String localName, String qName)
365 throws XmlParseException
367 if (localName.equals(TRIP)) {
368 ParseResult[] collected = collectParsedChildren(TripParse.class);
369 for (ParseResult pr : collected) {
370 Assert.assertTrue(pr instanceof TripParse);
371 m_trips.add((TripParse)pr);
377 public static class TripParse extends ParseResult {
378 private static final String TRIP = "Trip";
379 private static final String GHOST = "ghost";
380 private static final String TRIP_DEST = "TripDestination";
381 private static final String TRIP_START = "TripStartTime";
382 private static final String ADJ_SCHED_TIME = "AdjustedScheduleTime";
384 private static final String[] INTERNAL = {TRIP_DEST, TRIP_START, ADJ_SCHED_TIME };
385 private static final Object[][] EXTERNAL = { };
387 // Data gleaned from parsing
394 super(TRIP, INTERNAL, EXTERNAL);
396 m_dest = m_startTime = null;
401 public String getDestination() { return m_dest; }
402 public String getStartTime() { return m_startTime; }
403 public int getAdjustedScheduleTime() { return m_adjSchedTime; }
404 public boolean ghostAttrSet() { return m_ghost; }
407 public void handleMainAttributes(Attributes attr) throws MissingAttributeException
409 String scratch = this.getRequiredAttr(TRIP, attr, GHOST);
410 Assert.assertNotNull(scratch);
411 m_ghost = scratch.toLowerCase(Locale.CANADA).equals("true");
415 public void endContents(String uri, String localName, String qName,
416 String chars) throws XmlParseException
418 if (localName.equals(TRIP_DEST)) {
421 else if (localName.equals(TRIP_START)) {
424 else if (localName.equals(ADJ_SCHED_TIME)) {
425 m_adjSchedTime = Integer.parseInt(chars);
430 public void endExternal(String uri, String localName, String qName)
431 throws XmlParseException
433 Assert.fail("Should not be attempting to parse external tags.");
437 // Parse some XML containing subtags that are handled both internally and externally
439 public void test_parseRouteSummary() throws IOException, SAXException
441 RouteSummaryParse rsp = new RouteSummaryParse();
442 ByteArrayInputStream bais = null;
447 bais = new ByteArrayInputStream(ROUTE_SUMMARY_FOR_STOP.getBytes("UTF-8"));
448 XMLReader reader = XMLReaderFactory.createXMLReader();
449 ParseHandler ph = new ParseHandler(rsp);
450 reader.setContentHandler(ph);
451 reader.parse(new InputSource(bais));
453 Assert.assertEquals(1234, rsp.getStopNo());
454 Assert.assertEquals("ONE-TWO-THREE-FOUR", rsp.getStopDescription());
455 Assert.assertEquals("", rsp.getError());
457 Assert.assertEquals(2, rsp.getNumRoutes());
459 rp = rsp.getRoute(0);
460 Assert.assertNotNull(rp);
461 Assert.assertEquals(123, rp.getRouteNo());
462 Assert.assertEquals(0, rp.getDirectionID());
463 Assert.assertEquals("NORTH", rp.getDirection());
464 Assert.assertEquals("First Mall", rp.getHeading());
466 rp = rsp.getRoute(1);
467 Assert.assertNotNull(rp);
468 Assert.assertEquals(123, rp.getRouteNo());
469 Assert.assertEquals(1, rp.getDirectionID());
470 Assert.assertEquals("SOUTH", rp.getDirection());
471 Assert.assertEquals("Second Mall", rp.getHeading());
480 // Parse a 3-level external-tag hierarchy: RouteSummary contains Routes contains Trips
482 public void test_parseThreeLevels() throws IOException, SAXException
484 RouteSummaryParse rsp = new RouteSummaryParse();
485 ByteArrayInputStream bais = null;
491 bais = new ByteArrayInputStream(NEXT_TRIPS_FOR_STOP.getBytes("UTF-8"));
492 XMLReader reader = XMLReaderFactory.createXMLReader();
493 ParseHandler ph = new ParseHandler(rsp);
494 reader.setContentHandler(ph);
495 reader.parse(new InputSource(bais));
497 Assert.assertEquals(2438, rsp.getStopNo());
498 Assert.assertEquals("BRONSON SUNNYSIDE", rsp.getStopDescription());
499 Assert.assertEquals("", rsp.getError());
501 Assert.assertEquals(1, rsp.getNumRoutes());
503 rp = rsp.getRoute(0);
504 Assert.assertNotNull(rp);
505 Assert.assertEquals(4, rp.getRouteNo());
506 Assert.assertEquals(1, rp.getDirectionID());
507 Assert.assertEquals("Northbound", rp.getDirection());
508 Assert.assertEquals("Rideau C / Ctr Rideau", rp.getHeading());
510 Assert.assertEquals(3, rp.getNumTrips());
513 Assert.assertNotNull(tp);
514 Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
515 Assert.assertEquals("19:00", tp.getStartTime());
516 Assert.assertEquals(16, tp.getAdjustedScheduleTime());
517 Assert.assertEquals(false, tp.ghostAttrSet());
520 Assert.assertNotNull(tp);
521 Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
522 Assert.assertEquals("19:30", tp.getStartTime());
523 Assert.assertEquals(40, tp.getAdjustedScheduleTime());
524 Assert.assertEquals(true, tp.ghostAttrSet());
527 Assert.assertNotNull(tp);
528 Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
529 Assert.assertEquals("20:00", tp.getStartTime());
530 Assert.assertEquals(70, tp.getAdjustedScheduleTime());
531 Assert.assertEquals(true, tp.ghostAttrSet());