Skip to content

Commit d2027e9

Browse files
Prevent PyHTML from rendering in quirks mode
1 parent 1b2e076 commit d2027e9

File tree

12 files changed

+137
-64
lines changed

12 files changed

+137
-64
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ with improved documentation and type safety.
3232
... ),
3333
... )
3434
>>> print(str(my_website))
35+
<!DOCTYPE html>
3536
<html>
3637
<head>
3738
<title>
@@ -159,6 +160,7 @@ instantiated element.
159160
>>> p.br
160161
<class 'pyhtml.__tags.generated.br'>
161162
>>> print(str(p.html(p.body(p.br))))
163+
<!DOCTYPE html>
162164
<html>
163165
<body>
164166
<br/>

meta/generate_tag_defs.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ def _escape_children(self) -> bool:
1717
return False
1818
"""
1919

20+
GET_PRE_CONTENT = """
21+
def _get_tag_pre_content(self) -> Optional[str]:
22+
return {}
23+
"""
24+
2025

2126
def get_template_class(name: str):
2227
try:
@@ -96,6 +101,13 @@ def generate_tag_class(output: TextIO, tag: TagInfo):
96101
if not tag.escape_children:
97102
print(NO_ESCAPE_CHILDREN, file=output)
98103

104+
# Add pre-content declaration if needed
105+
if tag.pre_content is not None:
106+
print(
107+
GET_PRE_CONTENT.replace("{}", f"'{tag.pre_content}'"),
108+
file=output,
109+
)
110+
99111
# And a nice trailing newline to make flake8 happy
100112
print(file=output)
101113

meta/scrape_tags.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ class TagsYmlItem(TypedDict):
136136
escape_children: NotRequired[bool]
137137
"""Whether to escape the contents of the tag (default True)"""
138138

139+
pre_content: NotRequired[str]
140+
"""
141+
Pre-content for the element (eg `<!DOCTYPE html>`)
142+
"""
143+
139144

140145
TagsYaml = dict[str, TagsYmlItem]
141146
"""Type alias for type of tags.yml file"""
@@ -204,6 +209,11 @@ class TagInfo:
204209
List of attributes and their documentation.
205210
"""
206211

212+
pre_content: Optional[str]
213+
"""
214+
Pre-content for the element (eg `<!DOCTYPE html>`)
215+
"""
216+
207217

208218
def fetch_mdn():
209219
"""
@@ -449,6 +459,16 @@ def get_tag_escape_children(tags: TagsYaml, tag_name: str) -> bool:
449459
return tag.get('escape_children', True)
450460

451461

462+
def get_tag_pre_content(tags: TagsYaml, tag_name: str) -> Optional[str]:
463+
"""
464+
Return pre-content for the tag
465+
"""
466+
if tag_name not in tags:
467+
return None
468+
tag = tags[tag_name]
469+
return tag.get('pre_content', None)
470+
471+
452472
def make_mdn_link(tag: str) -> str:
453473
"""Generate an MDN docs link for the given tag"""
454474
return f"{MDN_ELEMENT_PAGE}/{tag}"
@@ -475,6 +495,7 @@ def elements_to_element_structs(
475495
mdn_link=make_mdn_link(name),
476496
escape_children=get_tag_escape_children(tag_attrs, name),
477497
attributes=attr_entries_to_object(tag_attrs, name),
498+
pre_content=get_tag_pre_content(tag_attrs, name)
478499
))
479500

480501
return output

meta/tags.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
%YAML 1.2
22
---
33

4+
html:
5+
pre_content: "<!DOCTYPE html>"
6+
attributes:
7+
lang:
8+
doc: Language used by the document
9+
type: Optional[str]
10+
411
# Rename <del> tag as it is a reserved keyword in Python
512
del:
613
rename: del_

pyhtml/__tag_base.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
Tag base class, including rendering logic
55
"""
6-
from typing import TypeVar
6+
from typing import Optional, TypeVar
77
from . import __util as util
88
from .__types import ChildrenType, AttributeType
99

@@ -74,6 +74,15 @@ def _get_default_attributes(
7474
"""
7575
return {}
7676

77+
def _get_tag_pre_content(self) -> Optional[str]:
78+
"""
79+
Return "pre-content" for the tag.
80+
81+
This is used by the `<html>` tag to add a `<!DOCTYPE html>` before the
82+
tag.
83+
"""
84+
return None
85+
7786
def _escape_children(self) -> bool:
7887
"""
7988
Returns whether the contents of the element should be escaped, or
@@ -97,6 +106,11 @@ def _render(self) -> list[str]:
97106

98107
# Tag and attributes
99108
opening = f"<{self._get_tag_name()}"
109+
110+
# Add pre-content
111+
if (pre := self._get_tag_pre_content()) is not None:
112+
opening = f"{pre}\n{opening}"
113+
100114
if len(attributes):
101115
opening += f" {util.render_tag_attributes(attributes)}>"
102116
else:

pyhtml/__tags/generated.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,52 @@ class html(Tag):
1515
"""
1616
Represents the root (top-level element) of an HTML document, so it is also referred to as the _root element_. All other elements must be descendants of this element.
1717
18-
18+
* `lang`: Language used by the document
1919
2020
[View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html)
2121
"""
2222
def __init__(
2323
self,
2424
*children: ChildrenType,
25-
25+
lang: Optional[str] = None,
2626
**attributes: AttributeType,
2727
) -> None:
2828
"""
2929
Represents the root (top-level element) of an HTML document, so it is also referred to as the _root element_. All other elements must be descendants of this element.
3030
31-
31+
* `lang`: Language used by the document
3232
3333
[View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html)
3434
"""
3535
attributes |= {
36-
36+
'lang': lang,
3737
}
3838
super().__init__(*children, **attributes)
3939

4040
def __call__( # type: ignore
4141
self,
4242
*children: ChildrenType,
43-
43+
lang: Optional[str] = None,
4444
**attributes: AttributeType,
4545
):
4646
"""
4747
Represents the root (top-level element) of an HTML document, so it is also referred to as the _root element_. All other elements must be descendants of this element.
4848
49-
49+
* `lang`: Language used by the document
5050
5151
[View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html)
5252
"""
5353
attributes |= {
54-
54+
'lang': lang,
5555
}
5656
return super().__call__(*children, **attributes)
5757

5858
def _get_default_attributes(self, given: dict[str, AttributeType]) -> dict[str, AttributeType]:
59-
return {}
59+
return {'lang': None}
60+
61+
62+
def _get_tag_pre_content(self) -> Optional[str]:
63+
return '<!DOCTYPE html>'
6064

6165

6266
class base(SelfClosingTag):

0 commit comments

Comments
 (0)