Zero Markup Language (ZML) Specification Version 0.1

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
  • 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 depended 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 depended 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
    {content}

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:

* Context Property
$ Data Section
& Resource
~ Route
! Translation
+ Model
% Logic
# Parser instructions

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.ym-grid:
    div.ym-g66.ym-gl:
      div.ym-gbox-left:
        {_children[0]}
    div.ym-g33.ym-gr:
      div.ym-gbox-right:
        {_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="ym-grid" >
  <div class="ym-g66 ym-gl" >
    <div class="ym-gbox-left" >
      <div class="content" >
        <p>left content</p>
      </div>
    </div>
  </div>
  <div class="ym-g33 ym-gr" >
    <div class="ym-gbox-right" >
      <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}

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, getparams=gp, postparams=pp)
    return html


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

Nominatim

This example shows the usage of the REST API of Nominatim. The API keys are imported from a separate file. You have to rename the file “keys.zml.default” to “keys.zml” and change the API key. Get your API key from https://developer.mapquest.com/plan_purchase/free/business_edition/business_edition_free

#import keys
#import components
#inherit base

&db:
  host: 'open.mapquestapi.com'

$places: @db/nominatim/v1/search?format=json&q=Cologne&key={appkey}

*content:
  %for p in places:
    p: {p.display_name}

The rendered result showing the nominatim results for the term ‘Cologne’:

<!DOCTYPE html>
<html>
  <head>
    <title>zml</title>
  </head>
  <body>
      <p>Köln, Regierungsbezirk Köln, Nordrhein-Westfalen, 50667-51149, Deutschland</p>
  <p>Köln, Regierungsbezirk Köln, Nordrhein-Westfalen, Deutschland</p>
  <p>Cologne, BS, LOM, Italia</p>
  <p>Cologne, BS, LOM, Italia</p>
  <p>Cologne, Auch, Gers, Midi-Pyrénées, France métropolitaine, 32430, France</p>
  <p>La Cologne, Tincourt-Boucly, Péronne, Somme, Picardie, France métropolitaine, 80240, France</p>
  <p>La Cologne, Doingt, Péronne, Somme, Picardie, France métropolitaine, 80200, France</p>
  <p>La Cologne, Péronne, Somme, Picardie, France métropolitaine, France</p>
  <p>La Cologne, Cartigny, Péronne, Somme, Picardie, France métropolitaine, 80200, France</p>
  <p>La Cologne, Péronne, Somme, Picardie, France métropolitaine, France</p>
  </body>
</html>

Wikipedia

There is a wikipedia rest example in the examples folder:

#import components
#inherit base

&db:
  host: 'en.wikipedia.org'

$pages: @db/w/api.php?action=query&list=search&format=json&srsearch=rest

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

The rendered result:

<!DOCTYPE html>
<html>
  <head>
    <title>zml</title>
  </head>
  <body>
      <p>Representational state transfer</p>
  <p>Rest</p>
  <p>ReStructuredText</p>
  <p>Rest (music)</p>
  <p>Shady Rest, California</p>
  <p>Travelers Rest</p>
  <p>Rest area</p>
  <p>Pilgrim&#39;s Rest, Arkansas</p>
  <p>Note value</p>
  <p>Pilgrim&#39;s Rest</p>
  </body>
</html>

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:

#namespace base=doonx.org/base

*linkto:
  a href=_path(action, _params):
    {_children[0]}

The _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. The routevars wrapped by moustaches in the route-section (~) with the corresponding action name will be replaced by the values of the second function parameter of the _path function.

The _params context property contains all parameters of a component. 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 _params context property. The linkto-component will forward the _params property as a parameter of the _path function.

base-linkto action='edit' id=1

Slots

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

html:
  head:
    title: zml
  body:
    nav: {nav}
    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 of id {id}'

*edit:
  p: 'edit view of id {id}'

*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 of id {id}'

*edit:
  p: 'edit view of id {id}'

*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.

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:
  host: 'en.wikipedia.org'

$pages: @db/w/api.php?action=query&list=search&format=json&srsearch=rest

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

The rendered result showing the wikipedia pages for the term ‘rest’:

<!DOCTYPE html>
<html>
  <head>
    <title>zml</title>
  </head>
  <body>
    <p>Representational state transfer</p>
    <p>Rest</p>
    <p>ReStructuredText</p>
    <p>Rest (music)</p>
    <p>Shady Rest, California</p>
    <p>Rest area</p>
    <p>Travelers Rest</p>
    <p>Pilgrim&#39;s Rest, Arkansas</p>
    <p>Note value</p>
    <p>Pilgrim&#39;s Rest</p>
  </body>
</html>

In the following example the resource named ‘db’ is configured with hostname, port, username and password. The ZML implementation loads the JSON data from the path http://localhost:5984/blog/_design/app/_view/posts. The JSON will be converted and is accessible by using the ‘posts’ context variable.

&db:
  host: 'localhost'
  port: 5984
  username: 'zml'
  password: 'secret'

$posts: @db/blog/_design/app/_view/posts


*content:
  %for post in posts.rows:
    p: {post.value.title}

The rendered result f.e. loading data for 3 blog posts from a couchdb:

<!DOCTYPE html>
<html>
  <head>
    <title>zml</title>
  </head>
  <body>
    <p>First blog post title</p>
    <p>Second post</p>
    <p>Third post</p>
  </body>
</html>

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'