040308d051e7e11c7612dcc213df94123b34efeb
[frank.git] / test / net / jaekl / qd / xml / ParseResultTest.java
1 // Copyright (C) 2004, 2014 Christian Jaekl
2
3 package net.jaekl.qd.xml;
4
5 import java.io.ByteArrayInputStream;
6 import java.io.IOException;
7 import java.util.ArrayList;
8
9 import junit.framework.Assert;
10
11 import org.junit.Test;
12 import org.xml.sax.InputSource;
13 import org.xml.sax.SAXException;
14 import org.xml.sax.XMLReader;
15 import org.xml.sax.helpers.XMLReaderFactory;
16
17 public class ParseResultTest {
18         // Some samples of XML that we're going to (try to) parse\
19         private static final String MINIMAL_XML = 
20                         "<Root/>";
21         private static final String MINIMAL_XML_WITH_PROLOGUE = 
22                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root/>";
23         private static final String XML_WITH_MINOR_CONTENT = 
24                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root><One/></Root>";
25         private static final String ROOT_INSIDE_SECONDARY_ELEMENT = 
26                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Secondary><Root><One/></Root></Secondary>";
27         private static final String PROLOGUE_AND_SECONDARY_ELEMENT = 
28                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Secondary/>";
29         private static final String SIMPLE_INTERNAL_TAGS = 
30                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root><One/><Two>content of two</Two><Three>3</Three></Root>";
31         private static final String ROUTE_SUMMARY_FOR_STOP = 
32                         "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
33                                         + "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" 
34                                         +  "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n" 
35                                         +  "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
36                                         + "     <soap:Body>\n"
37                                         + "             <GetRouteSummaryForStopResponse xmlns=\"http://octranspo.com\">\n"
38                                         + "                     <GetRouteSummaryForStopResult>\n"
39                                         + "                             <StopNo xmlns=\"http://tempuri.org/\">1234</StopNo>\n"
40                                         + "                             <StopDescription" 
41                                         + "                              xmlns=\"http://tempuri.org/\">ONE-TWO-THREE-FOUR</StopDescription>\n"
42                                         + "                             <Error xmlns=\"http://tempuri.org/\"/>\n"
43                                         + "                             <Routes xmlns=\"http://tempuri.org/\">\n"
44                                         + "                                     <Route>\n"
45                                         + "                                             <RouteNo>123</RouteNo>\n"
46                                         + "                                             <DirectionID>0</DirectionID>\n"
47                                         + "                                             <Direction>NORTH</Direction>\n"
48                                         + "                                             <RouteHeading>First Mall</RouteHeading>\n"
49                                         + "                                     </Route>\n"
50                                         + "                                     <Route>\n"
51                                         + "                                             <RouteNo>123</RouteNo>\n"
52                                         + "                                             <DirectionID>1</DirectionID>\n"
53                                         + "                                             <Direction>SOUTH</Direction>\n"
54                                         + "                                             <RouteHeading>Second Mall</RouteHeading>\n"
55                                         + "                                     </Route>\n"
56                                         + "                             </Routes>\n"
57                                         + "                     </GetRouteSummaryForStopResult>\n"
58                                         + "             </GetRouteSummaryForStopResponse>\n"
59                                         + "     </soap:Body>\n"
60                                         + "</soap:Envelope>\n";
61         private static final String NEXT_TRIPS_FOR_STOP = 
62                         "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
63                         + "<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\">"
64                         + "<soap:Body><GetRouteSummaryForStopResponse xmlns=\"http://octranspo.com\">"
65                         + "<GetRouteSummaryForStopResult>"
66                         + "<StopNo xmlns=\"http://tempuri.org/\">2438</StopNo>"
67                         + "<StopDescription xmlns=\"http://tempuri.org/\">BRONSON SUNNYSIDE</StopDescription>"
68                         + "<Error xmlns=\"http://tempuri.org/\"/><Routes xmlns=\"http://tempuri.org/\">"
69                         + "<Route><RouteNo>4</RouteNo><DirectionID>1</DirectionID><Direction>Northbound</Direction>"
70                         + "<RouteHeading>Rideau C / Ctr Rideau</RouteHeading>"
71                         + "<Trips>"
72                         + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination><TripStartTime>19:00</TripStartTime>"
73                         + "<AdjustedScheduleTime>16</AdjustedScheduleTime><AdjustmentAge>0.45</AdjustmentAge><LastTripOfSchedule/>"
74                         + "<BusType>4LB - IN</BusType><Latitude>45.408957</Latitude><Longitude>-75.664125</Longitude>"
75                         + "<GPSSpeed>66.4</GPSSpeed></Trip>"
76                         + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination>"
77                         + "<TripStartTime>19:30</TripStartTime><AdjustedScheduleTime>40</AdjustedScheduleTime><AdjustmentAge>-1</AdjustmentAge>"
78                         + "<LastTripOfSchedule/><BusType>4LB - IN</BusType><Latitude/><Longitude/><GPSSpeed/></Trip>"
79                         + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination><TripStartTime>20:00</TripStartTime>"
80                         + "<AdjustedScheduleTime>70</AdjustedScheduleTime><AdjustmentAge>-1</AdjustmentAge><LastTripOfSchedule/>"
81                         + "<BusType>4LB - IN</BusType><Latitude/><Longitude/><GPSSpeed/></Trip>"
82                         + "</Trips></Route></Routes></GetRouteSummaryForStopResult></GetRouteSummaryForStopResponse>"
83                         + "</soap:Body></soap:Envelope>";
84         
85         // Do the least possible parsing:  check for the <Root/> element only.
86         public static class MinimalParse extends ParseResult {
87                 private static final String[] INTERNAL = {};
88                 private static final Object[][] EXTERNAL = {} ;
89                 
90                 public MinimalParse() {
91                         super("Root", INTERNAL, EXTERNAL);
92                 }
93
94                 @Override
95                 public void endContents(String uri, String localName, String qName,
96                                 String chars) throws XmlParseException 
97                 {
98                         Assert.fail("Should not have any contents to end.");
99                 }
100
101                 @Override
102                 public void endExternal(String uri, String localName, String qName)
103                                 throws XmlParseException 
104                 {
105                         Assert.fail("Should not have any external tags to end.");
106                 }
107         }
108         
109         // Check that we can parse a minimal document without errors.
110         // Because there's no content being parsed (beyond the root element), there is 
111         // no "correct" behaviour to assert.  The test is to confirm that we 
112         // don't do anything incorrect--no calls to endContent() nor endExternal(),
113         // and no exceptions thrown along the way.
114         @Test
115         public void test_withMinimalParse() throws IOException, SAXException {
116                 MinimalParse mp = new MinimalParse();
117                 ByteArrayInputStream bais = null;
118                 
119                 String[] data = { 
120                                         MINIMAL_XML, 
121                                         MINIMAL_XML_WITH_PROLOGUE, 
122                                         XML_WITH_MINOR_CONTENT,
123                                         ROOT_INSIDE_SECONDARY_ELEMENT 
124                                 };
125                 
126                 for (String datum : data) {
127                         try {
128                                 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
129                                 XMLReader reader = XMLReaderFactory.createXMLReader();
130                                 ParseHandler ph = new ParseHandler(mp);
131                                 reader.setContentHandler(ph);
132                                 reader.parse(new InputSource(bais));
133                         }
134                         finally {
135                                 if (null != bais) { 
136                                         bais.close();
137                                 }
138                         }
139                 }
140         }
141         
142         // If we parse something that doesn't have the expected root element, we should generate an exception
143         @Test
144         public void test_minimalParseWithMismatchedRootElement() throws IOException {
145                 MinimalParse mp = new MinimalParse();
146                 ByteArrayInputStream bais = null;
147                 
148                 String[] data = { PROLOGUE_AND_SECONDARY_ELEMENT };
149                 
150                 for (String datum : data) {
151                         try {
152                                 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
153                                 XMLReader reader = XMLReaderFactory.createXMLReader();
154                                 ParseHandler ph = new ParseHandler(mp);
155                                 reader.setContentHandler(ph);
156                                 reader.parse(new InputSource(bais));
157                                 Assert.fail("Should have thrown an exception.");
158                         }
159                         catch ( SAXException se ) {
160                                 Throwable cause = se.getCause();
161                                 Assert.assertNotNull(cause);
162                                 Assert.assertTrue(cause instanceof MissingInfoException);
163                                 MissingInfoException mie = (MissingInfoException) cause;
164                                 Assert.assertEquals("Root", mie.getTagName());
165                         }
166                         finally {
167                                 if (null != bais) { 
168                                         bais.close();
169                                 }
170                         }
171                 }
172         }
173         
174         // Do the some simple parsing:  <Root/> and some subtags that are processed internally
175         public static class SimpleParse extends ParseResult {
176                 private static final String ONE = "One";
177                 private static final String TWO = "Two";
178                 private static final String THREE = "Three";
179                 
180                 private static final String[] INTERNAL = {ONE, TWO, THREE};
181                 private static final Object[][] EXTERNAL = {} ;
182                 
183                 String m_one;
184                 String m_two;
185                 String m_three;
186                 
187                 public SimpleParse() {
188                         super("Root", INTERNAL, EXTERNAL);
189                         
190                         m_one = m_two = m_three = null;
191                 }
192                 
193                 public String getOne() { return m_one; }
194                 public String getTwo() { return m_two; }
195                 public String getThree() { return m_three; }
196
197                 @Override
198                 public void endContents(String uri, String localName, String qName,
199                                 String chars) throws XmlParseException 
200                 {
201                         if (localName.equals(ONE)) {
202                                 m_one = chars;
203                         }
204                         else if (localName.equals(TWO)) {
205                                 m_two = chars;
206                         }
207                         else if (localName.equals(THREE)) {
208                                 m_three = chars;
209                         }
210                 }
211
212                 @Override
213                 public void endExternal(String uri, String localName, String qName)
214                                 throws XmlParseException 
215                 {
216                         Assert.fail("Should not have any external tags to end.");
217                 }
218         }
219         
220         // Parse some XML containing subtags that are handled internally by SimpleParse
221         @Test
222         public void test_parseWithInternalSubtags() throws IOException, SAXException 
223         {
224                 SimpleParse sp = new SimpleParse();
225                 ByteArrayInputStream bais = null;
226                 
227                 String[] data = {
228                                         SIMPLE_INTERNAL_TAGS
229                                 };
230                 
231                 for (String datum : data) {
232                         try {
233                                 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
234                                 XMLReader reader = XMLReaderFactory.createXMLReader();
235                                 ParseHandler ph = new ParseHandler(sp);
236                                 reader.setContentHandler(ph);
237                                 reader.parse(new InputSource(bais));
238                                 
239                                 Assert.assertEquals("", sp.getOne());
240                                 Assert.assertEquals("content of two", sp.getTwo());
241                                 Assert.assertEquals("3", sp.getThree());
242                         }
243                         finally {
244                                 if (null != bais) { 
245                                         bais.close();
246                                 }
247                         }
248                 }
249         }
250         
251         // Parse sub-tags, handling some internally and some externally
252         public static class RouteSummaryParse extends ParseResult {
253                 private static final String STOP_NO = "StopNo";
254                 private static final String STOP_DESCR = "StopDescription";
255                 private static final String ERROR = "Error";
256                 private static final String ROUTES = "Routes";
257                 private static final String ROUTE = "Route";
258                 
259                 private static final String[] INTERNAL = {STOP_NO, STOP_DESCR, ERROR, ROUTES};
260                 private static final Object[][] EXTERNAL = { {ROUTE, RouteParse.class} };
261                 
262                 // Data gleaned from parsing
263                 int m_stopNo;
264                 String m_stopDescr;
265                 String m_error;
266                 ArrayList<RouteParse> m_routes;
267                 
268                 public RouteSummaryParse() {
269                         super("GetRouteSummaryForStopResult", INTERNAL, EXTERNAL);
270                         
271                         m_stopNo = 0;
272                         m_stopDescr = m_error = null;
273                         m_routes = new ArrayList<RouteParse>();
274                 }
275                 
276                 public int getStopNo() { return m_stopNo; }
277                 public String getStopDescription() { return m_stopDescr; }
278                 public String getError() { return m_error; }
279                 public int getNumRoutes() { return m_routes.size(); }
280                 public RouteParse getRoute(int idx) { return m_routes.get(idx); }
281
282                 @Override
283                 public void endContents(String uri, String localName, String qName,
284                                 String chars) throws XmlParseException 
285                 {
286                         if (localName.equals(STOP_NO)) {
287                                 m_stopNo = Integer.parseInt(chars);
288                         }
289                         else if (localName.equals(STOP_DESCR)) {
290                                 m_stopDescr = chars;
291                         }
292                         else if (localName.equals(ERROR)) {
293                                 m_error = chars;
294                         }
295                 }
296
297                 @Override
298                 public void endExternal(String uri, String localName, String qName)
299                                 throws XmlParseException 
300                 {
301                         if (localName.equals(ROUTE)) {
302                                 ParseResult[] collected = collectParsedChildren(RouteParse.class);
303                                 for (ParseResult pr : collected) {
304                                         Assert.assertTrue(pr instanceof RouteParse);
305                                         m_routes.add((RouteParse)pr);
306                                 }
307                         }
308                 }
309         }
310         public static class RouteParse extends ParseResult {
311                 private static final String ROUTE = "Route";
312                 private static final String ROUTE_NO = "RouteNo";
313                 private static final String DIR_ID = "DirectionID";
314                 private static final String DIR = "Direction";
315                 private static final String HEADING = "RouteHeading";
316                 private static final String TRIPS = "Trips";
317                 private static final String TRIP = "Trip";
318                 
319                 private static final String[] INTERNAL = {ROUTE_NO, DIR_ID, DIR, HEADING, TRIPS};
320                 private static final Object[][] EXTERNAL = { {TRIP, TripParse.class} };
321                 
322                 // Data gleaned from parsing
323                 int m_routeNo;
324                 int m_dirID;
325                 String m_dir;
326                 String m_heading;
327                 ArrayList<TripParse> m_trips;
328                 
329                 public RouteParse() {
330                         super(ROUTE, INTERNAL, EXTERNAL);
331                         
332                         m_routeNo = m_dirID = 0;
333                         m_dir = m_heading = null;
334                         m_trips = new ArrayList<TripParse>();
335                 }
336                 
337                 public int getRouteNo() { return m_routeNo; }
338                 public int getDirectionID() { return m_dirID; }
339                 public String getDirection() { return m_dir; }
340                 public String getHeading() { return m_heading; }
341                 public int getNumTrips() { return m_trips.size(); }
342                 public TripParse getTrip(int idx) { return m_trips.get(idx); }
343
344                 @Override
345                 public void endContents(String uri, String localName, String qName,
346                                 String chars) throws XmlParseException 
347                 {
348                         if (localName.equals(ROUTE_NO)) {
349                                 m_routeNo = Integer.parseInt(chars);
350                         }
351                         else if (localName.equals(DIR_ID)) {
352                                 m_dirID = Integer.parseInt(chars);
353                         }
354                         else if (localName.equals(DIR)) {
355                                 m_dir = chars;
356                         }
357                         else if (localName.equals(HEADING)) {
358                                 m_heading = chars;
359                         }
360                 }
361
362                 @Override
363                 public void endExternal(String uri, String localName, String qName)
364                                 throws XmlParseException 
365                 {
366                         if (localName.equals(TRIP)) {
367                                 ParseResult[] collected = collectParsedChildren(TripParse.class);
368                                 for (ParseResult pr : collected) {
369                                         Assert.assertTrue(pr instanceof TripParse);
370                                         m_trips.add((TripParse)pr);
371                                 }
372                         }
373                         
374                 }
375         }
376         public static class TripParse extends ParseResult {
377                 private static final String TRIP = "Trip";
378                 private static final String TRIP_DEST = "TripDestination";
379                 private static final String TRIP_START = "TripStartTime";
380                 private static final String ADJ_SCHED_TIME = "AdjustedScheduleTime";
381                 
382                 private static final String[] INTERNAL = {TRIP_DEST, TRIP_START, ADJ_SCHED_TIME };
383                 private static final Object[][] EXTERNAL = { };
384                 
385                 // Data gleaned from parsing
386                 String m_dest;
387                 String m_startTime;
388                 int m_adjSchedTime;;
389                 
390                 public TripParse() {
391                         super(TRIP, INTERNAL, EXTERNAL);
392                         
393                         m_dest = m_startTime = null;
394                         m_adjSchedTime = 0;
395                 }
396                 
397                 public String getDestination() { return m_dest; }
398                 public String getStartTime() { return m_startTime; }
399                 public int getAdjustedScheduleTime() { return m_adjSchedTime; }
400
401                 @Override
402                 public void endContents(String uri, String localName, String qName,
403                                 String chars) throws XmlParseException 
404                 {
405                         if (localName.equals(TRIP_DEST)) {
406                                 m_dest = chars;
407                         }
408                         else if (localName.equals(TRIP_START)) {
409                                 m_startTime = chars;
410                         }
411                         else if (localName.equals(ADJ_SCHED_TIME)) {
412                                 m_adjSchedTime = Integer.parseInt(chars);
413                         }
414                 }
415
416                 @Override
417                 public void endExternal(String uri, String localName, String qName)
418                                 throws XmlParseException 
419                 {
420                         Assert.fail("Should not be attempting to parse external tags.");
421                 }
422         }
423         
424         // Parse some XML containing subtags that are handled both internally and externally
425         @Test
426         public void test_parseRouteSummary() throws IOException, SAXException 
427         {
428                 RouteSummaryParse rsp = new RouteSummaryParse();
429                 ByteArrayInputStream bais = null;
430                 
431                 try {
432                         RouteParse rp;
433                         
434                         bais = new ByteArrayInputStream(ROUTE_SUMMARY_FOR_STOP.getBytes("UTF-8"));
435                         XMLReader reader = XMLReaderFactory.createXMLReader();
436                         ParseHandler ph = new ParseHandler(rsp);
437                         reader.setContentHandler(ph);
438                         reader.parse(new InputSource(bais));
439                         
440                         Assert.assertEquals(1234, rsp.getStopNo());
441                         Assert.assertEquals("ONE-TWO-THREE-FOUR", rsp.getStopDescription());
442                         Assert.assertEquals("", rsp.getError());
443                         
444                         Assert.assertEquals(2, rsp.getNumRoutes());
445                         
446                         rp = rsp.getRoute(0);
447                         Assert.assertNotNull(rp);
448                         Assert.assertEquals(123, rp.getRouteNo());
449                         Assert.assertEquals(0, rp.getDirectionID());
450                         Assert.assertEquals("NORTH", rp.getDirection());
451                         Assert.assertEquals("First Mall", rp.getHeading());
452                         
453                         rp = rsp.getRoute(1);
454                         Assert.assertNotNull(rp);
455                         Assert.assertEquals(123, rp.getRouteNo());
456                         Assert.assertEquals(1, rp.getDirectionID());
457                         Assert.assertEquals("SOUTH", rp.getDirection());
458                         Assert.assertEquals("Second Mall", rp.getHeading());
459                 }
460                 finally {
461                         if (null != bais) { 
462                                 bais.close();
463                         }
464                 }
465         }
466         
467         // Parse a 3-level external-tag hierarchy:  RouteSummary contains Routes contains Trips
468         @Test
469         public void test_parseThreeLevels() throws IOException, SAXException 
470         {
471                 RouteSummaryParse rsp = new RouteSummaryParse();
472                 ByteArrayInputStream bais = null;
473                 
474                 try {
475                         RouteParse rp;
476                         TripParse tp;
477                         
478                         bais = new ByteArrayInputStream(NEXT_TRIPS_FOR_STOP.getBytes("UTF-8"));
479                         XMLReader reader = XMLReaderFactory.createXMLReader();
480                         ParseHandler ph = new ParseHandler(rsp);
481                         reader.setContentHandler(ph);
482                         reader.parse(new InputSource(bais));
483                         
484                         Assert.assertEquals(2438, rsp.getStopNo());
485                         Assert.assertEquals("BRONSON SUNNYSIDE", rsp.getStopDescription());
486                         Assert.assertEquals("", rsp.getError());
487                         
488                         Assert.assertEquals(1, rsp.getNumRoutes());
489                         
490                         rp = rsp.getRoute(0);
491                         Assert.assertNotNull(rp);
492                         Assert.assertEquals(4, rp.getRouteNo());
493                         Assert.assertEquals(1, rp.getDirectionID());
494                         Assert.assertEquals("Northbound", rp.getDirection());
495                         Assert.assertEquals("Rideau C / Ctr Rideau", rp.getHeading());
496                         
497                         Assert.assertEquals(3, rp.getNumTrips());
498                         
499                         tp = rp.getTrip(0);
500                         Assert.assertNotNull(tp);
501                         Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
502                         Assert.assertEquals("19:00", tp.getStartTime());
503                         Assert.assertEquals(16, tp.getAdjustedScheduleTime());
504                         
505                         tp = rp.getTrip(1);
506                         Assert.assertNotNull(tp);
507                         Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
508                         Assert.assertEquals("19:30", tp.getStartTime());
509                         Assert.assertEquals(40, tp.getAdjustedScheduleTime());
510
511                         tp = rp.getTrip(2);
512                         Assert.assertNotNull(tp);
513                         Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
514                         Assert.assertEquals("20:00", tp.getStartTime());
515                         Assert.assertEquals(70, tp.getAdjustedScheduleTime());
516                 }
517                 finally {
518                         if (null != bais) { 
519                                 bais.close();
520                         }
521                 }
522         }
523 }