# XML


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

`ft(tag, *children, **attrs)` builds an
[`FT`](https://fastcore.fast.ai/xml.html#ft) tree for HTML generation.
[`to_xml`](https://fastcore.fast.ai/xml.html#to_xml) renders the tree.
[FastHTML](https://fastht.ml) uses these functions to generate HTML.

Common tags have constructors such as `Div`, `P` and `A`. Pass
attributes as keywords, using `klass` for `class` and `fr` for `for`.
Underscores in attribute names become hyphens. You can also get and set
attributes on the element.

To supply children after attributes, call the element:
`Div(id='x')(child1, child2)`.

``` python
from IPython.display import Markdown
from pprint import pprint

from fastcore.test import test_eq, test_ne
```

## FT functions

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L55"
target="_blank" style="float:right; font-size:smaller">source</a>

### attrmap

``` python
def attrmap(
    o
):
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L62"
target="_blank" style="float:right; font-size:smaller">source</a>

### valmap

``` python
def valmap(
    o
):
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L83"
target="_blank" style="float:right; font-size:smaller">source</a>

### FT

``` python
def FT(
    tag:str, cs:tuple, attrs:dict=None, void_:bool=False, **kwargs
):
```

*A ‘Fast Tag’ structure, containing `tag`,`children`,and `attrs`*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L132"
target="_blank" style="float:right; font-size:smaller">source</a>

### ft

``` python
def ft(
    tag:str, *c, void_:bool=False, attrmap:<built-in function callable>=attrmap,
    valmap:<built-in function callable>=valmap, ft_cls:type=FT, **kw
):
```

*Create an [`FT`](https://fastcore.fast.ai/xml.html#ft) structure for
[`to_xml()`](https://fastcore.fast.ai/xml.html#to_xml)*

The main HTML tags are exported as
[`ft`](https://fastcore.fast.ai/xml.html#ft) partials.

Attributes are passed as keywords. Use ‘klass’ and ‘fr’ instead of
‘class’ and ‘for’, to avoid Python reserved word clashes.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L152"
target="_blank" style="float:right; font-size:smaller">source</a>

### Html

``` python
def Html(
    *c, doctype:bool=True, **kwargs
)->__main__.FT:
```

*An HTML tag, optionally preceeded by `!DOCTYPE HTML`*

``` python
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'}),),{})),{}))

``` python
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:

``` python
elem.id = 'newid'
print(elem.id, elem.get('id'), elem.get('foo', 'missing'))
elem
```

    newid newid missing

    p(('Some text',),{'id': 'newid'})

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L159"
target="_blank" style="float:right; font-size:smaller">source</a>

### Safe

``` python
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

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L231"
target="_blank" style="float:right; font-size:smaller">source</a>

### to_xml

``` python
def to_xml(
    *elms, lvl:int=0, indent:bool=True, do_escape:bool=True
):
```

*Convert [`ft`](https://fastcore.fast.ai/xml.html#ft) element tree into
an XML string*

``` python
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')
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L242"
target="_blank" style="float:right; font-size:smaller">source</a>

### FT.\_\_html\_\_

``` python
def __html__():
```

``` python
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')
```

``` python
to_xml(Div("<script>alert('XSS')</script>"), do_escape=True)
```

    "<div>&lt;script&gt;alert('XSS')&lt;/script&gt;</div>\n"

``` python
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>

``` python
c = I('hello')
print(c)
```

    <i>hello</i>

``` python
c
```

<i>hello</i>

``` python
class 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_output
```

``` python
print(h)
```

    <div>
      <div>
        <h1>Hello</h1>
        <div>hello</div>
      </div>
    </div>

``` python
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](https://jinja.palletsprojects.com/en/3.1.x/templates/#jinja-filters.escape):

``` python
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 &lt;3</p></div>

Attribute values can be [`FT`](https://fastcore.fast.ai/xml.html#ft)
elements. [`to_xml`](https://fastcore.fast.ai/xml.html#to_xml) renders
those values as markup:

``` python
print(to_xml(P('hi', value=Div('ho'))))
```

    <p value="<div>ho</div>">hi</p>

FT components also stringify with
[`to_xml`](https://fastcore.fast.ai/xml.html#to_xml):

``` python
print(Div('ho'))
```

    <div>ho</div>

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L256"
target="_blank" style="float:right; font-size:smaller">source</a>

### FT.\_\_hash\_\_

``` python
def __hash__():
```

*Return hash(self).*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L251"
target="_blank" style="float:right; font-size:smaller">source</a>

### FT.\_\_eq\_\_

``` python
def __eq__(
    other
):
```

*Return self==value.*

[`FT`](https://fastcore.fast.ai/xml.html#ft) object equality and hashing
is based on tag, attrs, and children.

``` python
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'))
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L259"
target="_blank" style="float:right; font-size:smaller">source</a>

### dict2xml

``` python
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`](https://fastcore.fast.ai/xml.html#dict2xml) converts
dictionary keys to tags and values to text content:

``` python
d = {'name': 'Jeremy', 'lang': 'Python'}
print(dict2xml(d))
```

    <name>Jeremy</name>
    <lang>Python</lang>

[`dict2xml`](https://fastcore.fast.ai/xml.html#dict2xml) returns the
value without a tag when the dictionary has one key and that key matches
`unwrap`:

``` python
d = {'name': 'Jeremy'}
print(dict2xml(d, unwrap='name'))
```

    Jeremy

## Display

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L266"
target="_blank" style="float:right; font-size:smaller">source</a>

### highlight

``` python
def highlight(
    s, lang:str='html'
):
```

*Markdown to syntax-highlight `s` in language `lang`*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L271"
target="_blank" style="float:right; font-size:smaller">source</a>

### showtags

``` python
def showtags(
    s
):
```

Call an element to add children after its attributes:

``` python
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)
    )
))
```

``` html
<body class="myclass"><div style="padding:3px">Some text 1&lt;2<i spurious>in italics</i><input name="me"><img src="filename" data="1"></div></body>
```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L281"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_getattr

``` python
def mk_getattr(
    f
):
```

*Create a module `__getattr__` for mapping undefined attributes to
kebab-case HTML tags via factory `f`*

``` python
from fastcore.xml import Foo_bar,Foo_Bar,BarBaz,BarBAZ
```

Import an otherwise undefined capitalized name to create a tag
constructor. Underscores in that name become hyphens:

``` python
print(Foo_bar())
print(Foo_Bar())
```

    <foo-bar></foo-bar>
    <foo-bar></foo-bar>

CamelCase names become lowercase tags with hyphens between words:

``` python
print(BarBaz())
```

    <bar-baz></bar-baz>

## Namespace-aware XML

[`E`](https://fastcore.fast.ai/xml.html#e) creates XML elements from
Python calls. Its `ns` argument maps namespace prefixes to URIs. The
`prefix` and `attr_ns` arguments set the default prefixes for element
and attribute names.

For example, `e = E('w', attr_ns='w', ns={'w': uri})` uses `w` for both
defaults. Calling `e.tcW(type='dxa', w=2400)` creates a `w:tcW` element
with `w:type` and `w:w` attributes.

[`E`](https://fastcore.fast.ai/xml.html#e) preserves the case of tag and
attribute names. Use `prefix__name` for an explicitly prefixed
attribute, or `attrs_` for literal attribute names.

Nesting an element under a different parent does not change its
namespace bindings.

XML serialization escapes text and writes booleans as
[`true`](https://fastcore.fast.ai/basics.html#true) or `false`. Override
`attr_value` for other conventions. `.bytes()` returns UTF-8.
[`ft`](https://fastcore.fast.ai/xml.html#ft) and
[`to_xml`](https://fastcore.fast.ai/xml.html#to_xml) retain their HTML
behavior.

``` python
from xml.etree.ElementTree import fromstring
from fastcore.test import expect_fail
```

[`XML`](https://fastcore.fast.ai/xml.html#xml) collects children in
order at construction time and omits `None`. Text stays separate from
markup.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L330"
target="_blank" style="float:right; font-size:smaller">source</a>

### XML

``` python
def XML(
    tag, children, attrs, ns
):
```

*A detached XML expression with fixed namespace bindings.*

A child keeps its namespace bindings when nested under a parent with
different bindings. Serialization declares the child’s changed bindings
on that child.

`str(expr)` and `expr.bytes()` serialize a standalone document. To
insert the markup into an existing document, pass its namespace bindings
to `expr.render(inherited)`.

`render({})` declares the expression’s bindings. For an unqualified
element, this includes `xmlns=""`. That declaration prevents the
destination’s default namespace from applying to the element.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L386"
target="_blank" style="float:right; font-size:smaller">source</a>

### XML.\_\_repr\_\_

``` python
def __repr__():
```

*Return repr(self).*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L378"
target="_blank" style="float:right; font-size:smaller">source</a>

### XML.bytes

``` python
def bytes():
```

*Serialize as UTF-8, without formatting whitespace.*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L375"
target="_blank" style="float:right; font-size:smaller">source</a>

### XML.\_\_str\_\_

``` python
def __str__():
```

*Return str(self).*

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L368"
target="_blank" style="float:right; font-size:smaller">source</a>

### XML.render

``` python
def render(
    inherited:NoneType=None
):
```

*Serialize, declaring the bindings not already in scope: `inherited`
maps the prefixes bound where the markup will be inserted, `{}` declares
every binding, and `None` renders a standalone document*

An [`E`](https://fastcore.fast.ai/xml.html#e) factory stores separate
defaults for element and attribute prefixes. WordprocessingML uses `w`
for both. The DrawingML example uses prefixed elements with unqualified
attributes. Configure these defaults on the factory without supplying a
schema.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/xml.py#L389"
target="_blank" style="float:right; font-size:smaller">source</a>

### E

``` python
def E(
    prefix:str='', # Default element prefix; empty uses the default namespace
    *, attr_ns:NoneType=None, # Default attribute prefix; None leaves attributes unqualified
    ns:NoneType=None, # Prefix-to-URI bindings, including prefixes used only in values
):
```

*Create a namespace-bound XML factory: `e.tag(...)` and `e('tag', ...)`
build detached expressions*

[`E()`](https://fastcore.fast.ai/xml.html#e) creates unqualified XML.
Attribute access supplies a tag name, and keyword arguments supply
attributes. An expression’s `repr` shows its markup. `.bytes()` returns
UTF-8. An unqualified root has no namespace declaration:

``` python
plain = E()
note = plain.Note('Please bring tea & biscuits.', priority=2, signed=False, draft=None)
parsed_note = fromstring(note.bytes())
test_eq(parsed_note.tag, 'Note')
test_eq(parsed_note.attrib, {'priority': '2', 'signed': 'false'})
test_eq(parsed_note.text, 'Please bring tea & biscuits.')
test_eq(repr(note), str(note))
assert 'xmlns' not in str(note)
note
```

``` xml
<Note xmlns="" priority="2" signed="false">Please bring tea &amp; biscuits.</Note>
```

Pass children individually or in lists, tuples and generators. `None`
omits a child. Calling an expression appends children and returns the
same expression.

Adding children consumes generators and preserves their order. Later
serializations reuse the collected children:

``` python
notes = plain.Notes(note)
assert notes((plain.Note(t) for t in ['Bring cups.', 'And a teapot.']), None) is notes
test_eq([n.text for n in fromstring(notes.bytes())],
        ['Please bring tea & biscuits.', 'Bring cups.', 'And a teapot.'])
test_eq(notes.bytes(), notes.bytes())
notes
```

``` xml
<Notes xmlns=""><Note priority="2" signed="false">Please bring tea &amp; biscuits.</Note><Note>Bring cups.</Note><Note>And a teapot.</Note></Notes>
```

### Element and attribute namespaces

An XML prefix names a namespace URI. WordprocessingML qualifies both
element and attribute names with `w`. Configure those two defaults once;
subsequent expressions use ordinary Python names. Case is preserved.

``` python
word_ns = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
rel_ns = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
e = E('w', attr_ns='w', ns={'w': word_ns, 'r': rel_ns})
width = e.tcW(type='dxa', w=2400)
parsed_width = fromstring(width.bytes())
test_eq(parsed_width.tag, f'{{{word_ns}}}tcW')
test_eq(parsed_width.attrib, {f'{{{word_ns}}}type': 'dxa', f'{{{word_ns}}}w': '2400'})
test_eq(width.attrs, {'w:type': 'dxa', 'w:w': '2400'})
width
```

``` xml
<w:tcW xmlns="" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" w:type="dxa" w:w="2400"/>
```

Other vocabularies use unqualified attributes on qualified elements.
Leaving `attr_ns` unset gives that behavior. An explicit prefix uses
`__` in a keyword: `r__embed` becomes `r:embed`. The `xml` prefix is
predefined.

``` python
drawing_ns = 'http://schemas.openxmlformats.org/drawingml/2006/main'
a = E('a', ns={'a': drawing_ns, 'r': rel_ns})
picture = a.blip(r__embed='rId7', cstate='print')
test_eq(fromstring(picture.bytes()).attrib, {f'{{{rel_ns}}}embed': 'rId7', 'cstate': 'print'})
paragraph = e.p(e.r(e.t(' Open ', xml__space='preserve')),
                e.hyperlink(e.r(e.t('the picture')), r__id='rId7'))
test_eq(''.join(fromstring(paragraph.bytes()).itertext()), ' Open the picture')
paragraph
```

``` xml
<w:p xmlns="" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><w:r><w:t xml:space="preserve"> Open </w:t></w:r><w:hyperlink r:id="rId7"><w:r><w:t>the picture</w:t></w:r></w:hyperlink></w:p>
```

### Literal names and namespace scope

For Python keywords, add a trailing underscore: `class_` becomes
`class`, and `e.del_()` creates `w:del`. Other underscores remain
literal.

Use `attrs_` to supply exact attribute names. These names bypass
`attr_ns`, including when they have no prefix.

Pass a string to the factory for tags such as `e('custom-name', ...)` or
an explicitly prefixed tag. An explicit tag prefix does not change the
attribute default.

``` python
marker = e('custom-name', name='intro', class_='chapter', source_id='s1',
           attrs_={'plain': 'unqualified', 'w:custom-name': 'literal'})
marker_attrs = fromstring(marker.bytes()).attrib
test_eq(marker_attrs['plain'], 'unqualified')
test_eq([marker_attrs[f'{{{word_ns}}}{k}'] for k in ['name', 'class', 'source_id', 'custom-name']],
        ['intro', 'chapter', 's1', 'literal'])
test_eq(fromstring(e.del_(e.r(e.delText('old')), id=7).bytes()).tag, f'{{{word_ns}}}del')
marker
```

``` xml
<w:custom-name xmlns="" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" w:name="intro" w:class="chapter" w:source_id="s1" plain="unqualified" w:custom-name="literal"/>
```

`ns={'': 'urn:catalog'}` sets the default namespace. Use `''`, not
`None`, as the mapping key. The default namespace applies to elements,
not unqualified attributes.

Here `Entry` uses the catalog namespace. The nested `plain.Note` still
has no namespace. Bindings supplied in `ns` are declared even when no
element or attribute name uses them. The example declares `kind` because
it appears in `ns`, although it is used only in the attribute value
`kind:Personal`.

``` python
catalog = E(ns={'': 'urn:catalog', 'kind': 'urn:note-types'})
entry = catalog.Entry(plain.Note('An unqualified child'), kind='kind:Personal')
parsed_entry = fromstring(entry.bytes())
test_eq(parsed_entry.tag, '{urn:catalog}Entry')
test_eq(parsed_entry[0].tag, 'Note')
test_eq(parsed_entry.attrib, {'kind': 'kind:Personal'})
assert 'xmlns' not in plain.Note('x').render()
assert 'xmlns=""' in plain.Note('x').render({}) and 'xmlns=""' in plain.Note('x').render({'': 'urn:catalog'})
entry
```

``` xml
<Entry xmlns="urn:catalog" xmlns:kind="urn:note-types" kind="kind:Personal"><Note xmlns="">An unqualified child</Note></Entry>
```

A reused prefix can mean different things in different expressions. The
child declares its own binding rather than taking its parent’s meaning.

``` python
other = E('w', ns={'w': 'urn:another-vocabulary'})
mixed = e.p(other.item(), e.r(e.t('Still WordprocessingML')))
parsed_mixed = fromstring(mixed.bytes())
test_eq([c.tag for c in parsed_mixed], ['{urn:another-vocabulary}item', f'{{{word_ns}}}r'])
mixed
```

``` xml
<w:p xmlns="" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><w:item xmlns:w="urn:another-vocabulary"/><w:r><w:t>Still WordprocessingML</w:t></w:r></w:p>
```

### Text is text

Serialization escapes markup in string children, including
[`Safe`](https://fastcore.fast.ai/xml.html#safe) strings.
[`XML`](https://fastcore.fast.ai/xml.html#xml) rejects byte-string
children.

Serialization adds no indentation or line breaks. Carriage returns in
text and whitespace in attributes survive parsing.

``` python
spacing = plain.Note(Safe('<em>not markup</em>'), '\rkeep this return', label='one\t"two"\nthree')
parsed_spacing = fromstring(spacing.bytes())
test_eq(parsed_spacing.text, '<em>not markup</em>\rkeep this return')
test_eq(parsed_spacing.get('label'), 'one\t"two"\nthree')
with expect_fail(TypeError): plain.Note(b'<raw/>')
spacing
```

``` xml
<Note xmlns="" label='one&#9;"two"&#10;three'>&lt;em&gt;not markup&lt;/em&gt;&#13;keep this return</Note>
```

[`E`](https://fastcore.fast.ai/xml.html#e) checks namespace bindings
when you create a factory. When you create an element, it checks element
and attribute names and rejects unknown prefixes with `ValueError`. Put
namespace declarations in `ns`, not in the attributes. These checks do
not validate the document against a schema.

``` python
with expect_fail(ValueError, contains='Unknown XML prefix'): plain('missing:Note')
with expect_fail(ValueError, contains='Invalid XML name'): plain('bad name')
with expect_fail(ValueError, contains='Illegal XML character'): plain.Note('\x00')
with expect_fail(ValueError, contains='namespace binding'): E(ns={'xml': 'urn:wrong'})
with expect_fail(TypeError): E(ns={None: 'urn:catalog'})
```

Two prefixes for the same URI still name the same attributes. Supplying
an attribute twice is an error, including a duplicate supplied through
`attrs_`.

``` python
aliases = E('w', attr_ns='w', ns={'w': word_ns, 'word': word_ns})
with expect_fail(ValueError, contains='Duplicate XML attribute'):
    aliases.tcW(w=2400, attrs_={'word:w': 1200})
```

Attribute values are serialized by `attr_value`. A vocabulary with its
own conventions overrides it. WordprocessingML’s on/off elements are one
example: their `val` takes `on` or `off`, not
[`true`](https://fastcore.fast.ai/basics.html#true) or `false`.

``` python
class OnOffE(E):
    def attr_value(self, tag, name, value):
        if isinstance(value, bool): return 'on' if value else 'off'
        return super().attr_value(tag, name, value)

onoff = OnOffE('w', attr_ns='w', ns={'w': word_ns})
test_eq(fromstring(onoff.cantSplit(val=False).bytes()).attrib, {f'{{{word_ns}}}val': 'off'})
onoff.tblHeader(val=True)
```
