==================================================== 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: #. ZML is easily readable by humans #. ZML templates a portable between different implementations and frameworks #. ZML is extensible #. ZML is a good base for the usage of javascript mvc frameworks in the templating #. 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: ::

One headline

"""" **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: ::

A teaser intro

One headline

Some article text...

more """" **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: :: """" 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: :: more **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: ::
"""" 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: ::

Some headline in the title variable

(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: ::

Richard

Langly

ringo@l4ngly.org

(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 {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: :: * 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.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: ::

left content

right content

"""" 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: ::

There are some simple rules to assign inline semantics to text. This bodytext has some important words which are emphasized.

"""" 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 rules to assign inline semantics to text. You can also use attributes inside the inline semantics: :: p: View this rules to assign inline semantics to text. The rendered result: ::

View this simple rules to assign inline semantics to text.

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("/") 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': :: zml

Köln, Regierungsbezirk Köln, Nordrhein-Westfalen, 50667-51149, Deutschland

Köln, Regierungsbezirk Köln, Nordrhein-Westfalen, Deutschland

Cologne, BS, LOM, Italia

Cologne, BS, LOM, Italia

Cologne, Auch, Gers, Midi-Pyrénées, France métropolitaine, 32430, France

La Cologne, Tincourt-Boucly, Péronne, Somme, Picardie, France métropolitaine, 80240, France

La Cologne, Doingt, Péronne, Somme, Picardie, France métropolitaine, 80200, France

La Cologne, Péronne, Somme, Picardie, France métropolitaine, France

La Cologne, Cartigny, Péronne, Somme, Picardie, France métropolitaine, 80200, France

La Cologne, Péronne, Somme, Picardie, France métropolitaine, France

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: :: zml

Representational state transfer

Rest

ReStructuredText

Rest (music)

Shady Rest, California

Travelers Rest

Rest area

Pilgrim's Rest, Arkansas

Note value

Pilgrim's Rest

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: :: zml 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': :: zml

Representational state transfer

Rest

ReStructuredText

Rest (music)

Shady Rest, California

Rest area

Travelers Rest

Pilgrim's Rest, Arkansas

Note value

Pilgrim's Rest

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: :: zml

First blog post title

Second post

Third post

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'