Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API

Overview

Dominate

Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API. It allows you to write HTML pages in pure Python very concisely, which eliminates the need to learn another template language, and lets you take advantage of the more powerful features of Python.

Python version Build status Coverage status

Python:

import dominate
from dominate.tags import *

doc = dominate.document(title='Dominate your HTML')

with doc.head:
    link(rel='stylesheet', href='style.css')
    script(type='text/javascript', src='script.js')

with doc:
    with div(id='header').add(ol()):
        for i in ['home', 'about', 'contact']:
            li(a(i.title(), href='/%s.html' % i))

    with div():
        attr(cls='body')
        p('Lorem ipsum..')

print(doc)

Output:

Lorem ipsum..

">
>
<html>
  <head>
    <title>Dominate your HTMLtitle>
    <link href="style.css" rel="stylesheet">
    <script src="script.js" type="text/javascript">script>
  head>
  <body>
    <div id="header">
      <ol>
        <li>
          <a href="/home.html">Homea>
        li>
        <li>
          <a href="/about.html">Abouta> li> <li> <a href="/contact.html">Contacta> li> ol> div> <div class="body"> <p>Lorem ipsum..p> div> body> html>

Installation

The recommended way to install dominate is with pip:

sudo pip install dominate

PyPI version PyPI downloads

Developed By

Git repository located at github.com/Knio/dominate

Examples

All examples assume you have imported the appropriate tags or entire tag set:

from dominate.tags import *

Hello, World!

The most basic feature of dominate exposes a class for each HTML element, where the constructor accepts child elements, text, or keyword attributes. dominate nodes return their HTML representation from the __str__, __unicode__, and render() methods.

print(html(body(h1('Hello, World!'))))
<html>
    <body>
        <h1>Hello, World!h1>
    body>
html>

Attributes

Dominate can also use keyword arguments to append attributes onto your tags. Most of the attributes are a direct copy from the HTML spec with a few variations.

For attributes class and for which conflict with Python's reserved keywords, you can use the following aliases:

class for
_class _for
cls fr
className htmlFor
class_name html_for
test = label(cls='classname anothername', fr='someinput')
print(test)
">
<label class="classname anothername" for="someinput">label>

Use data_* for custom HTML5 data attributes.

test = div(data_employee='101011')
print(test)
">
<div data-employee="101011">div>

You can also modify the attributes of tags through a dictionary-like interface:

header = div()
header['id'] = 'header'
print(header)
">
<div id="header">div>

Complex Structures

Through the use of the += operator and the .add() method you can easily create more advanced structures.

Create a simple list:

list = ul()
for item in range(4):
    list += li('Item #', item)
print(list)
<ul>
    <li>Item #0li>
    <li>Item #1li>
    <li>Item #2li>
    <li>Item #3li>
ul>

dominate supports iterables to help streamline your code:

print(ul(li(a(name, href=link), __pretty=False) for name, link in menu_items))
Home
  • About
  • Downloads
  • Links
  • ">
    <ul>
        <li><a href="/home/">Homea>li>
        <li><a href="/about/">Abouta>li>
        <li><a href="/downloads/">Downloadsa>li> <li><a href="/links/">Linksa>li> ul>

    A simple document tree:

    _html = html()
    _body = _html.add(body())
    header  = _body.add(div(id='header'))
    content = _body.add(div(id='content'))
    footer  = _body.add(div(id='footer'))
    print(_html)
    ">
    <html>
        <body>
            <div id="header">div>
            <div id="content">div>
            <div id="footer">div>
        body>
    html>

    For clean code, the .add() method returns children in tuples. The above example can be cleaned up and expanded like this:

    _html = html()
    _head, _body = _html.add(head(title('Simple Document Tree')), body())
    names = ['header', 'content', 'footer']
    header, content, footer = _body.add([div(id=name) for name in names])
    print(_html)
    ">
    <html>
        <head>
           <title>Simple Document Treetitle>
        head>
        <body>
            <div id="header">div>
            <div id="content">div>
            <div id="footer">div>
        body> html>

    You can modify the attributes of tags through a dictionary-like interface:

    header = div()
    header['id'] = 'header'
    print(header)
    ">
    <div id="header">div>

    Or the children of a tag though an array-line interface:

    header = div('Test')
    header[0] = 'Hello World'
    print(header)
    <div>Hello Worlddiv>

    Comments can be created using objects too!

    print(comment('BEGIN HEADER'))
    print(comment(p('Upgrade to newer IE!'), condition='lt IE9'))

    Rendering

    By default, render() tries to make all output human readable, with one HTML element per line and two spaces of indentation.

    This behavior can be controlled by the __pretty (default: True except for certain element types like pre) attribute when creating an element, and by the pretty (default: True), indent (default: ) and xhtml (default: False) arguments to render(). Rendering options propagate to all descendant nodes.

    a = div(span('Hello World'))
    print(a.render())
    <div>
      <span>Hello Worldspan>
    div>
    print(a.render(pretty=False))
    <div><span>Hello Worldspan>div>
    print(a.render(indent='\t'))
    <div>
    	<span>Hello Worldspan>
    div>
    a = div(span('Hello World'), __pretty=False)
    print(a.render())
    <div><span>Hello Worldspan>div>
    d = div()
    with d:
        hr()
        p("Test")
        br()
    print(d.render())
    print(d.render(xhtml=True))
    <div>
      <hr>
      <p>Testp><br>
    div>
    <div>
      <hr />
      <p>Testp><br />
    div>

    Context Managers

    You can also add child elements using Python's with statement:

    h = ul()
    with h:
        li('One')
        li('Two')
        li('Three')
    
    print(h)
    <ul>
        <li>Oneli>
        <li>Twoli>
        <li>Threeli>
    ul>

    You can use this along with the other mechanisms of adding children elements, including nesting with statements, and it works as expected:

    h = html()
    with h.add(body()).add(div(id='content')):
        h1('Hello World!')
        p('Lorem ipsum ...')
        with table().add(tbody()):
            l = tr()
            l += td('One')
            l.add(td('Two'))
            with l:
                td('Three')
    
    print(h)

    Hello World!

    Lorem ipsum ...

    One Two Three
    ">
    <html>
        <body>
            <div id="content">
                <h1>Hello World!h1>
                <p>Lorem ipsum ...p>
                <table>
                    <tbody>
                        <tr>
                            <td>Onetd>
                            <td>Twotd>
                            <td>Threetd>
                        tr> tbody> table> div> body> html>

    When the context is closed, any nodes that were not already added to something get added to the current context.

    Attributes can be added to the current context with the attr function:

    d = div()
    with d:
        attr(id='header')
    
     print(d)
    ">
    <div id="header">div>

    And text nodes can be added with the dominate.util.text function:

    from dominate.util import text
    para = p(__pretty=False)
    with para:
        text('Have a look at our ')
        a('other products', href='/products')
    
    print(para)
    other products

    ">
    <p>Have a look at our <a href="/products">other productsa>p>

    Decorators

    Dominate is great for creating reusable widgets for parts of your page. Consider this example:

    def greeting(name):
        with div() as d:
            p('Hello, %s' % name)
        return d
    
    print(greeting('Bob'))
    <div>
        <p>Hello, Bobp>
    div>

    You can see the following pattern being repeated here:

    def widget(parameters):
        with tag() as t:
            ...
        return t

    This boilerplate can be avoided by using tags (objects and instances) as decorators

    @div
    def greeting(name):
        p('Hello %s' % name)
    print(greeting('Bob'))
    <div>
        <p>Hello Bobp>
    div>

    The decorated function will return a new instance of the tag used to decorate it, and execute in a with context which will collect all the nodes created inside it.

    You can also use instances of tags as decorators, if you need to add attributes or other data to the root node of the widget. Each call to the decorated function will return a copy of the node used to decorate it.

    @div(h2('Welcome'), cls='greeting')
    def greeting(name):
        p('Hello %s' % name)
    
    print(greeting('Bob'))

    Welcome

    Hello Bob

    ">
    <div class="greeting">
        <h2>Welcomeh2>
        <p>Hello Bobp>
    div>

    Creating Documents

    Since creating the common structure of an HTML document everytime would be excessively tedious dominate provides a class to create and manage them for you: document.

    When you create a new document, the basic HTML tag structure is created for you.

    d = document()
    print(d)
    >
    <html>
        <head>
           <title>Dominatetitle>
        head>
        <body>body>
    html>

    The document class accepts title, doctype, and request keyword arguments. The default values for these arguments are Dominate, , and None respectively.

    The document class also provides helpers to allow you to access the title, head, and body nodes directly.

    d = document()
    >>> d.head
    <dominate.tags.head: 0 attributes, 1 children>
    >>> d.body
    <dominate.tags.body: 0 attributes, 0 children>
    >>> d.title
    u'Dominate'

    The document class also provides helpers to allow you to directly add nodes to the body tag.

    d = document()
    d += h1('Hello, World!')
    d += p('This is a paragraph.')
    print(d)
    >
    <html>
        <head>
           <title>Dominatetitle>
        head>
        <body>
            <h1>Hello, World!h1>
            <p>This is a paragraph.p>
        body>
    html>

    Embedding HTML

    If you need to embed a node of pre-formed HTML coming from a library such as markdown or the like, you can avoid escaped HTML by using the raw method from the dominate.util package:

    Example')) ">
    from dominate.util import raw
    ...
    td(raw('Example'))
    

    Without the raw call, this code would render escaped HTML with lt, etc.

    SVG

    The dominate.svg module contains SVG tags similar to how dominate.tags contains HTML tags. SVG elements will automatically convert _ to - for dashed elements. For example:

    from dominate.svg import *
    print(circle(stroke_width=5))
    ">
    <circle stroke-width="5">circle>
    Comments
    • I'd like to add support for SVG

      I'd like to add support for SVG

      If OK, I'd like to add support for SVG tags. There are a lot of tags, so I figured it better to submit a sample first. If you approve, I will subsequently add the remaining tags and tests for each.

      Regards, Steve J

      opened by washad 8
    • Make the single tags match the html 5 spec

      Make the single tags match the html 5 spec

      Current version br() --> "<br>" Proposed fix br() --> "<br />"

      Note: I haven't bumped the version number...


      This change is Reviewable

      opened by hsolbrig 8
    • Removed 'input' keyword reassignment

      Removed 'input' keyword reassignment

      Direct fix to #128. Additionally, I've added the missing assignment for leading underscores for del and map as these only supported tailing underscores.

      opened by Mrocza 5
    • support recursive with dominate.document()

      support recursive with dominate.document()

      import dominate
      from dominate.tags import *
      
      with dominate.document() as outer_doc:
          with dominate.document() as inner_doc:
              p('meow')
          print(inner_doc)
      print(outer_doc)
      

      output

      <!DOCTYPE html>
      <html>
        <head>
          <title>Dominate</title>
        </head>
        <body>
          <p>meow</p>
        </body>
      </html>
      <!DOCTYPE html>
      <html>
        <head>
          <title>Dominate</title>
        </head>
        <body>
          <html>
            <head>
              <title>Dominate</title>
            </head>
            <body>
              <p>meow</p>
            </body>
          </html>
        </body>
      </html>
      
      opened by MarisaKirisame 4
    • Rendered HTML is full of '=' and '3D' that we didn't code in

      Rendered HTML is full of '=' and '3D' that we didn't code in

      I'm running Python 3.8.6 with Dominate 2.6.0.

      I thought it was an issue with raw(), maybe, but when I rewrote the code, I still got all these = signs and 3D in the HTML.

       with p():
              a(name="Why am I receiving this email notification?",
                href="https://anyurl.org/",
                style="text-decoration: none;")
          p(raw(
              """<a href="https://anyurl.org/", 
              style="text-decoration: none;">Why am I receiving this email notification?</a>
              """))
      

      Below is the general structure of the code, the above p tags are nested in the same way as the last one in the below example:

        with self.doc:
            with table():
                with tr():
                    with td(style=VERTICAL_ALIGN_TOP):
                        img(src=SOME_URL)
                    with td():
                        with div(cls='header'):
                            p(raw("""<b>Do you see an issue with the Mapping?</b>"""))
      

      When I look at the html file produced, it has = at the end of every line, at exactly 79 characters.

      opened by PropeReferio 4
    • meta tag issues

      meta tag issues

      meta tag issues

      input with doc.head: meta('content="text/html;charset=utf-8", http-equiv="Content-Type"') meta('content="utf-8", http-equiv="encoding"') output <meta> <meta>

      input with doc.head: meta(raw('content="text/html;charset=utf-8" http-equiv="Content-Type"')) meta(raw('content="utf-8" http-equiv="encoding"')) output <meta> <meta>

      opened by Aladio 4
    •  Suppress rendering of boolean attributes when the value is False

      Suppress rendering of boolean attributes when the value is False

      The values "true" and "false" are not allowed on boolean attributes. To represent a false value, the attribute has to be omitted altogether.

      HTML 5.2, W3C Recommendation, 14 December 2017 https://www.w3.org/TR/html5/infrastructure.html#boolean-attribute

      Expected behaviour is demonstrated by the included test case:

      def test_boolean_attributes():
        assert input(type="checkbox", checked=True).render() == \
            '<input checked="checked" type="checkbox">'
        assert input(type="checkbox", checked=False).render() == \
            '<input type="checkbox">'
      
      opened by golightlyb 4
    • Bug in a tag when using '&' char in the href url

      Bug in a tag when using '&' char in the href url

      When providing an url to the href attribute of an a node the '&' character is always escaped therefore modifying the url. As the '&' is commonly used to separate parameters in url this is quite problematic.

      Very simple to reproduce:

      >>> from dominate.tags import a
      >>> test = a('toto', href='https://test.org/aaa?a=b&c=d')
      >>> test.render()
      '<a href="https://test.org/aaa?a=b&amp;c=d">toto</a>'
      

      Here we have a '&' instead of '&'. What would be expected is:

      >>> from dominate.tags import a
      >>> test = a('toto', href='https://test.org/aaa?a=b&c=d')
      >>> test.render()
      '<a href="https://test.org/aaa?a=b&c=d">toto</a>'
      

      Or am I missing something?

      BTW I tried to use href=raw('https://test.org/aaa?a=b&c=d') but with no luck (this was a desperate attempt).

      opened by pymaldebaran 4
    • ENH: LinkedData

      ENH: LinkedData

      Some of examples of building html containing linked data with dominate could be useful:

      • [x] html5 data-key="value"
      • [ ] HTML5 ( text/html; charset=utf-8 )
      • [ ] XHTML + RDFa ( application/xhtml+xml )
      • [ ] HTML5 + RDFa ( text/html; charset=utf-8 )
      • [ ] HTML + JSONLD ( application/ld+json; charset=utf-8 )

      References:

      • https://wrdrd.com/docs/consulting/knowledge-engineering#rdfa #rdf #rdfs #schemaorg
      • https://wrdrd.com/docs/consulting/knowledge-engineering#semantic-web-standards
      • https://wrdrd.com/docs/consulting/knowledge-engineering#schemaorg
        • https://schema.org/
          • Wikipedia: https://en.wikipedia.org/wiki/Schema.org
          • Homepage: https://schema.org
          • Download: https://schema.org/version/latest/
          • Source: https://github.com/schemaorg/schemaorg
          • Source: https://github.com/schemaorg/schemaorg/tree/sdo-phobos/data/releases/2.2
          • Docs: http://dataliberate.com/2016/02/evolving-schema-org-in-practice-pt1-the-bits-and-pieces/
          • Issues: https://github.com/schemaorg/schemaorg/issues
          • IssueLabels: https://github.com/schemaorg/schemaorg/labels
      opened by westurner 4
    • Rename fr and cls

      Rename fr and cls

      fr looks a bit ugly, reactjs had a similar problem, they are using htmlFor.

      For cls, they are using className.

      I think it might make sense to adapt these names, maybe with underscores.

      opened by ustun 4
    • AttributeError: module 'dominate.tags' has no attribute 'input'

      AttributeError: module 'dominate.tags' has no attribute 'input'

      Hello - we have been using the dominate python library for a while without issue and it has been a great utility for us. Today, I upgraded to the latest version (2.5.2) and noticed this error showing up. Is there a change in the API with the latest update in regards to this error or do you have any other idea what could have caused this for us?

      Traceback (most recent call last):
        File "/usr/local/lib/python3.8/site-packages/cherrypy/_cprequest.py", line 638, in respond
          self._do_respond(path_info)
        File "/usr/local/lib/python3.8/site-packages/cherrypy/_cprequest.py", line 697, in _do_respond
          response.body = self.handler()
        File "/usr/local/lib/python3.8/site-packages/cherrypy/lib/encoding.py", line 219, in __call__
          self.body = self.oldhandler(*args, **kwargs)
        File "/usr/local/lib/python3.8/site-packages/cherrypy/_cpdispatch.py", line 54, in __call__
          return self.callable(*self.args, **self.kwargs)
        File "/usr/src/python/am/server/modules/login.py", line 575, in index
          form.add(html.input(type="hidden",
      AttributeError: module 'dominate.tags' has no attribute 'input'
      
      opened by r-chris 3
    • (chore) Add unit tests to untested methods

      (chore) Add unit tests to untested methods

      This commit will cover methods that were not unit tested yet. It ignores imports for coverage as most tests are ran in multiple Python version. It also fixes an except, as lists cannot throw KeyError

      opened by RonNabuurs 1
    • Repeatedly calling attr() on same attribute doesn't accumulate values

      Repeatedly calling attr() on same attribute doesn't accumulate values

      For example, I'd expect the following to generate <button class="bar baz">foo</button>:

      from dominate.tags import *
      
      b = button("foo")
      with b:
        attr(className = "bar")
        attr(className = "baz")
      
      print(b)
      
      <button class="baz">foo</button>
      

      Accumulating is nice for proving a "base component" with a set of "base classes" that an end user could add to, for example:

      def myButton(label):
        b = button(label)
        with b:
          attr(className = "btn")
        return b
      
      b2 = myButton("foo")
      with b2:
        attr(className = "btn-primary")
      
      print(b2)
      

      Would ideally generate

      <button class="btn btn-primary">foo</button>
      
      opened by cpsievert 0
    • Some HTML5 tags are missing

      Some HTML5 tags are missing

      May want to consider programmatically constructing classes using https://developer.mozilla.org/en-US/docs/Web/HTML/Element

      For reference, this is how the {htmltools} package queries all non-obsolete tags https://github.com/rstudio/htmltools/blob/bc226a3/scripts/generate_known_tags.R#L37

      Happy to stab at a PR there's interest in this.

      opened by cpsievert 0
    • cannot put <meta charset= first">

      cannot put first

      In HTML5 it is recommended to explicitly specify the character encoding with a meta element in the head:

      <meta charset="utf-8">
      

      To make sure this encoding is also applied to the title of the document, it should come before the title element. However, if I try to do so with the code

      import dominate
      doc = dominate.document()
      with doc.head:
          dominate.tags.meta(charset='utf-8')
          dominate.tags.title('Document title')
      print(doc.render())
      

      the result is

      <!DOCTYPE html>
      <html>
        <head>
          <title>Dominate</title>
          <meta charset="utf-8">
          <title>Document title</title>
        </head>
        <body></body>
      </html>
      

      i.e. I get two title elements. If I specify the title instead as an argument of dominate.document, there is only one title element, but it is before the meta element.

      I see two ways around this:

      1. The title attribute of dominate.document is only used to generate a title element if none has been added manually.

      2. dominate.document gets an additional charset argument.

      My preference would be (1).

      opened by allefeld 3
    • javascript DOM for the style attribute, .css file format output

      javascript DOM for the style attribute, .css file format output

      WIP

      This update duplicates how the style attribute is accessed when writing javascript. This is a nice feature to have for the people that are familiar with it. While I was creating this I thought that it would also be handy to have it output in .css format as well. So I added that ability also.

      I have not tested it as of yet. I thought I would get your input before I went any further with it.

      Feature Request 
      opened by kdschlosser 0
    Owner
    Tom Flanagan
    Tom Flanagan
    Python binding to Modest engine (fast HTML5 parser with CSS selectors).

    A fast HTML5 parser with CSS selectors using Modest engine. Installation From PyPI using pip: pip install selectolax Development version from github:

    Artem Golubin 710 Jan 04, 2023
    Python module that makes working with XML feel like you are working with JSON

    xmltodict xmltodict is a Python module that makes working with XML feel like you are working with JSON, as in this "spec": print(json.dumps(xmltod

    Martín Blech 5k Jan 04, 2023
    Safely add untrusted strings to HTML/XML markup.

    MarkupSafe MarkupSafe implements a text object that escapes characters so it is safe to use in HTML and XML. Characters that have special meanings are

    The Pallets Projects 514 Dec 31, 2022
    Lektor-html-pretify - Lektor plugin to pretify the HTML DOM using Beautiful Soup

    html-pretify Lektor plugin to pretify the HTML DOM using Beautiful Soup. How doe

    Chaos Bodensee 2 Nov 08, 2022
    Standards-compliant library for parsing and serializing HTML documents and fragments in Python

    html5lib html5lib is a pure-python library for parsing HTML. It is designed to conform to the WHATWG HTML specification, as is implemented by all majo

    1k Dec 27, 2022
    A jquery-like library for python

    pyquery: a jquery-like library for python pyquery allows you to make jquery queries on xml documents. The API is as much as possible the similar to jq

    Gael Pasgrimaud 2.2k Dec 29, 2022
    Converts XML to Python objects

    untangle Documentation Converts XML to a Python object. Siblings with similar names are grouped into a list. Children can be accessed with parent.chil

    Christian Stefanescu 567 Nov 30, 2022
    That project takes as input special TXT File, divides its content into lsit of HTML objects and then creates HTML file from them.

    That project takes as input special TXT File, divides its content into lsit of HTML objects and then creates HTML file from them.

    1 Jan 10, 2022
    Modded MD conversion to HTML

    MDPortal A module to convert a md-eqsue lang to html Basically I ruined md in an attempt to convert it to html Overview Here is a demo file from parse

    Zeb 1 Nov 27, 2021
    inscriptis -- HTML to text conversion library, command line client and Web service

    inscriptis -- HTML to text conversion library, command line client and Web service A python based HTML to text conversion library, command line client

    webLyzard technology 122 Jan 07, 2023
    The awesome document factory

    The Awesome Document Factory WeasyPrint is a smart solution helping web developers to create PDF documents. It turns simple HTML pages into gorgeous s

    Kozea 5.4k Jan 07, 2023
    The lxml XML toolkit for Python

    What is lxml? lxml is the most feature-rich and easy-to-use library for processing XML and HTML in the Python language. It's also very fast and memory

    2.3k Jan 02, 2023
    A library for converting HTML into PDFs using ReportLab

    XHTML2PDF The current release of xhtml2pdf is xhtml2pdf 0.2.5. Release Notes can be found here: Release Notes As with all open-source software, its us

    2k Dec 27, 2022
    A HTML-code compiler-thing that lets you reuse HTML code.

    RHTML RHTML stands for Reusable-Hyper-Text-Markup-Language, and is pronounced "Rech-tee-em-el" despite how its abbreviation is. As the name stands, RH

    Duckie 4 Nov 15, 2021
    Bleach is an allowed-list-based HTML sanitizing library that escapes or strips markup and attributes

    Bleach Bleach is an allowed-list-based HTML sanitizing library that escapes or strips markup and attributes. Bleach can also linkify text safely, appl

    Mozilla 2.5k Dec 29, 2022
    Pythonic HTML Parsing for Humans™

    Requests-HTML: HTML Parsing for Humans™ This library intends to make parsing HTML (e.g. scraping the web) as simple and intuitive as possible. When us

    Python Software Foundation 12.9k Jan 01, 2023
    Generate HTML using python 3 with an API that follows the DOM standard specfication.

    Generate HTML using python 3 with an API that follows the DOM standard specfication. A JavaScript API and tons of cool features. Can be used as a fast prototyping tool.

    byteface 114 Dec 14, 2022
    Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API

    Dominate Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API. It allows you to write HTML pages in pure

    Tom Flanagan 1.5k Jan 09, 2023
    A python HTML builder library.

    PyML A python HTML builder library. Goals Fully functional html builder similar to the javascript node manipulation. Implement an html parser that ret

    Arjix 8 Jul 04, 2022