root/livinglogic.python.xist/src/ll/xist/scripts/dtd2xsc.py @ 4423:30bac4a06a01

Revision 4423:30bac4a06a01, 5.3 KB (checked in by Walter Doerwald <walter@…>, 9 years ago)

Start documentation for the XIST scripts.

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