==================================================== Zero Markup Language (ZML) Specification Version 0.2 ==================================================== 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) 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. 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: :: zml

Rest

Representational state transfer

ReStructuredText

Rest (music)

Rest in peace

The Rest

Bed rest

Ain't No Rest for the Wicked

Rabbit at Rest

The Rest of the Story

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' }