root/livinglogic.python.xist/src/ll/xist/scripts/dtd2xsc.py @ 2990:1f06b2a19f01

Revision 2990:1f06b2a19f01, 4.8 KB (checked in by Walter Doerwald <walter@…>, 12 years ago)

Try to guess the namespace name of none is specified. Fix tests.

Line 
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3
4## Copyright 1999-2007 by LivingLogic AG, Bayreuth/Germany.
5## Copyright 1999-2007 by Walter Dörwald
6##
7## All Rights Reserved
8##
9## See xist/__init__.py for the license
10
11
12"""
13<par>Module that helps to create &xist; namespace modules from &dtd;s.
14Needs <app>xmlproc</app> from the <app>PyXML</app> package.
15For usage information type:</par>
16<prog>
17dtd2xsc --help
18</prog>
19"""
20
21
22import sys, os.path, optparse
23
24from xml.parsers.xmlproc import dtdparser
25
26from ll import url
27from ll.xist import xsc, parsers, xnd
28
29
30def getxmlns(dtd):
31    """
32    Extract the value of all fixed <lit>xmlns</lit> attributes
33    """
34    found = set()
35    for elemname in dtd.get_elements():
36        element = dtd.get_elem(elemname)
37        for attrname in element.get_attr_list():
38            attr = element.get_attr(attrname)
39            if attrname=="xmlns" or u":" in attrname:
40                if attr.decl=="#FIXED":
41                    found.add(attr.default)
42                    continue # skip a namespace declaration
43    return found
44
45
46def dtd2xnd(dtd, xmlns=None):
47    """
48    Convert &dtd; information from the &url; <arg>dtdurl</arg> to an &xist; &dom;
49    using the <pyref module="ll.xist.xnd"><module>xnd</module></pyref> functionality.
50    """
51
52    dtd = dtdparser.load_dtd_string(dtd)
53
54    ns = xnd.Module()
55
56    if xmlns is None:
57        # try to guess the namespace name from the dtd
58        xmlns = getxmlns(dtd)
59        if len(xmlns) == 1:
60            xmlns = iter(xmlns).next()
61        else:
62            xmlns = None
63
64    # Add element info
65    elements = dtd.get_elements()
66    elements.sort()
67    for elemname in elements:
68        dtd_e = dtd.get_elem(elemname)
69        e = xnd.Element(elemname, xmlns=xmlns)
70
71        # Add attribute info for this element
72        attrs = dtd_e.get_attr_list()
73        if len(attrs):
74            attrs.sort()
75            for attrname in attrs:
76                dtd_a = dtd_e.get_attr(attrname)
77                if attrname=="xmlns" or u":" in attrname:
78                    continue # skip namespace declarations and global attributes
79                values = []
80                if dtd_a.type == "ID":
81                    type = "xsc.IDAttr"
82                else:
83                    type = "xsc.TextAttr"
84                    if isinstance(dtd_a.type, list):
85                        if len(dtd_a.type)>1:
86                            values = dtd_a.type
87                        else:
88                            type = "xsc.BoolAttr"
89                default = dtd_a.default
90                if dtd_a.decl=="#REQUIRED":
91                    required = True
92                else:
93                    required = None
94                a = xnd.Attr(name=attrname, type=type, default=default, required=required)
95                for v in values:
96                    a.values.append(v)
97                e.attrs.append(a)
98        ns.content.append(e)
99
100    # Iterate through the elements a second time and add model information
101    for elemname in elements:
102        e = dtd.get_elem(elemname)
103        model = e.get_content_model()
104        if model is None:
105            modeltype = "sims.Any"
106            modelargs = None
107        elif model == ("", [], ""):
108            modeltype = "sims.Empty"
109            modelargs = None
110        else:
111            def extractcont(model):
112                if len(model) == 3:
113                    result = {}
114                    for cont in model[1]:
115                        result.update(extractcont(cont))
116                    return result
117                else:
118                    return {model[0]: None}
119            model = extractcont(model)
120            modeltype = "sims.Elements"
121            modelargs = []
122            for cont in model:
123                if cont == "#PCDATA":
124                    modeltype = "sims.ElementsOrText"
125                elif cont == "EMPTY":
126                    modeltype = "sims.Empty"
127                else:
128                    modelargs.append(ns.element(cont))
129            if not modelargs:
130                if modeltype == "sims.ElementsOrText":
131                    modeltype = "sims.NoElements"
132                else:
133                    modeltype = "sims.NoElementsOrText"
134        e = ns.element(elemname)
135        e.modeltype = modeltype
136        e.modelargs = modelargs
137
138    # Add entities
139    ents = dtd.get_general_entities()
140    ents.sort()
141    for entname in ents:
142        if entname not in ("quot", "apos", "gt", "lt", "amp"):
143            ent = parsers.parsestring(dtd.resolve_ge(entname).value)
144            ns.content.append(xnd.CharRef(entname, codepoint=ord(unicode(ent[0])[0])))
145
146    # if the DTD has exactly one value for all fixed "xmlns" attributes and the user didn't specify xmlns, use this one
147    if xmlns is None and len(foundxmlns)==1:
148        ns.xmlns = foundxmlns.pop()
149    return ns
150
151
152def stream2xnd(stream, xmlns, shareattrs):
153    xnd = dtd2xnd(stream.read(), xmlns)
154
155    if shareattrs=="dupes":
156        xnd.shareattrs(False)
157    elif shareattrs=="all":
158        xnd.shareattrs(True)
159    return xnd
160
161
162def main():
163    p = optparse.OptionParser(usage="usage: %prog [options] <input.dtd >output_xmlns.py")
164    p.add_option("-x", "--xmlns", dest="xmlns", help="the namespace name for this module")
165    p.add_option("-s", "--shareattrs", dest="shareattrs", help="Should identical attributes be shared among elements?", choices=("none", "dupes", "all"), default="dupes")
166    p.add_option("-m", "--model", dest="model", default="once", help="Add sims information to the namespace", choices=("no", "all", "once"))
167    p.add_option("-d", "--defaults", action="store_true", dest="defaults", help="Output default values for attributes")
168
169    (options, args) = p.parse_args()
170    if len(args) != 0:
171        p.error("incorrect number of arguments")
172        return 1
173    print stream2xnd(sys.stdin, options.xmlns, options.shareattrs).aspy(model=options.model, defaults=options.defaults)
174
175
176if __name__ == "__main__":
177    sys.exit(main())
Note: See TracBrowser for help on using the browser.