diff --git a/fastcore/_modidx.py b/fastcore/_modidx.py
index 9d2cf843..ab96eb72 100644
--- a/fastcore/_modidx.py
+++ b/fastcore/_modidx.py
@@ -564,6 +564,7 @@
'fastcore.xml.XT.attrs': ('xml.html#xt.attrs', 'fastcore/xml.py'),
'fastcore.xml.XT.children': ('xml.html#xt.children', 'fastcore/xml.py'),
'fastcore.xml.XT.tag': ('xml.html#xt.tag', 'fastcore/xml.py'),
+ 'fastcore.xml.__getattr__': ('xml.html#__getattr__', 'fastcore/xml.py'),
'fastcore.xml._attrmap': ('xml.html#_attrmap', 'fastcore/xml.py'),
'fastcore.xml._escape': ('xml.html#_escape', 'fastcore/xml.py'),
'fastcore.xml._to_attr': ('xml.html#_to_attr', 'fastcore/xml.py'),
diff --git a/fastcore/xml.py b/fastcore/xml.py
index bb9ed2cb..118b6d9f 100644
--- a/fastcore/xml.py
+++ b/fastcore/xml.py
@@ -1,13 +1,13 @@
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/11_xml.ipynb.
# %% auto 0
-__all__ = ['voids', 'XT', 'xt', 'to_xml', 'highlight', 'showtags', 'Html', 'Head', 'Title', 'Meta', 'Link', 'Style', 'Body',
- 'Pre', 'Code', 'Div', 'Span', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Strong', 'Em', 'B', 'I', 'U', 'S',
- 'Strike', 'Sub', 'Sup', 'Hr', 'Br', 'Img', 'A', 'Nav', 'Ul', 'Ol', 'Li', 'Dl', 'Dt', 'Dd', 'Table', 'Thead',
- 'Tbody', 'Tfoot', 'Tr', 'Th', 'Td', 'Caption', 'Col', 'Colgroup', 'Form', 'Input', 'Textarea', 'Button',
- 'Select', 'Option', 'Label', 'Fieldset', 'Legend', 'Details', 'Summary', 'Main', 'Header', 'Footer',
- 'Section', 'Article', 'Aside', 'Figure', 'Figcaption', 'Mark', 'Small', 'Iframe', 'Object', 'Embed', 'Param',
- 'Video', 'Audio', 'Source', 'Canvas', 'Svg', 'Math', 'Script', 'Noscript', 'Template', 'Slot']
+__all__ = ['XT', 'xt', 'to_xml', 'highlight', 'showtags', 'Html', 'Head', 'Title', 'Meta', 'Link', 'Style', 'Body', 'Pre', 'Code',
+ 'Div', 'Span', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Strong', 'Em', 'B', 'I', 'U', 'S', 'Strike', 'Sub',
+ 'Sup', 'Hr', 'Br', 'Img', 'A', 'Nav', 'Ul', 'Ol', 'Li', 'Dl', 'Dt', 'Dd', 'Table', 'Thead', 'Tbody', 'Tfoot',
+ 'Tr', 'Th', 'Td', 'Caption', 'Col', 'Colgroup', 'Form', 'Input', 'Textarea', 'Button', 'Select', 'Option',
+ 'Label', 'Fieldset', 'Legend', 'Details', 'Summary', 'Main', 'Header', 'Footer', 'Section', 'Article',
+ 'Aside', 'Figure', 'Figcaption', 'Mark', 'Small', 'Iframe', 'Object', 'Embed', 'Param', 'Video', 'Audio',
+ 'Source', 'Canvas', 'Svg', 'Math', 'Script', 'Noscript', 'Template', 'Slot']
# %% ../nbs/11_xml.ipynb 2
from .utils import *
@@ -27,7 +27,10 @@ def _attrmap(o):
# %% ../nbs/11_xml.ipynb 5
class XT(list):
- def __init__(self, tag, cs, attrs=None, **kwargs): super().__init__([tag, cs, {**(attrs or {}), **kwargs}])
+ def __init__(self, tag, cs, attrs=None, void_=False, **kwargs):
+ super().__init__([tag, cs, {**(attrs or {}), **kwargs}])
+ self.void_ = void_
+
@property
def tag(self): return self[0]
@property
@@ -36,7 +39,7 @@ def children(self): return self[1]
def attrs(self): return self[2]
def __setattr__(self, k, v):
- if k.startswith('__') or k in ('tag','cs','attrs'): return super().__setattr__(k,v)
+ if k.startswith('__') or k in ('tag','cs','attrs','void_'): return super().__setattr__(k,v)
self.attrs[k.lstrip('_').replace('_', '-')] = v
def __getattr__(self, k):
@@ -44,11 +47,11 @@ def __getattr__(self, k):
return self.attrs[k.lstrip('_').replace('_', '-')]
# %% ../nbs/11_xml.ipynb 6
-def xt(tag:str, *c, **kw):
+def xt(tag:str, *c, void_=False, **kw):
"Create an XML tag structure `[tag,children,attrs]` for `toxml()`"
if len(c)==1 and isinstance(c[0], types.GeneratorType): c = tuple(c[0])
kw = {_attrmap(k):v for k,v in kw.items() if v is not None}
- return XT(tag.lower(),c,kw)
+ return XT(tag.lower(),c,kw, void_=void_)
# %% ../nbs/11_xml.ipynb 7
_g = globals()
@@ -65,12 +68,9 @@ def xt(tag:str, *c, **kw):
for o in _all_: _g[o] = partial(xt, o.lower())
# %% ../nbs/11_xml.ipynb 14
-voids = set('area base br col command embed hr img input keygen link meta param source track wbr !doctype'.split())
-
-# %% ../nbs/11_xml.ipynb 15
def _escape(s): return '' if s is None else escape(s) if isinstance(s, str) else s
-# %% ../nbs/11_xml.ipynb 16
+# %% ../nbs/11_xml.ipynb 15
def _to_attr(k,v):
if isinstance(v,bool):
if v==True : return str(k)
@@ -82,7 +82,7 @@ def _to_attr(k,v):
if qt in v: qt = "'"
return f'{k}={qt}{v}{qt}'
-# %% ../nbs/11_xml.ipynb 17
+# %% ../nbs/11_xml.ipynb 16
def to_xml(elm, lvl=0):
"Convert `xt` element tree into an XML string"
if elm is None: return ''
@@ -97,24 +97,31 @@ def to_xml(elm, lvl=0):
sattrs = (_to_attr(k,v) for k,v in attrs.items())
stag += ' ' + ' '.join(sattrs)
- cltag = '' if tag in voids else f'{tag}>'
+ isvoid = getattr(elm, 'void_', False)
+ cltag = '' if isvoid else f'{tag}>'
if not cs: return f'{sp}<{stag}>{cltag}\n'
if len(cs)==1 and not isinstance(cs[0],(list,tuple)) and not hasattr(cs[0],'__xt__'):
return f'{sp}<{stag}>{_escape(cs[0])}{cltag}\n'
res = f'{sp}<{stag}>\n'
res += ''.join(to_xml(c, lvl=lvl+2) for c in cs)
- if tag not in voids: res += f'{sp}{cltag}\n'
+ if not isvoid: res += f'{sp}{cltag}\n'
return res
-# %% ../nbs/11_xml.ipynb 19
+# %% ../nbs/11_xml.ipynb 18
def highlight(s, lang='xml'):
"Markdown to syntax-highlight `s` in language `lang`"
return f'```{lang}\n{to_xml(s)}\n```'
-# %% ../nbs/11_xml.ipynb 20
+# %% ../nbs/11_xml.ipynb 19
def showtags(s):
return f"""
"""
XT._repr_markdown_ = highlight
+
+# %% ../nbs/11_xml.ipynb 20
+def __getattr__(tag):
+ if tag.startswith('_') or tag[0].islower(): raise AttributeError
+ def _f(*c, target_id=None, **kwargs): return xt(tag, *c, target_id=target_id, **kwargs)
+ return _f
diff --git a/nbs/11_xml.ipynb b/nbs/11_xml.ipynb
index 15066021..c6232d82 100644
--- a/nbs/11_xml.ipynb
+++ b/nbs/11_xml.ipynb
@@ -72,7 +72,10 @@
"source": [
"#|export\n",
"class XT(list):\n",
- " def __init__(self, tag, cs, attrs=None, **kwargs): super().__init__([tag, cs, {**(attrs or {}), **kwargs}])\n",
+ " def __init__(self, tag, cs, attrs=None, void_=False, **kwargs):\n",
+ " super().__init__([tag, cs, {**(attrs or {}), **kwargs}])\n",
+ " self.void_ = void_\n",
+ "\n",
" @property\n",
" def tag(self): return self[0]\n",
" @property\n",
@@ -81,7 +84,7 @@
" def attrs(self): return self[2]\n",
"\n",
" def __setattr__(self, k, v):\n",
- " if k.startswith('__') or k in ('tag','cs','attrs'): return super().__setattr__(k,v)\n",
+ " if k.startswith('__') or k in ('tag','cs','attrs','void_'): return super().__setattr__(k,v)\n",
" self.attrs[k.lstrip('_').replace('_', '-')] = v\n",
"\n",
" def __getattr__(self, k):\n",
@@ -97,11 +100,11 @@
"outputs": [],
"source": [
"#| export\n",
- "def xt(tag:str, *c, **kw):\n",
+ "def xt(tag:str, *c, void_=False, **kw):\n",
" \"Create an XML tag structure `[tag,children,attrs]` for `toxml()`\"\n",
" if len(c)==1 and isinstance(c[0], types.GeneratorType): c = tuple(c[0])\n",
" kw = {_attrmap(k):v for k,v in kw.items() if v is not None}\n",
- " return XT(tag.lower(),c,kw)"
+ " return XT(tag.lower(),c,kw, void_=void_)"
]
},
{
@@ -236,17 +239,6 @@
"elem"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "c7de63a4",
- "metadata": {},
- "outputs": [],
- "source": [
- "#| export\n",
- "voids = set('area base br col command embed hr img input keygen link meta param source track wbr !doctype'.split())"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -300,13 +292,14 @@
" sattrs = (_to_attr(k,v) for k,v in attrs.items())\n",
" stag += ' ' + ' '.join(sattrs)\n",
"\n",
- " cltag = '' if tag in voids else f'{tag}>'\n",
+ " isvoid = getattr(elm, 'void_', False)\n",
+ " cltag = '' if isvoid else f'{tag}>'\n",
" if not cs: return f'{sp}<{stag}>{cltag}\\n'\n",
" if len(cs)==1 and not isinstance(cs[0],(list,tuple)) and not hasattr(cs[0],'__xt__'):\n",
" return f'{sp}<{stag}>{_escape(cs[0])}{cltag}\\n'\n",
" res = f'{sp}<{stag}>\\n'\n",
" res += ''.join(to_xml(c, lvl=lvl+2) for c in cs)\n",
- " if tag not in voids: res += f'{sp}{cltag}\\n'\n",
+ " if not isvoid: res += f'{sp}{cltag}\\n'\n",
" return res"
]
},
@@ -327,8 +320,8 @@
"
{escape(to_xml(s))}