Zero Markup Language (ZML) Specification Version 0.3

Introduction

Zero Markup Language (abbreviated ZML) is a template language and is designed to be human-friendly, lean and flexible. This specification is both an introduction to the ZML language and the concepts supporting it, and also a complete specification of the information needed to develop applications for processing ZML.

ZML represents hierarchically structured data in conjunction with template logic in an integrated system. Documents are structured by using indentation instead of opening and closing tags. ZML is designed for a platform- and implementation-independent use.

Currently there is only one implementation of the ZML specification. (the python “zml”-module implementing the specification for a zml renderer). We try to encourage the creation of implementations in different programming languages by developing a reliable specification standard.

The specification versions below 1.0 are working drafts. The first release version 1.0 will maintain backward compatibility for all 1.x versions by asserting the validation of a test suite. (a collection of tests for testing the output of an implementation to be the same as the output of previous versions. The maintainers of the different implementations should supply a community driven test-suite to assert the backward-compatibility for versatile test-cases.

Goals

The design goals for ZML are, in decreasing priority:

  1. ZML is easily readable by humans

  2. ZML templates a portable between different implementations and frameworks

  3. ZML is extensible

  4. ZML is a good base for the usage of javascript mvc frameworks in the templating

  5. ZML is easy to implement and use

Syntax

Elements

An element is described by

  • an element name (f.e. ‘h1’, ‘div’, ‘p’) followed by

  • a colon followed by

  • one space character followed by

  • quoted content of the element

Single H1 headline with text

h1: 'One headline'

Result:

<h1>One headline</h1>

Teaser consisting of intro, title headline, bodytext, image and link.

p.intro: 'A teaser intro'
h1: 'One headline'
img src='article.jpg'
p: 'Some article text...'
a href='article1.html': 'more'

Result:

<p class="intro">A teaser intro</p>
<h1>One headline</h1>
<img src="article.jpg">
<p>Some article text...</p>
<a href="article1.html">more</a>

Nesting of structures:

div#sidebar:
  div.teaser:
    p.intro: 'A teaser intro'
    h1: 'One headline'
    img src='article.jpg'
    p: 'Some article text'
    a href='article1.html': 'more'

Result:

<div id="sidebar">
  <div class="teaser">
    <p class="intro">A teaser intro</p>
    <h1>One headline</h1>
    <img src="article.jpg">
    <p>Some article text</p>
    <a href="article1.html">more</a>
  </div>
</div>

Attributes

Attributes are described by:

  • attribute name

optional group of:

  • equal sign [=]

  • attribute value

Attribute value can be quoted strings (single quote) or context variables. Quoted strings may include context properties surrounded by moustaches.

a href='article1.html': 'more'

Result:

<a href="article1.html">more</a>

Empty attributes are described by a single attribute name without the optional group of equal sign and quoted value.

form:
  input type='text' disabled

Result:

<form>
  <input type="text" disabled>

Moustaches

Moustaches describe elements of the template context. The template context is data which is rendered together with the zml template by a renderer into html code.

Render a “title” variable of the template context:

h1: '{title}'

Result:

<h1>Some headline in the title variable<h1>

(context dependent example)


Render a user object of the template context with the properties firstname, lastname and email:

div.card:
  p: '{user.firstname}'
  p: '{user.lastname}'
  p: '{user.email}'

Result:

<div class="card" >
  <p>Richard</p>
  <p>Langly</p>
  <p>ringo@l4ngly.org</p>
</div>

(context dependent example)


Inheriting templates

A template can inherit to another template with the #inherit statement: The template declares a context node for usage in the inheriting template by prepending the star sign:

%inherit 2col

*col1_content:
  div.panel:
    %for user in users:
      h1: 'User'
      div.card:
        %if user.active:
          p: '{user.firstname}'
          p: '{user.lastname}'
          p: '{user.email}'
        %else:
          p: 'The user is not active'

The inheriting template uses the context node by accessing it in the moustache:

html:
  head:
    title: 'ZML'
  body:
    h1: 'zml - zero markup language'
    div.grid:
      div.m66:
        div.left: '{*col1_content}'
      div.m33:
        div.right: 'some sidebar stuff'

Components

You can import components from a separate file:

%import components
html:
  head:
    title: 'ZML'
    %for style in page.stylesheets:
      base-style src=style

    %for script in page.scripts:
      script src=script
  body:
    base-menu items=pages

The %import statement imports the file components.zml and loads its components. The component statement “base-menu” loads a component “menu” from the “base”-namespace. In this example there is a parameter for the compoment named “items”. The value “pages” is a property of the context, which can be supplied in a controller which calls the render function of the template with a template context or by using a data section.


Glyphs

In ZML there are glyphs which are used as a prefix to a descriptor to create special sections:

* Views
+ Models
% Logic
! Translation
# Data
& Metadata
@ Resources
~ Routes
| Slots
< Signal handler
> Signal emitter
" Comment

Namespaces

The file components.zml contains a %namespace statement which set the namespace-alias for the namespace “doonx.org/base” to “base”.

The components are defined with a * star symbol followed by the name of the component. The * (star) symbol is one of the “glyphs”, which are used in ZML to define special sections. (See chapter “Glyphs”)

%namespace base=doonx.org/base

*menu:
  div.menu:
    ul.navitems:
      %for item in items:
        li: '{#item.title}'

*style:
  link rel='stylesheet' type='text/css' href='{src}'

Content wraps for components

You can wrap content in components. The child elements of a component instance can be referenced with the _children descriptor of the template context.

Referencing the child contents of the grid component with the _children descriptor in a components file:

%namespace base=doonx.org/base
*grid6633:
  div.grid:
    div.g66.gl:
      div.gboxleft: '{_children[0]}'
    div.g33.gr:
      div.gboxright: '{_children[1]}'

Usage of the grid component with child contents:

%import components
base-grid6633:
  div.content:
    p: 'left content'
  div.content:
    p: 'right content'

The rendered result:

<div class="grid" >
  <div class="g66 gl" >
    <div class="gboxleft" >
      <div class="content" >
        <p>left content</p>
      </div>
    </div>
  </div>
  <div class="g33 gr" >
    <div class="gboxright" >
      <div class="content" >
        <p>right content</p>
      </div>
    </div>
  </div>
</div>

Text content nodes

You can omit the element name on the left side of the colon to create text content nodes without wrapping them in tags:

p:
  : 'There are some'
  strong: 'simple'
  : 'rules to assign inline semantics to text.'
  : 'This bodytext has some'
  span.highlight:
    em.red data-info='animate': 'important words'
  : 'which are emphasized.'

The rendered result:

<p>
  There are some
  <strong>simple</strong>
  rules to assign inline semantics to text.
  This bodytext has some
  <span class="highlight" >
    <em class="red"  data-info="animate">important words</em>
  </span>
  which are emphasized.
</p>

Inline semantics

You can use the same syntax which you use for nested nodes also inside inline content inside angle brackets. Please note that the syntax is an abbreviated notation compared to html as there is no opening or closing tag, but only one zml-node inside angle brackets.

p: 'View this <strong: simple> rules to assign inline semantics to text.'

You can also use attributes inside the inline semantics:

p: 'View this <strong.green data-info="cite": simple> rules to assign inline semantics to text.'

The rendered result:

<p>View this <strong class="green" data-info="cite">simple</strong> rules to assign inline semantics to text.</p>

Data sections

You can include data inside ZML by declaring context-nodes with a #-prefix. The context nodes can be accessed with dot notation inside moustaches:

%import components
%inherit base

#users:
  -
    firstname: 'Richard'
    lastname: 'Langly'
    email: 'ringo@l4ngly.org'
    active: True
  -
    firstname: 'Melvin'
    lastname: 'Frohike'
    email: 'melvin@frohike1.net'
    active: True
  -
    firstname: 'John Fitzgerald'
    lastname: 'Byers'
    email: 'jfb@byers23.org'
    active: True

#pages:
  -
    title: 'About'
    url: '/about'
  -
    title: 'Services'
    url: '/services'
  -
    title: 'Contact'
    url: '/contact'

#page:
  stylesheets:
    - 'files/css/base.css'
    - 'files/css/content.css'
  scripts:
    - 'files/js/jquery.js'
    - 'files/js/main.js'

#test1:
  test2:
    test3: 4+3

*content:
  %for user in users:
    div.card:
      %if user.active:
        p: '{user.firstname}'
        p: '{user.lastname}'
        p: '{user.email}'
      %else:
        p: 'The user is not active'
  div: '{test1.test2.test3}'

Lists

Lists are defined with the ‘-‘ Glyph.

#nodes:
  - 'first'
  - 'second'
  - 'third'

%for node in nodes:
  div: '{node}'

The rendered result:

<div>first
</div>
<div>second
</div>
<div>third
</div>

Flask

You can find a flask example in the example folder. Rendering of ZML templates inside flask:

from flask import Flask
from flask import request
app = Flask(__name__)
import zml

@app.route("/")
def index():
    return default({})

@app.route("/<path:path>")
def default(path=None):
    gp = request.args.to_dict()
    pp = request.form.to_dict()
    html = zml.render('page.zml', path=path, get_params=gp, post_params=pp)
    return html


if __name__ == "__main__":
    app.run(debug=True)

Routes

Routes are defined by using ~ as a prefix. The routes will be used by a dispatcher and linkto components.

%import components
%inherit base

~routes:
  list: '/blog/posts'
  show: '/blog/post/{id}'
  edit: '/blog/post/{id}/edit'

*content:
  ul:
    li:
      base-linkto action='list': 'List'
    li:
      base-linkto action='show' id=1: 'Details'
    li:
      base-linkto action='edit' id=1: 'Edit'

The rendered result:

<!DOCTYPE html>
<html>
  <head>
    <title>zml</title>
  </head>
  <body>
    <ul>
      <li><a href="/blog/posts">List</a></li>
      <li><a href="/blog/post/1">Details</a></li>
      <li><a href="/blog/post/1/edit">Edit</a></li>
    </ul>
  </body>
</html>

The linkto-component of the base namespace is simply defined:

*linkto:
  a href='{*core-path context=_context}': '{_value}'
    %for child in _children:
      '{child}'

The core-path function is a core function of the zml implementation. It will return an url path by interpolating the parameters into the route path definition.

F.e. the following line will use the linkto-component with the parameters action and id. The component can access the parameters by using the _contextz context property. The linkto-component will forward the _context property as a parameter of the core-path function.

~routes:
  edit: '/blog/post/{id}/edit'

base-linkto action='edit' id=1': 'Some content'

Slots

Slots are defined with a | (pronounced “pipe”) prefix.:

html:
  head:
    title: 'ZML'
  body:
    main:
      |main
    footer:
      |footer

The dispatcher maps the URLs to views, which are defined with a * (star). The mapping is defined in the ~ routes section.

%import components
%inherit base

~main:
  index: '/'
  list: 'blog/posts'
  show: 'blog/post/{id}'
  edit: 'blog/post/{id}/edit'

~footer:
  userfooter: '/'
  devfooter: 'develop/'

*nav:
  ul.mainmenu:
    li:
      base-linkto action='list': 'List'
    li:
      base-linkto action='show' id=1: 'Show item with id 1'
    li:
      base-linkto action='edit' id=1: 'Edit item with id 1'
    li:
      base-linkto action='devfooter' router='footer': 'A developer sub section with a different footer'


*index:
  p: 'index view'
  div: x: '{_request.get.x}'

*list:
  p: 'posts view'

*show:
  p: 'show view'

*edit:
  p: 'edit view'

*userfooter:
  ul.footernav:
    li: 'user footer item 1'
    li: 'user footer item 2'
    li: 'user footer item 3'

*devfooter:
  ul.footernav:
    li: 'dev footer item 1'
    li: 'dev footer item 2'
    li: 'dev footer item 3'

Open http://127.0.0.1:5000/ in your browser. The rendered result depends on the url you enter:

http://localhost:5000/blog/posts shows the posts view.

http://localhost:5000/blog/1 shows the detail view.

http://localhost:5000/blog/post/1/edit shows the edit view.

Views

Views are defined with a * glyph: The dispatcher maps the URLs to views, which are defined with a * (star). The mapping is defined in the ~ routes section. The dispatcher uses the route variables defined with moustaches f.e. {id} in the routes. See ~ section of the main router:

%import components
%inherit base

~main:
  index: '/'
  list: 'blog/posts'
  show: 'blog/post/{id}'
  edit: 'blog/post/{id}/edit'

~footer:
  userfooter: '/'
  devfooter: 'develop/'

*nav:
  ul.mainmenu:
    li:
      base-linkto action='list': 'List'
    li:
      base-linkto action='show' id=1: 'Show item with id 1'
    'li:
      base-linkto action='edit' id=1: 'Edit item with id 1'
    li:
      base-linkto action='devfooter' router='footer': 'A developer sub section with a different footer'


*index:
  p: 'index view'
  div: x: {_request.get.x}

*list:
  p: 'posts view'

*show:
  p: 'show view'

*edit:
  p: 'edit view'

*userfooter:
  ul.footernav:
    li: 'user footer item 1'
    li: 'user footer item 2'
    li: 'user footer item 3'

*devfooter:
  ul.footernav:
    li: 'dev footer item 1'
    li: 'dev footer item 2'
    li: 'dev footer item 3'

Open http://127.0.0.1:5000/ in your browser. The rendered result depends on the url you enter:

http://localhost:5000/blog/posts shows the posts view.

http://localhost:5000/blog/1 shows the detail view.

http://localhost:5000/blog/post/1/edit shows the edit view.

Translations

Add translations with the !-glyph followed by the two-letter code of the language.

%import components
%inherit base

!en:
  labels:
    title: 'Title'
    date: 'Date'
    bodytext: 'Bodytext'
  buttons:
    save: 'Save'
!de:
  labels:
    title: 'Titel'
    date: 'Datum'
    bodytext: 'Haupttext'
  buttons:
    save: 'Speichern'

*content:
  form:
    div.formrow:
      label: !labels.title
      input type='text' name='title'
    div.formrow:
      label: !labels.bodytext
      textarea name='bodytext'
    button type='submit': !buttons.save

Models

Add models to your app by using the +-glyph followed by the model descriptor. The base-form viewhelper expands the model to a form. The different model properties will be rendered with suitable form fields.

%import components
%inherit base

!en:
  labels:
    title: 'Title'
    date: 'Date'
    bodytext: 'Bodytext'
  buttons:
    save: 'Save'
!de:
  labels:
    title: 'Titel'
    date: 'Datum'
    bodytext: 'Haupttext'
  buttons:
    save: 'Speichern'

+post:
  title:
    &label: !labels.title
    &type: 'str'
  date:
    &label: !labels.date
    &type: 'datetime'
  bodytext:
    &label: !labels.bodytext
    &type: 'str'

*content:
  base-form model=+post

RESTful resources

RESTful resource are defined by using @ as a prefix. The resources can be used to load data into data sections.

In the following example the resource named ‘db’ is configured with a wikipedia api hostname. The ZML implementation loads the JSON data from the path /w/api.php?action=query&list=search&format=json&srsearch=rest. The JSON will be converted and is accessible by using the ‘pages’ context variable.

%import components
%inherit base

@db: 'en.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch=rest'

#pages: @db

*content:
  %for p in #pages.query.search:
    p: '{#p.title}'

The rendered result:

<html>
  <head>
    <title>zml</title>
  </head>
  <body>
    <p>Rest
    </p>
    <p>Representational state transfer
    </p>
    <p>ReStructuredText
    </p>
    <p>Rest (music)
    </p>
    <p>Rest in peace
    </p>
    <p>The Rest
    </p>
    <p>Bed rest
    </p>
    <p>Ain't No Rest for the Wicked
    </p>
    <p>Rabbit at Rest
    </p>
    <p>The Rest of the Story
    </p>
  </body>
</html>

Transclusions

External resources are defined using @ as a prefix. The resources can be used to load data into data sections or to reference external models or views.

In the following example the object #richard_langly is transcluded from an external object.

Immutable resources

The resource must be content-addressed and immutable. The current python implementation uses IPFS to retrieve external objects. The objects are stored decentralised and permanent (if pinned). The referencing servers/clients MUST pin the objects to prevent garbage collection. https://ipfs.io/

By using ‘speaking’ descriptors for the resource definition (f.e. @mybestfriend) we get a usable notation of references to external resources.

Content identifiers

CIDs are based on the content’s cryptographic hash. That means:

  • Any difference in content will produce a different CID and

  • The same piece of content added to two different IPFS nodes using the same settings will produce exactly the same CID.

By using content identifiers instead of URLs, we enforce linking between immutable objects, so the references can never be broken.

https://docs.ipfs.io/guides/concepts/cid/

@mybestfriend: 'QmPpZzmzgbpqVyqhp8qDWgDPsK4G22xUYrsNpQ5vnKtTYk'

#person:
  first_name: 'Melvin'
  last_name: 'Frohike'
  profession: 'Developer'
  friend: @mybestfriend#richard_langly

The ZML implementation loads the external object from http://localhost:8080/ipfs/QmPpZzmzgbpqVyqhp8qDWgDPsK4G22xUYrsNpQ5vnKtTYk

The IPFS object contains the ZML code for the object definition:

#richard_langly:
  first_name: 'Richard'
  last_name: 'Langly'
  profession: 'Developer'

The object is transcluded into the property ‘friend’ of the object #person.

Transclusion of external defined models

In the following example the model ‘person’ is transcluded from an external document:

&id: 'neo.codes/persons/richard_langly'
&version: '1.0'
&description: 'Richard Langly'
@zmlperson: 'QmZ6s5H3zB8hJyCynKMn7WS3RQ17udZHsFbofcB3aUQBpo'
@zmladdress: 'QmTCSMqzWZ6MvYekWpEFm7Yf9HLbDWB99vJ48xieauEE7J'
&type: @zmlperson+person

#first_name: 'Richard'
#last_name: 'Langly'
#email: 'ringo@l4ngly.org'
#profession: 'Developer'
#contact:
  &type: @zmladdress+address
  first_name: #first_name
  last_name: #last_name
  email: #email
#billing_address: #contact

By using external defined models we build up an ubiquitous language of object types.

Canonicalization

Cryptographic operations like hashing and signing depend on that the target data does not change during serialization. With ZML we have no need to canonicalize the data in order to yield a consistent form, because the ZML standard cares for a canonical serialization of data objects.

In order to support consistent hashing of ZML documents for persistence into distributed hash tables (f.e. the IPFS merkledag) the canonical form allows unique content ids (CID standard).

Multiline contents

Multiline contents can be created by opening the multiline statement with a single quotation mark:

#ciphertext: '
----BEGIN PGP MESSAGE-----

jA0EBwMCKFOWDIApgLLx0o8BOb85gzkxIdVAE3tSIX9R/3yXthBUd5QPemx1Lfiz
pHpjmG/DOKJ1aN9ZwqzksAlgqLTf8UPRG9Ch/MPZoy9Q1R5KJv6QKlMPbn5XHqqo
NW5jSV5g2bX5pcl1FUqbCI9yfyDCw98Rxap01qWXxmlkD7uTp5tL2CFmg3SlDVKb
hAX8YpCjSYNDKlXL56O6rg==
=0C/y
-----END PGP MESSAGE-----
'

This will lead to the following local context:

{
'ciphertext': 'jA0EBwMCKFOWDIApgLLx0o8BOb85gzkxIdVAE3tSIX9R/3yXthBUd5QPemx1Lfiz\n    pHpjmG/DOKJ1aN9ZwqzksAlgqLTf8UPRG9Ch/MPZoy9Q1R5KJv6QKlMPbn5XHqqo\n    NW5jSV5g2bX5pcl1FUqbCI9yfyDCw98Rxap01qWXxmlkD7uTp5tL2CFmg3SlDVKb\n    hAX8YpCjSYNDKlXL56O6rg==\n    =0C/y\n'
}