Mokvino Web

Scripts for maintaining websites

Description

This is a collection of scripts for GNU Awk, GNU Make and Mokvino which can be used to build and maintain websites. It makes it easy to maintain a consistent appearance across pages, repeat often used content with macros, build SSP sitemaps, create documents in multiple languages, and generate content from tables, while supporting incremental updates.

This software is intended as a successor to Website maintenance scripts, but using Mokvino instead of GNU M4. As a result, it can't really compete on speed, but it has finer granularity for detecting exactly which files need to be rebuilt. It also employs a pure transformation from M4-like syntax into HTML, whereas in Website maintenance scripts, the source is an unclear mixture of M4 and HTML.

I use these scripts and libraries to build many of my websites.

I will try to document this package here as much as I can, in due course. In the meantime, please contact me if you want to know how to use it, or if you want me to document some specific functionality.

Installation

For Ubuntu:

sudo apt-get install coreutils diffutils findutils gzip inkscape libbatik-java netpbm pdf2svg poppler-utils ps2eps texlive-extra-utils unoconv xsltproc icoutils webp

To install, check out a working copy of the stable branch:

svn co http://scc-forge.lancaster.ac.uk/svn-repos/webtools/m5web/branches/stable/ m5web
make
sudo make install

This installs in $PREFIX, which is /usr/local by default. The installation consists of $PREFIX/include/m5web.mk, and several Mokvino files in $PREFIX/include/m5web/, plus other scripts in $PREFIX/share/m5web/. To update to the latest version, switch to your working copy, and do:

svn update
make
sudo make install

To install in a different location, override PREFIX:

make PREFIX="$HOME/software" install

If you do this often enough, you might prefer to set up an m5web-env.mk file in the path that GNU Make searches, and override PREFIX there.

Directory structure

Mokvino Web allows you to maintain a hierarchy of source files from which webpages will be built. By default, this hierarchy has the following structure:

When you're in the root directory, running make will attempt to build webpages out of the files in src/, and place them in pub/www/. Site-wide macro definitions and GNU Awk libraries should go in etc/. Intermediate files are placed in the var/ hierarchy. You only need the src/www/ files to begin with; the others will be created as required.

By default, WWWPREFIX is set to pub/www/. You can override it in Makefile to cause pages to be created in another directory. Make sure the value includes a trailing slash.

Invocation

When you invoke make, be sure that it will search for included files in $PREFIX/include/. (PREFIX is specified during installation.) You can do this with the -I switch, either directly on the command, or by setting MAKEFLAGS:

make -I"$HOME/software/include"

Configuration

Makefile should minimally contain the following:

all:: m5web-all

# Identify documents to be built here.  For example:
DOCS += index
DOCS += styles/main
DOCS += styles/print

# Identify simple image copies or conversions.  For example:
PNGS += icons/foo
JPEGS += icons/bar
SVGS += icons/baz
SVGZS += icons/qux

TARGET_LANGUAGE=en-GB

INDEXBASE=

include m5web.mk

That example expects the following files to exist:

  • src/www/index.m5
  • src/www/styles/main.m5
  • src/www/styles/print.m5
  • src/www/icons/foo.sfx1
  • src/www/icons/bar.sfx2
  • src/www/icons/baz.sfx3
  • src/www/icons/qux.sfx4

…where sfx1 is png or a suffix for an image type that can be converted to PNG, sfx2 is jpg or a suffix for an image type that can be converted to JPEG, etc.

Suffixes

You can configure the suffixes used for different file types, in both source and public directories. For a given type, identified by a label TYPE, there are upto four pre-defined variables in Make:

  • TYPE_DFL_SUFFIX — This is an internal suffix which you should not override. It is also the suffix used inside var/www/.

  • TYPE_SRC_SUFFIX — This is the suffix identifying the type within src/www/. You probably won't need to change this, but you can if it makes things more convenient. It defaults to $(TYPE_DFL_SUFFIX).

  • TYPE_SUFFIX — This is the suffix identifying the type in pub/www. It defaults to $(TYPE_DFL_SUFFIX).

  • TYPE_LNK_SUFFIX — This is the suffix used to link to files of this type. It defaults to $(TYPE_SUFFIX). Clear it to enable content negotiation, or simply to make the link independent of the underlying technology.

The types HTML, CSS, XHTML, XSLT2HTML, PHP2HTML and STDMAP don't define a source suffix TYPE_SRC_SUFFIX, as they are usually created from Mokvino files. Other types, mainly graphics, define all four variables.

Many of these variables, especially those of the form TYPE_SUFFIX and TYPE_LNK_SUFFIX, are exported to Mokvino under the same names.

Multiple installations

You might want to have multiple installations of your site's source, each requiring variations in the configuration to suit the different environments. For example, to build your public pages, you might want to use hyperlinks without suffixes, and have each page available in all languages defined for it. Makefile would then have to contain:

all:: m5web-all

HTML_LNK_SUFFIX=

Meanwhile, for testing and preparation, you could have a local installation that only generates pages in English, and uses full suffixes, even on index pages. For that, you'd need:

all:: m5web-all

TARGET_LANGUAGE=en-GB
INDEXBASE=

To achieve both, without causing conflicts in your version-control system, use this instead:

all:: m5web-all

-include mysite-env.mk

This will cause GNU Make to search for mysite-env.mk in the same directory as Makefile, then to try other locations as defined by -Idirectory. This gives you a chance to inject local configuration by putting it in that file, without causing an error if not found.

Editing with Emacs

If you're editing with Emacs, you can make it switch into m4-mode automatically when it encounters a Mokvino file, by adding this to your ~/.emacs file:

(add-to-list 'auto-mode-alist '("\\.m5\\'" . m4-mode))

Oddly, Emacs recognizes \-escaped characters in this mode, which means it already handles Mokvino's default escaping, even though M4 doesn't recognize that kind of escaping (as far as I know)!

You also might want to enable a soft line wrap:

(add-hook 'm4-mode-hook 'visual-line-mode)

User-defined macros

Groups of related user-defined macros should be placed in etc/foo.m5, where foo is the name of a module to be defined. The user may define as many uniquely named modules as necessary. The content of a user-defined module will usually have the following form:

shift(

# User-defined macros go here.

)\

The enclosing shift ensures that blank lines within the module will be essentially ignored.

To use the module from any source file, including other modules, write:

load(`foo')\

This kind of call is idempotent; if the module has already been loaded, its contents won't be expanded again. Mokvino Web also records the fact that the source file that causes a module to be loaded will be dependent on the file that holds the module. Use loadsys instead of load if you want to prevent that record.

Hypertext pages

To generate HTML files from your Mokvino source file, you need to import some Mokvino Web macros:

# In src/www/foo.m5
import(`m5web/html')\
import(`m5web/matrix')\
matrix(`langs'=``en-GB'',

`fmts'=``HTML'',

`title'=``My Home Page'',

`body'=``

'p(``Welcome to my world!'')`

'')\

This states that you want to build pub/www/foo.en-GB.html (if TARGET_LANGUAGE is not set in Makefile), or pub/www/foo.html (if TARGET_LANGUAGE is set to en-GB). The page's content will be similar to:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html lang="en-GB">
<head>
  <meta name="generator" content=
  "HTML Tidy for Linux (vers 25 March 2009), see www.w3.org">
  <meta http-equiv="Content-Type" content=
  "text/html; charset=utf-8">
  <meta name="viewport" content=
  "width=device-width; initial-scale=1">

  <title>My Home Page</title>
</head>

<body>
  <p>Welcome to my world!</p>
</body>
</html>

You can add other entries to <head> by defining the parameter head:

`head'=``

'meta(``by'', ``foo@example.com'')`

'',

To set the description or keywords, use the parameters desc and keywords.

If you need to type a character that is special to HTML, just type it. Mokvino Web will automatically convert it into an escape sequence such as &lt;. If you need a special character that Mokvino uses, escape it with a backslash, e.g., \'. Escape a hash # with a Unicode sequence: \u0023. This means that your source code does not have to be escaped according to what it will finally be output as.

Consistent mark-up structure

To get a consistent document structure across all pages, it is recommended to write a module to define a macro to call matrix in the right way, e.g.:

# In etc/page.m5
import(`m5web/html')\
import(`m5web/matrix')\
define(`PAGE', `indir(`matrix'$!?vname$!?langs$!?fmts, `flags'=`C',

`title'=`ifelse($title,,, `$title` — '')`My Website''$!?desc,

`head'=``

'lrel_html(``start'', `index')`

'$head'$!?body.class,

`body'=``
...
'$body`
...
''$?@`'cshiftp(`body',`title',`desc',`head'$!?@))',

`title,desc,body')\

Then you can simply write:

# In src/www/foo.m5
load(`page')\
PAGE(`langs'=``en-GB'',

`fmts'=``HTML'',

`title'=``My Home Page'',

`body'=``

'p(``Welcome to my world!'')`

'')\

Loading modules automatically

Don't want to have to go back to the top of a page to load a module to use a macro? Define the macro in etc/page.m5, so it's available to every page! But hold on — any changes to the macro will cause the entire site to be rebuilt, because almost everything uses etc/page.m5.

You can avoid that by keeping the macro in a separate module, but defining a loader with the same name in etc/page.m5:

# In etc/page.m5
define(`IMPLNOTE', `load(`implnote')\
indir(`$0'$!?@$?@)')
# In etc/implnote.m5
define(`IMPLNOTE', ``This is an implementation note.'')

Your page will now only depend on etc/implnote.m5 if it uses IMPLNOTE.

The idiom is likely to be well used, so a predefined macro autoload is available to do the work for you, with the first argument being the module to load, and remaining arguments being macros defined by it:

# In etc/page.m5
autoload(`implnote', `IMPLNOTE')\

But there's still a problem. Adding a new loader to etc/page.m5 also will cause the entire site to be rebuilt. The solution is to ensure your site is up-to-date first, then define the loader, then touch all your targets so they won't be rebuilt. Then touch the sources of pages that use the new macro. If it's truly new, that won't be very many!

More robustly, you could place your auto-loading macros in a separate module:

# In etc/pagesys.m5
autoload(`implnote', `IMPLNOTE')\

…and then load it from their original location without recording dependencies:

# In etc/page.m5
loadsys(`pagesys')\

Predefining macros

If you want some macros predefined, without having to load any modules explcitly, you can specify a file containing them in Makefile:

# In Makefile
M5WEB_PREDEFINED += etc/common.m5

It will not be treated as a module, so don't try to load it as one with load or loadsys. Its main use is to set up auto-loaded macros.

Basic elements

The module m5web/html includes m5web/hypertext, which defines several macros to build basic HTML elements. For block elements, there are p, div, pre, blockquote, etc. For in-line elements, there are span, samp, code, etc. Most of these can take parameters such as id, title, class, lang. The values of id and lang parameters should be single-quoted, while title and class should be double-quoted.

Multilingual pages

Apache can be set up to serve the several pages in different languages under the same address, using content negotiation. If you want a page to be available in several languages, you can list them in the langs parameter of matrix (or your chosen wrapper, like PAGE):

# In src/www/foo.m5
load(`page')\
import(`mokvino/language')\
PAGE(`langs'=``en-GB', `fr'',

`fmts'=``HTML'',

`title'=`iflang(``fr'', ``Ma page d'accueil'', ``My Home Page'')',

`body'=``

'p(`iflang(``fr'', ``Bienvenue dans mon monde!'', ``Welcome to my world!'')'')`

'')\

If TARGET_LANGUAGE is not set, this generates two pages, pub/www/foo.en-GB.html and pub/www/foo.fr.html. The module mokvino/language provides a macro iflang, which can be used to select content based on which of these files is being generated. The first parameter, and every alternate, is actually a list of matching languages.

If you set lang on an enclosing element, it will influence calls to iflang embedded in its content. For example:

div(`lang'=`de', `iflang(``de'', ``Guten Tag!'', ``Good day!'')')

…yields:

<div lang="de">Guten Tag!</div>

…regardless of the language of the containing page.

External linking

link(``http://www.google.com/'', ``Google'')

…yields:

<a href="http://www.google.com/">Google</a>

If you try to put one link in another, the inner one will be disabled. For example:

link(``http://www.facebook.com/'', ``Facebook and 'link(``http://www.google.com/'', ``Google'')` are great'')

…yields:

<a href="http://www.facebook.com/">Facebook and <span>Google</span> are great</a>

In an English context, this code:

link(``http://www.example.com/'', ``Example'', `title'=``Exemple'', `tlang'=`fr')

…yields:

<a title="Exemple" href="http://www.example.com/" lang="fr"><span lang="en">Example</span></a>

Internal linking

When a file such as src/www/foo/bar.m5 is processed, it is expanded with a predefined macro VNAME with the value `foo/bar'. The macro matrix uses this to work out that files such as pub/www/foo/bar.html are to be created. It also temporarily defines VFILE as VNAME plus the suffix of the type it is generating.

VFILE is used to create relative links between pages within the same site. The macro reluri in m5web/paths takes two parameters, name and suffix, and relativizes `$name'$suffix against VFILE. For example:

import(`m5web/paths')\
define(`VFILE', `foo/bar.html')\
reluri(`foo/fong', ``.html'')
reluri(`pics/pretty', ``.png'')
reluri(`home', ``.xhtml'')
reluri(`foo/bar', ``.html'')
reluri(`foo/bar', ``.xhtml'')

…yields the following:

fong.html
../pics/pretty.png
../home.xhtml
bar.html
bar.xhtml

The value of INDEXBASE specifies which links should map to a directory-like URI. By default, it is index, so links to index and foo/index, etc, will actually be encoded as ./ and foo/. For example:

import(`m5web/paths')\
define(`INDEXBASE', `home')\
define(`VFILE', `foo/bar.html')\
reluri(`foo/fong', ``.html'')
reluri(`pics/pretty', ``.png'')
reluri(`home', ``.xhtml'')
reluri(`foo/home', ``.xhtml'')
reluri(`foo/bar', ``.html'')
reluri(`foo/bar', ``.xhtml'')

…yields the following:

fong.html
../pics/pretty.png
../
./
bar.html
bar.xhtml

You should set INDEXBASE to nothing when generating a local page hierarchy, so that no link to a path ending in index gets converted to a link that doesn't work on a local filesystem.

In m5web/hypertext, the first parameter of macro llink is a path $vname (comparable with VNAME), followed by content, and wraps $content in an <a> linking to the specified path, appending $suffix. However, if this path is the same as the page you're linking from, it will wrap the content in a <span> instead. The optional parameter class is used to set the class of the <a> or <span>, while liveclass only sets the class of <a>, and deadclass only sets the class of <span>. To reference a fragment, provide the fragment identifier as anchor. For example, in src/www/baz/qux.m5:

llink(`foo/bar', `suffix'=``.html'', `anchor'=`top', ``Top of page'')

…yields the following:

<a href="../foo/bar.html#top">Top of page</a>

You're more likely to use llink_html, which is identical, except that suffix has a default value of HTML_LNK_SUFFIX, which itself has a default value of `.html'.

To set an anchor point in a page, set id on an appropriate element:

div(`id'=`here', ``The content'')

This sets the id attribute on the <div>, and it influences llink, such that links to this element from within it will be dead.

Tables

m5web/hypertext includes a macro table for building tables. The usual HTML attributes can be set with parameters like id, lang, border, summary, etc. The cellspacing and cellpadding attributes are controlled with the parameters spacing and padding.

The body of the <table> begins with a <caption>, if caption is defined. Its attributes can be controlled by parameters such as caption.lang, etc.

The value of $columns is expanded next. Here, use the macros col(``class'') to create <col> elements, colgroup(``class'', `col col col') to create <colgroup> elements with a span implied by content, and colspan(3, ``class'') to create empty <colgroup> elements with an explicit span.

If the parameter head is defined, a <thead> is created next to contain $head. $foot is then similarly wrapped in a <tfoot>. These can be filled with calls to macros tr, th and td to create HTML elements of the same names.

The value of the first parameter content then completes the <table> element, and should contain the main table data, consisting of calls to macros tr, th and td, optionally partitioned into tbodys.

The macros th and td take the expected parameters, except for cols which expands to the attribute colspan, and rows which expands to rowspan.

Forms

m5web/hypertext includes three macros for HTML forms. getform creates <form method="GET" action="'$action`">'$content`</form>, where action and content are the first two parameters. posttextform creates a POST form using application/x-www-form-urlencoded as the content type. postform creates a POST form using multipart/form-data. There are also three macros lgetform, lposttextform and lpostform, which take a local document name instead of action, and an optional suffix parameter, both passed to reluri.

For a plain button, use button. For a submit button, use submitbutton. For a reset button, use resetbutton. These take content as their first parameter.

textfield(``name'', ``value'') defines a text field, taking optional parameters with the usual names for attributes, except max for maxlength. passwordfield(``name'', ``value'') defines an obscured password field, with the same optional parameters. hiddenfield(``name'', ``value'') defines a hidden field.

uploadfield expands to <input type="file">, again with the usual optional parameters.

radiobutton and checkbox generate <input type="radio"> and <input type="checkbox">, with name and value being the first two parameters. The attribute checked is controlled by the presence of the parameter checked, so you only need checkbox(``name'', ``value'', `checked'=) to set it.

selectfield expands to <select>…</select>, and macros optgroup and option should be used to produce the expected elements within it.

label should be used to wrap a control in <label>…</label>. labelfor(`foo', ``Text'') should be used to remotely attach the label Text to an element such as textfield(`id'=`foo').

Stylesheets

There isn't a great deal of support for writing CSS stylesheets. If you want a simple mapping from source file, e.g., src/www/styles/foo.m5 to CSS file pub/www/styles/foo.css, use this:

# In src/www/styles/foo.m5
import(`m5web/css')\
write(WWWPREFIX`'VNAME`'CSS_SUFFIX, ``

body {
  background-color: white;
  color: black;
}

'')\

Make sure you also list the source file as one to be processed:

# In Makefile
DOCS += styles/foo

If you have a lot of stylesheets, you could define a user module to simplify that:

# In etc/style.m5
import(`m5web/css')\
define(`CSSDOC', `pushdef(`VFILE', `VNAME'qindir(`CSS_LNK_SUFFIX'))\
write(WWWPREFIX`'VNAME`'CSS_SUFFIX,

``@charset "UTF-8";
@namespace url(http://www.w3.org/1999/xhtml);

'$1')\
popdef(`VFILE')')\

Then you just write:

# In src/www/styles/foo.m5
load(`style')\
CSSDOC(``

body {
  background-color: white;
  color: black;
}

'')\

A macro is defined to simplify setting of colours:

'colours(```body''', ``\u0023000'', ``\u0023fff'', ``\u002300f'', ``\u0023551a8b'', ``\u0023f00'', ``\u0023c70'')`

The first parameter is a list of selectors to which a range of colour settings will be applied. The second parameter is the foreground colour, the third the background, the fourth the link colour, the fifth for visited links, the sixth for active links, and the seventh for hovering. Many of these can be omitted, taking on one of the earlier ones as defaults.

reluri is also made available by importing m5web/css.

To link a sheet styles/foo into a page, add to the head section the following:

lrel_css(``stylesheet'', `styles/foo')

Sitemaps

You can create a sitemap compatible with SSP.

# In src/www/navigation.m5
import(`m5web/sitemap')\
import(`m5web/matrix')\
matrix(`langs'=``en-GB'',

`fmts'=``STDMAP'',

`content'=``

'litem_html(``Home'', `index')`

'litem_html(``Contact'', `contact', `role'=``contact'')`

'lrefitem(`help')`

'',

`dead'=``

'litem_html(`id'=`help', ``Help'', `help')`

'')\

Top-level items should go in the content section. The dead section allows you to declare items that are only used by reference.

To activate the sitemap from a page, add the following to the head section of the page:

meta(``schema.ssm'', ``http://standard-sitemap.org/2007/ns'')
lrel(``ssm.location'', `navigation', `suffix'=`STDMAP_LNK_SUFFIX')

Content from tabular data

You can define a simple text-based table of data in src/tables/foo.tab, and a script in src/tables/foo.awk to generate content from it. First, you need to indicate that the table exists, and that potentially all documents depend on it:

TABLES += foo
TABLE_USERS_foo += $(DOCS)

Let's suppose that the table contains the following:

john|23|John Smith
fred|42|Fred Flintstone
wilma|19|Wilma Cargo

You can write a script to process this:

# In src/tables/foo.awk
BEGIN {
    FS = "|";
}

{
    skip_blanks();
    start_fields();
    id = next_field();
    age = next_field();
    name = next_field();

    outfile = open_make();
    printf "FOO_PEOPLE += %s\n", id | outfile;

    outfile = open_quantum("info-" id ".m5");
    printf "define(`AGE/%s', `%d')\\\n", id, age | outfile;
    close(outfile):

    outfile = open_quantum("link-" id ".m5");
    printf "define(`NAME/%s', ``%s'')\\\n", id, name | outfile;
    close(outfile):
}

First, the open_make call will create a file containing:

FOO_PEOPLE += fred
FOO_PEOPLE += john
FOO_PEOPLE += wilma

(Note that this file is normally sorted by default, so don't split any unit over multiple lines.)

Back in Makefile, that file is automatically included, so you can use these to identify pages for each person:

DOCS += $(FOO_PEOPLE:%=person/%)

From a Mokvino file, you can load any of the files named by open_quantum calls, e.g.:

loadtable(`foo/link-john')\

indir(`NAME/john')

By using loadtable, Mokvino Web will remember that this file depends on the one generated by the GNU Awk script. If you change the source table such that the data in foo/link-john.m5 changes, the referencing file will be rebuilt. If you change only a different entry, the table will be reprocessed, but the referencing file won't be rebuilt. Use loadtablesys if you don't want to record the dependency.

(Again, note that files created with open_quantum are sorted by lines. Ensure that every unit written to them is not split over multiple lines.)

Using close(outfile) isn't strictly necessary, but it is recommended to avoid the Awk script running out of file descriptors.

Tidying mark-up

By default, generated mark-up documents are passed through HTMLTidy. When debugging, this can be problematic, as the line numbers given by Tidy become meaningless when its input has been discarded. An untidy version is therefore kept in var/m5web/www/VNAME.untidy.

You can disable tidying up by using:

define(`matrix.tidy.HTML', ``cat'')\

Use the suffix HTML to match the fmts parameter given to matrix. The default is defined as follows:

define(`matrix.tidy.HTML', `M5WEB_LIB`/m5web/wraptidy -f var/m5web/www/'VNAME`.'FORMAT`.'DOCUMENT_LANGUAGE`.tidy-errs -utf8 -i -q'')

Note that it doesn't call tidy directly, but instead calls a script wraptidy provided by Mokvino Web, which prevents warnings generated by the command from being treated as errors by Make.

XML can also be tidied, but be aware that HTMLTidy sometimes strips away necessary spaces from XML, with disappointing results.

Compression

You can cause the final output of a file to be compressed. For a given page, you could write:

define(`matrix.compress.HTML', ``gzip -9'')\
define(`HTML_SUFFIX', ``.html.gz'')\

For a site-wide effect, you could define:

# In mysite-env.mk

HTML_COMPRESS=gzip -9
HTML_SUFFIX=.html.gz
HTML_LNK_SUFFIX=

HTML_COMPRESS specifies the command to use to perform the compression. HTML_SUFFIX must be augmented to tell the server that the files are compressed. Clearing HTML_LNK_SUFFIX means that internal URIs won't include suffixes, to enable content negotiation.

If you have src/www/icons/foo.svg, and you want to create a compressed version of it in pub/www/icons/foo.svgz, you just have to list it as one to be compressed:

# In Makefile
SVGZS += icons/foo

If you're testing on a local filesystem, and your browser can't understand that foo.svgz is an SVG that needs to be decompressed first, you could disable compression for all SVGs using:

# In mysite-env.mk

SVG2SVGZ=cat
SVGZ_SUFFIX=.svg

Hypertext from XSL templates

XSLT files are XML templates that most browsers can transform into HTML. A server process that can create dynamic content (like CGI) can generate application-specific XML instead of HTML directly, to decouple it from layout and style decisions that apply only to hypertext. The XML is prefaced with a processing instruction such as:

<?xml-stylesheet type='text/xsl' href='search.xsl' ?>

This tells the browser to also fetch search.xsl, and use it to transform the XML following the processing instruction into hypertext. To change the layout of the hypertext, re-configure the server process to tag its output with a different XSLT file. The developer of the server process is no longer concerned with layout issues, and the website designer is not concerned with internal application issues.

You can generate XSLT files from Mokvino Web by listing XSLT2HTML as one of the formats:

load(`page')\
PAGE(`langs'=``en-GB'',

`fmts'=``XSLT2HTML'',

`expr'=`qname(`ns'=`http://example.com/ns/app/2016', `meta-index')',

`title'=``My Home Page'',

`body'=``

'p(``Welcome to my world!'')`

'')\

You'll probably want to create a module to assist with some of the boilerplating:

# In etc/templates.m5
shift(

define(`APP_NAMESPACE', ``http://example.com/ns/app/2016'')

define(`QNAME', `qname(`ns'=APP_NAMESPACE, `$name')', `name')

)

The macro qname ensures that a suitable namespace prefix for your application will be chosen in the generated XSLT. If you're not using namespaces in the application-specific XML, you can forgo it.

load(`page')\
load(`templates')\
PAGE(`langs'=``en-GB'',

`fmts'=``XSLT2HTML'',

`expr'=`QNAME(`meta-index')',

`title'=``My Home Page'',

`body'=``

'p(``Welcome to my world!'')`

'')\

The argument expr specifies the top-level expression to match against, so the template that generates the <html> element and basic page structure looks like this:

<xsl:template match='ns0:meta-index'>
<xsl:element name='html'>
... the usual page elements here ...
</xsl:element>
</xsl:template>

Arguments like head, body and title will contribute to its content the same as they do for HTML. If you need to define other templates, list them in the argument extra:

`extra'=``

'block_template(`QNAME(`entry')', ``...'')`

'block_template(`QNAME(`group')', ``...'')`

'',

(Use inline_template to generate hypertext in an inline context.)

You can then refer to them in other content:

'apply_templates(`QNAME(`entry')')`

Other macros you might use are value_of to generate an element <xsl:value-of>; unescaped_value_of to turn off output escaping; xsl_if to generate either <xsl:if> or <xsl:choose>, depending on the number of tests; xsl_foreach(``expr'', ``content'') to generate <xsl:for-each>.

Variables and parameters

A variable is declared with xsl_var(``root'', ``/*/@root''), yielding:

<xsl:variable name="root" select="/*/@root" />

If you use xsl_varblock(``root'', `value_of(``/*/@root'')') instead, you get a declaration like:

<xsl:variable name="root">
  <xsl:value-of select="/*/@root" />
</xsl:variable>

Parameters are similarly defined with xsl_par and xsl_parblock.

Conditional attributes

Most element attributes come with a counterpart attr.wrap, e.g., checked.wrap for checked on checkbox. Use it if you need to wrap a condition around an entire attribute:

'checkbox(`name'=``enable'', `value'=``yes'', `checked'=, `checked.wrap'=`xsl_if(``@default=\'on\''', `$$1')')`

Relative URIs

reluri is modified so that it resolves internal links simply by prefixing the value of the XSLT variable $m5web_base. By default, this is set to the value of the Mokvino macro DOCUMENT_BASE, which is copied from the Make variable DOCUMENT_BASE, which defaults to file://$(PWD)/$(WWWPREFIX). If you set DOCUMENT_BASE_EXPR, e.g., like this:

M5FLAGS += -DDOCUMENT_BASE_EXPR='`'"/*/@deploy-base'"

…that will instead set $m5web_base from a declaration such as:

<xsl:param name='m5web_base' select='/*/@deploy-base' />

…which means that your application-specific XML can provide the URL prefix dynamically. If you're not able to add such an attribute to the XML, you could set DOCUMENT_BASE_INC to an absolute path to load a separately managed XSLT file:

define(`DOCUMENT_BASE_INC', ``/commands/context.php'')

Conversion of images

Mokvino Web has several rules for converting between various image formats, including bitmaps and vector graphics. All you have to do is provide the source files in src/www/, and list what files you want of each type in Makefile. For example:

# In Makefile
JPEGS += animals/dog
JPEGS += animals/pig

PNGS += tools/bucket
PNGS += tools/spade
SVGS += logo

This indicates that you want the following files to be created:

  • pub/www/animals/dog.jpg
  • pub/www/animals/pig.jpg
  • pub/www/tools/bucket.png
  • pub/www/tools/spade.png
  • pub/www/logo.svg

Suppose you then provide the following:

  • src/www/animals/dog.jpg
  • src/www/animals/pig.png
  • src/www/tools/bucket.png
  • src/www/tools/spade.png
  • src/www/logo.svg

Then animals/dog.jpg, tools/bucket.png, tools/spade.png and logo.svg are created simply by copying from src/www/ to pub/www/. animals/pig.jpg will be converted from animals/pig.png.

Rasterizing SVGs

If you provide these files:

  • src/www/logo.dim
  • src/www/logo.svg

…and declare that you want a PNG image with the same basename:

# In Makefile
PNGS += logo

…then Mokvino Web will generate pub/www/logo.png from logo.svg as an image with dimensions specified in logo.dim. For example:

width=40
height=32

It's possible to leave out one of the dimensions to let it be computed by the rasterizer.

The conversion is performed by rasterizer, which is part of Batik, or by Inkscape, which is the default. You can change this by setting SVG2PNG=rasterizer.

If you want to create large and small PNGs of a single SVG, define labels for each size using SCALES:

SCALES += big
SCALES += small

PNGS += $(SCALES:%=icons/foo-%)

Then define dimensions for the labelled PNGS:

# In src/www/icons/foo-big.dim
width=32
height=32
# In src/www/icons/foo-small.dim
width=16
height=16

This works by copying the source SVG unchanged to var/www/icons/foo-small.svg and var/www/icons/foo-big.svg, from which the standard rules can convert to PNG.

If you want several images of the same scale to have the same dimensions, you can create the dimensions dynamically:

var/www/%-small.dim: src/small.dim
        cp --reflink=auto '$<' '$@'

Favicons

To create an image of type image/x-icon (as originally used for rel="shortcut icon") from SVG, simply list the unsuffixed name in ICOS, e.g.:

ICOS += icons/favicon

This uses predefined sizes ico16, ico32, and ico48, and creates dimensions for them automatically. By default, it uses Batik's rasterizer to convert SVG to PNG, as it preserves aspect ratio. Specify the following if you want to use Inkscape instead:

ICO_SVG2PNG=inkscape

Export of LibreOffice figures

Mokvino Web can convert ODG files into cropped PDFs using unoconv and pdfcrop. It also converts ODGs to SVGs (without backgrounds) using inkscape and xsltproc. If you specify any of these:

PDFS += figure1
SVGS += figure1
SVGZS += figure1

…and you provide src/www/figure1.odg, that will be enough to generate:

  • src/www/figure1.svg
  • src/www/figure1.svgz
  • src/www/figure1.pdf

Note that the output from unoconv can be disappointing for some features, like gradient fills. It also always fails for me if LibreOffice is running at the same time, so I have to quit that first.

Content negotiation of WebP images

Select which images should be available as WebPs. For example, if you want all PNGs and JPEGs to be available as WebPs:

WEBPS += $(sort $(PNGS) $(JPEGS))

Instigate content negotiation on links to PNGs and JPEGs by unsetting their link suffixes:

JPEG_LNK_SUFFIX=
PNG_LNK_SUFFIX=

These are probably settings you only want to use in mysite-env.mk for your public installation.

You should also configure your web server to slightly prefer PNGs over WebPs, so that browsers that stupidly accept any image format won't get WebP by mistake:

AddType image/webp;qs=0.9 .webp

Files

File Size Last modified Description Requirements and optionals
stable branch (SVN) GNU Make GNU Awk Mokvino HTMLTidy

Valid HTML 4.01!
Made with Mokvino
The Standard-Sitemap ProtocolSitemap Supported