1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package libcore.xml; 17 18 import static org.junit.Assert.assertThrows; 19 20 import java.io.IOException; 21 import java.io.StringReader; 22 import java.util.ArrayList; 23 import junit.framework.TestCase; 24 import org.junit.Assert; 25 import org.xml.sax.Attributes; 26 import org.xml.sax.ContentHandler; 27 import org.xml.sax.ErrorHandler; 28 import org.xml.sax.InputSource; 29 import org.xml.sax.SAXException; 30 import org.xml.sax.SAXNotRecognizedException; 31 import org.xml.sax.SAXNotSupportedException; 32 import org.xml.sax.SAXParseException; 33 import org.xml.sax.helpers.DefaultHandler; 34 import org.xmlpull.v1.XmlPullParser; 35 import org.xmlpull.v1.XmlPullParserException; 36 import org.xmlpull.v1.XmlPullParserFactory; 37 import org.xmlpull.v1.sax2.Driver; 38 39 public class XmlToSax2DriverTest extends TestCase { 40 41 private static final String XML = 42 "<note type=\"email\" foo=\"bar\">" 43 + "<to>John</to>" 44 + "<from>Smith</from>" 45 + "<heading>Lunch today</heading>" 46 + "<body>Hi, shall we go to lunch at 12?</body>" 47 + "</note>"; 48 49 private Driver driver; 50 51 @Override setUp()52 protected void setUp() throws Exception { 53 super.setUp(); 54 55 driver = new Driver(); 56 } 57 testConstructor()58 public void testConstructor() { 59 Driver driver = null; 60 try { 61 driver = new Driver(); 62 } catch (XmlPullParserException e) { 63 fail("Unexpected exception: " + e.getMessage()); 64 } 65 66 assertEquals(0, driver.getLength()); 67 assertEquals(1, driver.getColumnNumber()); 68 assertEquals(1, driver.getLineNumber()); 69 } 70 testConstructor_parametrized()71 public void testConstructor_parametrized() { 72 XmlPullParserFactory factory; 73 XmlPullParser parser = null; 74 75 try { 76 factory = XmlPullParserFactory.newInstance(null, null); 77 parser = factory.newPullParser(); 78 } catch (XmlPullParserException e) { 79 fail("Couldn't create factory and parser"); 80 } 81 Driver driver = null; 82 83 try { 84 driver = new Driver(parser); 85 } catch (XmlPullParserException e) { 86 fail("Unexpected exception: " + e.getMessage()); 87 } 88 89 assertEquals(0, driver.getLength()); 90 assertEquals(1, driver.getColumnNumber()); 91 assertEquals(1, driver.getLineNumber()); 92 } 93 testGetColumnNumber()94 public void testGetColumnNumber() { 95 assertEquals(driver.getColumnNumber(), 1); 96 } 97 testSetProperty()98 public void testSetProperty() throws Exception { 99 assertThrows(SAXNotSupportedException.class , () -> driver.setProperty( 100 "http://xml.org/sax/properties/declaration-handler", // DECLARATION_HANDLER_PROPERTY 101 "")); 102 103 assertThrows(SAXNotSupportedException.class ,() -> driver.setProperty( 104 "http://xml.org/sax/properties/lexical-handler", // LEXICAL_HANDLER_PROPERTY 105 "")); 106 107 // This may be the only key accpeted by the KXmlParser. 108 String key = "http://xmlpull.org/v1/doc/properties.html#location"; 109 driver.setProperty(key, "123"); 110 assertEquals("123", driver.getProperty(key)); 111 112 assertThrows(SAXNotSupportedException.class ,() -> driver.setProperty("abc", "")); 113 } 114 testGetSetContentHandler()115 public void testGetSetContentHandler() throws XmlPullParserException { 116 assertTrue(driver.getContentHandler() instanceof DefaultHandler); 117 118 ContentHandler handler = new DefaultHandler(); 119 driver.setContentHandler(handler); 120 assertEquals(driver.getContentHandler(), handler); 121 122 driver.setContentHandler(null); 123 assertNull(driver.getContentHandler()); 124 } 125 testGetSetDTDHandler()126 public void testGetSetDTDHandler() { 127 assertNull(driver.getDTDHandler()); 128 129 driver.setDTDHandler(new DefaultHandler()); 130 assertNull(driver.getDTDHandler()); 131 132 driver.setDTDHandler(null); 133 assertNull(driver.getDTDHandler()); 134 } 135 testGetSetEntityResolver()136 public void testGetSetEntityResolver() { 137 assertNull(driver.getEntityResolver()); 138 139 driver.setEntityResolver(new DefaultHandler()); 140 assertNull(driver.getEntityResolver()); 141 142 driver.setEntityResolver((publicId, systemId) -> null); 143 assertNull(driver.getEntityResolver()); 144 145 driver.setEntityResolver(null); 146 assertNull(driver.getEntityResolver()); 147 } 148 testGetSetErrorHandler()149 public void testGetSetErrorHandler() { 150 assertTrue(driver.getContentHandler() instanceof DefaultHandler); 151 152 ErrorHandler handler = new DefaultHandler(); 153 driver.setErrorHandler(handler); 154 assertEquals(handler, driver.getErrorHandler()); 155 156 driver.setErrorHandler(null); 157 assertNull(driver.getErrorHandler()); 158 } 159 testGetSetFeature()160 public void testGetSetFeature() throws SAXNotSupportedException, SAXNotRecognizedException { 161 final String NAMESPACES_FEATURE = "http://xml.org/sax/features/namespaces"; 162 final String NAMESPACE_PREFIXES_FEATURE = "http://xml.org/sax/features/namespace-prefixes"; 163 final String VALIDATION_FEATURE = "http://xml.org/sax/features/validation"; 164 final String PROCESS_DOCDECL_FEATURE = 165 "http://xmlpull.org/v1/doc/features.html#process-docdecl"; 166 final String REPORT_NAMESPACE_ATTRIBUTES_FEATURE = 167 "http://xmlpull.org/v1/doc/features.html#report-namespace-prefixes"; 168 final String RELAXED_FEATURE = "http://xmlpull.org/v1/doc/features.html#relaxed"; 169 170 final Object[][] expectations = { 171 {NAMESPACE_PREFIXES_FEATURE, false}, 172 {VALIDATION_FEATURE, false}, 173 {PROCESS_DOCDECL_FEATURE, false}, 174 {REPORT_NAMESPACE_ATTRIBUTES_FEATURE, false}, 175 {NAMESPACES_FEATURE, true}, 176 }; 177 178 for (Object[] f : expectations) { 179 final String feature = (String) f[0]; 180 final boolean result = (boolean) f[1]; 181 try { 182 assertEquals(result, driver.getFeature(feature)); 183 } catch (SAXNotSupportedException | SAXNotRecognizedException e) { 184 fail("Unexpected exception: " + e.getMessage()); 185 } 186 } 187 188 final String[] settable = { 189 NAMESPACES_FEATURE, 190 PROCESS_DOCDECL_FEATURE, 191 RELAXED_FEATURE, 192 }; 193 for (String feature : settable) { 194 for (boolean value : new boolean[]{ false, true }) { 195 driver.setFeature(feature, value); 196 assertEquals(feature, value, driver.getFeature(feature)); 197 } 198 } 199 } 200 testGetIndex()201 public void testGetIndex() throws NoSuchFieldException, IllegalAccessException { 202 assertEquals(-1, driver.getIndex("hello")); 203 assertEquals(-1, driver.getIndex("encoding")); 204 assertEquals(-1, driver.getIndex("version")); 205 } 206 testGetIndex_namespaced()207 public void testGetIndex_namespaced() { 208 assertEquals(-1, driver.getIndex("", "version")); 209 } 210 testGetLength()211 public void testGetLength() { 212 assertEquals(0, driver.getLength()); 213 } 214 testGetLineNumber()215 public void testGetLineNumber() { 216 assertEquals(1, driver.getLineNumber()); 217 } 218 testGetLocalName()219 public void testGetLocalName() { 220 try { 221 driver.getLocalName(0); 222 } catch (IndexOutOfBoundsException e) { 223 // expected 224 } 225 } 226 testGetProperty()227 public void testGetProperty() { 228 try { 229 driver.getProperty(""); 230 } catch (IndexOutOfBoundsException e) { 231 // expected 232 } catch (SAXNotSupportedException | SAXNotRecognizedException e) { 233 fail("Unexpected exception: " + e.getMessage()); 234 } 235 } 236 testGetPublicId()237 public void testGetPublicId() { 238 assertNull(driver.getPublicId()); 239 } 240 testGetQName()241 public void testGetQName() { 242 try { 243 driver.getQName(0); 244 } catch (IndexOutOfBoundsException e) { 245 // expected 246 } 247 } 248 testGetSystemId()249 public void testGetSystemId() { 250 assertNull(driver.getSystemId()); 251 } 252 testGetType()253 public void testGetType() { 254 assertEquals("CDATA", driver.getType(0)); 255 assertNull(driver.getType("value")); 256 assertNull(driver.getType("", "value")); 257 } 258 testGetUri()259 public void testGetUri() { 260 try { 261 driver.getURI(0); 262 } catch (IndexOutOfBoundsException e) { 263 // expected 264 } 265 } 266 testGetValue()267 public void testGetValue() { 268 try { 269 driver.getValue(0); 270 } catch (IndexOutOfBoundsException e) { 271 // expected 272 } 273 assertNull("CDATA", driver.getValue("value")); 274 assertNull("CDATA", driver.getValue("", "value")); 275 } 276 testParse_String()277 public void testParse_String() { 278 try { 279 driver.parse("systemId"); 280 } catch (SAXException | IOException e) { 281 // expected 282 } 283 284 String systemId = null; 285 try { 286 driver.parse(systemId); 287 } catch (SAXException | IOException e) { 288 // expected 289 } 290 } 291 testParse_InputSource()292 public void testParse_InputSource() throws IOException, SAXException { 293 InputSource source = new InputSource(); 294 source.setCharacterStream(new StringReader(XML)); 295 source.setSystemId("systemId"); 296 297 final int[] errors = {0, 0, 0}; 298 driver.setErrorHandler(new ErrorHandler() { 299 @Override 300 public void warning(SAXParseException exception) throws SAXException { 301 errors[0]++; 302 } 303 304 @Override 305 public void error(SAXParseException exception) throws SAXException { 306 errors[1]++; 307 } 308 309 @Override 310 public void fatalError(SAXParseException exception) throws SAXException { 311 errors[2]++; 312 } 313 }); 314 315 // Four events counter: { "startDocument", "endDocument", "startElement", "endElement" } 316 final int[] events = {0, 0, 0, 0}; 317 final ArrayList<String> tagsEncountered = new ArrayList<>(); 318 final ArrayList<String> textsEncountered = new ArrayList<>(); 319 driver.setContentHandler(new DefaultHandler() { 320 @Override 321 public void startDocument() throws SAXException { 322 super.startDocument(); 323 events[0]++; 324 } 325 326 @Override 327 public void endDocument() throws SAXException { 328 super.endDocument(); 329 events[1]++; 330 } 331 332 @Override 333 public void startElement(String uri, String localName, String qName, 334 Attributes attributes) throws SAXException { 335 super.startElement(uri, localName, qName, attributes); 336 events[2]++; 337 tagsEncountered.add(localName); 338 if ("note".equals(localName)) { 339 assertEquals(2, attributes.getLength()); 340 assertEquals("type", attributes.getLocalName(0)); 341 assertEquals("email", attributes.getValue(0)); 342 assertEquals("foo", attributes.getLocalName(1)); 343 assertEquals("bar", attributes.getValue(1)); 344 } 345 } 346 347 @Override 348 public void characters(char[] ch, int start, int length) throws SAXException { 349 super.characters(ch, start, length); 350 textsEncountered.add(StringFactory.newStringFromChars(ch, start, length)); 351 } 352 353 @Override 354 public void endElement(String uri, String localName, String qName) throws SAXException { 355 super.endElement(uri, localName, qName); 356 events[3]++; 357 } 358 }); 359 driver.parse(source); 360 361 assertEquals("systemId", driver.getSystemId()); 362 Assert.assertArrayEquals(new int[]{0, 0, 0}, errors); 363 Assert.assertArrayEquals(new int[]{1, 1, 5, 5}, events); 364 Assert.assertArrayEquals(new String[]{"note", "to", "from", "heading", "body"}, 365 tagsEncountered.toArray()); 366 Assert.assertArrayEquals(new String[]{ 367 "John", "Smith", "Lunch today", "Hi, shall we go to lunch at 12?" 368 }, textsEncountered.toArray()); 369 } 370 testParseSubtree()371 public void testParseSubtree() throws XmlPullParserException, IOException { 372 XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); 373 parser.setInput(new StringReader(XML)); 374 375 final int[] errors = {0, 0, 0}; 376 driver.setErrorHandler(new ErrorHandler() { 377 @Override 378 public void warning(SAXParseException exception) throws SAXException { 379 errors[0]++; 380 } 381 382 @Override 383 public void error(SAXParseException exception) throws SAXException { 384 errors[1]++; 385 } 386 387 @Override 388 public void fatalError(SAXParseException exception) throws SAXException { 389 errors[2]++; 390 } 391 }); 392 393 // Four events counter: { "startDocument", "endDocument", "startElement", "endElement" } 394 final int[] events = {0, 0, 0, 0}; 395 final ArrayList<String> tagsEncountered = new ArrayList<>(); 396 final ArrayList<String> textsEncountered = new ArrayList<>(); 397 driver.setContentHandler(new DefaultHandler() { 398 @Override 399 public void startDocument() throws SAXException { 400 super.startDocument(); 401 events[0]++; 402 } 403 404 @Override 405 public void endDocument() throws SAXException { 406 super.endDocument(); 407 events[1]++; 408 } 409 410 @Override 411 public void startElement(String uri, String localName, String qName, 412 Attributes attributes) throws SAXException { 413 super.startElement(uri, localName, qName, attributes); 414 events[2]++; 415 tagsEncountered.add(localName); 416 if ("note".equals(localName)) { 417 assertEquals(2, attributes.getLength()); 418 assertEquals("type", attributes.getLocalName(0)); 419 assertEquals("email", attributes.getValue(0)); 420 assertEquals("foo", attributes.getLocalName(1)); 421 assertEquals("bar", attributes.getValue(1)); 422 } 423 } 424 425 @Override 426 public void characters(char[] ch, int start, int length) throws SAXException { 427 super.characters(ch, start, length); 428 textsEncountered.add(StringFactory.newStringFromChars(ch, start, length)); 429 } 430 431 @Override 432 public void endElement(String uri, String localName, String qName) throws SAXException { 433 super.endElement(uri, localName, qName); 434 events[3]++; 435 } 436 }); 437 438 try { 439 driver.parseSubTree(parser); 440 } catch (SAXException e) { 441 // expected, as START_TAG should have been read already 442 } catch (IOException e) { 443 fail("Unexpected exception: " + e.getMessage()); 444 } 445 assertEquals(XmlPullParser.START_TAG, parser.next()); 446 try { 447 driver.parseSubTree(parser); 448 } catch (SAXException | IOException e) { 449 fail("Unexpected exception: " + e.getMessage()); 450 } 451 452 Assert.assertArrayEquals(new int[]{0, 0, 0}, errors); 453 Assert.assertArrayEquals(new int[]{0, 0, 5, 5}, events); 454 Assert.assertArrayEquals(new String[]{"note", "to", "from", "heading", "body"}, 455 tagsEncountered.toArray()); 456 Assert.assertArrayEquals(new String[]{ 457 "John", "Smith", "Lunch today", "Hi, shall we go to lunch at 12?" 458 }, textsEncountered.toArray()); 459 } 460 testStartElement()461 public void testStartElement() throws XmlPullParserException, IOException, SAXException { 462 boolean[] called = {false}; 463 ContentHandler handler = new DefaultHandler() { 464 @Override 465 public void startElement(String uri, String localName, String qName, 466 Attributes attributes) throws SAXException { 467 super.startElement(uri, localName, qName, attributes); 468 called[0] = true; 469 } 470 }; 471 ExtendsDriver d = new ExtendsDriver(handler); 472 d.setContentHandler(handler); 473 d.parse(new InputSource(new StringReader(XML))); 474 475 assertTrue(called[0]); 476 } 477 478 private static class ExtendsDriver extends Driver { 479 480 private final ContentHandler handler; 481 ExtendsDriver(ContentHandler handler)482 public ExtendsDriver(ContentHandler handler) throws XmlPullParserException { 483 this.handler = handler; 484 } 485 486 @Override startElement(String namespace, String localName, String qName)487 protected void startElement(String namespace, String localName, String qName) 488 throws SAXException { 489 super.startElement(namespace, localName, qName); 490 handler.startElement(namespace, localName, qName, this); 491 } 492 } 493 } 494