1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package catalog;
  24 
  25 import java.io.IOException;
  26 import java.nio.file.Paths;
  27 
  28 import javax.xml.catalog.Catalog;
  29 import javax.xml.catalog.CatalogException;
  30 import javax.xml.catalog.CatalogFeatures;
  31 import javax.xml.catalog.CatalogFeatures.Feature;
  32 import javax.xml.catalog.CatalogManager;
  33 import javax.xml.catalog.CatalogResolver;
  34 import javax.xml.catalog.CatalogUriResolver;
  35 import javax.xml.parsers.ParserConfigurationException;
  36 import javax.xml.parsers.SAXParser;
  37 import javax.xml.parsers.SAXParserFactory;
  38 import javax.xml.transform.Source;
  39 
  40 import org.testng.Assert;
  41 import org.testng.annotations.BeforeClass;
  42 import org.testng.annotations.DataProvider;
  43 import org.testng.annotations.Listeners;
  44 import org.testng.annotations.Test;
  45 import org.xml.sax.Attributes;
  46 import org.xml.sax.ErrorHandler;
  47 import org.xml.sax.InputSource;
  48 import org.xml.sax.SAXException;
  49 import org.xml.sax.XMLReader;
  50 import org.xml.sax.ext.DefaultHandler2;
  51 
  52 /*
  53  * @bug 8081248, 8144966, 8146606, 8146237, 8151154, 8150969, 8151162, 8152527, 8154220
  54  * @summary Tests basic Catalog functions.
  55  */
  56 //@Listeners({jaxp.library.FilePolicy.class}) //uncomment this line after 8161176 is resolved
  57 public class CatalogTest {
  58     static final String KEY_FILES = "javax.xml.catalog.files";
  59 
  60     public String filepath;
  61 
  62     /*
  63      * Initializing fields
  64      */
  65     @BeforeClass
  66     public void setUpClass() throws Exception {
  67         String file1 = getClass().getResource("first_cat.xml").getFile();
  68         if (System.getProperty("os.name").contains("Windows")) {
  69             filepath = file1.substring(1, file1.lastIndexOf("/") + 1);
  70         } else {
  71             filepath = file1.substring(0, file1.lastIndexOf("/") + 1);
  72         }
  73     }
  74 
  75     /*
  76      * @bug 8150187
  77      * NPE is expected if the systemId is null. The specification for systemId
  78      * is as follows:
  79      * A system identifier is required on all external entities. XML
  80      * requires a system identifier on all external entities, so this value is
  81      * always specified.
  82      */
  83     @Test(expectedExceptions = NullPointerException.class)
  84     public void sysIdCantBeNull() {
  85         CatalogResolver catalogResolver = CatalogManager.catalogResolver(CatalogFeatures.defaults());
  86         InputSource is = catalogResolver.resolveEntity("-//FOO//DTD XML Dummy V0.0//EN", null);
  87     }
  88 
  89     /*
  90      * @bug 8156845
  91      * Verifies that an URI reference with a urn:publicid is correctly resolved
  92      * with an uri entry with a publicId.
  93      *
  94      * @param expectedFile is not used in this test, it's kept since we're
  95      * copying the JCK test and its dataProvider. This test may be reused for
  96      * other cases in that test.
  97      */
  98     @Test(dataProvider = "resolveUri")
  99     public void testMatch1(String cFile, String href, String expectedFile, String expectedUri, String msg) {
 100         String catalogFile = getClass().getResource(cFile).getFile();
 101         CatalogUriResolver cur = CatalogManager.catalogUriResolver(CatalogFeatures.defaults(), catalogFile);
 102         Source source = cur.resolve(href, null);
 103         Assert.assertNotNull(source, "Source returned is null");
 104         Assert.assertEquals(expectedUri, source.getSystemId(), msg);
 105     }
 106 
 107     /*
 108      * @bug 8154220
 109      * Verifies that the file input is validated properly. Valid input includes
 110      * multiple file paths separated by semicolon.
 111      */
 112     @Test(dataProvider = "hierarchyOfCatFilesData")
 113     public void hierarchyOfCatFiles2(String systemId, String expectedUri) {
 114         String file1 = getClass().getResource("first_cat.xml").getFile();
 115         String file2 = getClass().getResource("second_cat.xml").getFile();
 116         String files = file1 + ";" + file2;
 117 
 118         try {
 119             System.setProperty(KEY_FILES, files);
 120             CatalogResolver catalogResolver = CatalogManager.catalogResolver(CatalogFeatures.defaults());
 121             String sysId = catalogResolver.resolveEntity(null, systemId).getSystemId();
 122             Assert.assertEquals(sysId, Paths.get(filepath + expectedUri).toUri().toString().replace("///", "/"), "System ID match not right");
 123         } finally {
 124             System.clearProperty(KEY_FILES);
 125         }
 126 
 127     }
 128 
 129     /*
 130      * @bug 8152527
 131      * This test is the same as the JDK test ResolveEntityTests:testMatch1.
 132      * Verifies that the CatalogResolver resolves a publicId and/or systemId as
 133      * expected.
 134      */
 135     @Test(dataProvider = "resolveEntity")
 136     public void testMatch1(String cfile, String prefer, String sysId, String pubId, String expectedUri, String expectedFile, String msg) {
 137         String catalogFile = getClass().getResource(cfile).getFile();
 138         CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).build();
 139         CatalogResolver catalogResolver = CatalogManager.catalogResolver(features, catalogFile);
 140         InputSource is = catalogResolver.resolveEntity(pubId, sysId);
 141         Assert.assertNotNull(is, msg);
 142         String expected = (expectedUri == null) ? expectedFile : expectedUri;
 143         Assert.assertEquals(expected, is.getSystemId(), msg);
 144     }
 145 
 146     /*
 147      * @bug 8151162
 148      * Verifies that the Catalog matches specified publicId or systemId and returns
 149      * results as expected.
 150      */
 151     @Test(dataProvider = "matchWithPrefer")
 152     public void matchWithPrefer(String prefer, String cfile, String publicId, String systemId, String expected) {
 153         String catalogFile = getClass().getResource(cfile).getFile();
 154         Catalog c = CatalogManager.catalog(CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).build(), catalogFile);
 155         String result;
 156         if (publicId != null && publicId.length() > 0) {
 157             result = c.matchPublic(publicId);
 158         } else {
 159             result = c.matchSystem(systemId);
 160         }
 161         Assert.assertEquals(expected, result);
 162     }
 163 
 164     /*
 165      * @bug 8151162
 166      * Verifies that the CatalogResolver resolves specified publicId or systemId
 167      * in accordance with the prefer setting.
 168      * prefer "system": resolves with a system entry.
 169      *                  Exception: use the public entry when the catalog contains
 170      *                  only public entry and only publicId is specified.
 171      * prefer "public": attempts to resolve with a system entry;
 172      *                  attempts to resolve with a public entry if no matching
 173      *                  system entry is found.
 174      */
 175     @Test(dataProvider = "resolveWithPrefer")
 176     public void resolveWithPrefer(String prefer, String cfile, String publicId, String systemId, String expected) {
 177         String catalogFile = getClass().getResource(cfile).getFile();
 178         CatalogFeatures f = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).with(CatalogFeatures.Feature.RESOLVE, "ignore").build();
 179         CatalogResolver catalogResolver = CatalogManager.catalogResolver(f, catalogFile);
 180         String result = catalogResolver.resolveEntity(publicId, systemId).getSystemId();
 181         Assert.assertEquals(expected, result);
 182     }
 183 
 184     /**
 185      * @bug 8150969
 186      * Verifies that the defer attribute set in the catalog file takes precedence
 187      * over other settings, in which case, whether next and delegate Catalogs will
 188      * be loaded is determined by the defer attribute.
 189      */
 190     @Test(dataProvider = "invalidAltCatalogs", expectedExceptions = CatalogException.class)
 191     public void testDeferAltCatalogs(String file) {
 192         String catalogFile = getClass().getResource(file).getFile();
 193         CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.DEFER, "true").build();
 194         /*
 195           Since the defer attribute is set to false in the specified catalog file,
 196           the parent catalog will try to load the alt catalog, which will fail
 197           since it points to an invalid catalog.
 198         */
 199         Catalog catalog = CatalogManager.catalog(features, catalogFile);
 200     }
 201 
 202     /**
 203      * @bug 8151154
 204      * Verifies that the CatalogFeatures' builder throws IllegalArgumentException
 205      * on invalid file inputs.
 206      * @param file the file path
 207      */
 208     @Test(dataProvider = "invalidPaths", expectedExceptions = IllegalArgumentException.class)
 209     public void testFileInput(String file) {
 210             CatalogFeatures features = CatalogFeatures.builder()
 211                 .with(CatalogFeatures.Feature.FILES, file)
 212                 .build();
 213     }
 214 
 215     /**
 216      * @bug 8146237
 217      * PREFER from Features API taking precedence over catalog file
 218      */
 219     @Test
 220     public void testJDK8146237() {
 221         String catalogFile = getClass().getResource("JDK8146237_catalog.xml").getFile();
 222 
 223         try {
 224             CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, "system").build();
 225             Catalog catalog = CatalogManager.catalog(features, catalogFile);
 226             CatalogResolver catalogResolver = CatalogManager.catalogResolver(catalog);
 227             String actualSystemId = catalogResolver.resolveEntity("-//FOO//DTD XML Dummy V0.0//EN", "http://www.oracle.com/alt1sys.dtd").getSystemId();
 228             Assert.assertTrue(actualSystemId.contains("dummy.dtd"), "Resulting id should contain dummy.dtd, indicating a match by publicId");
 229 
 230         } catch (Exception e) {
 231             Assert.fail(e.getMessage());
 232         }
 233     }
 234 
 235     /*
 236        @bug 8146606
 237        Verifies that the resulting systemId does not contain duplicate slashes
 238     */
 239     @Test
 240     public void testRewriteSystem() {
 241         String catalog = getClass().getResource("rewriteCatalog.xml").getFile();
 242 
 243         try {
 244             CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 245             String actualSystemId = resolver.resolveEntity(null, "http://remote.com/dtd/book.dtd").getSystemId();
 246             Assert.assertTrue(!actualSystemId.contains("//"), "result contains duplicate slashes");
 247         } catch (Exception e) {
 248             Assert.fail(e.getMessage());
 249         }
 250 
 251     }
 252 
 253     /*
 254        @bug 8146606
 255        Verifies that the resulting systemId does not contain duplicate slashes
 256     */
 257     @Test
 258     public void testRewriteUri() {
 259         String catalog = getClass().getResource("rewriteCatalog.xml").getFile();
 260 
 261         try {
 262 
 263             CatalogUriResolver resolver = CatalogManager.catalogUriResolver(CatalogFeatures.defaults(), catalog);
 264             String actualSystemId = resolver.resolve("http://remote.com/import/import.xsl", null).getSystemId();
 265             Assert.assertTrue(!actualSystemId.contains("//"), "result contains duplicate slashes");
 266         } catch (Exception e) {
 267             Assert.fail(e.getMessage());
 268         }
 269     }
 270 
 271     /*
 272        @bug 8144966
 273        Verifies that passing null as CatalogFeatures will result in a NPE.
 274     */
 275     @Test(expectedExceptions = NullPointerException.class)
 276     public void testFeatureNull() {
 277         CatalogResolver resolver = CatalogManager.catalogResolver(null, "");
 278 
 279     }
 280 
 281     /*
 282        @bug 8144966
 283        Verifies that passing null as the path will result in a NPE.
 284     */
 285     @Test(expectedExceptions = NullPointerException.class)
 286     public void testPathNull() {
 287         String path = null;
 288         CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), path);
 289     }
 290 
 291     /*
 292        Tests basic catalog feature by using a CatalogResolver instance to
 293     resolve a DTD reference to a locally specified DTD file. If the resolution
 294     is successful, the Handler shall return the value of the entity reference
 295     that matches the expected value.
 296      */
 297     @Test(dataProvider = "catalog")
 298     public void testCatalogResolver(String test, String expected, String catalogFile, String xml, SAXParser saxParser) {
 299         String catalog = null;
 300         if (catalogFile != null) {
 301             catalog = getClass().getResource(catalogFile).getFile();
 302         }
 303         String url = getClass().getResource(xml).getFile();
 304         try {
 305             CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 306             XMLReader reader = saxParser.getXMLReader();
 307             reader.setEntityResolver(cr);
 308             MyHandler handler = new MyHandler(saxParser);
 309             reader.setContentHandler(handler);
 310             reader.parse(url);
 311             System.out.println(test + ": expected [" + expected + "] <> actual [" + handler.getResult() + "]");
 312             Assert.assertEquals(handler.getResult(), expected);
 313         } catch (SAXException | IOException e) {
 314             Assert.fail(e.getMessage());
 315         }
 316     }
 317 
 318     /*
 319        Verifies that when there's no match, in this case only an invalid
 320     catalog is provided, the resolver will throw an exception by default.
 321     */
 322     @Test
 323     public void testInvalidCatalog() {
 324         String catalog = getClass().getResource("catalog_invalid.xml").getFile();
 325 
 326         String test = "testInvalidCatalog";
 327         try {
 328             CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 329             String actualSystemId = resolver.resolveEntity(null, "http://remote/xml/dtd/sys/alice/docAlice.dtd").getSystemId();
 330         } catch (Exception e) {
 331             String msg = e.getMessage();
 332             if (msg != null) {
 333                 if (msg.contains("No match found for publicId")) {
 334                     Assert.assertEquals(msg, "No match found for publicId 'null' and systemId 'http://remote/xml/dtd/sys/alice/docAlice.dtd'.");
 335                     System.out.println(test + ": expected [No match found for publicId 'null' and systemId 'http://remote/xml/dtd/sys/alice/docAlice.dtd'.]");
 336                     System.out.println("actual [" + msg + "]");
 337                 }
 338             }
 339         }
 340     }
 341 
 342     /*
 343        Verifies that if resolve is "ignore", an empty InputSource will be returned
 344     when there's no match. The systemId is then null.
 345     */
 346     @Test
 347     public void testIgnoreInvalidCatalog() {
 348         String catalog = getClass().getResource("catalog_invalid.xml").getFile();
 349         CatalogFeatures f = CatalogFeatures.builder()
 350                 .with(Feature.FILES, catalog)
 351                 .with(Feature.PREFER, "public")
 352                 .with(Feature.DEFER, "true")
 353                 .with(Feature.RESOLVE, "ignore")
 354                 .build();
 355 
 356         String test = "testInvalidCatalog";
 357         try {
 358             CatalogResolver resolver = CatalogManager.catalogResolver(f, "");
 359             String actualSystemId = resolver.resolveEntity(null, "http://remote/xml/dtd/sys/alice/docAlice.dtd").getSystemId();
 360             System.out.println("testIgnoreInvalidCatalog: expected [null]");
 361             System.out.println("testIgnoreInvalidCatalog: expected [null]");
 362             System.out.println("actual [" + actualSystemId + "]");
 363             Assert.assertEquals(actualSystemId, null);
 364         } catch (Exception e) {
 365             Assert.fail(e.getMessage());
 366         }
 367     }
 368 
 369 
 370     /*
 371         DataProvider: used to verify CatalogUriResolver's resolve function.
 372         Data columns:
 373         catalog, uri or publicId, expectedFile, expectedUri, msg
 374 
 375         This DataProvider is copied from JCK ResolveTests' dataMatch1
 376      */
 377     @DataProvider(name = "resolveUri")
 378     Object[][] getDataForUriResolver() {
 379         return new Object[][]{
 380             {"uri.xml", "urn:publicid:-:Acme,+Inc.:DTD+Book+Version+1.0", null, "http://local/base/dtd/book.dtd", "Uri in publicId namespace is incorrectly unwrapped"},
 381         };
 382     }
 383 
 384     /*
 385         DataProvider: used to verify hierarchical catalogs. Refer to JCK test
 386     hierarchyOfCatFiles2.
 387      */
 388     @DataProvider(name = "hierarchyOfCatFilesData")
 389     Object[][] getHierarchyOfCatFilesData() {
 390         return new Object[][]{
 391             {"http://www.oracle.com/sequence.dtd", "first.dtd"},
 392             {"http://www.oracle.com/sequence_next.dtd", "next.dtd"},
 393             {"http://www.oracle.com/sequence_second.dtd", "second.dtd"}
 394         };
 395     }
 396 
 397     /*
 398         DataProvider: used to verify CatalogResolver's resolveEntity function.
 399         Data columns:
 400         catalog, prefer, systemId, publicId, expectedUri, expectedFile, msg
 401      */
 402     @DataProvider(name = "resolveEntity")
 403     Object[][] getDataForMatchingBothIds() {
 404         String expected = "http://www.groupxmlbase.com/dtds/rewrite.dtd";
 405         return new Object[][]{
 406             {"rewriteSystem_id.xml", "system", "http://www.sys00test.com/rewrite.dtd", "PUB-404", expected, expected, "Relative rewriteSystem with xml:base at group level failed"},
 407         };
 408     }
 409 
 410     static String id = "http://openjdk.java.net/xml/catalog/dtd/system.dtd";
 411     /*
 412        DataProvider: used to verify how prefer settings affect the result of the
 413         Catalog's matching operation.
 414         Data columns:
 415         prefer, catalog, publicId, systemId, expected result
 416      */
 417     @DataProvider(name = "matchWithPrefer")
 418     Object[][] getDataForMatch() {
 419         return new Object[][]{
 420             {"public", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 421             {"public", "sysOnly.xml", id, "", null},
 422             {"public", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 423             {"system", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 424             {"system", "sysOnly.xml", id, "", null},
 425             {"system", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 426             {"public", "pubOnly.xml", "", id, null},
 427             {"public", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 428             {"public", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 429             {"system", "pubOnly.xml", "", id, null},
 430             {"system", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 431             {"system", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 432         };
 433     }
 434 
 435     /*
 436        DataProvider: used to verify how prefer settings affect the result of the
 437         CatalogResolver's resolution operation.
 438         Data columns:
 439         prefer, catalog, publicId, systemId, expected result
 440      */
 441     @DataProvider(name = "resolveWithPrefer")
 442     Object[][] getDataForResolve() {
 443         return new Object[][]{
 444             {"system", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 445             {"system", "pubOnly.xml", "", id, null},
 446             {"system", "pubOnly.xml", id, id, null},
 447             {"public", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 448             {"public", "pubOnly.xml", "", id, null},
 449             {"public", "pubOnly.xml", id, id, "http://local/base/dtd/public.dtd"},
 450             {"system", "sysOnly.xml", id, "", null},
 451             {"system", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 452             {"system", "sysOnly.xml", id, id, "http://local/base/dtd/system.dtd"},
 453             {"public", "sysOnly.xml", id, "", null},
 454             {"public", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 455             {"public", "sysOnly.xml", id, id, "http://local/base/dtd/system.dtd"},
 456             {"system", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 457             {"system", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 458             {"system", "sysAndPub.xml", id, id, "http://local/base/dtd/system.dtd"},
 459             {"public", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 460             {"public", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 461             {"public", "sysAndPub.xml", id, id, "http://local/base/dtd/system.dtd"},
 462         };
 463     }
 464     /*
 465        DataProvider: catalogs that contain invalid next or delegate catalogs.
 466                      The defer attribute is set to false.
 467      */
 468     @DataProvider(name = "invalidAltCatalogs")
 469     Object[][] getCatalogs() {
 470         return new Object[][]{
 471             {"defer_false_2.xml"},
 472             {"defer_del_false.xml"}
 473         };
 474     }
 475 
 476     /*
 477        DataProvider: for testing the verification of file paths by
 478                      the CatalogFeatures builder
 479      */
 480     @DataProvider(name = "invalidPaths")
 481     Object[][] getFiles() {
 482         return new Object[][]{
 483             {null},
 484             {""},
 485             {"file:a/b\\c"},
 486             {"file:/../../.."},
 487             {"c:/te:t"},
 488             {"c:/te?t"},
 489             {"c/te*t"},
 490             {"in|valid.txt"},
 491             {"shema:invalid.txt"},
 492         };
 493     }
 494 
 495     /*
 496        DataProvider: provides test name, expected string, the catalog, and XML
 497        document.
 498      */
 499     @DataProvider(name = "catalog")
 500     Object[][] getCatalog() {
 501         return new Object[][]{
 502             {"testSystem", "Test system entry", "catalog.xml", "system.xml", getParser()},
 503             {"testRewriteSystem", "Test rewritesystem entry", "catalog.xml", "rewritesystem.xml", getParser()},
 504             {"testRewriteSystem1", "Test rewritesystem entry", "catalog.xml", "rewritesystem1.xml", getParser()},
 505             {"testSystemSuffix", "Test systemsuffix entry", "catalog.xml", "systemsuffix.xml", getParser()},
 506             {"testDelegateSystem", "Test delegatesystem entry", "catalog.xml", "delegatesystem.xml", getParser()},
 507             {"testPublic", "Test public entry", "catalog.xml", "public.xml", getParser()},
 508             {"testDelegatePublic", "Test delegatepublic entry", "catalog.xml", "delegatepublic.xml", getParser()},
 509         };
 510     }
 511 
 512     SAXParser getParser() {
 513         SAXParser saxParser = null;
 514         try {
 515             SAXParserFactory factory = SAXParserFactory.newInstance();
 516             factory.setNamespaceAware(true);
 517             saxParser = factory.newSAXParser();
 518         } catch (ParserConfigurationException | SAXException e) {
 519         }
 520 
 521         return saxParser;
 522     }
 523 
 524     /**
 525      * SAX handler
 526      */
 527     public class MyHandler extends DefaultHandler2 implements ErrorHandler {
 528 
 529         StringBuilder textContent = new StringBuilder();
 530         SAXParser saxParser;
 531 
 532         MyHandler(SAXParser saxParser) {
 533             textContent.setLength(0);
 534             this.saxParser = saxParser;
 535         }
 536 
 537         String getResult() {
 538             return textContent.toString();
 539         }
 540 
 541         @Override
 542         public void startElement(String uri, String localName, String qName, Attributes attributes)
 543                 throws SAXException {
 544             textContent.delete(0, textContent.length());
 545             try {
 546                 System.out.println("Element: " + uri + ":" + localName + " " + qName);
 547             } catch (Exception e) {
 548                 throw new SAXException(e);
 549             }
 550 
 551         }
 552 
 553         @Override
 554         public void characters(char ch[], int start, int length) throws SAXException {
 555             textContent.append(ch, start, length);
 556         }
 557     }
 558 }