5
5
ANNO_ATTR , ANNOTATION , ANY , ASSIGN , ATTR , CHOICE , DATATAG , DATATYPES ,
6
6
DEFAULT_NS , DEFINE , DIV , DOCUMENTATION , ELEM , EMPTY , EXCEPT , GRAMMAR ,
7
7
GROUP , INTERLEAVE , LIST , LITERAL , MAYBE , MIXED , NAME , NOT_ALLOWED , NS ,
8
- PARAM , PARENT , REF , ROOT , SEQ , SOME , TEXT ,
8
+ PARAM , PARENT , REF , ROOT , SEQ , SOME , TEXT , LITERAL_TYPE
9
9
)
10
10
11
11
import html
12
12
13
13
QUANTS = {SOME : 'oneOrMore' , MAYBE : 'optional' , ANY : 'zeroOrMore' }
14
- TYPELIB_NS = 'http://www.w3.org/2001/XMLSchema-datatypes'
14
+ TYPELIBS = {
15
+ 'xsd' : 'http://www.w3.org/2001/XMLSchema-datatypes'
16
+ }
15
17
NAMESPACES = {
16
18
'a' : 'http://relaxng.org/ns/compatibility/annotations/1.0' ,
17
19
'xml' : 'http://www.w3.org/XML/1998/namespace' ,
@@ -25,15 +27,20 @@ def __init__(self, indent=None):
25
27
26
28
def reset (self ):
27
29
self .buf = []
28
- self .needs = {}
29
- self .types = None
30
30
self .ns = {}
31
+ self .typelibs = {}
31
32
self .default = ''
32
33
self .level = 0
33
34
34
35
def write (self , s ):
35
36
self .buf .append (self .indent * self .level + s )
36
37
38
+ def datatype_library (self , prefix ):
39
+ assert prefix in self .typelibs or prefix in TYPELIBS , prefix
40
+ if prefix not in self .typelibs :
41
+ self .typelibs [prefix ] = TYPELIBS [prefix ]
42
+ return self .typelibs [prefix ]
43
+
37
44
def namespace (self , ns ):
38
45
assert ns in self .ns or ns in NAMESPACES , ns
39
46
if ns not in self .ns :
@@ -43,11 +50,9 @@ def namespace(self, ns):
43
50
def toxml (self , node ):
44
51
45
52
self .reset ()
46
- types = None
47
53
for n in node .value :
48
54
if n .type == DATATYPES :
49
- types = n .value [0 ]
50
- self .types = types
55
+ self .typelibs [n .name ] = n .value [0 ]
51
56
elif n .type == DEFAULT_NS :
52
57
self .default = n .value [0 ]
53
58
if n .name is not None :
@@ -63,9 +68,10 @@ def toxml(self, node):
63
68
self .visit (node .value )
64
69
for ns , url in sorted (self .ns .items ()):
65
70
prelude .append (' xmlns:%s="%s"' % (ns , url ))
66
- if types is not None or self .needs .get ('types' ):
67
- url = types if types is not None else TYPELIB_NS
68
- prelude .append (' datatypeLibrary="%s"' % url )
71
+
72
+ # if xsd:* ever referenced, print it at the grammar level
73
+ if 'xsd' in self .typelibs :
74
+ prelude .append (' datatypeLibrary="%s"' % self .typelibs ['xsd' ])
69
75
70
76
prelude [- 1 ] = prelude [- 1 ] + '>'
71
77
self .write ('</grammar>' )
@@ -78,6 +84,19 @@ def anno_attrs(self, nodes):
78
84
return ''
79
85
return ' ' + ' ' .join ('%s="%s"' % attr for attr in pairs )
80
86
87
+ def type_attrs (self , name ):
88
+ if ':' in name :
89
+ prefix , name = name .split (':' , 1 )
90
+ ns = self .datatype_library (prefix )
91
+ else :
92
+ assert name in ('string' , 'token' ) # these are the only "built-in" datatypes
93
+ ns = ""
94
+
95
+ attrs = ' type="%s"' % name
96
+ if ns != TYPELIBS ['xsd' ]:
97
+ attrs += ' datatypeLibrary="%s"' % ns # write all exceptions explicitly
98
+ return attrs
99
+
81
100
def visit (self , nodes , ctx = None , indent = True ):
82
101
'''Visiting a list of nodes, writes out the XML content to the internal
83
102
line-based buffer. By default, adds one level of indentation to the
@@ -89,22 +108,22 @@ def visit(self, nodes, ctx=None, indent=True):
89
108
90
109
if not isinstance (x , parser .Node ):
91
110
raise TypeError ("Not a Node: " + repr (x ))
92
- elif x .type in set ([ANNO_ATTR , DATATYPES , DEFAULT_NS , NS ]):
111
+ elif x .type in set ([ANNO_ATTR , LITERAL_TYPE , DATATYPES , DEFAULT_NS , NS ]):
93
112
continue
94
113
95
114
attribs = self .anno_attrs (x .value )
96
115
if x .type == DEFINE :
97
-
98
- op , attrib = x . value [ 0 ]. name , ''
99
- if op in set ([ '|=' , '&=' ]) :
100
- modes = { '|' : 'choice' , '&' : 'interleave' }
101
- attrib = ' combine="%s"' % modes [ op [ 0 ]]
116
+ for op in ( x . name for x in x . value if x . type == 'ASSIGN' ):
117
+ modes = { '|=' : 'choice' , '&=' : 'interleave' }
118
+ if op in modes :
119
+ attribs = ( ' combine="%s"' % modes [ op ]) + attribs
120
+ break ;
102
121
103
122
if x .name == 'start' :
104
- self .write ('<start%s%s >' % ( attrib , attribs ) )
123
+ self .write ('<start%s>' % attribs )
105
124
else :
106
- bits = x .name , attrib , attribs
107
- self .write ('<define name="%s"%s%s >' % bits )
125
+ bits = x .name , attribs
126
+ self .write ('<define name="%s"%s>' % bits )
108
127
109
128
self .visit (x .value )
110
129
if x .name == 'start' :
@@ -158,9 +177,20 @@ def visit(self, nodes, ctx=None, indent=True):
158
177
self .write ('<name ns="%s">%s</name>' % (ns , name ))
159
178
elif x .type in set ([REF , PARENT ]):
160
179
bits = x .type .lower (), x .name , attribs
161
- self .write ('<%s name="%s"%s/>' % bits )
180
+ if not x .value : # no parameters
181
+ self .write ('<%s name="%s"%s/>' % bits )
182
+ else :
183
+ self .write ('<%s name="%s"%s>' % bits )
184
+ self .visit (x .value )
185
+ self .write ('</%s>' % x .type .lower ())
162
186
elif x .type == LITERAL :
187
+ types = [n .name for n in x .value if isinstance (n , parser .Node ) and n .type == LITERAL_TYPE ]
188
+ if types :
189
+ assert len (types ) == 1
190
+ attribs += self .type_attrs (types [0 ])
191
+
163
192
bits = attribs , html .escape (x .name )
193
+
164
194
self .write ('<value%s>%s</value>' % bits )
165
195
self .visit (x .value , indent = False )
166
196
elif x .type == ANNOTATION :
@@ -178,6 +208,11 @@ def visit(self, nodes, ctx=None, indent=True):
178
208
tail = html .escape ('' .join (literals )) + '</%s>' % x .name
179
209
180
210
bits = x .name , attribs , end , tail
211
+
212
+ if ':' in x .name :
213
+ parts = x .name .split (':' , 1 )
214
+ ns = self .namespace (parts [0 ])
215
+
181
216
self .write ('<%s%s%s>%s' % bits )
182
217
if not rest :
183
218
continue
@@ -195,9 +230,12 @@ def visit(self, nodes, ctx=None, indent=True):
195
230
self .write ('</%s>' % x .name )
196
231
197
232
elif x .type == DOCUMENTATION :
198
- self .namespace ('a' )
199
- fmt = '<a:documentation>%s</a:documentation>'
200
- self .write (fmt % html .escape ('\n ' .join (x .value )))
233
+ xmlns_attr = ''
234
+ if self .namespace ('a' ) != NAMESPACES ['a' ]:
235
+ xmlns_attr = ' xmlns:a="%s"' % NAMESPACES ['a' ] # the user is already using namespace a: for something else
236
+
237
+ fmt = '<a:documentation%s>%s</a:documentation>'
238
+ self .write (fmt % (xmlns_attr , html .escape ('\n ' .join (x .value ))))
201
239
elif x .type == GROUP :
202
240
if len (x .value ) == 1 and x .value [0 ].type != SEQ :
203
241
self .visit (x .value , indent = False )
@@ -212,14 +250,10 @@ def visit(self, nodes, ctx=None, indent=True):
212
250
elif x .type == SEQ :
213
251
self .visit (x .value , indent = False )
214
252
elif x .type == DATATAG :
215
- self .needs ['types' ] = True
216
253
if not x .value : # no parameters
217
- self .write ('<data type="%s" />' % x .name )
254
+ self .write ('<data%s />' % self . type_attrs ( x .name ) )
218
255
else :
219
- name = x .name
220
- if name not in ('string' , 'token' ):
221
- name = x .name .split (':' , 1 )[1 ]
222
- self .write ('<data type="%s">' % name )
256
+ self .write ('<data%s>' % self .type_attrs (x .name ))
223
257
self .visit (x .value )
224
258
self .write ('</data>' )
225
259
elif x .type == PARAM :
@@ -234,11 +268,13 @@ def visit(self, nodes, ctx=None, indent=True):
234
268
self .visit (x .value , ctx = x .type )
235
269
self .write ('</attribute>' )
236
270
elif x .type == ROOT :
237
- # Verify the included document has the same metadata
238
271
for n in x .value :
272
+ # Record included document's custom datatypes
239
273
if n .type == DATATYPES :
240
- assert self .types == n .value [0 ]
241
- elif n .type == DEFAULT_NS :
274
+ self .typelibs [n .name ] = n .value [0 ]
275
+
276
+ # Verify the included document has the same metadata
277
+ if n .type == DEFAULT_NS :
242
278
assert self .default == n .value [0 ]
243
279
elif n .type == NS :
244
280
assert n .name in self .ns
0 commit comments