YML – Why a Markup Language?!

YML 2.7.6 of Thu 25 May 2023 – Copyleft 2007-2023, Volker BirkDownload YML 2

YSLT – an introduction

Especially the XSLT programmer can benefit from YML. Usually many attributes of XSLT are annoying in programming praxis:

In short, it's ugly ;-)

Usually the result is, that many programmers are avoiding XSLT as a programming language. It's a pity, because for processing XML data, it can do much more than “formatting” as “stylesheets”.

The idea of YSLT now is to supply a simple language, which programmers want to use, to make the features of XSLT accessible to a broader base of people.

YSLT can be used much simpler; it's just a Y Language for XSLT. I'm using it for my blog, here you can download YBlog2, a simple blogging software in YSLT.

Here you can find the YSLT specification.

Hello, World

In YSLT, the hello world program reads like this:

include yslt.yml2
textstylesheet template "/" | hello, world

The include line includes the YSLT Y Language declarations. The second line generates an XSLT hello world program. You can generate it using:

% yml2c -o hello.xsl hello.ysl2

This results in the following program:

<xsl:stylesheet xmlns:func="http://exslt.org/functions"
xmlns:dyn="http://exslt.org/dynamic" xmlns:str="http://exslt.org/strings"
xmlns:math="http://exslt.org/math"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
extension-element-prefixes="exsl func str dyn set math"
xmlns:set="http://exslt.org/sets" version="1.0"
xmlns:exsl="http://exslt.org/common"><xsl:output method="text"/>
<xsl:variable name="space" select="'                                 '"/>
<xsl:param name="autoindent" select="4"/><xsl:template match="/">
<xsl:param name="_indent" select="0"/><xsl:value-of
select="substring($space, 1, $_indent+0*$autoindent)"/>hello, world
</xsl:template></xsl:stylesheet>

You can execute this program with any XSL processor, for example the Free Software xsltproc.

% yml2c -o hello.xsl hello.ysl2
% echo '<empty/>' > empty.xml          
% xsltproc hello.xsl empty.xml 
hello, world
% _

Or you can just use yml2proc:

% yml2proc -My hello.ysl2
hello, world
% _

Programming in YSLT

Because YSLT is just a Y Language for XSLT, you can do anything with YSLT you can do with XSLT in just nicer syntax ;-) To read what XSLT is all about, I recommend the official W3C documentation for XSLT.

So this document is just an beginners guide, if you want to understand XSLT completely, better read the W3C stuff.

Programming YSLT means programming with a pure functional language.

Lovers of Lisp or Haskell will not see any problems. But many programmers are used to have a programming language with a procedural imperative paradigma like Java, C/C++ or Python. Why should they use a functional language?

Actually, if a functional language is practical or not, depends of what's to do – “the right tool for the task”, one could say.

Because processing XML data means traversing a (document) tree, recursive algorithms are a clear advantage. And this is the reason, why choosing a functional language is a good choice for that job.

It's a little bit like with SQL – for it's job, it's excellent, but no-one wants to write a complete application in SQL (and also not in one of the Turing-complete SQL extension languages like PL/SQL).

Generating HTML out of a DSL

Of course, you can use YSLT as you would use XSLT. Let's say, you have data in XML documents, and you want to have an excerpt out of this data. This is a common task, comparable to SELECT data out of an SQL database. But we have no database, we have something like this file customers.yml2:

decl customer(*id, *name) { id *id, name *name };

list {
    customer 23, "Kurt Meier";
    customer 42, "Lieschen Schmidt";
}

Let's say, we want to output this into an XHTML document, where the list is showed as a table. Then we would do the following:

The XHTML document should be like the following and include the list in the body:

include yslt.yml2

stylesheet {
    template "/" html {
        head title "Customer List";
        body apply "list";
    }

In the example above, stylesheet declares the main program. Then we define a template, which is executed automatically starting reading the root '/' of the document tree of the XML document we're processing.

Using the XML document as input, we're creating this HTML tree:

<html>
    <head>
        <title>Customer List</title>
    </head>
    <body>

    <!-- ... ->

    </body>
</html>

How do we create the list? Well, let's use a table with customers:

    template "list" table apply "customer";

What to do per customer? Well, generate a table row with two columns, one for id and one for name:

    template "customer" tr {
        td value "id";
        td value "name";
    }
}

That was it. We now can run our small program:

% yml2proc -y customer.ysl2 -x customer.xml -o customer.html -P

The result looks like this:

% cat customer.html 
<?xml version="1.0"?>
<html>
  <head>
    <title>Customer List</title>
  </head>
  <body>
    <table>
      <tr>
        <td>23</td>
        <td>Kurt Meier</td>
      </tr>
      <tr>
        <td>42</td>
        <td>Lieschen Schmidt</td>
      </tr>
    </table>
  </body>
</html>
% _

Here you can download the complete program.

How to generate code with YSLT

Generating code is easy with YSLT, if you follow these steps:

  1. design the software you want to generate using patterns and write that in a DSL
  2. write target code once for each pattern
  3. deconstruct a compiler for the target code for each pattern
  4. fine tune until the diff is empty

Let's test by example. First let's say, we have a pattern of entities we want to implement as Java beans. The enities in our DSL are:

decl entity +name;
decl attr +type +name;
decl aggregates +entity;

structure {
    entity Customer {
        attr String name;
        attr int creditLimit;
        aggregates Order;
    }

    entity Order {
        attr String no;
        attr String description;
        attr int amount;
    }
}

How to write that in a Java Program? Well, following the second step, we're writing our target code manually; for this simple sample, let's be naive and save the following into a file named Customer.java.target:

import java.util.Vector;
import java.util.Collections;
import base.Entity;

class Customer extends Entity {
    Customer {
        id = genId();
    }

    // attribute name
    
    private String name;
    public String getName() {
        return name;   
    }
    public void setName(String value) {
        name = value;
    }

    // attribute creditLimit

    private int creditLimit;
    public int getCreditLimit() {
        return creditLimit;   
    }
    public void setCreditLimit(int value) {
        creditLimit = value;
    }

    // Order aggregation

    protected Vector orderList = new Vector();
    void addOrder(Order entity) {
        orderList.add(entity);
    }
    void removeOrder(Order entity) {
        orderList.remove(entity);
    }
    Iterator orderIterator() {
        return orderList.iterator();
    }
}

The third step does most of the work. First we cite this code into gen_entity.ysl2 and create the basic form of an YSLT script:

include yslt.yml2

tstylesheet {
    template "/" {
        | import java.util.Vector;
        | import java.util.Collections;
        | import base.Entity;
        | 
        | class Customer extends Entity {
        |     Customer {
        |         id = genId();
        |     }
        | 
        |     // attribute name
        |     
        |     private String name;
        |     public String getName() {
        |         return name;   
        |     }
        |     public void setName(String value) {
        |         name = value;
        |     }
        | 
        |     // attribute creditLimit
        | 
        |     private int creditLimit;
        |     public int getCreditLimit() {
        |         return creditLimit;   
        |     }
        |     public void setCreditLimit(int value) {
        |         creditLimit = value;
        |     }
        | 
        |     // Order aggregation
        | 
        |     protected Vector orderList = new Vector();
        |     void addOrder(Order entity) {
        |         orderList.add(entity);
        |     }
        |     void removeOrder(Order entity) {
        |         orderList.remove(entity);
        |     }
        |     Iterator orderIterator() {
        |         return orderList.iterator();
        |     }
        | }
    }
}

Now for the deconstruction. I think, it'll be best, if we create a .java file for each entity. So we're moving the whole thing into a template for each entity creating a file, and we're applying this for each entity using the name of each Entity as parameter. We're adding some distinction of cases, too.

When we apply, the indention system of YSLT will add an indention level, so we can take out rendundant whitespace; for the first apply we don't want this, so we're giving the number 0 as the indention level.

In attributes, braces {…} let us insert the value of an XPath expression into our DSL, while inside quoted text the same is done by the angle double quotes «…»:

include yslt.yml2

tstylesheet {
    template "/structure" apply "Entity", 0;

    template "Entity" document "{@name}.java" {
        if "aggregates" {
            | import java.util.Vector;
            | import java.util.Collections;
        }

        | import base.Entity;
        | 
        | class «@name» extends Entity {
        |     «@name» {
        |         id = genId();
        |     }
        | 
[...]
        |         orderList.remove(entity);
        |     }
        |     Iterator orderIterator() {
        |         return orderList.iterator();
        |     }
        | }
    }
}

Well, not bad. Now for the pattern of an attribute and an aggregation, respectively.

include yslt.yml2

tstylesheet {
    template "/structure" apply "Entity";

    template "Entity" document "{@name}.java" {
        if "aggregates" {
            | import java.util.Vector;
            | import java.util.Collections;
        }

        | import base.Entity;
        | 
        | class «@name» extends Entity {
        |     «@name» {
        |         id = genId();
        |     }
        | 

        apply "attr|aggregates";

        | }
    }

    template "attr" {
        |
        | // attribute «@name»
        |
        | private «@type» «@name»
        | public «@type» get«@name»() {
        |     return «@name»
        | }
        | public void set«@name»(«@type» value) {
        |     «@name» = value;
        | }
    }
    
    template "aggregates" {
        |
        | // «@entity» aggregation
        | 
        | protected Vector «@entity»List = new Vector();
        | void add«@entity»(«@entity» entity) {
        |     «@entity»List.add(entity);
        | }
        | void remove«@entity»(«@entity» entity) {
        |     «@entity»List.remove(entity);
        | }
        | Iterator «@entity»Iterator() {
        |     return «@entity»List.iterator();
        | }
    }
}

As you can see, we're deconstructing step by step. This is a good idea to get into code generation with YSLT, but it remains a good idea even for the advanced programmer: it keeps a clear view on what's happening.

In the last step, test it out and make a diff to your target code. You will see that our example needs some beautifying: in Java, camel case is important and makes you some work to revert characters to uppercase or lowercase, respectively. For that work you'll see that XPath's translate() function is a little bit ugly ;-) So we're defining an operator for that at the top of the file:

define operator "“(.*?)”" as call "ucase" with "text", "%1";

Inside the template, we're defining the ucase function:

    function "ucase" {
        param "text";

        value "translate(substring($text,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')";
        value "substring($text, 2)";
    }
}

Now we can replace one quoting with another; have a look at the getter and setter methods:

include yslt.yml2

define operator "“(.*?)”" as call "ucase" with "text", "%1";

tstylesheet {
    template "/structure" apply "Entity";

[...]

    template "attr" {
        |
        | // attribute «@name»
        |
        | private «@type» «@name»
        | public «@type» get“@name”() {
        |     return «@name»
        | }
        | public void set“@name”(«@type» value) {
        |     «@name» = value;
        | }
    }

[...]

    function "ucase" {
        param "text";

        value "translate(substring($text,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')";
        value "substring($text, 2)";
    }
}

Well, the rest is a pure laborious task ;-) Feel free to complete. And: use diff!

A more advanced example: generating SQL DDL out of an UML diagram

Well, now for something real ;-) This is a very common task: somebody models with an UML class diagram, and you want to have SQL DDL, which generates a matching database structure.

Let's go:

First, lets use a stylesheet, which declares the needed XMI namespaces:

include yslt.yml2

tstylesheet xmlns:uml="http://schema.omg.org/spec/UML/2.1",
            xmlns:xmi="http://schema.omg.org/spec/XMI/2.1" {

Now, search the Model for all content:

   template "/" apply "xmi:XMI/uml:Model/packagedElement", 0;

We're translating UML Packages into underscore separated prefixes:

    template "packagedElement[@xmi:type='uml:Package']" {
        param "name", "''";
        if "$name=''"  apply "packagedElement", 0 { with "name", "@name"; }
        if "$name!=''" apply "packagedElement", 0 { with "name", "concat($name, '_', @name)"; }
    }

Each Class is represented by a table in the database:

    template "packagedElement[@xmi:type='uml:Class']" {
        param "name";

        | CREATE TABLE $name_@name (
        apply "ownedAttribute";
        | );
    }

Finally, for each different data type for an attribute we're outputting different fields with different types:

    template "ownedAttribute[@xmi:type='uml:Property' and type/@xmi:type='uml:PrimitiveType']" {
        0> @name
        choose {
            when "type/@href='http://schema.omg.org/spec/UML/2.1/uml.xml#String'"
                >  VARCHAR

            // [...] for other types, extend when clauses
        }
        if "position()!=last()" > ,
        text "\n";
    }
}

Our little sample only supports VARCHAR, but it is an easy game to play to complete that.

Here you can download the XMI 2 DDL compiler sample. I used BOUML to create a small UML sample file as XMI 2.1.

To compile that, use:

% yml2proc -y xmi2ddl.ysl2 -x demo.xmi 
CREATE TABLE demo_Customer (
    id VARCHAR,
    name VARCHAR
);
% _

In the samples directory you'll find a prettier solution using some declares to prettify.

Features of YSLT

Because YSLT just generates XSLT programs, it will be a good idea to read the XSLT Documentation as well as the XPath Documentation.

In the following, you find a List of YSLT Functions and a List of YSLT Operators.

List of YSLT Functions

apply(select, *indent=1)

Generates the <xsl:apply-templates /> tag. The *indent pointer gives the number of indention levels to add (default: 1) for the Indention System.

Example:

apply "attr|aggregation", mode=define;

assert(test, msg)

Generates <xsl:value-of select="yml:assert(test, msg)". See the yml:assert() XPath extension. This function does not generate anything when not being called by ysltproc with the --debug switch.

attrib(name, namespace)

Generates the <xsl:attribute /> tag.

call(name)

Generates the <call-template /> tag. Used to call a function().

Example:

call "ucase" with "text", "$name";

choose()

Generates the <xsl:choose /> tag. Use in a choose() ... when()... otherwise()... structure.

Example:

choose {
    when "$id=1"
        > yes
    when "$id=2"
        > no
    otherwise
        error "invalid id";
}

comment()

Generates the <xsl:comment /> tag.

Example:

comment "this comment will remain in XML";

const(name, select)

Generates the <xsl:variable /> tag.

Example:

const "pi", 3.14;

copy(select)

Generates the <xsl:copy-of /> tag.

Example:

copy ".";

debug(msg)

Generates <xsl:value-of select="yml:debug(msg)". See the yml:debug() XPath extension. This function does not generate anything when not being called by ysltproc with the --debug switch.

def(name)

Generates the EXSLT <func:funcion /> tag.

document(href, method)

Generates the EXSLT <exsl:document /> tag.

Example:

template "entity" document "{@name}.java" {
    […]
}

element(name, namespace)

Generates the <xsl:element /> tag.

error()

Generates the <xsl:message /> tag with attribute terminate set to "yes".

estylesheet(*output="xml")

Does the same as stylesheet(), but additionally declares the EXSLT functions of the groups exsl, func, str, dyn, set and math and declares the corresponding name spaces.

for(select)

Generates the <xsl:for-each /> tag.

Example:

for "../structure[@name='hello'" > @type

foreach(select)

Same as for().

function(name)

Generates the <xsl:template /> tag. Used by calling with call().

Example:

function "ucase" {
    param "text";

    value "translate(substring($text,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')";
    value "substring($text, 2)";
}

if(test)

Generates the <xsl:if /> tag.

Example:

if "position()<last()" > , 

import(href)

Generates the <xsl:import /> tag.

key(name, match, use)

Generates the <xsl:key /> tag.

message()

Generates the <xsl:message /> tag.

otherwise()

Generates the <xsl:otherwise /> tag. Use in a choose() ... when()... otherwise()... structure.

output(method)

Generates the <xsl:output /> tag.

param(name, select)

Generates the <xsl:param /> tag.

Example:

param "x", 42;

processing(name)

Generates the <xsl:processing-instruction /> tag.

raw()

Generates the <xsl:text /> tag. Sets the attribute disable-output-escaping to "yes".

result(select)

Generates the EXSLT <func:result /> tag.

stylesheet(*output="xml")

Generates the XSLT <stylesheet /> tag. Additionally generates an <output /> tag in the body, with attribute method set to the value of the pointer *output (default: "xml").

The content you're giving is placed in the body after the <output /> tag.

The version attribute is set to "1.0" and XML namespace xsl is correctly defined.

In short: use for a stylesheet, just give the output type as parameter, if you don't want to to generate XML but HTML ("html") oder plain text ("text").

stylesheet() additionally generates tags for the Indention System.

template(match)

Generates the <xsl:template /> tag. Additionally generates tags for the Indention System.

Example:

template "attr", mode=declare
    | attribute «@type» «@name»

text()

Generate the <xsl:text /> tag.

textstylesheet()

Same as estylesheet(), but *output is now "text", that means the stylesheet outputs plain text.

tstylesheet()

Same as textstylesheet().

value(select)

Generates the <xsl:value-of /> tag.

Example:

value "@name";

warning()

Generates the <xsl:message /> tag with attribute terminate set to "no".

when()

Generates the <xsl:when /> tag. Use in a choose() ... when()... otherwise()... structure.

with(name, select)

Generates the <xsl:with-param /> tag.

Example:

call "ucase" with "text", "$name";

List of YSLT Text Operators

Operator «…»

Generate YSLT Function Call value('…').

Debugging Functions

YML defines two functions in namespace http://fdik.org/yml, which are enabled if the command line option --debug is given in yml2proc.

yml:assert(test, msg)

If XPath expression test evaluates to false() or to an empty set, XPath expression msg is printed to stderr; the compilation then aborts with an error.

Better don't use it directly, use the assert() YSLT function.

yml:debug(msg)

Prints XPath expression msg to stderr.

Better don't use it directly, use the debug() YSLT function.

Standard Function Library

Additionally, you can include standardlib.ysl2 in the body of your stylesheet. Then you'll have these extra functions:

yml:dec2hex(dec, digits=8)

Converts number dec into a string with a hexadecimal representation filled up to digits digits. If you're omitting the second parameter, it is set to 8.

yml:hex2dec(hex)

Converts the string hex consisting of hexadecimal digits into a number.

yml:lcase(text)

Converts all uppercase letters of string text into lowercase ones.

yml:ucase(text)

Converts all lowercase letters of string text into uppercase ones.

<< back to YML Features ^Top^ > > explain the Tool Chain (source)