from IPython.display import Markdown
from pprint import pprint
from fastcore.test import test_eq, test_neXML
ft(tag, *children, **attrs) builds an FT tree — a lightweight XML/HTML element — and to_xml renders it (this is the foundation fasthtml builds on). The common HTML tags are predefined as ft partials (Div, P, A, …). Attributes are passed as keywords: use klass and fr for the reserved words class and for, _ in an attr name becomes -, and attrs can be got and set on the object directly. Children can also be supplied after the attrs by calling the element, e.g. Div(id='x')(child1, child2):
FT functions
FT
def FT(
tag:str, cs:tuple, attrs:dict=None, void_:bool=False, **kwargs
):A ‘Fast Tag’ structure, containing tag,children,and attrs
ft
def ft(
tag:str, *c, void_:bool=False, attrmap:callable=attrmap, valmap:callable=valmap, ft_cls:type=FT, **kw
):The main HTML tags are exported as ft partials.
Attributes are passed as keywords. Use ‘klass’ and ‘fr’ instead of ‘class’ and ‘for’, to avoid Python reserved word clashes.
Html
def Html(
*c, doctype:bool=True, **kwargs
)->FT:An HTML tag, optionally preceeded by !DOCTYPE HTML
samp = Html(
Head(Title('Some page')),
Body(Div('Some text\nanother line', (Input(name="jph's"), Img(src="filename", data=1)),
cls=['myclass', 'another'],
style={'padding':1, 'margin':2}))
)
pprint(samp)(!doctype((),{'html': True}),
html((head((title(('Some page',),{}),),{}), body((div(('Some text\nanother line', input((),{'name': "jph's"}), img((),{'src': 'filename', 'data': 1})),{'class': 'myclass another', 'style': 'padding:1; margin:2'}),),{})),{}))
elem = P('Some text', id="myid")
print(elem.tag)
print(elem.children)
print(elem.attrs)p
('Some text',)
{'id': 'myid'}
You can get and set attrs directly:
elem.id = 'newid'
print(elem.id, elem.get('id'), elem.get('foo', 'missing'))
elemnewid newid missing
p(('Some text',),{'id': 'newid'})
Safe
def Safe(
*args, **kwargs
):str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.
Conversion to XML/HTML
to_xml
def to_xml(
*elms, lvl:int=0, indent:bool=True, do_escape:bool=True
):Convert ft element tree into an XML string
t = Div(P('hi', klass='a'), hx_get='/get')
test_eq(to_xml(t), '<div hx-get="/get">\n <p class="a">hi</p>\n</div>\n')
test_eq(to_xml(Div(id='x')('child')), '<div id="x">child</div>\n')test_eq(to_xml(B('Bold Text'), Div(P('Paragraph Text'))),
'<b>Bold Text</b>\n<div>\n <p>Paragraph Text</p>\n</div>\n')to_xml(Div("<script>alert('XSS')</script>"), do_escape=True)"<div><script>alert('XSS')</script></div>\n"
h = to_xml(samp, do_escape=False)
print(h)<!doctype html>
<html>
<head>
<title>Some page</title>
</head>
<body>
<div class="myclass another" style="padding:1; margin:2">
Some text
another line <input name="jph's">
<img src="filename" data="1"> </div>
</body>
</html>
c = I('hello')
print(c)<i>hello</i>
cclass PageTitle:
def __ft__(self): return H1("Hello")
class HomePage:
def __ft__(self): return Div(PageTitle(), Div('hello'))
h = to_xml(Div(HomePage()))
expected_output = """<div>
<div>
<h1>Hello</h1>
<div>hello</div>
</div>
</div>
"""
assert h == expected_outputprint(h)<div>
<div>
<h1>Hello</h1>
<div>hello</div>
</div>
</div>
h = to_xml(samp, indent=False)
print(h)<!doctype html><html><head><title>Some page</title></head><body><div class="myclass another" style="padding:1; margin:2">Some text
another line<input name="jph's"><img src="filename" data="1"></div></body></html>
Interoperability both directions with Django and Jinja using the html() protocol:
def _esc(s): return s.__html__() if hasattr(s, '__html__') else Safe(escape(s))
r = Safe('<b>Hello from Django</b>')
print(to_xml(Div(r)))
print(_esc(Div(P('Hello from fastcore <3'))))<div><b>Hello from Django</b></div>
<div><p>Hello from fastcore <3</p></div>
FT attributes are rendered with to_xml:
print(to_xml(P('hi', value=Div('ho'))))<p value="<div>ho</div>">hi</p>
FT components also stringify with to_xml:
print(Div('ho'))<div>ho</div>
FT object equality and hashing is based on tag, attrs, and children.
test_eq(Div('hello', id='x'), Div('hello', id='x'))
test_ne(Div('hello'), Div('goodbye'))
test_ne(Div('hello', id='a'), Div('hello', id='b'))
test_ne(P('hello'), Div('hello'))
test_eq(hash(Div('hello', id='x')), hash(Div('hello', id='x')))
assert hash(Div('hello')), hash(Div('goodbye'))dict2xml
def dict2xml(
d, do_escape:bool=False, unwrap:NoneType=None
):Convert d to XML tags, one per key/value pair, unwrapping if only single key in unwrap exists
dict2xml takes a Python dictionary and converts each key-value pair into an XML element where the key becomes the tag name and the value becomes its text content. All elements are concatenated via to_xml:
d = {'name': 'Jeremy', 'lang': 'Python'}
print(dict2xml(d))<name>Jeremy</name>
<lang>Python</lang>
If there’s only one key, it is returned if it matches anything in unwrap:
d = {'name': 'Jeremy'}
print(dict2xml(d, unwrap='name'))Jeremy
Display
highlight
def highlight(
s, lang:str='html'
):Markdown to syntax-highlight s in language lang
You can also reorder the children to come after the attrs, if you use this alternative syntax for FT where the children are in a second pair of () (behind the scenes this is because FT implements __call__ to add children).
hl_md(
Body(klass='myclass')(
Div(style='padding:3px')(
'Some text 1<2',
I(spurious=True)('in italics'),
Input(name='me'),
Img(src="filename", data=1)
)
))<body class="myclass"><div style="padding:3px">Some text 1<2<i spurious>in italics</i><input name="me"><img src="filename" data="1"></div></body>mk_getattr
def mk_getattr(
f
):Create a module __getattr__ for mapping undefined attributes to kebab-case HTML tags via factory f
from fastcore.xml import Foo_bar,Foo_Bar,BarBaz,BarBAZAny _ is converted to -:
print(Foo_bar())
print(Foo_Bar())<foo-bar></foo-bar>
<foo-bar></foo-bar>
Mixed case is converted too:
print(BarBaz())<bar-baz></bar-baz>