CoCalc Shared Fileswww / cgi-bin / mfd / XHTML10.pyOpen in CoCalc with one click!
Author: William A. Stein
1
"""XHTML10 -- generate XHTML conformant to the 1.0 standard. See:
2
3
http://www.w3.org/TR/xhtml1/
4
5
Implemented similarly to HTML40; see the docs in that module.
6
7
"""
8
9
__version__ = "$Revision: 1.4 $"[11:-4]
10
11
import string
12
from string import lower, join, replace
13
from sys import stdout
14
15
coreattrs = {'id': 1, 'klass': 1, 'style': 1, 'title': 1}
16
i18n = {'lang': 1, 'dir': 1}
17
intrinsic_events = {'onload': 1, 'onunload': 1, 'onclick': 1,
18
'ondblclick': 1, 'onmousedown': 1, 'onmouseup': 1,
19
'onmouseover': 1, 'onmousemove': 1, 'onmouseout': 1,
20
'onfocus': 1, 'onblur': 1, 'onkeypress': 1,
21
'onkeydown': 1, 'onkeyup': 1, 'onsubmit': 1,
22
'onreset': 1, 'onselect': 1, 'onchange': 1 }
23
24
attrs = coreattrs.copy()
25
attrs.update(i18n)
26
attrs.update(intrinsic_events)
27
28
alternate_text = {'alt': 1}
29
image_maps = {'shape': 1, 'coords': 1}
30
anchor_reference = {'href': 1}
31
target_frame_info = {'target': 1}
32
tabbing_navigation = {'tabindex': 1}
33
access_keys = {'accesskey': 1}
34
35
tabbing_and_access = tabbing_navigation.copy()
36
tabbing_and_access.update(access_keys)
37
38
visual_presentation = {'height': 1, 'width': 1, 'border': 1, 'align': 1,
39
'hspace': 1, 'vspace': 1}
40
41
cellhalign = {'align': 1, 'char': 1, 'charoff': 1}
42
cellvalign = {'valign': 1}
43
44
font_modifiers = {'size': 1, 'color': 1, 'face': 1}
45
46
links_and_anchors = {'href': 1, 'hreflang': 1, 'type': 1, 'rel': 1, 'rev': 1}
47
borders_and_rules = {'frame': 1, 'rules': 1, 'border': 1}
48
49
from SGML import Markup, Comment
50
from XML import XMLPI
51
52
DOCTYPE = Markup("DOCTYPE",
53
'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' \
54
'"DTD/xhtml1-transitional.dtd"')
55
DOCTYPE_frameset = Markup("DOCTYPE",
56
'html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" ' \
57
'"DTD/xhtml1-frameset.dtd"')
58
59
class Element(XMLPI):
60
61
defaults = {}
62
63
def __init__(self, *content, **attr):
64
self.dict = {}
65
if not hasattr(self, 'name'): self.name = lower(self.__class__.__name__)
66
if self.defaults: self.update(self.defaults)
67
self.update(attr)
68
if not self.content_model and content:
69
raise TypeError, "No content for this element"
70
self.content = list(content)
71
72
def update(self, d2):
73
for k, v in d2.items(): self[k] = v
74
75
def __setitem__(self, k, v):
76
kl = lower(k)
77
if self.attlist.has_key(kl): self.dict[kl] = v
78
else: raise KeyError, "Invalid attribute for this element"
79
80
start_tag_string = "<%s %s>"
81
start_tag_no_attr_string = "<%s>"
82
end_tag_string = "</%s>"
83
84
def start_tag(self):
85
a = self.str_attribute_list()
86
return a and self.start_tag_string % (self.name, a) \
87
or self.start_tag_no_attr_string % self.name
88
89
def end_tag(self):
90
return self.end_tag_string % self.name
91
92
93
class PrettyTagsMixIn:
94
95
def writeto(self, fp=stdout, indent=0, perlevel=2):
96
myindent = '\n' + " "*indent
97
fp.write(myindent+self.start_tag())
98
for c in self.content:
99
if hasattr(c, 'writeto'):
100
getattr(c, 'writeto')(fp, indent+perlevel, perlevel)
101
else:
102
fp.write(str(c))
103
fp.write(self.end_tag())
104
105
def __str__(self, indent=0, perlevel=2):
106
myindent = (perlevel and '\n' or '') + " "*indent
107
s = [myindent, self.start_tag()]
108
for c in self.content:
109
try: s.append(apply(c.__str__, (indent+perlevel, perlevel)))
110
except: s.append(str(c))
111
s.append(self.end_tag())
112
return join(s,'')
113
114
class CommonElement(Element): attlist = attrs
115
116
class PCElement(PrettyTagsMixIn, CommonElement): pass
117
118
class A(CommonElement):
119
120
attlist = {'charset': 1}
121
attlist.update(CommonElement.attlist)
122
attlist.update(links_and_anchors)
123
attlist.update(image_maps)
124
attlist.update(target_frame_info)
125
attlist.update(tabbing_and_access)
126
127
128
class ABBR(CommonElement): pass
129
class ACRONYM(CommonElement): pass
130
class CITE(CommonElement): pass
131
class CODE(CommonElement): pass
132
class DFN(CommonElement): pass
133
class EM(CommonElement): pass
134
class KBD(CommonElement): pass
135
class PRE(CommonElement): pass
136
class SAMP(CommonElement): pass
137
class STRONG(CommonElement): pass
138
class VAR(CommonElement): pass
139
class ADDRESS(CommonElement): pass
140
class B(CommonElement): pass
141
class BIG(CommonElement): pass
142
class I(CommonElement): pass
143
class S(CommonElement): pass
144
class SMALL(CommonElement): pass
145
class STRIKE(CommonElement): pass
146
class TT(CommonElement): pass
147
class U(CommonElement): pass
148
class SUB(CommonElement): pass
149
class SUP(CommonElement): pass
150
151
class DD(PCElement): pass
152
class DL(PCElement): pass
153
class DT(PCElement): pass
154
class NOFRAMES(PCElement): pass
155
class NOSCRIPTS(PCElement): pass
156
class P(PCElement): pass
157
158
class AREA(PCElement):
159
160
attlist = {'nohref': 0}
161
attlist.update(PCElement.attlist)
162
attlist.update(image_maps)
163
attlist.update(anchor_reference)
164
attlist.update(tabbing_and_access)
165
attlist.update(alternate_text)
166
167
class MAP(AREA): pass
168
169
class BASE(PrettyTagsMixIn, Element):
170
171
attlist = anchor_reference.copy()
172
attlist.update(target_frame_info)
173
content_model = None
174
175
class BDO(Element):
176
177
attlist = coreattrs.copy()
178
attlist.update(i18n)
179
180
class BLOCKQUOTE(CommonElement):
181
182
attlist = {'cite': 1}
183
attlist.update(CommonElement.attlist)
184
185
class Q(BLOCKQUOTE): pass
186
187
class BR(PrettyTagsMixIn, Element):
188
189
attlist = coreattrs
190
content_model = None
191
192
class BUTTON(CommonElement):
193
194
attlist = {'value': 1, 'type': 1, 'disabled': 0}
195
attlist.update(CommonElement.attlist)
196
attlist.update(tabbing_and_access)
197
198
class CAPTION(Element):
199
200
attlist = {'align': 1}
201
attlist.update(attrs)
202
203
class COLGROUP(PCElement):
204
205
attlist = {'span': 1, 'width': 1}
206
attlist.update(PCElement.attlist)
207
attlist.update(cellhalign)
208
attlist.update(cellvalign)
209
210
class COL(COLGROUP): content_model = None
211
212
class DEL(Element):
213
214
attlist = {'cite': 1, 'datetime': 1}
215
attlist.update(attrs)
216
217
class INS(DEL): pass
218
219
class FIELDSET(PCElement): pass
220
221
class LEGEND(PCElement):
222
223
attlist = {'align': 1}
224
attlist.update(PCElement.attlist)
225
attlist.update(access_keys)
226
227
class BASEFONT(Element):
228
229
attlist = {'id': 1}
230
attlist.update(font_modifiers)
231
content_model = None
232
233
class FONT(Element):
234
235
attlist = font_modifiers.copy()
236
attlist.update(coreattrs)
237
attlist.update(i18n)
238
239
class FORM(PCElement):
240
241
attlist = {'action': 1, 'method': 1, 'enctype': 1, 'accept_charset': 1,
242
'target': 1}
243
attlist.update(PCElement.attlist)
244
245
class FRAME(PrettyTagsMixIn, Element):
246
247
attlist = {'longdesc': 1, 'src': 1, 'frameborder': 1,
248
'marginwidth': 1, 'marginheight': 1, 'noresize': 0,
249
'scrolling': 1}
250
attlist.update(coreattrs)
251
content_model = None
252
253
class FRAMESET(PrettyTagsMixIn, Element):
254
255
attlist = {'rows': 1, 'cols': 1, 'border': 1}
256
attlist.update(coreattrs)
257
attlist.update(intrinsic_events)
258
259
class Heading(PCElement):
260
261
attlist = {'align': 1}
262
attlist.update(attrs)
263
264
def __init__(self, level, *content, **attr):
265
self.level = level
266
apply(PCElement.__init__, (self,)+content, attr)
267
268
def start_tag(self):
269
a = self.str_attribute_list()
270
return a and "<H%d %s>" % (self.level, a) or "<H%d>" % self.level
271
272
def end_tag(self):
273
return self.content_model and "</H%d>\n" % self.level or ''
274
275
class HEAD(PrettyTagsMixIn, Element):
276
277
attlist = {'profile': 1}
278
attlist.update(i18n)
279
280
class HR(Element):
281
282
attlist = {'align': 1, 'noshade': 0, 'size': 1, 'width': 1}
283
attlist.update(coreattrs)
284
attlist.update(intrinsic_events)
285
content_model = None
286
287
class HTML(PrettyTagsMixIn, Element):
288
289
attlist = i18n
290
291
class TITLE(HTML): pass
292
293
class BODY(PCElement):
294
295
attlist = {'background': 1, 'text': 1, 'link': 1, 'vlink': 1, 'alink': 1,
296
'bgcolor': 1}
297
attlist.update(PCElement.attlist)
298
299
class IFRAME(PrettyTagsMixIn, Element):
300
301
attlist = {'longdesc': 1, 'src': 1, 'frameborder': 1,
302
'marginwidth': 1, 'marginheight': 1, 'scrolling': 1,
303
'align': 1, 'height': 1, 'width': 1}
304
attlist.update(coreattrs)
305
306
class IMG(CommonElement):
307
308
attlist = {'src': 1, 'longdesc': 1, 'usemap': 1, 'ismap': 0}
309
attlist.update(PCElement.attlist)
310
attlist.update(visual_presentation)
311
attlist.update(alternate_text)
312
content_model = None
313
314
class INPUT(CommonElement):
315
316
attlist = {'type': 1, 'value': 1, 'checked': 0, 'disabled': 0,
317
'readonly': 0, 'size': 1, 'maxlength': 1, 'src': 1,
318
'usemap': 1, 'accept': 1, 'border': 1}
319
attlist.update(CommonElement.attlist)
320
attlist.update(tabbing_and_access)
321
attlist.update(alternate_text)
322
content_model = None
323
324
class LABEL(CommonElement):
325
326
attlist = {'label_for': 1}
327
attlist.update(CommonElement.attlist)
328
attlist.update(access_keys)
329
330
class UL(PCElement):
331
332
attlist = {'compact': 0}
333
attlist.update(CommonElement.attlist)
334
335
class OL(UL):
336
337
attlist = {'start': 1}
338
attlist.update(UL.attlist)
339
340
class LI(UL):
341
342
attlist = {'value': 1, 'type': 1}
343
attlist.update(UL.attlist)
344
345
class LINK(PCElement):
346
347
attlist = {'charset': 1, 'media': 1}
348
attlist.update(PCElement.attlist)
349
attlist.update(links_and_anchors)
350
content_model = None
351
352
class META(PrettyTagsMixIn, Element):
353
354
attlist = {'http_equiv': 1, 'content': 1, 'scheme': 1}
355
attlist.update(i18n)
356
content_model = None
357
358
class OBJECT(PCElement):
359
360
attlist = {'declare': 0, 'classid': 1, 'codebase': 1, 'data': 1,
361
'type': 1, 'codetype': 1, 'archive': 1, 'standby': 1,
362
'height': 1, 'width': 1, 'usemap': 1}
363
attlist.update(PCElement.attlist)
364
attlist.update(tabbing_navigation)
365
366
class SELECT(PCElement):
367
368
attlist = {'size': 1, 'multiple': 0, 'disabled': 0}
369
attlist.update(CommonElement.attlist)
370
attlist.update(tabbing_navigation)
371
372
class OPTGROUP(PCElement):
373
374
attlist = {'disabled': 0, 'label': 1}
375
attlist.update(CommonElement.attlist)
376
377
class OPTION(OPTGROUP):
378
379
attlist = {'value': 1, 'selected': 0}
380
attlist.update(OPTGROUP.attlist)
381
382
class PARAM(Element):
383
384
attlist = {'id': 1, 'value': 1, 'valuetype': 1, 'type': 1}
385
386
class SCRIPT(Element):
387
388
attlist = {'charset': 1, 'type': 1, 'src': 1, 'defer': 0}
389
390
class SPAN(CommonElement):
391
392
attlist = {'align': 1}
393
attlist.update(CommonElement.attlist)
394
395
class DIV(PrettyTagsMixIn, SPAN): pass
396
397
class STYLE(PrettyTagsMixIn, Element):
398
399
attlist = {'type': 1, 'media': 1, 'title': 1}
400
attlist.update(i18n)
401
402
class TABLE(PCElement):
403
404
attlist = {'cellspacing': 1, 'cellpadding': 1, 'summary': 1, 'align': 1,
405
'bgcolor': 1, 'width': 1}
406
attlist.update(CommonElement.attlist)
407
attlist.update(borders_and_rules)
408
409
class TBODY(PCElement):
410
411
attlist = CommonElement.attlist.copy()
412
attlist.update(cellhalign)
413
attlist.update(cellvalign)
414
415
class THEAD(TBODY): pass
416
class TFOOT(TBODY): pass
417
class TR(TBODY): pass
418
419
class TH(TBODY):
420
421
attlist = {'abbv': 1, 'axis': 1, 'headers': 1, 'scope': 1,
422
'rowspan': 1, 'colspan': 1, 'nowrap': 0, 'width': 1,
423
'height': 1}
424
attlist.update(TBODY.attlist)
425
426
class TD(TH): pass
427
428
class TEXTAREA(CommonElement):
429
430
attlist = {'rows': 1, 'cols': 1, 'disabled': 0, 'readonly': 0}
431
attlist.update(CommonElement.attlist)
432
attlist.update(tabbing_and_access)
433
434
def CENTER(*content, **attr):
435
c = apply(DIV, content, attr)
436
c['align'] = 'center'
437
return c
438
439
def H1(content=[], **attr): return apply(Heading, (1, content), attr)
440
def H2(content=[], **attr): return apply(Heading, (2, content), attr)
441
def H3(content=[], **attr): return apply(Heading, (3, content), attr)
442
def H4(content=[], **attr): return apply(Heading, (4, content), attr)
443
def H5(content=[], **attr): return apply(Heading, (5, content), attr)
444
def H6(content=[], **attr): return apply(Heading, (6, content), attr)
445
446
class CSSRule(PrettyTagsMixIn, Element):
447
448
attlist = {'font': 1, 'font_family': 1, 'font_face': 1, 'font_size': 1,
449
'border': 1, 'border_width': 1, 'color': 1,
450
'background': 1, 'background_color': 1, 'background_image': 1,
451
'text_align': 1, 'text_decoration': 1, 'text_indent': 1,
452
'line_height': 1, 'margin_left': 1, 'margin_right': 1,
453
'clear': 1, 'list_style_type': 1}
454
content = []
455
content_model = None
456
457
def __init__(self, selector, **decl):
458
self.dict = {}
459
self.update(decl)
460
self.name = selector
461
462
start_tag_string = "%s { %s }"
463
464
def end_tag(self): return ''
465
466
def str_attribute(self, k):
467
kt = replace(k, '_', '-')
468
if self.attlist[k]: return '%s: %s' % (kt, str(self[k]))
469
else: return self[k] and kt or ''
470
471
def str_attribute_list(self):
472
return join(map(self.str_attribute, self.dict.keys()), '; ')
473
474
nbsp = "&nbsp;"
475
476
def quote_body(s):
477
r=replace; return r(r(r(s, '&', '&amp;'), '<', '&lt;'), '>', '&gt;')
478
479
safe = string.letters + string.digits + '_,.-'
480
481
def url_encode(s):
482
l = []
483
for c in s:
484
if c in safe: l.append(c)
485
elif c == ' ': l.append('+')
486
else: l.append("%%%02x" % ord(c))
487
return join(l, '')
488
489
def URL(*args, **kwargs):
490
url_path = join(args, '/')
491
a = []
492
for k, v in kwargs.items():
493
a.append("%s=%s" % (url_encode(k), url_encode(v)))
494
url_vals = join(a, '&')
495
return url_vals and join([url_path, url_vals],'?') or url_path
496
497
def Options(options, selected=[], **attrs):
498
opts = []
499
for o, v in options:
500
opt = apply(OPTION, (o,), attrs)
501
opt['value'] = v
502
if v in selected: opt['selected'] = 1
503
opts.append(opt)
504
return opts
505
506
def Select(options, selected=[], **attrs):
507
return apply(SELECT, tuple(apply(Options, (options, selected))), attrs)
508
509
def Href(url, text, **attrs):
510
h = apply(A, (text,), attrs)
511
h['href'] = url
512
return h
513
514
def Mailto(address, text, subject='', **attrs):
515
if subject:
516
url = "mailto:%s?subject=%s" % (address, subject)
517
else:
518
url = "mailto:%s" % address
519
return apply(Href, (url, text), attrs)
520
521
def Image(src, **attrs):
522
i = apply(IMG, (), a)
523
i['src'] = src
524
return i
525
526
def StyledTR(element, row, klasses):
527
r = TR()
528
for i in range(len(row)):
529
r.append(klasses[i] and element(row[i], klass=klasses[i]) \
530
or element(row[i]))
531
return r
532
533
def StyledVTable(klasses, *rows, **attrs):
534
t = apply(TABLE, (), attrs)
535
t.append(COL(span=len(klasses)))
536
for row in rows:
537
r = StyledTR(TD, row[1:], klasses[1:])
538
h = klasses[0] and TH(row[0], klass=klasses[0]) \
539
or TH(row[0])
540
r.content.insert(0,h)
541
t.append(r)
542
return t
543
544
def VTable(*rows, **attrs):
545
t = apply(TABLE, (), attrs)
546
t.append(COL(span=len(rows[0])))
547
for row in rows:
548
r = apply(TR, tuple(map(TD, row[1:])))
549
r.content.insert(0, TH(row[0]))
550
t.append(r)
551
return t
552
553
def StyledHTable(klasses, headers, *rows, **attrs):
554
t = apply(TABLE, (), attrs)
555
t.append(COL(span=len(headers)))
556
t.append(StyledTR(TH, headers, klasses))
557
for row in rows: t.append(StyledTR(TD, row, klasses))
558
return t
559
560
def HTable(headers, *rows, **attrs):
561
t = apply(TABLE, (), attrs)
562
t.append(COL(span=len(headers)))
563
t.append(TR, tuple(map(TH, headers)))
564
for row in rows: t.append(TR(apply(TD, row)))
565
return t
566
567
def DefinitionList(*items, **attrs):
568
dl = apply(DL, (), attrs)
569
for dt, dd in items: dl.append(DT(dt), DD(dd))
570
return dl
571
572
573