A pure Python DOM that goes way beyond minidom
- Python
3.10+ - HTML, SVG, DOM, events, web APIs, animation, and a JavaScript-like runtime
- Built for generating, parsing, traversing, and manipulating real document trees
python3 -m pip install domonic
python3 -m pip install --upgrade domonicFor development:
python3 -m pip install -r requirements-dev.txtfrom domonic.html import *
print(html(body(h1('Hello, World!'))))
# <html><body><h1>Hello, World!</h1></body></html>to pretty format and insert the doctype, use an f-string:
mydom = html(body(h1('Hello, World!'), a("somelink", _href="somepage.html")))
print(f"{mydom}")<!DOCTYPE html>
<html>
<body>
<h1>Hello, World!</h1>
<a href="somepage.html">somelink</a>
</body>
</html>from domonic import domonic
mydom = domonic.parseString('<somehtml...')or parse an entire webpage with the window module...
from domonic.window import window
window.location = "https://un5mkq9urycyna8.julianrbryant.com"
print(window.document.title)html5lib is bundled as the default parser as it's pure python. However see the Parsers section below as other parser backends are supported and are much faster.
Use xpath or css selectors on command line...
domonic -x https://un5q089ru7t40.julianrbryant.com '//a' | uniq | sort- html : Generate html with Python 3 😎
- dom : DOM API in Python 3 😲
- javascript : js API in Python 3 😳 + (dQuery, d3)
- JSON : utils for loading / decorating / transforming
- SVG || mathml || aframe || x3d tags - generators for popular tags
- terminal || cmd : call terminal commands with Python3 😱
See the docs/code for more features or examples in the repo
Simply import what you need...
from domonic import div, span, input as myinput, html as rootor use the tags package if you want a namespace. i.e.
import domonic.tags
print(domonic.tags.h1)
# or
import domonic.tags as tags
str(tags.div)
# or
import domonic.tags as html
print(html.span)Prepend attributes with an underscore ( avoids clashing with Python keywords )
test = label(_class='classname', _for="someinput")
print(test)<label class="classname" for="someinput"></label>domonic is a pure Python DOM whose tree is composed of objects. i.e
div()
# <domonic.html.div object at 0x106b0e6b0>cast str() on any element to render it without formatting.
el = str(div())
print(el)
# <div></div>There's also a render method that takes 2 parameters, some domonic and an optional output file.
page = div(span('Hello World'))
render(f"{page}", 'index.html') # notice use of f-string to pretty print the htmlFor rendering options see DOMConfig.
from domonic.dom import DOMConfig
print(DOMConfig.GLOBAL_AUTOESCAPE) # Default False
print(DOMConfig.RENDER_OPTIONAL_CLOSING_TAGS) # Default True
print(DOMConfig.RENDER_OPTIONAL_CLOSING_SLASH) # Defaults True
print(DOMConfig.SPACE_BEFORE_OPTIONAL_CLOSING_SLASH) # Default FalseDOM manipulation with Python, built with the actual platform in mind.
Create your own elements with the DOM API
from domonic.dom import *
site = html()
el = document.createElement('myelement')
site.appendChild(el)
print(site)
# <html><myelement></myelement></html>mysite.querySelectorAll('button')
mysite.querySelectorAll("a[rel=nofollow]")
mysite.querySelectorAll("a[href='#services']")
mysite.querySelectorAll("a[href$='technology']")
mysite.querySelectorAll('.fa-twitter')
somelinks = mysite.querySelectorAll("a[href*='twitter']")
for l in somelinks:
print(l.href)To use the DOM either reference your root 'html' node or import the dom modules global 'document'
# access the document via the html tag
mydom = html()
# mydom.getElementbyID...
# or by importing the document global
from domonic.dom import document
# document.createElement...
print(document)The aim is to track the real platform as closely as possible :
Check the code/docs to see what's currently implemented.
There is a Javascript package that mirrors a large, practical slice of the JS API:
from domonic.javascript import Math
print(Math.random())
from domonic.javascript import Array
myArr=Array(1,2,3)
print(myArr.splice(1))
# [2, 3]
from domonic.javascript import URL
url = URL('https://un5zgjzanxc0.julianrbryant.com/blog/article-one#some-hash')
print(url.protocol) # https
print(url.host) # somesite.com
print(url.pathname) # /blog/article-one
print(url.hash) # #some-hash
# Use Global class to import all the js methods from the global namespace i.e
# from domonic.javascript import Global
# Global.decodeURIComponent(...
# Global.encodeComponent(...
# Global.setInterval(...
# from domonic.javascript import Date, String, Number
# etc..Use setInterval and clearInterval with params
from domonic.javascript import setInterval, clearInterval
x=0
def hi(inc):
global x
x = x+inc
print(x)
test = setInterval(hi, 1000, 2)
import time
time.sleep(5)
clearInterval(test)
print(f"Final value of x:{x}")Or for a single delayed function call use setTimeout, clearTimeout
from domonic.javascript import setTimeout, clearTimeout
timeoutID = setTimeout(hi, 1000)You can call () on a stringvar to transform it into a Node
from domonic.javascript import String
test = String("Hi there!")
test('div', _style="font-color:red;")
str(test('div', _style="font-color:red;"))
# <div style="font-color:red;">Hi there!</div>a-tags inherit URL:
from domonic.html import *
atag = a(_href="https://un5zgjzanxc0.julianrbryant.com:8000/blog/article-one#some-hash")
print('href:', atag.href)
# href: https://un5zgjzanxc0.julianrbryant.com:8000/blog/article-one#some-hash
print('protocol:', atag.protocol)
# protocol: https:
print('port:', atag.port)
# port: 8000
atag.protocol = "http"
atag.port = 8983
print(atag)
# <a href="https://umnfgjzanxc0.julianrbryant.com:8983/blog/article-one#some-hash">For writing and using regular Javascript, load from a src...
script(_src="/docs/5.0/dist/js/bootstrap.bundle.min.js", _integrity="sha384-1234", _crossorigin="anonymous"),
# <script src="/docs/5.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-1234" crossorigin="anonymous"></script>or do inline js by opening triple quotes...
script("""
let itbe = ""
"""),Styling is supported. Styles get passed to the style tag on render...
mytag = div("hi", _id="test")
mytag.style.backgroundColor = "black"
mytag.style.fontSize = "12px"
print(mytag)
# <div id="test" style="background-color:black;font-size:12px;">hi</div>To use css use a link tag as you usually would...
link(_href="styles.css", _rel="stylesheet"),or use triple quotes to open style tag...
style("""
.placeholder-img {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
"""),use decorators to wrap elements around function results
from domonic.decorators import el
@el(html, True)
@el(body)
@el(div)
def test():
return 'hi!'
print(test())
# <html><body><div>hi!</div></body></html>
# returns pyml objects so call str to render
assert str(test()) == '<html><body><div>hi!</div></body></html>'It returns the tag object by default. You can pass True as a second param to the decorator to return a rendered string instead. Also accepts strings as first param i.e. custom tags.
Python doesn't allow hyphens in parameter names. so use variable keyword argument syntax for custom data-tags
div("test", **{"_data-test":"test"} )
# <div data-test="test">test</div>or for example a colon...
t = div( **{"_:test":"something"} )
str(t)
# <div :test="something"></div>Decorate any function that returns Python objects to return json instead
from domonic.decorators import as_json
import domonic.JSON as JSON
@as_json
def somefunc():
myObj = {"hi":[1,2,3]}
return myObj
print( somefunc() )
# {"hi":[1,2,3]}
print( JSON.is_json(somefunc()) )
# Trueconvert json arrays into html tables...
import domonic.JSON as JSON
# i.e. containting flat json array of dicts... [{"id":"01","name": "some item"},{"id":"02","name": "some other item"}]
json_data = JSON.parse_file('somefile.json')
mytable = JSON.tablify(json_data)
print(mytable)convert json arrays into csv files...
import domonic.JSON as JSON
json_data = JSON.parse_file('somefile.json')
JSON.csvify(json_data, 'data.csv')convert csv files to json...
import domonic.JSON as JSON
json_data =JSON.csv2json("data.csv")
print(json_data)All tags extend 'Element'. So will have DOM and magic methods available to them. See the docs.
circ = svg(
circle(_cx="50", _cy="50", _r="40", _stroke="green", **{"_stroke-width": "4"}, _fill="yellow"),
_width="100", _height="100",
)
mysvg = svg()
mysvg.appendChild(circ / 10)
print(mysvg)Tween values with the built-in tween library:
from domonic.lerpy.easing import *
from domonic.lerpy.tween import *
someObj = {'x':0,'y':0,'z':0}
twn = Tween( someObj, { 'x':10, 'y':5, 'z':3 }, 6, Linear.easeIn )
twn.start()3d tags can be used if you import the js
from domonic.html import *
from domonic.xml.aframe import *
from domonic.CDN import *
_scene = scene(
box(_position="-1 0.5 -3", _rotation="0 45 0", _color="#4CC3D9"),
sphere(_position="0 1.25 -5", _radius="1.25", _color="#EF2D5E"),
cylinder(_position="1 0.75 -3", _radius="0.5", _height="1.5", _color="#FFC65D"),
plane(_position="0 0 -4", _rotation="-90 0 0", _width="4", _height="4", _color="#7BC8A4"),
sky(_color="#ECECEC")
)
_webpage = html(head(),body(
script(_src=CDN_JS.AFRAME), # < NOTICE you need to import aframe to use it
str(_scene)
)
)
render( _webpage, 'hello.html' )dQuery uses the º symbol (alt+0).
from domonic.html import *
from domonic.dQuery import º
d = html(head(body(li(_class='things'), div(_id="test"))))
print( º('#test') )
# <div id="test">
print( º('.things') )
# <li class="things">
mydiv = º('<div class="test2"></div>')
# <domonic.dQuery.o object at 0x107d5c9a0>
b = º('#test').append(mydiv)
print(b)
# <div id="test"><div class="test2"></div></div>dQuery is included here because it is useful for proving out the DOM against a demanding consumer API, not just as a convenience wrapper.
There is a command line package that can call bash/unix/posix and other apps on the command line:
This package only works on nix systems as it effectively just passes stuff off to subprocess.
from domonic.terminal import *
print(ls())
print(ls("-al"))
print(ls("../"))
print(pwd())
print(mkdir('somedir'))
print(touch('somefile'))
print(git('status'))
for file in ls( "-al" ):
print("Line : ", file)
for f in ls():
try:
print(f)
print(cat(f))
except Exception as e:
pass
for i, l in enumerate(cat('LICENSE.txt')):
print(i,l)
print(man("ls"))
print(echo('test'))
print(df())
print(du())
for thing in du():
print(thing)
print(find('.'))
# print(ping('eventual.technology'))# < TODO - need to strean output
print(cowsay('moo'))
# print(wget('eventual.technology'))
print(date())
print(cal())or just run arbitrary commands...
from domonic.terminal import command
command.run("echo hi")Take a look at the code in terminal.py to see all the commands as there's loads. (Disclaimer: not all tested.)
Windows users can use now use cmd.
from domonic.cmd import *
print(dir())
print(dir("..\\")) https://un5n7d11d75ttf5zzbwcagk4ym.julianrbryant.com/
Use the command line interface to help you out.
To view the online docs:
domonic -hSee the version:
domonic -vQuickly create a domonic project for prototyping:
domonic -p myprojectEvaluate some domonic pyml:
domonic -e 'html(head(),body(div()))'Use xpath on a website from the command line:
domonic -x https://un5q089ru7t40.julianrbryant.com '//a'Use css selectors on a website from the command line:
domonic -q https://un5q089ru7t40.julianrbryant.com 'a'Use XPath or CSS selectors against a local file:
domonic --xpath-file ./page.html '//title'
domonic --query-file ./page.html 'a.cta'Pipe HTML in directly:
curl -s https://un5mkq9urycyna8.julianrbryant.com | domonic -x '//a' --count
cat page.html | domonic -q 'a.cta' --attr hrefOutput controls:
domonic -q https://un5mkq9urycyna8.julianrbryant.com 'a' --text
domonic -q https://un5mkq9urycyna8.julianrbryant.com 'a' --attr href --first
domonic -x https://un5mkq9urycyna8.julianrbryant.com '//a' --countCreate a project non-interactively with a chosen server:
domonic -p myproject --server fastapiBlueberry : A browser based file OS. Working example of how components can work.
ezcron : A cron viewer
bombdisposer : A basic game
htmlx : A low dependency lightweight (DOM only) version of domonic
Checkout the docs for more examples i.e. generating sitemaps or using domonic with server frameworks like Flask, Django, Sanic, FastAPI and others.
There are also several usage examples in the repo, so pull it and have a look.
. venv/bin/activate
pip install -r requirements-dev.txt
cd examples
python lifecalendar.pyThe tests are useful both as regression coverage and as working code examples.
See Makefile to run all tests:
make test # or testpc on Windows to avoid terminal testsTesting a single function:
python -m unittest tests.test_javascript.TestCase.test_javascript_array
python -m unittest tests.test_dQuery.TestCase.test_addClass
python -m unittest tests.test_geom.TestCase.test_vec2
python3 -m unittest tests.test_cmd.TestCase.test_cmd_dir # only windowsTest a whole module
python -m unittest tests.test_html
python -m unittest tests.test_CDNto see coverage
coverage run -m unittest discover tests/
coverage reportor...
pip install pytest
pytest testsYou can choose a parser per call (see below on parser install notes):
from domonic import domonic
page = domonic.parseString("<p>Hello</p>", parser="html5_parser")
page = domonic.parseString("<p>Hello</p>", parser="html5lib") # bundled with domonic
page = domonic.parseString("<p>Hello</p>", parser="justhtml")
page = domonic.parseString("<p>Hello</p>", parser="markupever") # stupidly fast
page = domonic.parseString("<p>Hello</p>", parser="selectolax")
page = domonic.parseString("<p>Hello</p>", parser="expat")Or set the default parser for your app:
from domonic import domonic
domonic.set_default_parser("html5_parser")
page = domonic.parseString("<p>Hello</p>")Parser install notes (listed in order of speed when used with domonic):
markupever:pip install markupever🚀 Rusthtml5_parser:pip install html5-parser lxml🚀 c++selectolax:pip install selectolax lxml🚀 c++justhtml:pip install justhtml🐢 pythonhtml5lib:pip install html5lib🐢 pythonexpat: 🐌 built into Python. (chokes on html5 or malformed content)
html5lib has a direct treebuilder integration.
markupever also uses an lxml adapter but we can get content w/o pretty formatting
html5_parser and selectolax have to use an lxml adapter as there's no hooks
justhtml uses html5lib adapter-backed parser (parse + conversion == slower).
*html5parser was a hidden feature on older version of domonic
Contributions are welcome! If you'd like to contribute, please follow these steps:
- Fork the repository.
- Create a new branch for your feature or bug fix.
- Write your code and add tests if applicable.
- Submit a pull request with a clear description of your changes.
For more details, see the CONTRIBUTING.md file.