{ "cells": [ { "cell_type": "code", "execution_count": 1, "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": 2, "id": "55944805", "metadata": {}, "outputs": [], "source": [ "#| export\n", "from fastcore.utils import *\n", "\n", "from dataclasses import dataclass, asdict\n", "import types\n", "from functools import partial\n", "from html import escape" ] }, { "cell_type": "code", "execution_count": 3, "id": "42d18e5c", "metadata": {}, "outputs": [], "source": [ "from IPython.display import Markdown\n", "from pprint import pprint" ] }, { "cell_type": "code", "execution_count": 4, "id": "4dbb9a59", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def _attrmap(o):\n", " o = dict(htmlClass='class', cls='class', klass='class', fr='for', htmlFor='for').get(o, o)\n", " return o.lstrip('_').replace('_', '-')" ] }, { "cell_type": "code", "execution_count": 5, "id": "149067dd", "metadata": {}, "outputs": [], "source": [ "#|export\n", "class XT(list): pass" ] }, { "cell_type": "code", "execution_count": 6, "id": "06718948", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def xt(tag:str, *c, **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):str(v) for k,v in kw.items() if v is not None}\n", " return XT([tag.lower(),c,kw])" ] }, { "cell_type": "code", "execution_count": 7, "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": 8, "id": "9a8b4ddb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['html',\n", " (['head', (['title', ('Some page',), {}],), {}],\n", " ['body',\n", " (['div',\n", " (['p', ('Some text',), {}],\n", " ['input', (), {'name': 'me'}],\n", " ['img', (), {'src': 'filename'}]),\n", " {'class': 'myclass'}],),\n", " {}]),\n", " {}]\n" ] } ], "source": [ "samp = Html(\n", " Head(Title('Some page')),\n", " Body(Div(P('Some text'), Input(name='me'), Img(src=\"filename\"), klass='myclass'))\n", ")\n", "pprint(samp)" ] }, { "cell_type": "code", "execution_count": 9, "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'.split())" ] }, { "cell_type": "code", "execution_count": 10, "id": "b89d088a", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def to_xml(elm, lvl=0):\n", " \"Convert `xt` element tree into an XML string\"\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):\n", " if isinstance(elm, str): elm = escape(elm)\n", " return f'{elm}\\n'\n", "\n", " tag,cs,attrs = elm\n", " stag = tag\n", " if attrs:\n", " sattrs = (f'{k}=\"{escape(str(v), quote=False)}\"' for k,v in attrs.items())\n", " stag += ' ' + ' '.join(sattrs)\n", " \n", " cltag = '' if tag in voids else f''\n", " if not cs: return f'{sp}<{stag}>{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", " return res" ] }, { "cell_type": "code", "execution_count": 11, "id": "d3d23c48", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " \n", " \n", "Some page\n", " \n", " \n", " \n", "
\n", "

\n", "Some text\n", "

\n", " \n", " \n", "
\n", " \n", "\n", "\n" ] } ], "source": [ "print(to_xml(samp))" ] }, { "cell_type": "code", "execution_count": 12, "id": "798ae1d2", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def highlight(s, lang='html'):\n", " \"Markdown to syntax-highlight `s` in language `lang`\"\n", " return f'```{lang}\\n{to_xml(s)}\\n```'" ] }, { "cell_type": "code", "execution_count": 16, "id": "39fab735", "metadata": {}, "outputs": [], "source": [ "#| export\n", "def showtags(s):\n", " return f\"\"\"
\n",
    "{escape(to_xml(s))}\n",
    "
\"\"\"\n", "\n", "XT._repr_html_ = showtags" ] }, { "cell_type": "markdown", "id": "df973d4e", "metadata": {}, "source": [ "# Export -" ] }, { "cell_type": "code", "execution_count": 17, "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": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.8" } }, "nbformat": 4, "nbformat_minor": 5 }