Managing XML Files With Sublime Text 2

Comments

The challenge this week: to get started with creating the simplest CMS for recipes. This week’s challenge includes setting things in motion using simple ST2 templates.

About the challenge

This is part of an ongoing project, a simple recipe manager. I already have a bunch of recipes in various formats which I want to consolidate, manage, and print using an existing HTML template I am happy with. To start things off I will use Sublime Text 2’s templating and build tools to edit files quickly.

Some artefacts are available on GitHub.

Data storage

XML is pretty much the most flexible data format I can think of. It can be XSL-transformed easily into other formats, including HTML. And it can be viewed directly in a web browser as well. In theory I could apply a CSS directly to it, but I already have HTML / CSS I am happy with, so I’d rather transform the XML to that.

The document structure will be very simple, a bunch of tags (title, descriptions, source, etc) only a few of which have another level of subtags of their own - ingredients, directions, tags, and cuisine. A simple pseudo-YAML representation would be (* meaning ‘zero or more of this node’, and ? meaning ‘optional node’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
recipe
  title
  description
  source
  cuisine
    style
    region
    approach
  tags
    tag*
  directions
    step*
  ingredients
    serves
    group?
      ingredient*
        quantity
        measurement
        name
        preparation

Using Sublime Text 2 to manage XML files

1
2
3
4
Feature:
    In order to create and view recipes quickly and with little fuss
    As a geeky recipe author
    I need a system of templates in place

To hit the ground running and create some XML files quickly before the app is ready, I will use Sublime Text 2 for managing files.

Using Sublime Text 2 File templates and snippets

1
2
3
4
5
6
7
8
9
10
Scenario: Using Sublime Text 2 templates and snippets
    Given that I have ST2
    And the File Templates package is installed
    When I hit CMD-SHIFT-P
    And select 'Create File from templates' from the list
    And select 'User: recipe.xml' from the next list
    And enter the name of the recipe in the 'Name' field
    Then an XML document will be saved to the recipes folder
    And it will be open in ST2 for editing
    And I can tab through the fields quickly

The File Templates package extends the built in Snippet system to file creations. This scenario is about setting it up and using it.

  • I created my Sublime project
  • I installed the File Templates package using ST2 Package Control
  • Created my template in Packages / User. Sadly, templates are global, they can’t be project specific. However, the template can point to a file which can sit somewhere within the currently opened project if needed, which is handy. In my case I just hardcoded the complete path as I will never use those templates for anyhing else.
  • Another handy thing with the templates is that you can set up some variables, and when you create a new file ST2 will show field names for each of them. I am not entirely sure how more useful than tab stops this feature is in this particular case, but worth trying it out anyway.
  • My template is below. The actual template is in a file called recipe.xml in my project. The arguments node lists all the variables I will be asked about when creating a new file. One of them, $name, will be used to save the file as.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<!-- the file with the actual template -->
<file>/PATH/recipe.xml</file>
<!-- what the results will be called. $name is the argument further down -->
<filename>$name.xml</filename>
<!-- where in the projects files will be saved -->
<path>/PATH/recipes</path>
<arguments>
  <name>Recipe name:</name>
  <description>Description:</description>
  <soource>Source</source>
  <serves>Serves:</serves>
</arguments>
</template>
  • The XML template itself is pretty simple. It includes a link to an xsl document for transforming, tabstops, and a few step nodes. The ingredient nodes, being slightly more complex, will sit in a snippet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<recipe lang="en-uk">
    <title><![CDATA[$name]]></title>
    <description><![CDATA[$description]]></description>
    <source><![CDATA[$source]]></source>
    <cuisine>
        <!-- CUISINE -->
        <style><$1/style>
        <region>$2</region>
        <approach>$3</approach>
    </cuisine>
    <tags>
        <!-- TAG -->
        <tag>$4</tag>
    </tags>
    <directions>
        <!-- STEPS -->
        <step><![CDATA[$5]]></step>
        <step><![CDATA[$6]]></step>
        <step><![CDATA[$7]]></step>
        <step><![CDATA[$8]]></step>
        <step><![CDATA[$9]]></step>
        <step><![CDATA[$10]]></step>
        <step><![CDATA[$11]]></step>
        <step><![CDATA[$12]]></step>
        <step><![CDATA[$13]]></step>
    </directions>
    <ingredients>
        <serves>$serves</serves>
        <!-- INGREDIENT -->

    </ingredients>
</recipe>
  • For the ingredients, I created a ST2 snippet for each ingredient. As the snippet content is inside a CDATA node, I couldn’t find a way to have CDATAs within in it. I will have to remember to use XML entities there.
1
2
3
4
5
6
7
8
9
10
11
12
<snippet>
    <content><![CDATA[
<ingredient>
    <quantity>$1</quantity>
    <measurement>$2</measurement>
    <name>$3</name>
    <preparation>$4</preparation>
</ingredient>

]]></content>
    <tabTrigger>ing</tabTrigger>
</snippet>
  • Tested, and everything works as expected.

Viewing and printing the recipe

1
2
3
4
5
Scenario: Viewing and printing the recipe
     Given that I have a recipe in an XML document
     When I view it in a web browser
     Then it will look as I expect
     And it will look the same when printed
  • I am not a fan of applying css directly to XML, so I will use XSL instead to transform the XML to the HTML template I already have. The only snag is that the XML has to be served from a web server, as some browsers don’t allow XML to load XSL locally.
  • That’s not a problem though, as I already have Apache running locally, so I simply created a folder for the recipes there, and an _assets folder with my XSL / CSS / imagas.
  • The XSL is basically the HTML document I already had, pasted in a template that matches the root node, with a lot of value-ofs for variable substitution and a couple of for-each loops for steps and ingredients. I don’t print out meta information, as it uses valuable space, so these are not needed in the output.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="utf8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8" indent="yes" standalone="yes" omit-xml-declaration="yes" doctype-public="html" doctype-system="html" />
<xsl:preserve-space elements="*"/>

<xsl:template match="/"><html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<head>
<link rel="stylesheet" href="_assets/css.css" media="all" />
</head>
<body>
<div id="measured_height_container">
  <div id="recipe_title"><xsl:value-of select="/recipe/title" /></div>

  <div id="recipe_contents">

    <div id="recipe_directions">
    <ol>
      <xsl:for-each select="/recipe/directions/step">
        <li> <xsl:value-of select="." /></li>
      </xsl:for-each>
    </ol>
    </div>

    <div id="sidebar">

      <div id="recipe_ingredients">
        Serves <xsl:value-of select="/recipe/ingredients/serves" />
        <dl>
          <xsl:for-each select="/recipe/ingredients/ingredient">
            <dt>
                <span class="ingredient_quantity"><xsl:value-of select="quantity" /></span>
                <span class="ingredient_measurement"><xsl:value-of select="measurement" /></span>
            </dt>
            <dd>
                <span class="ingredient_name"><xsl:value-of select="name" /></span>
                <span class="ingredient_preparation"><xsl:value-of select="preparation" /></span>
            </dd>
          </xsl:for-each>
        </dl>

      </div>

      <div id="recipe_description">
      <xsl:value-of select="/recipe/description" />
      </div>
    </div>
  </div>
</div>
</body>
</html>
</xsl:template>

</xsl:stylesheet>
  • I add a link to the XSL inside the XML template
1
2
3
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="_assets/recipe.xsl"?>
....
  • I created a couple of recipes manually, loaded them in browsers, and tried to print them. It all looks good.

Challenge 100% complete

That was a simple quick job. It is actually quite a quick way to do data entry, but of course I am limited to using one machine. In part 2 I will start importing some data into it.

Some artefacts are available on GitHub.

Comments