MP4H(1)
πΈ NAME πΈ
okay so this is mp4h β the Macro Processor for HTML Documents β and youβre reading the MP4H(1) page from EN Tools. Hii bestie π
β¨ VERSION β¨
This documentation describes mp4h version 1.3.0. Thatβs the version vibe check for this page. π¦
π¦ INTRODUCTION π¦
The mp4h software is a macro-processor specifically designed to deal with HTML documents. It allows powerful programming constructs, with a syntax familiar to HTML authors. β¨
This software is based on Meta-HTML <URL:http://www.metahtml.org/>, written by Brian J. Fox. Even if both syntaxes look similar, source code is completely different. Indeed, a subset of Meta-HTML was used as a part of a more complex program, WML (Website Meta Language <URL:http://www.engelschall.com/sw/wml/>) written by Ralf S. Engelschall and which I maintain since January 1999. For licensing reasons, it was hard to hack Meta-HTML and so I decided to write my own macro-processor. π
Instead of rewriting it from scratch, I preferred using another macro-processor engine. I chose GNU m4 <URL:http://www.gnu.org/software/m4/>, written by RenΓ© Seindal, because of its numerous advantages: this software is stable, robust and very well documented. This version of mp4h is derived from GNU m4 version 1.4n, which is a development version. π₯
The mp4h software is not an HTML editor; its unique goal is to provide an easy way to define its own macros inside HTML documents. There is no plan to add functionalities to automagically produce valid HTML documents, if you want to clean up your code or validate it, simply use a post-processor like tidy <URL:http://www.w3.org/People/Raggett/tidy/>. π
π COMMAND LINE OPTIONS π
Optional arguments are enclosed within square brackets. All option synonyms have a similar syntax, so when a long option accepts an argument, short option do too. π«Ά
Syntax call is
mp4h [options] [filename [filename] ...]
Options are described below. If no filename is specified, or if its name is -, then characters are read on standard input. π
β‘ Operation modes β‘
--helpβ display an help message and exit--versionβ outputmp4hversion information and exit-E,--fatal-warningsβ stop execution after first warning-Q,--quiet,--silentβ suppress some warnings for builtins-S,--safety-level="NUMBER"β disable risky functions;0means no filtering,1disableexecuteand2disable this one too plus all filesystem related functions:file-exists,real-path,get-file-properties,directory-contentsandinclude
π Preprocessor features π
-I,--include="DIRECTORY"β search this directory for includes and packages-D,--define="NAME"[=VALUE]""β set variableNAMEtoVALUE, or empty-U,--undefine="COMMAND"β delete builtinCOMMAND-s,--synclinesβ generate#line NO "FILE"'lines
π§ Parser features π§
-c,--caseless="NUMBER"β set case sensitiveness according to the bits ofNUMBER. A null bit means symbol is case sensitive, and bits are defined as followed:0for tags,1for variables and2for entities. Default value is3, i.e. only entities are case sensitive.-e,--encoding="NAME"β specify document encoding. Valid options are8bit(default) orutf8.-X,--expansion="NUMBER"β set parser behaviour according to the bits ofNUMBER
NUMBER is a combination of
1β do not parse unknown tags2β unknown tags are assumed being simple4β trailing star in tag name do not make this tag simple8β an unmatched end tag closes all previous unmatched begin tags16β interpret backslashes as printf32β remove trailing slash in tag attributes64β do not remove trailing star in tag name128β do not remove leading star in tag name256β do not add a space before trailing slash in tag attributes1024β suppress warnings about bad nested tags2048β suppress warnings about missing trailing slash
In version 1.3.0, default value is 3114=2+8+32+1024+2048. π
π Limits control π
-H,--hashsize="PRIME"β set symbol lookup hash table size (default509)-L -nesting-limit="NUMBER"β change artificial nesting limit (default250)
π₯ Debugging π₯
-d,--debug="FLAGS"β set debug level (noFLAGSimpliesaeq)-t,--trace="NAME"β traceNAMEwhen it will be defined-l,--arglength="NUMBER"β restrict macro tracing size-o,--error-output="FILE"β redirect debug and trace output
Flags are any of:
tβ trace for all macro calls, not only debugging-on'edaβ show actual argumentseβ show expansioncβ show before collect, after collect and after callxβ add a unique macro call id, useful withcflagfβ say current input file namelβ say current input line numberpβ show results of path searchesmβ show results of module operationsiβ show changes in input filesVβ shorthand for all of the above flags
π DESCRIPTION π
The mp4h software is a macro-processor, which means that keywords are replaced by other text. This chapter describes all primitives. As mp4h has been specially designed for HTML documents, its syntax is very similar to HTML, with tags and attributes. One important feature has no equivalent in HTML: comments until end of line. All text following three colons is discarded until end of line, like
;;; This is a comment
π Function Macros π
Note: All examples in this documentation are processed through mp4h with expansion flags set to zero (see a description of possible expansion flags at the end of document), it is why simple tags contain a trailing slash. But mp4h can output plain HTML files with other expansion flags. π
The definition of new tags is the most common task provided by mp4h. As with HTML, macro names are case insensitive, unless -c option is used to change this default behaviour. In this documentation, only lowercase letters are used. There are two kinds of tags: simple and complex. A simple tag has the following form:
<name [attributes] />
whereas a complex tag looks like:
<name [attributes]>
body
</name>
Since version 0.9.1, mp4h knows XHTML syntax too, so your input file may conform to HTML or XHTML syntax. In this manual, we adopt the latter, which is why simple tags have a trailing slash in attributes. If you want to produce HTML files with this input file, you may either choose an adequate --expansion flag or use a post-processor like tidy <URL:http://www.w3.org/People/Raggett/tidy/>. π¦
When a simple tag is defined by mp4h, it can be parsed even if the trailing slash is omitted, because mp4h knows that this tag is simple. But it is a good practice to always append a trailing slash to simple tags. π
In macro descriptions below, a slash indicates a simple tag, and a V letter that attributes are read verbatim (without expansion) (see the chapter on macro expansion for further details). β¨
π define-tag π
"name" "[attributes=verbatim]" "[endtag=required]"
"[whitespace=delete]"
This function lets you define your own tags. First argument is the command name. Replacement text is the function body.
Source:
<define-tag foo>bar</define-tag>
<foo/>
Output:
bar
Even if spaces have usually few incidence on HTML syntax, it is important to note that
<define-tag foo>bar</define-tag>
and
<define-tag foo>
bar
</define-tag>
are not equivalent, the latter form contains two newlines that were not present in the former. π
"whitespace=delete" β Some spaces are suppressed in replacement text, in particular any leading or trailing spaces, and newlines not enclosed within angle brackets.
"endtag=required" β Define a complex tag
Source:
<define-tag foo>bar</define-tag>
<foo/>
Output:
bar
Source:
<define-tag bar endtag=required>;;;
body is: %body</define-tag>
<bar>Here it is</bar>
Output:
body is: Here it is
"attributes=verbatim" β By default attributes are expanded before text is replaced. If this attribute is used, attributes are inserted into replacement text without expansion.
Source:
<define-tag foo>quux</define-tag>
<define-tag bar attributes=verbatim endtag=required>
Body: %Ubody
Attributes: %Uattributes
</define-tag>
<bar txt="<foo/>">Here we go</bar>
Output:
Body: Here we go
Attributes: txt=<foo/>
π§ provide-tag π§
"name" "[attributes=verbatim]" "[endtag=required]"
"[whitespace=delete]"
This command is similar to the previous one, except that no operation is performed if this command was already defined. π«Ά
πΈ let "S" πΈ
"new=old"
Copy a function. This command is useful to save a macro definition before redefining it.
Source:
<define-tag foo>one</define-tag>
<let bar=foo />
<define-tag foo>two</define-tag>
<foo/><bar/>
Output:
twoone
π¦ undef "S" π¦
"name"
Delete a command definition.
Source:
<define-tag foo>one</define-tag>
<undef foo />
<foo/>
Output:
<foo />
β set-hook β
"name" "[position=before|after]" "[action=insert|append|replace]"
Add text to a predefined macro. This mechanism allows modifications of existing macros without having to worry about its type, whether it is complex or not.
Source:
<let foo=add />
<set-hook foo position=before>
Before</set-hook>
<set-hook foo position=after>
After</set-hook>
<foo 1 2 3 4 />
Output:
Before10
After
π get-hook "S" π
"name" "[position=before|after]"
Print current hooks of a macro.
Source:
Text inserted with position=before:<get-hook foo position=before />!
Text inserted with position=after:<get-hook foo position=after />!
Output:
Text inserted with position=before:
Before!
Text inserted with position=after:
After!
π₯ attributes-quote "S" π₯
"%attributes"
Like %attributes, except that "attr=value" pairs are printed with double quotes surrounding attribute values, and a leading space is added if some text is printed.
Source:
<define-tag foo>;;;
%attributes
<img<attributes-quote %attributes />/>
</define-tag>
<foo id="logo" src="logo.gif" name="Logo" alt="Our logo" />
<foo/>
Output:
id=logo src=logo.gif name=Logo alt=Our logo
<img id="logo" src="logo.gif" name="Logo" alt="Our logo"/>
<img/>
π attributes-extract "S" π
"name1" "[,name2[,name3...]]" "%attributes"
Extract from %attributes the "attr=value" pairs for names matching any of name1, name2....
Source:
<define-tag img whitespace=delete>
<img* <attributes-extract name,src,alt %attributes /> />
</define-tag>
<img id="logo" src="logo.gif" name="Logo" alt="Our logo" />
Output:
<img src=logo.gif name=Logo alt=Our logo />
attributes-remove"S"β"name1" "[,name2[,name3...]]" "%attributes"
This one yeets the attr=value pairs out of %attributes whenever their name matches any of name1, name2, ....
Source:
<define-tag img whitespace=delete>
<img* <attributes-quote <attributes-remove name,src,alt %attributes />/> />
</define-tag>
<img id="logo" src="logo.gif" name="Logo" alt="Our logo" />
Output:
<img id="logo" />
π Quick note, bestie: the two previous functions are special because, unlike literally every other macro, their expansion does not form a group. That's necessary for parsing the resulting list of attributes.
In both of those functions, attribute names can be regular expressions. The main goal of these primitives is to help you write macros that accept any kind of attributes without having to declare them. A canonical example is:
Source:
<define-tag href whitespace=delete>
<preserve url name />
<set-var <attributes-extract url,name %attributes />/>
<a <attributes-quote <attributes-remove url,name %attributes />/>
href="<get-var url />"><get-var name /></a>
<restore url name />
</define-tag>
<href class=web url="http://www.foo.com" name="Welcome" />
Output:
<a class="web"
href="http://www.foo.com">Welcome</a>
But now we want to add an image attribute, so we may write:
Source:
<define-tag href whitespace=delete>
<preserve url name image />
<set-var <attributes-extract url,name,image %attributes />/>
<a <attributes-quote <attributes-remove url,name,image %attributes />/>
href="<get-var url />">
<if <get-var image />
<img <attributes-quote <attributes-remove url,name,image %attributes />/>
src="<get-var image />" alt="<get-var name />" border=0 />
<get-var name />
/>
</a>
<restore url name image />
</define-tag>
<href class=web url="http://www.foo.com" name="Welcome" image="foo.png"/>
Output:
<a class="web"
href="http://www.foo.com"><img class="web"
src="foo.png" alt="Welcome" border=0 /></a>
We need a mechanism to tell mp4h that some attributes refer to specific HTML tags. One solution is to prepend the attribute with the tag name, like:
Source:
<define-tag href whitespace=delete>
<preserve url name image />
<set-var <attributes-extract url,name,image %attributes />/>
<a <attributes-quote <attributes-extract a:.* %attributes />/>
href="<get-var url />">
<if <get-var image />
<img <attributes-quote <attributes-extract img:.* %attributes />/>
src="<get-var image />" alt="<get-var name />" />
<get-var name />
/>
</a>
<restore url name image />
</define-tag>
<href a:class=web img:id=logo img:border=1
url="http://www.foo.com" name="Welcome" image="foo.png" />
Output:
<a a:class="web"
href="http://www.foo.com"><img img:id="logo" img:border="1"
src="foo.png" alt="Welcome" /></a>
This example shows that regular expressions can be used inside attribute names, but it's still incomplete because we want to remove the prefix from the attributes. One solution is with subst-in-string, but there's a more elegant one:
Source:
<define-tag href whitespace=delete>
<preserve url name image />
<set-var <attributes-extract url,name,image %attributes />/>
<a <attributes-quote <attributes-extract :a:(.*) %attributes />/>
href="<get-var url />">
<if <get-var image />
<img <attributes-quote <attributes-extract :img:(.*) %attributes />/>
src="<get-var image />" alt="<get-var name />" />
<get-var name />
/>
</a>
<restore url name image />
</define-tag>
<href :a:class=web :img:id=logo :img:border=1
url="http://www.foo.com" name="Welcome" image="foo.png" />
Output:
<a class="web"
href="http://www.foo.com"><img id="logo" border="1"
src="foo.png" alt="Welcome" /></a>
When there are subexpressions inside regular expressions, those get printed instead of the whole expression. Also note that I put a colon before the prefix so they don't get mixed up with XML namespaces.
π§ Entities π§
Entities are macros just like tags, but they don't take any arguments. Tags are normally used to mark up text, while entities contain already marked-up text. Also, unlike tags, entities are case sensitive by default.
An entity has this form:
&entity;
define-entityβ"name"
This function lets you define your own entities. The first argument is the entity name. The replacement text is the function body.
Source:
<define-entity foo>bar</define-entity>
&foo;
Output:
bar
πΈ Variables πΈ
Variables are a special case of simple tags because they don't accept attributes. Their use is actually different: variables contain text, while macros act like operators. A really nice feature is that variables can be manipulated as arrays. You can think of them as newline-separated lists, which unlocks powerful manipulation functions β we'll see them below.
set-var"S"β"name[=value]" "[name[=value]] ..."
This command sets variables.
set-var-verbatim"S""V"β"name[=value]" "[name[=value]] ..."
Same as above, but attributes are read verbatim.
set-var-xβ"name=variable-name"
This command assigns a variable the value of the body of the command. It's especially useful when variable values contain newlines and/or quotes.
Note: the variable can't be indexed with this command. Also note that this command behaves asset-var-verbatim: the body is not expanded until the variable is shown withget-var.
get-var"S"β"name" "[name] ..."
Shows variable contents. If a numeric value within square brackets is appended to a variable name, it represents the index of an array. The first index of arrays is 0 by convention.
Source:
<set-var version="0.10.1" />
This is version <get-var version />
<set-var-x name="osversion">Operating sytem is
"<include command="uname" /><include command="uname -r" />"</set-var-x>
<get-var osversion />
Output:
This is version 0.10.1
Operating sytem is
"Linux
7.2.11
"
Source:
<set-var foo="0
1
2
3" />
<get-var foo[2] foo[0] foo />
Output:
200
1
2
3
get-var-once"S""V"β"name" "[name] ..."
Same as above, but attributes are not expanded.
Source:
<define-tag foo>0.10.1</define-tag>
<set-var version="<foo/>" />;;;
Here is version <get-var version />
<set-var-verbatim version="<foo/>" />;;;
Here is version <get-var version />
<set-var-verbatim version="<foo/>" />;;;
Here is version <get-var-once version />
Output:
Here is version 0.10.1
Here is version 0.10.1
Here is version <foo/>
preserve"S"β"name" "[name] ..."
All variables are global, and there is no variable or macro scope. For this reason a stack is used to preserve variables. When this command is invoked, the arguments are names of variables; their values are put at the top of the stack, and the variables are reset to an empty string.
restore"S"β"name" "[name] ..."
This is the opposite: the arguments are names of variables, which are set to the value found at the top of the stack, and the stack is popped down.
Note: thepreservetag pushes its last argument first, whereasrestorefirst pops its first argument.
Source:
<define-tag foo whitespace=delete>
<preserve src name text />
<set-var %attributes />
Inside: src=<get-var src /> name=<get-var name /> text=<get-var text />
<restore src name text />
</define-tag>
<set-var src=foo.png text="Hello, World!" />
Before: src=<get-var src /> name=<get-var name /> text=<get-var text />
<foo src=bar name=quux />
After: src=<get-var src /> name=<get-var name /> text=<get-var text />
Output:
Before: src=foo.png name= text=Hello, World!
Inside: src=bar name=quux text=
After: src=foo.png name= text=Hello, World!
unset-var"S"β"name" "[name] ..."
Undefines variables.
var-exists"S"β"name"
Returns "true" when this variable exists.
increment"S"β"name" "[by=value]"
Increments the variable whose name is the first argument. The default increment is one.
by=valueβ changes the increment amount.
Source:
<set-var i=10 />
<get-var i />
<increment i /><get-var i />
<increment i by="-3" /><get-var i />
Output:
10
11
8
decrement"S"β"name" "[by=value]"
Decrements the variable whose name is the first argument. The default decrement is one.
by=valueβ changes the decrement amount.
Source:
<set-var i=10 />
<get-var i />
<decrement i /><get-var i />
<decrement i by="3" /><get-var i />
Output:
10
9
6
copy-var"S"β"src""dest"
Copies a variable into another.
Source:
<set-var i=10 />
<copy-var i j />
<get-var j />
Output:
10
defvar"S"β"name""value"
If this variable is not defined or is defined to an empty string, then it is set to the second argument.
Source:
<unset-var title />
<defvar title "Title" /><get-var title />
<defvar title "New title" /><get-var title />
Output:
Title
Title
symbol-info"S"β"name"
Shows information on symbols. If it's a variable name, the "STRING" word is printed as well as the number of lines contained within this variable.
If it's a macro name, one of these messages is printed: "PRIM COMPLEX", "PRIM TAG", "USER COMPLEX", or "USER TAG".
Source:
<set-var x="0\n1\n2\n3\n4" />
<define-tag foo>bar</define-tag>
<define-tag bar endtag=required>quux</define-tag>
<symbol-info x />
<symbol-info symbol-info />
<symbol-info define-tag />
<symbol-info foo />
<symbol-info bar />
Output:
β¦and the list wraps up with these last few entries, bestie:
STRING5PRIM TAGPRIM COMPLEXUSER TAGUSER COMPLEX
β¨ String Functions β¨
These are the little text-manipulation girlies and honestly? They ate. π
string-length"S"β signature is"string"
Prints the length of the string. That's it, that's the whole job, and it does it flawlessly. π
Source:
<set-var foo="0
1
2
3" />;;;
<string-length <get-var foo /> />
<set-var foo="0 1 2 3" />;;;
<set-var l=<string-length <get-var foo /> /> />;;;
<get-var l />
Output:
7
7
downcase"S"β signature is"string"
Convert to lowercase letters. Shhh. π€«
Source:
<downcase "Does it work?" />
Output:
does it work?
upcase"S"β signature is"string"
Convert to uppercase letters. LOUDER. π’
Source:
<upcase "Does it work?" />
Output:
DOES IT WORK?
capitalize"S"β signature is"string"
Convert to a title, with a capital letter at the beginning of every word. Main Character Energy for your strings. π
Source:
<capitalize "Does it work?" />
Output:
Does It Work?
substring"S"β signature is"string" "[start [end]]"
Extracts a substring from a string. First argument is original string, second and third are respectively start and end indexes. By convention first character has a null index β so like, we start counting at zero, obviously. π’
Source:
<set-var foo="abcdefghijk" />
<substring <get-var foo /> 4 />
<substring <get-var foo /> 4 6 />
Output:
efghijk
ef
string-eq"S"β signature is"string1" "string2" "[caseless=true]"
Returns "true" if first two arguments are equal. If they're not? It just gives you nothing. Silent treatment fr fr. π
Source:
1:<string-eq "aAbBcC" "aabbcc" />
2:<string-eq "aAbBcC" "aAbBcC" />
Output:
1:
2:true
caseless=true Comparison is case insensitive. Case? Never heard of her. π
Source:
1:<string-eq "aAbBcC" "aabbcc" caseless=true />
2:<string-eq "aAbBcC" "aAbBcC" caseless=true />
Output:
1:true
2:true
string-neq"S"β signature is"string1" "string2" "[caseless=true]"
Returns "true" if the first two arguments are not equal. The hater of the equality family. π€
Source:
1:<string-neq "aAbBcC" "aabbcc" />
2:<string-neq "aAbBcC" "aAbBcC" />
Output:
1:true
2:
caseless=true Comparison is case insensitive.
Source:
1:<string-neq "aAbBcC" "aabbcc" caseless=true />
2:<string-neq "aAbBcC" "aAbBcC" caseless=true />
Output:
1:
2:
string-compare"S"β signature is"string1" "string2" "[caseless=true]"
Compares two strings and returns one of the values less, greater or equal depending on this comparison. It's giving referee energy. π§ββοΈ
Source:
1:<string-compare "aAbBcC" "aabbcc" />
2:<string-compare "aAbBcC" "aAbBcC" />
Output:
1:less
2:equal
caseless=true Comparison is case insensitive.
Source:
1:<string-compare "aAbBcC" "aabbcc" caseless=true />
Output:
1:equal
char-offsets"S"β signature is"string" "character" "[caseless=true]"
Prints an array containing indexes where the character appear in the string. Every single time it shows up, bestie. It's thorough. π
caseless=true Comparison is case insensitive.
Source:
1:<char-offsets "abcdAbCdaBcD" a />
2:<char-offsets "abcdAbCdaBcD" a caseless=true />
Output:
1:0
8
2:0
4
8
printf"S"β signature is"format" "string" "[string ...]"
Prints according to a given format. Currently only the %s flag character is recognized, and "$" extension is supported to change order of arguments. So you can be a little chaotic and reorder your args, no cap. π
Source:
1:<printf "foo %s bar %s" baz 10 />
2:<printf "foo %2$s bar %1$s" baz 10 />
Output:
1:foo baz bar 10
2:foo 10 bar baz
πΈ Regular Expressions πΈ
Regular expression support is provided by the PCRE (Perl Compatible Regular Expressions) library package, which is open source software, copyright by the University of Cambridge. This is a very nice piece of software, latest versions are available at "<URL:ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/>". Iconic library, honestly. π«Ά
Before version 1.0.6, POSIX regular expressions were implemented. For this reason, the following macros recognize two attributes, "caseless=true" and "singleline=true|false". But Perl allows a much better control on regular expressions with so called modifiers, which are assed to the new "reflags" attribute. It may contain one or more modifiers:
iβ Matching is case insensitivemβ Treat string as multiple lines. When set, a"^"matches any beginning of line, and"$"any end of line. By default, they match begin and end of string.sβ Treat string as single line. A dot (".") may also match a newline, whereas it does not by default.xβ Allow formatted regular expression, that means whitespaces, newlines and comments are removed from regular expression before processing.
Note: Attribute"singleline=true"is a synonym for the"s"modifier, whereas"singleline=false"is a synonym for the"m"modifier. This behaviour was different up to mp4h1.0.6. β οΈ So if you're on an old version, double-check yourself before you wreck yourself.
subst-in-string"S"β signature is"string" "regexp" "[replacement]" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]"
Replace a regular expression in a string by a replacement text. πͺ
Source:
<set-var foo="abcdefghijk" />
<subst-in-string <get-var foo /> "[c-e]" />
<subst-in-string <get-var foo /> "([c-e])" "\\1 " />
Output:
abfghijk
abc d e fghijk
Source:
<set-var foo="abcdefghijk\nabcdefghijk\nabcdefghijk" />
<subst-in-string <get-var foo /> ".$" "" />
<subst-in-string <get-var foo /> ".$" "" singleline=false />
<subst-in-string <get-var foo /> "
([a-c]) | [0-9]
" ":\\1:" reflags=x />
Output:
abcdefghijk
abcdefghijk
abcdefghij
abcdefghij
abcdefghij
abcdefghij
:a::b::c:defghijk
:a::b::c:defghijk
:a::b::c:defghijk
subst-in-var"S"β signature is"name" "regexp" "[replacement]" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]"
Performs substitutions inside variable content. Same slay, different target. π
match"S"β signature is"string" "regexp" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]" "[action=report|extract|delete|startpos|endpos|length]"
This one has options, and every single one is a mood:
action=report Prints "true" if string contains regexp.
action=extract Prints the expression matching regexp in string.
action=delete Prints the string without the expression matching regexp in string.
action=startpos Prints the first char of the expression matching regexp in string. If there is no match, returns -1. π
action=endpos Prints the last char of the expression matching regexp in string. If there is no match, returns -1. π
action=length Prints the length of the expression matching regexp in string.
Source:
1:<match "abcdefghijk" "[c-e]+" />
2:<match "abcdefghijk" "[c-e]+" action=extract />
3:<match "abcdefghijk" "[c-e]+" action=delete />
4:<match "abcdefghijk" "[c-e]+" action=startpos />
5:<match "abcdefghijk" "[c-e]+" action=endpos />
6:<match "abcdefghijk" "[c-e]+" action=length />
Output:
1:true
2:cde
3:abfghijk
4:2
5:5
6:3
π¦ Arrays π¦
With mp4h one can easily deal with string arrays. Variables can be treated as a single value or as a newline separated list of strings. Thus after defining
<set-var digits="0
1
2
3" />
one can view its content or one of these values:
Source:
<get-var digits />
<get-var digits[2] />
Output:
0
1
2
3
2
array-size"S"β signature is"name"
Returns an array size which is the number of lines present in the variable. Counting lines like it's a group chat member list. π
Source:
<array-size digits />
Output:
4
array-push"S"β signature is"name" "value"
Add a value (or more if this value contains newlines) at the end of an array. It's the "and one more thing" of arrays. β¨
Source:
<array-push digits "10\n11\n12" />
<get-var digits />
Output:
0
1
2
3
10
11
12
array-pop"S"β signature is"name"
Remove the toplevel value of an array and returns this string. Bye bestie, you're out of the group. π
array-topvalue"S"β signature is"name"
Prints the last entry of an array. The final boss. π₯
Source:
<array-topvalue digits />
Output:
12
array-add-unique"S"β signature is"name" "value" "[caseless=true]"
Add a value at the end of an array if this value is not already present in this variable. No duplicates allowed, we are not doing that today. π ββοΈ
Source:
<array-add-unique digits 2 />
<get-var digits />
Output:
0
1
2
3
10
11
12
caseless=true Comparison is case insensitive.
array-concat"S"β signature is"name1" "[name2] ..."
Concatenates all arrays into the first one. Gluing the girlies together. π«Ά
Source:
<set-var foo="foo" />
<set-var bar="bar" />
<array-concat foo bar /><get-var foo />
Output:
foo
bar
array-member"S"β signature is"name" "value" "[caseless=true]"
If value is contained in array, returns its index otherwise returns -1.
Source:
<array-member digits 11 />
Output:
5
caseless=true Comparison is case insensitive.
array-shift"S"β signature is"name" "offset" "[start=start]"
Shifts an array. If offset is negative, indexes below 0 are lost. If offset is positive, first indexes are filled with empty strings. It's giving sliding into your DMs, index-style. π
Source:
<array-shift digits 2 />
Now: <get-var digits />
<array-shift digits -4 />
And: <get-var digits />
Output:
Now:
0
1
2
3
10
11
12
And: 2
3
10
11
12
start=start Change origin of shifts (default is 0).
Source:
<array-shift digits -2 start=2 /><get-var digits />
Output:
2
3
12
sort"S"β signature is"name" "[caseless=true]" "[numeric=true]" "[sortorder=reverse]"
Sort lines of an array in place. Default is to sort lines alphabetically. Get you a macro that puts things in order, literally. π€
Source:
<sort digits /><get-var digits />
Output:
12
2
3
caseless=true Comparison is case insensitive.
numeric=true Sort lines numerically
Source:
<sort digits numeric=true /><get-var digits />
Output:
2
3
12
sortorder=reverse Reverse sort order
Source:
<sort digits numeric=true sortorder=reverse />;;;
<get-var digits />
Output:
12
3
2
π Numerical operators π
Okay so these operators perform basic arithmetic operations. When all operands are integers, the result is an integer too; otherwise it is a float. These operators are self-explanatory, but bestie we are still listing every single one because we do not skip.
add"S"β the add one, obvi.
"number1" "number2" "[number3] ..."
substract"S"β yep, spelledsubstractright here in the page, don't come for me. It's the subtract one.
"number1" "number2" "[number3] ..."
multiply"S"β times it up.
"number1" "number2" "[number3] ..."
divide"S"β division vibes.
"number1" "number2" "[number3] ..."
min"S"β the smallest one.
"number1" "number2" "[number3] ..."
max"S"β the biggest one.
"number1" "number2" "[number3] ..."
Source:
<add 1 2 3 4 5 6 />
<add 1 2 3 4 5 6. />
Output:
21
21.000000
Source:
<define-tag factorial whitespace=delete>
<ifeq %0 1 1 <multiply %0 "<factorial <substract %0 1 /> />" /> />
</define-tag>
<factorial 6 />
Output:
720
modulo"S"β the remainder girlie.
"number1" "number2"
Unlike functions listed above, the modulo function cannot handle more than 2 arguments, and these arguments must be integers.
Source:
<modulo 345 7 />
Output:
2
Those functions compare two numbers and return "true" when this comparison is true. If one argument is not a number, comparison is false.
gt"S"β returns"true"if first argument is greater than second.
"number1" "number2"
lt"S"β returns"true"if first argument is lower than second.
"number1" "number2"
eq"S"β returns"true"if arguments are equal.
"number1" "number2"
neq"S"β returns"true"if arguments are not equal.
"number1" "number2"
π¦ Relational operators π¦
not"S"β returns"true"if string is empty, otherwise returns an empty string.
"string"
and"S"β returns the last argument if all arguments are non empty.
"string" "[string] ..."
or"S"β returns the first non empty argument.
"string" "[string] ..."
π Flow functions π
group"S""V"β groups multiple statements into a single one. Some examples will be seen below with conditional operations.
"expression" "[expression] ..." "[separator=string]"
A less intuitive but very helpful use of this macro is to preserve newlines when whitespace=delete is specified.
Source:
<define-tag text1>
Text on
3 lines without
whitespace=delete
</define-tag>
<define-tag text2 whitespace=delete>
Text on
3 lines with
whitespace=delete
</define-tag>
<define-tag text3 whitespace=delete>
<group "Text on
3 lines with
whitespace=delete" />
</define-tag>
<text1/>
<text2/>
<text3/>
Output:
Text on
3 lines without
whitespace=delete
Text on3 lines withwhitespace=delete
Text on
3 lines with
whitespace=delete
Note that newlines are suppressed in text2 and result is certainly unwanted.
compoundβ likegroup, but this tag is complex.
"expression" "[expression] ..." "[separator=string]"
separator=string By default arguments are put aside. This attribute define a separator inserted between arguments.
disjoin"S"β does the opposite job togroup, its argument is no more treated as a single object when processed by another command.
"expression"
noexpand"S""V"β prints its arguments without expansion. They will never be expanded unless theexpandtag is used to cancel thisnoexpandtag.
"command" "[command] ..."
expand"S"β cancels thenoexpandtag.
"command" "[command] ..."
Source:
<subst-in-string "=LT=define-tag foo>bar=LT=/define-tag>" "=LT=" "<" />
<foo/>
<subst-in-string "=LT=define-tag foo>quux=LT=/define-tag>" "=LT="
"<noexpand "<" />" />
<foo/>
Output:
bar
<define-tag foo>quux</define-tag>
bar
if"S""V"β if string is non empty, second argument is evaluated otherwise third argument is evaluated.
"string" "then-clause" "[else-clause]"
Source:
<define-tag test whitespace=delete>
<if %0 "yes" "no" />
</define-tag>
<test "string" />
<test "" />
Output:
yes
no
ifeq"S""V"β if first two arguments are identical strings, third argument is evaluated otherwise fourth argument is evaluated.
"string1" "string2" "then-clause" "[else-clause]"
ifneq"S""V"β if first two arguments are not identical strings, third argument is evaluated otherwise fourth argument is evaluated.
"string1" "string2" "then-clause" "[else-clause]"
whenβ when argument is not empty, its body is evaluated.
"string"
while"V"β while condition is true, body function is evaluated.
"cond"
Source:
<set-var i=10 />
<while <gt <get-var i /> 0 />>;;;
<get-var i /> <decrement i />;;;
</while>
Output:
10 9 8 7 6 5 4 3 2 1
foreachβ this macro is similar to theforeachPerl's macro: a variable loops over array values and function body is evaluated for each value.
"variable" "array" "[start=start]" "[end=end]" "[step=pas]"
first argument is a generic variable name, and second is the name of an array.
Source:
<set-var x="1\n2\n3\n4\n5\n6" />
<foreach i x><get-var i /> </foreach>
Output:
1 2 3 4 5 6
start=start Skips first indexes.
Source:
<set-var x="1\n2\n3\n4\n5\n6" />
<foreach i x start=3><get-var i /> </foreach>
Output:
4 5 6
end=end Stops after index has reached that value.
Source:
<set-var x="1\n2\n3\n4\n5\n6" />
<foreach i x end=3><get-var i /> </foreach>
Output:
1 2 3
step=step Change index increment (default is 1). If step is negative, array is treated in reverse order.
Source:
<set-var x="1\n2\n3\n4\n5\n6" />
<foreach i x step=2><get-var i /> </foreach>
<foreach i x step=-2><get-var i /> </foreach>
Output:
1 3 5
6 4 2
var-case"S""V"β this command performs multiple conditions with a single instruction.
"var1=value1 action1" "[var2=value2 action2 ..."
Source:
<set-var i=0 />
<define-tag test>
<var-case
x=1 <group <increment i /> x<get-var i /> />
x=2 <group <decrement i /> x<get-var i /> />
y=1 <group <increment i /> y<get-var i /> />
y=2 <group <decrement i /> y<get-var i /> />
/>
</define-tag>
<set-var x=1 y=2 /><test/>
<set-var x=0 y=2 /><test/>
Output:
x1y0
y-1
break"S"β breaks the innermostwhileloop. Source:
<set-var i=10 />
<while <gt <get-var i /> 0 />>;;;
<get-var i /> <decrement i />;;;
<ifeq <get-var i /> 5 <break/> />;;;
</while>
Output:
10 9 8 7 6
return"S"β this command immediately exits from the innermost macro. A message may also be inserted.
"[up=number]" "string"
But this macro changes token parsing so its use may become very hazardous in some situations.
up=number This attribute determines how much levels have to be exited. By default only one level is skipped. With a null value, all current macros are exited from. A negative value do the same, and stops processing current file.
warning"S"β prints a warning on standard error.
"string"
exit"S"β immediately exits program.
"[status=rc]" "[message=string]"
message=string Prints a message to the standard error.
status=rc Selects the code returned by the program (-1 by default).
at-end-of-fileβ this is a special command: its content is stored and will be expanded after end of input.
π File functions π
directory-contents"S"β returns a newline separated list of files contained in a given directory.
"dirname" "[matching=regexp]"
Source:
<directory-contents . matching=".*\\.mp4h$" />
Output:
real-path"S"β resolves all symbolic links, extra/characters and references to/./and/../in pathname, and expands into the resulting absolute pathname. All but the last component of pathname must exist when real-path is called.
"patname=pathname"
This tag is particularly useful when comparing if file or directory names are identical.
Source:
<real-path pathname=<__file__/> />
Output:
/build/source/src/wml_backend/p2_mp4h/doc/mp4h.mp4h
file-exists"S"β returns"true"if file exists.
"filename"
get-file-properties"S"β returns an array of file properties values. These values are (in order): size, type, ctime, mtime, atime, owner and group.
"filename"
Source:
<get-file-properties <__file__/> />
Output:
68678
FILE
1792784871
1
1792203947
nixbld
nixbld
include"S"β insert the contents of a file in the file system β if thefileattribute is given β or the output from executing a system command β if thecommandattribute is given β into the input stream. For backwards compatibility, if neither thefilenor thecommandattributes are given, the first argument is taken as a file to include.
"file=filename | command=command-line" "[alt=action]"
"[verbatim=true]"
file=filename The given file is read and inserted into the input stream. This attribute cannot be combined with the command attribute.
Files are first searched in current directory, then in directories specified on command-line with the -I option, next in directories listed in the MP4HLIB environment variable (it used to be MP4HPATH for versions prior to 1.3), and last under the compile-time location ("/usr/local/lib/mp4h/1.3.0:/usr/local/share/mp4h" by default).
command=command-line The given command line is executed on the operating system, and the output of it is inserted in the input stream. This attribute cannot be combined with the file attribute.
The given command line is executed using the popen(3) standard C library routine. The command is executed using the standard system shell, which on Posix compliant systems is sh(1).
"alt=action"β if the file isn't found, this alternate action gets handled instead. But bestie, if this attribute isn't set and the file isn't found? yeah that's an error, it gets raised. Also heads up: this attribute has no effect when thecommandattribute is specified. none. zero. π"verbatim=true"β the file content is included without expansion. it's basically the same vibe as using them4undivertmacro with a filename as argument.
Source:
<include command="uname -a" />
Output:
Linux localhost 7.2.11 #1-NixOS SMP PREEMPT Thu Oct 15 10:00:08 UTC 2026 aarch64 GNU/Linux
useS
"name=package"
Load definitions from a package file. that's it, that's the whole job β¨
comment
This tag literally does nothing β its body is just discarded. main character energy of being unbothered tbh π
set-eol-commentS
"[string]"
Change comment characters. swap the fit, keep the function π
set-quotesS
"[string string]" "[display=visible]"
So by default, all characters between <@[ and ]@> pairs are read without parsing. When you call this macro with no argument at all, it inhibits this feature. When you call it with two arguments, it redefines the begin and end delimiters. Rule check tho: the begin delimiter must begin with a left-angle bracket, and the end delimiter must end with a right-angle bracket. no exceptions bestie π₯
"display=visible"
Delimiters are also written into output. so you actually see them. iconic.
π Diversion functions π
Okay so diversions are literally a way of temporarily saving output. The output of mp4h can at any time be diverted to a temporary file, and then be reinserted into the output stream β undiverted β again at a later time. it's giving "hold my text, I'll be right back" π«Ά
Numbered diversions are counted from 0 upwards, and diversion number 0 is the normal output stream. The number of simultaneous diversions is limited mainly by the memory used to describe them, because mp4h tries to keep diversions in memory. However β and this is the important bit β there's a limit to the overall memory usable by all diversions taken altogether. When that maximum is about to be exceeded, a temporary file is opened to receive the contents of the biggest diversion still in memory, freeing that memory for the other diversions. So theoretically, it's possible that the number of diversions gets limited by the number of available file descriptors. lowkey wild but okay π
divertS
"[ divnum=diversion-number ]"
Output is diverted using this tag, where diversion-number is the diversion to be used. If the divnum attribute is left out, diversion-number is assumed to be zero. If output is diverted to a non-existent diversion, it's simply discarded β which is genuinely useful for suppressing unwanted output. see the example below π
When all mp4h input has been processed, all existing diversions are automatically undiverted, in numerical order. no manual cleanup needed, slay.
Several calls of divert with the same argument do not overwrite the previous diverted text β they append to it.
Source:
<divert divnum="-1"/>
This is sent nowhere...
<divert/>
This is output.
Output:
This is sent nowhere...
This is output.
undivertS
"[ divnum=diversion-number ]"
This tag explicitly undiverts diverted text saved in the diversion with the specified number. If the divnum attribute is not given, all diversions are undiverted, in numerical order.
Important detail: when diverted text is undiverted, it is not reread by mp4h β it's copied directly to the current output. Which means it is not an error to undivert into a diversion. go off.
Unlike m4, the mp4h undivert tag does not allow a file name as argument. You can accomplish the same thing with the include tag using the verbatim="true" attribute. iykyk π¦
Source:
<divert divnum="1"/>
This text is diverted.
<divert/>
This text is not diverted.
<undivert divnum="1"/>
Output:
This text is diverted.
This text is not diverted.
divnumS
This tag expands to the number of the current diversion. simple, effective, iconic.
Source:
Initial <divnum/>
<divert divnum="1"/>
Diversion one: <divnum/>
<divert divnum="2"/>
Diversion two: <divnum/>
<divert/>
Output:
Initial 0
Diversion one: 1
Diversion two: 2
π Debugging functions π
When constructs become complex it can be genuinely hard to debug them. The functions listed below are super useful when you just cannot figure out what is wrong. Fair warning tho: these functions are not perfect yet and must be improved in future releases. π
function-defS
"name"
Prints the replacement text of a user defined macro. For instance, the macro used to generate all the examples of this documentation is:
Source:
<function-def example />
Output:
<set-var-verbatim verb-body=%ubody /><subst-in-var verb-body "<" "<" />
<subst-in-var verb-body ">" ">" /><subst-in-var verb-body "^\n*" "" /><subst-in-var verb-body "^" " " reflags=m /><set-var body=%body /><subst-in-var body "<three-colon/>[^;\n]*\n[ \t]*" "" /><subst-in-var body "<three-colon/>$" "" reflags=m /><subst-in-var body "^\n*" "" /><subst-in-var body "^" " " reflags=m /><group "Source:
<get-var-once verb-body />
Output:
<get-var-once body />
" />
debugmodeS
"string"
This command acts like the -d flag but can be dynamically changed. π
debugfileS
"filename"
Selects a file where debugging messages are diverted. If this filename is empty, debugging messages are sent back to standard error, and if it is set to -, these messages are discarded.
Note: there is no way to print these debugging messages into the document being processed. nope. not a thing. π
debugging-onS
"name" "[name] ..."
Declare these macros traced, i.e. information about these macros will be printed if the -d flag or the debugmode macro are used. π
debugging-offS
"name" "[name] ..."
These macros are no more traced. bye bestie π«Ά
π Miscellaneous π
__file__S
"[name]"
Without an argument, this macro prints the current input filename. With an argument, this macro sets the string returned by future invocations of this macro. β¨
__line__S
"[number]"
Without an argument, this macro prints the current line number in the input file. With an argument, this macro sets the number returned by future invocations of this macro.
Source:
This is <__file__/>, line <__line__/>.
Output:
This is /build/source/src/wml_backend/p2_mp4h/doc/mp4h.mp4h, line 2201.
And if you closely look at the source code you'll see that this number is wrong. Yup fr. The number line is actually the end of the entire block containing this instruction. π we love a documented bug.
__version__S
Prints the version of mp4h. that's the whole thing. π
dnlS
Discard all characters until newline is reached. This macro ensures the following string is a comment and does not depend on the value of the comment characters. so it always works, no cap.
Source:
<dnl/>This is a comment
foo
<dnl/>This is a comment
bar
Output:
foo
bar
dateS
"[epoch]"
Prints local time according to the epoch passed as argument. If there's no argument, the current local time is printed. β°
"time"
An epoch time specification.
"format"
A format specification as used with the strftime(3) C library routine.
Source:
<date/>
<set-var info=<get-file-properties <__file__/> /> />
<date <get-var info[2] /> />
<date time="<get-var info[2] />" format="%Y-%m-%d %H:%M:%S" />
Output:
Fri Oct 23 19:48:39 2026
Fri Oct 23 19:47:51 2026
2026-10-23 19:47:51
timerS
Prints the time spent since the last call to this macro. The printed value is the number of clock ticks, and so is dependent on your CPU. yes, your CPU specifically. π
Source:
The number of clock ticks since the beginning of generation of
this documentation by &mp4h; is:
<timer/>
Output:
The number of clock ticks since the beginning of generation of
this documentation by B<mp4h> is:
user 1
sys 0
mp4h-l10nS
"name=value"
Set locale-specific variables. By default, the portable C locale is selected. Since locales have different names on different platforms, you must refer to your system documentation to find which values are adapted to your system. no guessing, go read the docs bestie π
mp4h-output-radixS
"number"
Change the output format of floats by setting the number of digits after the decimal point. Default is to print numbers in the %6.f format.
Source:
<add 1.2 3.4 />
<mp4h-output-radix 2 />
<add 1.2 3.4 />
Output:
4.600000
4.60
π¦ EXTERNAL PACKAGES π¦
So it's possible to include external files with the include command. Files are searched in this exact order, bestie, don't skip steps:
- first in the current directory,
- then in directories specified on the command line with the
-Ioption, - next in directories listed in the
MP4HLIBenvironment variable (it used to beMP4HPATHfor versions prior to 1.3), - and last under the compile-time location (
/usr/local/lib/mp4h/1.3.0:/usr/local/share/mp4hby default).
Another way to include packages is with the use command. There are two differences between use and include: first, the package name has no suffix; and more importantly, a package cannot be loaded more than once. one and done, periodt. π₯
π§ MACRO EXPANSION π§
This part describes the internal mechanism of macro expansion. It must be as precise and exhaustive as possible, so contact me at <URL:mailto:barbier@imacs.polytechnique.fr> if you have any suggestion. fr fr, the author wants your feedback π
π¦ Basics π¦
Let's begin with some examples:
Source:
<define-tag foo>
This is a simple tag
</define-tag>
<define-tag bar endtag=required>
This is a complex tag
</define-tag>
<foo/>
<bar>Body function</bar>
Output:
This is a simple tag
This is a complex tag
User defined macros may have attributes, just like HTML tags. To handle these attributes in the replacement text, the following conventions have been adopted (mostly derived from Meta-HTML):
- The sequence
%nameis replaced by the command name. π·οΈ - Attributes are numbered from 0. In the replacement text,
%0is replaced by the first argument,%1by the 2nd, etc. And since there's no limitation on the number of arguments,%20is the 21st argument β not the third followed by the0letter. read that again, bestie π
Source:
<define-tag href>
<a href="%0">%1</a>
</define-tag>
<href http://www.gimp.org "The Gimp" />
Output:
<a href="http://www.gimp.org">The Gimp</a>
- The sequence
%#prints the number of attributes. π’ - The sequence
%%is replaced by%, which is useful in nested definitions.
Source:
Here's the %# situation in action, because nested macros were doing the absolute most:
<define-tag outer>;;;
outer, # attributes: %#
<define-tag inner1>;;;
inner1, # attributes: %#;;;
</define-tag>;;;
<define-tag inner2>;;;
inner2, # attributes: %%#;;;
</define-tag>;;;
<inner1 %attributes and some others />
<inner2 %attributes and some others />
</define-tag>
<outer list attributes />
Output:
outer, # attributes: 2
inner1, # attributes: 2
inner2, # attributes: 5
- Sequence
%attributesis replaced by the space separated list of attributes.
Source:
<define-tag mail1>
<set-var %attributes />
<get-var name />
<get-var mail />
</define-tag>
<set-var name="" mail="" />
<mail1 name="Dr. Foo" mail="hello@foo.com" />
Output:
Dr. Foo
hello@foo.com
- Sequence
%bodyis replaced by the body of a complex macro.
Source:
<define-tag mail2 endtag=required whitespace=delete>
<set-var %attributes />
<a href="mailto:<get-var mail />">%body</a>
</define-tag>
<mail2 mail="hello@foo.com">
<img src="photo.png" alt="Dr. Foo" border=0 />
</mail2>
Output:
<a href="mailto:hello@foo.com">
<img src="photo.png" alt="Dr. Foo" border=0 />
</a>
- The two forms above accept modifiers. When
%Aattributesor%Abodyis used, a newline separated list of attributes is printed.
Source:
<define-tag show-attributes whitespace=delete>
<set-var list="%Aattributes" i=0 />
<foreach attr list>
<group "%<get-var i />: <get-var attr />" />
<increment i />
</foreach>
</define-tag>
<show-attributes name="Dr. Foo" mail="hello@foo.com" />
Output:
%0: name=Dr. Foo%1: mail=hello@foo.com
- Another alternate form is obtained by replacing
"A"by"U", in which case text is replaced but will not be expanded. This does make sense only when macro has been defined with"attributes=verbatim", otherwise attributes are expanded before replacement.
Source:
<define-tag show1>
Before expansion: %Uattributes
After expansion: %attributes
</define-tag>
<define-tag show2 attributes=verbatim>
Before expansion: %Uattributes
After expansion: %attributes
</define-tag>
<define-tag bar>and here %attributes</define-tag>
<show1 <bar we go /> />
<show2 <bar we go /> />
Output:
Before expansion: and here we go
After expansion: and here we go
Before expansion: <bar we go />
After expansion: and here we go
- Modifiers
"A"and"U"can be combined.
Note: Input expansion is completely different in Meta-HTML and in mp4h. With Meta-HTML it is sometimes necessary to use other constructs like%xbodyand%qbody. In order to improve compatibility with Meta-HTML, these constructs are recognized and are interpreted like%body. Another feature provided for compatibility reason is the fact that for simple tags%bodyand%attributesare equivalent. These features are in the current mp4h version but may disappear in future releases.
πΈ Attributes πΈ
Attributes are separated by spaces, tabulations or newlines, and each attribute must be a valid mp4h entity. For instance with the definitions above, "<bar>" can not be an attribute since it must be finished by "</bar>". But this is valid:
<foo <foo/> />
or even
<foo <foo name=src url=ici /> />
In these examples, the "foo" tag has only one argument.
Under certain circumstances it is necessary to group multiple statements into a single one. This can be done with double quotes or with the "group" primitive, e.g.
<foo "This is the 1st attribute"
<group and the second /> />
Note: Unlike HTML single quotes can not replace double quotes for this purpose.
If double quotes appear in an argument, they must be escaped by a backslash "\".
Source:
<set-var text="Text with double quotes \" inside" />
<get-var text />
Output:
Text with double quotes " inside
π§ Macro evaluation π§
Macros are characterized by
- name
- container status (simple or complex)
- if attributes are expanded or not
- function type (primitive or user defined macro)
- for primitives, address of corresponding code in memory and for user defined macros the replacement text
Characters are read on input until a left angle bracket is found. Then macro name is read. After that attributes are read, verbatim or not depending on how this macro as been defined. And if this macro is complex, its body is read verbatim. When this is finished, some special sequences in replacement text are replaced (like %body, %attributes, %0, %1, etc.) and resulting text is put on input stack in order to be rescanned.
Note: By default attributes are evaluated before any replacement.
Consider the following example, to change text in typewriter font:
<define-tag text-tt endtag=required whitespace=delete>
<tt>%body</tt>
</define-tag>
This definition has a major drawback:
Source:
<text-tt>This is an <text-tt>example</text-tt></text-tt>
Output:
<tt>This is an <tt>example</tt></tt>
We would like the inner tags be removed.
First idea is to use an auxiliary variable to know whether we still are inside such an environment:
<set-var _text:tt=0 />
<define-tag text-tt endtag=required whitespace=delete>
<increment _text:tt />
<ifeq <get-var _text:tt /> 1 "<tt*>" />
%body
<ifeq <get-var _text:tt /> 1 "</tt*>" />
<decrement _text:tt />
</define-tag>
(the presence of asterisks in HTML tags is explained in next section).
Source:
<text-tt>This is an <text-tt>example</text-tt></text-tt>
Output:
<tt>This is an example</tt>
But if we use simple tags, as in the example below, our definition does not seem to work. It is because attributes are expanded before they are put into replacement text.
Source:
<define-tag opt><text-tt>%attributes</text-tt></define-tag>
<opt "This is an <opt example />" />
Output:
<tt>This is an <tt>example</tt></tt>
If we want to prevent this problem we have to forbid attributes expansion with
Source:
<define-tag opt attributes=verbatim>;;;
<text-tt>%attributes</text-tt>;;;
</define-tag>
<opt "This is an <opt example />" />
Output:
<tt>This is an example</tt>
π© Expansion flags π©
When you want to embed some server-side scripting language in your pages, you face up some weird problems, like in
<a href=<%= $url %>>Hello</a>
The question is how do mp4h know that this input has some extra delimiters? The answer is that mp4h should not try to handle some special delimiters, because it cannot handle all of them (there are ASP, ePerl, PHP,... and some of them are customizable). Now, remember that mp4h is a macro-processor, not an XML parser. So we must focus on macros, and format our input file so that it can be parsed without any problem. Previous example may be written
<a href="<%= $url %>">Hello</a>
because quotes prevent inner right-angle bracket from closing the "a" tag.
Another common problem is when we need to print only a begin or an end tag alone. For instance it is very desirable to define its own headers and footers with
<define-tag header>
<html*>
<head>
... put some information here ....
</head>
<body* bgcolor="#ffffff" text="#000000">
</define-tag>
<define-tag footer>
</body*>
</html*>
</define-tag>
Asterisks mark these tags as pseudo-simple tags, which means that they are complex HTML tags, but used as simple tags within mp4h because tags would not be well nested otherwise.
This asterisk is called ``trailing star'', it appears at the end of the tag name.
Sometimes HTML tags are not parsable, as in this javascript code:
...
document.write('<*img src="foo.gif"');
if (text) document.write(' alt="'+text+'"');
document.write('>');
...
The ``leading star'' is an asterisk between left-angle bracket and tag name, which prevents this tag from being parsed.
That said we can now understand what the "--expansion" flag is for. It controls how expansion is performed by mp4h. It is followed by an integer, which is a bit sum of the following values
1β do not parse unknown tags. When set, HTML tags are not parsed. When unset, HTML tags are parsed, i.e. that attributes and/or body is collected.2β unknown tags are assumed being simple. When set, HTML tags are simple by default. When unset, HTML tags are complex by default, unless their attribute contain a trailing slash or a trailing star appear just after tag name (see below).4β trailing star in tag name do not make this tag simple. When set, trailing star in tag name has no special effect. When unset, it causes an HTML tag to be simple.8β an unmatched end tag closes all previous unmatched begin tags. When set, all missing end closing tags are automatically inserted. When unset, an unmatched end tag is discarded and interpreted as normal text, so processing goes on until matching and tag is found.16β interpret backslashes as printf. When set, backslashes before non special characters are removed. When unset, they are preserved.32β remove trailing slash in tag attributes. When set, remove trailing slash in tag attributes on output. When unset, they are preserved.64β do not remove trailing star in tag name. When set, trailing star after tag name are preserved on output. When unset, they are removed.128β do not remove leading star in tag name. When set, leading star before tag name are preserved on output. When unset, they are removed.256β do not add a space before trailing slash in tag attributes. By default, a space is inserted before trailing slash in tag attributes. When set, this space is not prepended.1024β suppress warnings about bad nested tags. When set, warnings about bad nested tags are not displayed. When unset, they are printed on standard error.2048β suppress warnings about missing trailing slash. When set, warnings about missing trailing slash are not displayed. When unset, they are printed on standard error.
Run
mp4h -h
to find default value. Current value matches HTML syntax, and it will tend to zero when XHTML syntax becomes more familiar.
β¨ AUTHOR β¨
So who actually made this iconic little thing? That would be Denis Barbier π and you can absolutely slide into his inbox at:
Denis Barbier "<URL:mailto:barbier@imacs.polytechnique.fr>"
Mp4h also has its own homepage, like a whole main-character web residence π β¨:
"<URL:http://www.engelschall.com/sw/mp4h/>"
Go visit, bestie, it's giving home base π¦
π THANKS π
Okay but fr fr, none of this exists in a vacuum and the author is not about to pretend otherwise π«Ά.
- Sincere thanks to Brian J. Fox for writing Meta-HTML β literally the blueprint, no cap π₯
- Sincere thanks to Rene Seindal... wait, make that Rene Seindal π β for maintaining this wonderful macro parser called GNU m4, which is the whole engine room of the operation π
Like, the gratitude here is genuine and also extremely deserved. Both of these people ate and left no crumbs π
And that's the whole page, bestie! You made it to the end π
Sign-off szn: this manual is brought to you by EN Tools, stamped with the very iconic date 1980-01-01, and it lives in section MP4H(1) of your manual pages π Go forth and expand some macros, you gorgeous nerd π§β¨
π¦ where this page comes from
- nixpkgs unstable: wml 2.32.0-unstable-2026-05-08
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms