Thursday, July 25, 2013

Domain Specific Markup Language Php

How does a DSML help?

Here’s what I want to write:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<steps>
  <step>
      Put your left foot in
      <tests>
          <test>Left foot is in front of you</test>
          <test>Balanced on right foot</test>
      </tests>
  </step>
  <step>
      Take your left foot out
      <cli>
          student@server $ out /dev/left-foot
      </cli>
      <tests>
          <test>Left foot is behind you</test>
          <test>Balanced on right foot</test>
      </tests>
  </step>
</steps>
This is starting to look like XML, without accidentally becoming XHTML. The joy of my DSML is that I’m writing a language that knows what I mean, not that I want a new layer of quoting rules.

Rewriting Custom Tags into HTML

Let’s start with the easy work, let’s convert the <steps> list and the <step> elements back into <ol> and <li>s. I’m using the QueryPath library. It’s a very similar API to jQuery, and because I do the transform server-side, I can provide well-formed pages to clients without JavaScript (like search engine spiders).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require 'QueryPath/QueryPath.php';

$qp = htmlqp($dsml_file); //The DSML in the previous code block

foreach($qp->find(":root steps > step") as $step){
  $content = $step->innerHTML();
  $step->replaceWith("<li>" . $content . "</li>");
}

foreach($qp->find(":root steps:first") as $elm){
  $content = $elm->innerHTML();
  $elm->replaceWith("<ol id='tests'>" . $content . "</ol>");
}

$qp->writeHTML();
?>

Adding Application Logic and Error Checking

Of course, nobody’s perfect, so let’s add some rules to catch operator error:
1
2
3
4
5
6
7
8
9
<?php
if($qp->find(":root steps")){ //We already translated steps:first into an ol
  warn("You have more than one <steps> collection.");
}

if($qp->find(":root step")){ //We already translated any step that is a direct child of steps
  warn("You have <step> elements outside of the <steps> container.");
}
?>
While I’m writing lessons, my warn() function adds bold red error messages to the top of the parsed document. In production, warn() will quietly log them.

Tags that are Smarter than HTML

Those were simple replacements, you can do that with a regular expression and some duck tape. Let’s make this <cli> tag fix my problems with HTML’s <pre>:
  • I want to be able to indent the content for easier editing.
  • I want the </pre> tag on its own line, without showing the student an empty line at the bottom of the code block.
In other words, I want it to work like this:
1
2
3
4
<li>
  <pre class='cli'>
student@server $ out /dev/left-foot</pre>
</li>
But let me edit it like this:
1
2
3
4
5
<li>
  <cli>
      student@server $ out /dev/left-foot
  </cli>
</li>
Here’s the code that does it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
foreach($qp->find(":root cli") as $elm){
  $content = $elm->innerHTML();

  //Accept Windows or Unixy EOL
  $content_array = preg_split('/(\r\n|\r|\n)/', $content);

  //Get rid of whitespace on left and right.
  $content_array = array_map("trim", $content_array);

  //Get rid of trailing empty lines
  while(end($content_array) == ""){ array_pop($content_array); }

  //Reassemble with uniform EOL
  $content = implode("\r\n", $content_array);
  $elm->replaceWith("<pre class='console'>" . $content . "</pre>");
}
?>

DSML my Users Care About

In lessons, when we introduce new terms, the student can hover over them to get a Bootstrap Popover that loads the definition from our glossary, dynamically. Here’s how that used to look in our code:
1
2
3
Now edit my.cnf:
<pre>
$ <abbr href='/glossary/sudoedit'>sudoedit</abbr> /etc/my.cnf</pre>
Here’s how I want it to look:
1
2
3
4
Now edit my.cnf:
<cli>
  $ <explain>sudoedit</explain> /etc/my.cnf
</cli>
And here’s how we do it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
function term_to_url($title){
  $title = strtolower($title);
  $title = preg_replace('/[ \t\r\n]+/', '-', $title);
  $title = preg_replace('/[^a-z0-9\-_]/', '', $title);
  return $title;
}

foreach($qp->find(":root explain") as $elm){
  $content = $elm->innerHTML();

  $url = '/glossary/' . term_to_url($elm->text());
  if(file_exists('..' . $url)){
      $elm->replaceWith("<abbr href='$url'>" . $content . "</abbr>");
  }else{
      warn("No glossary entry for $url");
      $elm->replaceWith($content);
  }
}
?>
Now we get warnings about terms we haven’t written glossary entries for (and we don’t call attention to them, to avoid embarrassment in front of students). Tagging glossary entries is easier (so we’ll do it more). And we’re free to make dramatic changes to the way we present glossary terms without touching a zillion lesson files. For example:
  • We could slipstream in all the definitions into data attributes instead of fetching via AJAX.
  • We could paste all the definitions as numbered footnotes on the page.
  • We could switch the HTML we emit to the browser to use the <dfn> tag instead of <abbr>.

Now go forth, and HTML no more.

HTML is pretty great, but I wouldn’t want to write in it.
  • A DSML can get you closer to your problem domain, not just in code, but in presentation.
  • A DSML can free you to write content without bogging you down in implementation details.
  • A DSML can even make it easier to develop and update features spread across content.
Original Post:   
http://www.wingtiplabs.com/blog/posts/2013/03/18/domain-specific-markup-language/
https://docs.google.com/file/d/0B5nZNPW48dpFUTI1Mi1qZWhVVkE/edit?usp=sharing 

No comments:

Post a Comment