YML – Why a Markup Language?!
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.
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
% _
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.
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).
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.
Generating code is easy with YSLT, if you follow these steps:
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!
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.
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.
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;
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.
Generates the <xsl:attribute />
tag.
Generates the <call-template />
tag. Used to call a function()
.
Example:
call "ucase" with "text", "$name";
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";
}
Generates the <xsl:comment />
tag.
Example:
comment "this comment will remain in XML";
Generates the <xsl:variable />
tag.
Example:
const "pi", 3.14;
Generates the <xsl:copy-of />
tag.
Example:
copy ".";
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.
Generates the EXSLT <func:funcion />
tag.
Generates the EXSLT <exsl:document />
tag.
Example:
template "entity" document "{@name}.java" {
[…]
}
Generates the <xsl:element />
tag.
Generates the <xsl:message />
tag with attribute terminate
set to "yes".
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.
Generates the <xsl:for-each />
tag.
Example:
for "../structure[@name='hello'" > @type
Same as for()
.
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)";
}
Generates the <xsl:if />
tag.
Example:
if "position()<last()" > ,
Generates the <xsl:import />
tag.
Generates the <xsl:key />
tag.
Generates the <xsl:message />
tag.
Generates the <xsl:otherwise />
tag. Use in a choose() ... when()... otherwise()...
structure.
Generates the <xsl:output />
tag.
Generates the <xsl:param />
tag.
Example:
param "x", 42;
Generates the <xsl:processing-instruction />
tag.
Generates the <xsl:text />
tag. Sets the attribute disable-output-escaping
to "yes".
Generates the EXSLT <func:result />
tag.
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.
Generates the <xsl:template />
tag. Additionally generates tags for the Indention System.
Example:
template "attr", mode=declare
| attribute «@type» «@name»
Generate the <xsl:text />
tag.
Same as estylesheet()
, but *output
is now "text", that means the stylesheet outputs plain text.
Same as textstylesheet()
.
Generates the <xsl:value-of />
tag.
Example:
value "@name";
Generates the <xsl:message />
tag with attribute terminate
set to "no".
Generates the <xsl:when />
tag. Use in a choose() ... when()... otherwise()...
structure.
Generates the <xsl:with-param />
tag.
Example:
call "ucase" with "text", "$name";
«…»
Generate YSLT Function Call value('…')
.
YML defines two functions in namespace http://fdik.org/yml, which are enabled if the command line option --debug is given in yml2proc.
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.
Prints XPath expression msg
to stderr.
Better don't use it directly, use the debug() YSLT function.
Additionally, you can include standardlib.ysl2
in the body of your stylesheet. Then you'll have these extra functions:
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.
Converts the string hex
consisting of hexadecimal digits into a number.
Converts all uppercase letters of string text
into lowercase ones.
Converts all lowercase letters of string text
into uppercase ones.