{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "d0c84aa5", "metadata": {}, "outputs": [], "source": [ "#| default_exp xml" ] }, { "cell_type": "markdown", "id": "61bf8203", "metadata": {}, "source": [ "# XML\n", "\n", "> Concise generation of XML." ] }, { "cell_type": "code", "execution_count": null, "id": "55944805", "metadata": {}, "outputs": [], "source": [ "#| export\n", "from fastcore.utils import *\n", "\n", "import types,json\n", "\n", "from dataclasses import dataclass, asdict\n", "from typing import Mapping\n", "from functools import partial\n", "from html import escape" ] }, { "cell_type": "code", "execution_count": null, "id": "42d18e5c", "metadata": {}, "outputs": [], "source": [ "from IPython.display import Markdown\n", "from pprint import pprint" ] }, { "cell_type": "code", "execution_count": null, "id": "4dbb9a59", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _attrmap(o):\n", " o = dict(htmlClass='class', cls='class', _class='class', klass='class',\n", " _for='for', fr='for', htmlFor='for').get(o, o)\n", " return o.lstrip('_').replace('_', '-')" ] }, { "cell_type": "code", "execution_count": null, "id": "149067dd", "metadata": {}, "outputs": [], "source": [ "#|export\n", "class XT(list):\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", " def children(self): return self[1]\n", " @property\n", " def attrs(self): return self[2]\n", "\n", " def __setattr__(self, 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", " if k.startswith('__') or k not in self.attrs: raise AttributeError(k)\n", " return self.attrs[k.lstrip('_').replace('_', '-')]" ] }, { "cell_type": "code", "execution_count": null, "id": "06718948", "metadata": {}, "outputs": [], "source": [ "#| export\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, void_=void_)" ] }, { "cell_type": "code", "execution_count": null, "id": "45489975", "metadata": {}, "outputs": [], "source": [ "#| export\n", "_g = globals()\n", "_all_ = ['Html', 'Head', 'Title', 'Meta', 'Link', 'Style', 'Body', 'Pre', 'Code',\n", " 'Div', 'Span', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Strong', 'Em', 'B',\n", " 'I', 'U', 'S', 'Strike', 'Sub', 'Sup', 'Hr', 'Br', 'Img', 'A', 'Link', 'Nav',\n", " 'Ul', 'Ol', 'Li', 'Dl', 'Dt', 'Dd', 'Table', 'Thead', 'Tbody', 'Tfoot', 'Tr',\n", " 'Th', 'Td', 'Caption', 'Col', 'Colgroup', 'Form', 'Input', 'Textarea',\n", " 'Button', 'Select', 'Option', 'Label', 'Fieldset', 'Legend', 'Details',\n", " 'Summary', 'Main', 'Header', 'Footer', 'Section', 'Article', 'Aside', 'Figure',\n", " 'Figcaption', 'Mark', 'Small', 'Iframe', 'Object', 'Embed', 'Param', 'Video',\n", " 'Audio', 'Source', 'Canvas', 'Svg', 'Math', 'Script', 'Noscript', 'Template', 'Slot']\n", "\n", "for o in _all_: _g[o] = partial(xt, o.lower())" ] }, { "cell_type": "markdown", "id": "732e44ab", "metadata": {}, "source": [ "The main HTML tags are exported as `xt` partials.\n", "\n", "Attributes are passed as keywords. Use 'klass' and 'fr' instead of 'class' and 'for', to avoid Python reserved word clashes." ] }, { "cell_type": "code", "execution_count": null, "id": "9a8b4ddb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['html',\n", " (['head', (['title', ('Some page',), {}],), {}],\n", " ['body',\n", " (['div',\n", " ('Some text',\n", " ['input', (), {'name': 'me'}],\n", " ['img', (), {'data': 1, 'src': 'filename'}]),\n", " {'class': 'myclass'}],),\n", " {}]),\n", " {}]\n" ] } ], "source": [ "samp = Html(\n", " Head(Title('Some page')),\n", " Body(Div('Some text', Input(name='me'), Img(src=\"filename\", data=1), klass='myclass'))\n", ")\n", "pprint(samp)" ] }, { "cell_type": "markdown", "id": "314eb4bc", "metadata": {}, "source": [ "The three elements of the list can also be accessed with property names, so you don't have to remember their order." ] }, { "cell_type": "code", "execution_count": null, "id": "5c6c57e9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "p\n", "('Some text',)\n", "{'id': 'myid'}\n" ] } ], "source": [ "elem = P('Some text', id=\"myid\")\n", "print(elem.tag)\n", "print(elem.children)\n", "print(elem.attrs)" ] }, { "cell_type": "markdown", "id": "bb61f88e", "metadata": {}, "source": [ "You can also get and set attrs directly:" ] }, { "cell_type": "code", "execution_count": null, "id": "5c7f175d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "newid\n" ] }, { "data": { "text/plain": [ "['p', ('Some text',), {'id': 'newid'}]" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "elem.id = 'newid'\n", "print(elem.id)\n", "elem" ] }, { "cell_type": "code", "execution_count": null, "id": "254c8ff3", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _escape(s): return '' if s is None else escape(s) if isinstance(s, str) else s" ] }, { "cell_type": "code", "execution_count": null, "id": "0255b96f", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _to_attr(k,v):\n", " if isinstance(v,bool):\n", " if v==True : return str(k)\n", " if v==False: return ''\n", " if isinstance(v,str): v = escape(v, quote=True)\n", " elif isinstance(v, Mapping): v = json.dumps(v)\n", " else: v = str(v)\n", " qt = '\"'\n", " if qt in v: qt = \"'\"\n", " return f'{k}={qt}{v}{qt}'" ] }, { "cell_type": "code", "execution_count": null, "id": "b89d088a", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def to_xml(elm, lvl=0):\n", " \"Convert `xt` element tree into an XML string\"\n", " if elm is None: return ''\n", " if isinstance(elm, tuple): return '\\n'.join(to_xml(o) for o in elm)\n", " if hasattr(elm, '__xt__'): elm = elm.__xt__()\n", " sp = ' ' * lvl\n", " if not isinstance(elm, list): return f'{_escape(elm)}\\n'\n", "\n", " tag,cs,attrs = elm\n", " stag = tag\n", " if attrs:\n", " sattrs = (_to_attr(k,v) for k,v in attrs.items())\n", " stag += ' ' + ' '.join(sattrs)\n", "\n", " isvoid = getattr(elm, 'void_', False)\n", " cltag = '' if isvoid else f''\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 not isvoid: res += f'{sp}{cltag}\\n'\n", " return res" ] }, { "cell_type": "code", "execution_count": null, "id": "d3d23c48", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " \n", " Some page\n", " \n", " \n", "
\n", "Some text\n", " \n", " \n", "
\n", " \n", "\n", "\n" ] } ], "source": [ "h = to_xml(samp)\n", "print(h)" ] }, { "cell_type": "code", "execution_count": null, "id": "798ae1d2", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def highlight(s, lang='xml'):\n", " \"Markdown to syntax-highlight `s` in language `lang`\"\n", " return f'```{lang}\\n{to_xml(s)}\\n```'" ] }, { "cell_type": "code", "execution_count": null, "id": "39fab735", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def showtags(s):\n", " return f\"\"\"
\n",
    "{escape(to_xml(s))}\n",
    "
\"\"\"\n", "\n", "XT._repr_markdown_ = highlight" ] }, { "cell_type": "code", "execution_count": null, "id": "530666f8", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def __getattr__(tag):\n", " if tag.startswith('_') or tag[0].islower(): raise AttributeError\n", " def _f(*c, target_id=None, **kwargs): return xt(tag, *c, target_id=target_id, **kwargs)\n", " return _f" ] }, { "cell_type": "markdown", "id": "df973d4e", "metadata": {}, "source": [ "# Export -" ] }, { "cell_type": "code", "execution_count": null, "id": "ad32b076", "metadata": {}, "outputs": [], "source": [ "#|hide\n", "import nbdev; nbdev.nbdev_export()" ] }, { "cell_type": "code", "execution_count": null, "id": "84fe290c", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "python3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }