root/livinglogic.python.xist/src/ll/xist/scripts/dtd2xsc.py @ 4289:e40f5dc756bf

Revision 4289:e40f5dc756bf, 5.5 KB (checked in by Walter Doerwald <walter@…>, 9 years ago)

Enhance xml2xsc.py so it can read multiple XML files.

xnd genating function can now read source from URLs, strings and streams.

Line 
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3
4## Copyright 1999-2010 by LivingLogic AG, Bayreuth/Germany
5## Copyright 1999-2010 by Walter Dörwald
6##
7## All Rights Reserved
8##
9## See ll/__init__.py for the license
10
11
12"""
13Module that helps to create XIST namespace modules from DTDs. Needs xmlproc__
14
15__http://www.garshol.priv.no/download/software/xmlproc/
16
17For usage information type::
18
19    $ dtd2xsc --help
20
21"""
22
23
24__docformat__ = "reStructuredText"
25
26
27import sys, os.path, argparse, contextlib
28
29try:
30    from xml.parsers.xmlproc import dtdparser
31except ImportError:
32    from xmlproc import dtdparser
33
34from ll import misc, url
35from ll.xist import xsc, parse, xnd
36
37
38__docformat__ = "reStructuredText"
39
40
41def getxmlns(dtd):
42    """
43    Extract the value of all fixed ``xmlns`` attributes
44    """
45    found = set()
46    for elemname in dtd.get_elements():
47        element = dtd.get_elem(elemname)
48        for attrname in element.get_attr_list():
49            attr = element.get_attr(attrname)
50            if attrname=="xmlns" or u":" in attrname:
51                if attr.decl=="#FIXED":
52                    found.add(attr.default)
53                    continue # skip a namespace declaration
54    return found
55
56
57def adddtd2xnd(ns, dtd, xmlns=None, model="simple", duplicates="reject"):
58    """
59    Append DTD information from :var:`dtd` to the :class:`xnd.Module` object
60    :var:`ns`.
61    """
62
63    dtd = dtdparser.load_dtd_string(dtd)
64
65    if xmlns is None:
66        # try to guess the namespace name from the dtd
67        xmlns = getxmlns(dtd)
68        if len(xmlns) == 1:
69            xmlns = iter(xmlns).next()
70        else:
71            xmlns = None
72
73    # Add element info
74    elements = dtd.get_elements()
75    elements.sort()
76    for elemname in elements:
77        dtd_e = dtd.get_elem(elemname)
78        e = xnd.Element(elemname, xmlns=xmlns)
79
80        # Add attribute info for this element
81        attrs = dtd_e.get_attr_list()
82        if len(attrs):
83            attrs.sort()
84            for attrname in attrs:
85                dtd_a = dtd_e.get_attr(attrname)
86                if attrname=="xmlns" or u":" in attrname:
87                    continue # skip namespace declarations and global attributes
88                values = []
89                if dtd_a.type == "ID":
90                    type = "xsc.IDAttr"
91                else:
92                    type = "xsc.TextAttr"
93                    if isinstance(dtd_a.type, list):
94                        if len(dtd_a.type) > 1:
95                            values = dtd_a.type
96                        else:
97                            type = "xsc.BoolAttr"
98                default = dtd_a.default
99                if dtd_a.decl=="#REQUIRED":
100                    required = True
101                else:
102                    required = None
103                a = xnd.Attr(name=attrname, type=type, default=default, required=required, values=values)
104                e.attrs[a.name] = a
105        e.add(ns, duplicates=duplicates)
106
107    # Iterate through the elements a second time and add model information
108    for elemname in elements:
109        e = dtd.get_elem(elemname)
110        model = e.get_content_model()
111        if model is None:
112            modeltype = "sims.Any"
113            modelargs = None
114        elif model == ("", [], ""):
115            modeltype = "sims.Empty"
116            modelargs = None
117        else:
118            def extractcont(model):
119                if len(model) == 3:
120                    result = {}
121                    for cont in model[1]:
122                        result.update(extractcont(cont))
123                    return result
124                else:
125                    return {model[0]: None}
126            model = extractcont(model)
127            modeltype = "sims.Elements"
128            modelargs = []
129            for cont in model:
130                if cont == "#PCDATA":
131                    modeltype = "sims.ElementsOrText"
132                elif cont == "EMPTY":
133                    modeltype = "sims.Empty"
134                else:
135                    modelargs.append(ns.elements[(cont, xmlns)])
136            if not modelargs:
137                if modeltype == "sims.ElementsOrText":
138                    modeltype = "sims.NoElements"
139                else:
140                    modeltype = "sims.NoElementsOrText"
141        e = ns.elements[(elemname, xmlns)]
142        if model == "simple":
143            modeltype = modeltype == "sims.Empty"
144            modelargs = None
145        e.modeltype = modeltype
146        e.modelargs = modelargs
147
148    # Add entities
149    ents = dtd.get_general_entities()
150    ents.sort()
151    for entname in ents:
152        if entname not in ("quot", "apos", "gt", "lt", "amp"):
153            try:
154                ent = parse.tree(dtd.resolve_ge(entname).value, parse.Encoder("utf-8"), parse.SGMLOP(encoding="utf-8"), parse.NS(), parse.Node())
155            except xsc.IllegalEntityError:
156                pass
157            else:
158                e = xnd.CharRef(entname, codepoint=ord(unicode(ent[0])[0]))
159                e.add(ns, duplicates=duplicates)
160
161
162def urls2xnd(urls, xmlns, shareattrs, duplicates):
163    ns = xnd.Module()
164    with url.Context():
165        for u in urls:
166            with contextlib.closing(u.openread()) as f:
167                adddtd2xnd(ns, f.read(), xmlns, duplicates)
168
169    if shareattrs=="dupes":
170        ns.shareattrs(False)
171    elif shareattrs=="all":
172        ns.shareattrs(True)
173    return ns
174
175
176def dtd2xnd(dtd, xmlns=None, duplicates="reject"):
177    ns = xnd.Module()
178    adddtd2xnd(ns, dtd, xmlns, duplicates)
179    return ns
180
181
182def main(args=None):
183    p = argparse.ArgumentParser(description="Convert DTDs to XIST namespace (on stdout)")
184    p.add_argument("urls", metavar="urls", type=url.URL, help="ULRs of DTDs to be parsed", nargs="+")
185    p.add_argument("-x", "--xmlns", dest="xmlns", metavar="NAME", help="the namespace name for this module")
186    p.add_argument("-s", "--shareattrs", dest="shareattrs", help="Should identical attributes be shared among elements? (default: %(default)s)", choices=("none", "dupes", "all"), default="dupes")
187    p.add_argument("-m", "--model", dest="model", default="once", help="Add sims information to the namespace (default: %(default)s)", choices=("no", "simple", "fullall", "fullonce"))
188    p.add_argument("-d", "--defaults", dest="defaults", help="Output default values for attributes? (default: %(default)s)", action=misc.FlagAction, default=False)
189    p.add_argument(      "--duplicates", dest="duplicates", help="How to handle duplicate elements from multiple DTDs (default: %(default)s)", choices=("reject", "allow", "merge"), default="reject")
190
191    args = p.parse_args(args)
192    print urls2xnd(args.urls, args.xmlns, args.shareattrs, args.model, args.duplicates).aspy(model=args.model, defaults=args.defaults)
193
194
195if __name__ == "__main__":
196    sys.exit(main())
Note: See TracBrowser for help on using the browser.