xref: /aosp_15_r20/external/libxml2/python/tests/input_callback.py (revision 7c5688314b92172186c154356a6374bf7684c3ca)
1#!/usr/bin/env python3
2#
3# This tests custom input callbacks
4#
5import sys
6import setup_test
7import libxml2
8try:
9    import StringIO
10    str_io = StringIO.StringIO
11except:
12    import io
13    str_io = io.StringIO
14
15# We implement a new scheme, py://strings/ that will reference this dictionary
16pystrings = {
17    'catalogs/catalog.xml' :
18'''<?xml version="1.0" encoding="utf-8"?>
19<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
20<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
21  <rewriteSystem systemIdStartString="http://example.com/dtds/" rewritePrefix="../dtds/"/>
22</catalog>''',
23
24    'xml/sample.xml' :
25'''<?xml version="1.0" encoding="utf-8"?>
26<!DOCTYPE root SYSTEM "http://example.com/dtds/sample.dtd">
27<root>&sample.entity;</root>''',
28
29    'dtds/sample.dtd' :
30'''
31<!ELEMENT root (#PCDATA)>
32<!ENTITY sample.entity "replacement text">'''
33}
34
35prefix = "py://strings/"
36startURL = prefix + "xml/sample.xml"
37catURL = prefix + "catalogs/catalog.xml"
38
39def my_input_cb(URI):
40    if not(URI.startswith(prefix)):
41        return None
42    path = URI[len(prefix):]
43    if path not in pystrings:
44        return None
45    return str_io(pystrings[path])
46
47
48def run_test(desc, docpath, catalog, exp_status="verified", exp_err=[], test_callback=None,
49        root_name="root", root_content="replacement text"):
50    opts = libxml2.XML_PARSE_DTDLOAD | libxml2.XML_PARSE_NONET | libxml2.XML_PARSE_COMPACT
51    actual_err = []
52
53    def my_global_error_cb(ctx, msg):
54        actual_err.append((-1, msg))
55    def my_ctx_error_cb(arg, msg, severity, reserved):
56        actual_err.append((severity, msg))
57
58    libxml2.registerErrorHandler(my_global_error_cb, None)
59    try:
60        parser = libxml2.createURLParserCtxt(docpath, opts)
61        parser.setErrorHandler(my_ctx_error_cb, None)
62        if catalog is not None:
63            parser.addLocalCatalog(catalog)
64        if test_callback is not None:
65            test_callback()
66        parser.parseDocument()
67        doc = parser.doc()
68        actual_status = "loaded"
69        e = doc.getRootElement()
70        if e.name == root_name and e.content == root_content:
71            actual_status = "verified"
72        doc.freeDoc()
73    except libxml2.parserError:
74        actual_status = "not loaded"
75
76    if actual_status != exp_status:
77        print("Test '%s' failed: expect status '%s', actual '%s'" % (desc, exp_status, actual_status))
78        sys.exit(1)
79    elif actual_err != exp_err:
80        print("Test '%s' failed" % desc)
81        print("Expect errors:")
82        for s,m in exp_err: print("  [%2d] '%s'" % (s,m))
83        print("Actual errors:")
84        for s,m in actual_err: print("  [%2d] '%s'" % (s,m))
85        sys.exit(1)
86
87
88# Check that we cannot read custom schema without custom callback
89run_test(desc="Loading entity without custom callback",
90        docpath=startURL, catalog=None,
91        exp_status="not loaded", exp_err=[
92            (-1, "I/O "),
93            (-1, "warning : "),
94            (-1, "failed to load \"py://strings/xml/sample.xml\": No such file or directory\n")
95            ])
96
97# Register handler and try to load the same entity
98libxml2.registerInputCallback(my_input_cb)
99run_test(desc="Loading entity with custom callback",
100        docpath=startURL, catalog=None,
101        exp_status="loaded", exp_err=[
102            (  4, 'failed to load "http://example.com/dtds/sample.dtd": Attempt to load network entity\n'),
103            (  4, "Entity 'sample.entity' not defined\n")
104            ])
105
106# Register a catalog (also accessible via pystr://) and retry
107run_test(desc="Loading entity with custom callback and catalog",
108        docpath=startURL, catalog=catURL)
109
110# Unregister custom callback when parser is already created
111run_test(desc="Loading entity and unregistering callback",
112        docpath=startURL, catalog=catURL,
113        test_callback=lambda: libxml2.popInputCallbacks(),
114        exp_status="loaded", exp_err=[
115            ( 3, "failed to load \"py://strings/dtds/sample.dtd\": No such file or directory\n"),
116            ( 4, "Entity 'sample.entity' not defined\n")
117            ])
118
119# Try to load the document again
120run_test(desc="Retry loading document after unregistering callback",
121        docpath=startURL, catalog=catURL,
122        exp_status="not loaded", exp_err=[
123            (-1, "I/O "),
124            (-1, "warning : "),
125            (-1, "failed to load \"py://strings/xml/sample.xml\": No such file or directory\n")
126            ])
127
128# But should be able to read standard I/O yet...
129run_test(desc="Loading using standard i/o after unregistering callback",
130        docpath="tst.xml", catalog=None,
131        root_name='doc', root_content='bar')
132
133# Now pop ALL input callbacks, should fail to load even standard I/O
134try:
135    while True:
136        libxml2.popInputCallbacks()
137except IndexError:
138    pass
139
140run_test(desc="Loading using standard i/o after unregistering all callbacks",
141        docpath="tst.xml", catalog=None,
142        exp_status="not loaded", exp_err=[
143            (-1, "I/O "),
144            (-1, "warning : "),
145            (-1, "failed to load \"tst.xml\": No such file or directory\n")
146            ])
147
148print("OK")
149sys.exit(0);
150