MP4H(1)
๐ธ MP4H(1) โ HTML Tools ๐
ok so bestie, welcome to the mp4h manual, aka Macro Processor for HTML Documents, and it lives in the HTML Tools section of your manual pages. grab a strawberry drink ๐, this one's long but it slays.
โจ NAME โจ
mp4h โ Macro Processor for HTML Documents ๐
โจ VERSION โจ
this whole doc is describing mp4h version 1.3.1. periodt. ๐
โจ INTRODUCTION โจ
ok so picture this: mp4h is a macro-processor that was literally built for HTML documents. it lets you do powerful programming-construct things, but the syntax is giving very much "familiar to HTML authors" energy, so if you write HTML you're already halfway home. ๐ โจ
this software is based on Meta-HTML <URL:http://www.metahtml.org/>, which was written by Brian J. Fox. and yeah, even though both syntaxes look similar, the source code is completely different. fr fr. a subset of Meta-HTML was used as part of a bigger, more complex program called WML (Website Meta Language) <URL:http://www.thewml.org/>, written by Ralf S. Engelschall, and which the author has maintained since January 1999. but for licensing reasons it was hard to hack Meta-HTML, so the move was to just write a whole new macro-processor. iconic, honestly. ๐
and instead of rewriting everything from scratch, the author chose to build on another macro-processor engine: GNU m4 <URL:http://www.gnu.org/software/m4/>, written by Rene Seindal. why? because it's got advantages for days โ 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. iykyk.
bestie, real talk:mp4his not an HTML editor. its one and only goal is giving you an easy way to define your own macros inside HTML documents. there is no plan to add functionality that automagically produces valid HTML documents. so if you wanna clean up your code or validate it, just use a post-processor liketidy<URL:http://www.w3.org/People/Raggett/tidy/>. ๐งน
โจ COMMAND LINE OPTIONS โจ
optional arguments are wrapped in square brackets, okay? and all option synonyms have a similar syntax, so when a long option takes an argument, the short option does too. no cap. ๐ซถ
the 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โ output mp4h version 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,1disable"execute"and2disable this one too plus all filesystem related functions:"file-exists","real-path","get-file-properties","directory-contents"and"include".
โ ๏ธ okay the safety levels are literally the vibe check for risky functions โ crank it up if you don't trust the input, bestie.
๐ธ Preprocessor features
-I,--include="DIRECTORY"โ search this directory for includes and packages-D,--define="NAME"[=VALUE]""โ set variable NAME to VALUE, or empty-U,--undefine="COMMAND"โ delete builtin COMMAND-s,--synclinesโ generate `#line NO "FILE"' lines
๐ฆ Parser features
-c,--caseless="NUMBER"โ set case sensitiveness according to the bits of"NUMBER". 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 are`8bit'(default) or`utf8'.-X,--expansion="NUMBER"โ set parser behaviour according to the bits of"NUMBER"
NUMBER is a combination of
1do not parse unknown tags2unknown tags are assumed being simple4trailing star in tag name do not make this tag simple8an unmatched end tag closes all previous unmatched begin tags16interpret backslashes as printf32remove trailing slash in tag attributes64do not remove trailing star in tag name128do not remove leading star in tag name256do not add a space before trailing slash in tag attributes1024suppress warnings about bad nested tags2048suppress warnings about missing trailing slash
In version 1.3.1, 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 (no FLAGS implies`aeq')-t,--trace="NAME"โ trace NAME when it will be defined-l,--arglength="NUMBER"โ restrict macro tracing size-o,--error-output="FILE"โ redirect debug and trace output
Flags are any of:
ttrace for all macro calls, not only debugging-on'edashow actual argumentseshow expansioncshow before collect, after collect and after callxadd a unique macro call id, useful withcflagfsay current input file namelsay current input line numberpshow results of path searchesmshow results of module operationsishow changes in input filesVshorthand for all of the above flags
โจ DESCRIPTION โจ
ok so mp4h is a macro-processor, which literally means keywords get replaced by other text. this chapter describes all the primitives. ๐
and since mp4h was specially designed for HTML documents, its syntax is very similar to HTML โ tags and attributes, the whole thing. but there's one important feature that has no equivalent in HTML: comments until end of line. everything after three colons gets yeeted until the end of the line, like:
;;; This is a comment
๐ง Function Macros
note bestie: all the examples in this documentation are processed through mp4h with expansion flags set to zero (there's a description of possible expansion flags at the end of the document), which is why simple tags contain a trailing slash. but mp4h can output plain HTML files with other expansion flags. ๐
defining new tags is like, the most common task mp4h does. and just like HTML, macro names are case insensitive, unless the "-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 looks like:
<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's good practice to always append a trailing slash to simple tags. ๐
in the macro descriptions below, a slash indicates a simple tag, and a V letter means 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. so it's the "only if not already here" bestie. ๐
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. bye bye ๐
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. so you can glow up a macro without touching its whole structure. ๐ โจ
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:
okay so picking up right where we left off ๐
โ here's that attributes-extract example finishing its little moment:
<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"โ signature is"name1" "[,name2[,name3...]]" "%attributes". It removes from%attributesall theattr=valuepairs whose names match any of name1, name2, โฆโฆ. Like a little cleanup crew ๐งน
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" />
Note: these two previous functions are special besties, because unlike literally every other macro, their expansion does not form a group. That's necessary so the resulting list of attributes can actually be parsed. no cap.
In those two functions, the names of attributes can be regular expressions ๐ The whole point of these primitives is to help you write macros that accept any kind of attributes without having to declare them all up front. A canonical example is this:
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 say we want to add an image attribute too. So we'd 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>
So 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 this:
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 uses 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 the regular expressions, they 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. iykyk ๐
๐ธ Entities ๐ธ
Entities are macros in exactly the same way as tags, except they don't take any arguments. Tags are normally used to mark up text, but entities contain text that's already marked up. Also โ heads up โ unlike tags, entities are case sensitive by default. ๐
An entity looks like this:
&entity;
define-entityโ takes"name". This function is how you define your own entities. The first argument is the entity name, and the replacement text is the function body. Slay ๐
Source:
<define-entity foo>bar</define-entity>
&foo;
Output:
bar
โจ Variables โจ
Variables are a special case of simple tags, because they do not accept attributes. Their whole vibe is different too, since variables contain text whereas macros act like operators. A really nice feature about variables is that you can manipulate them like arrays ๐ฆ For real โ variables can be treated as newline-separated lists, which unlocks some powerful manipulation functions, as we'll see below.
set-var"S"โ takes"name[=value]" "[name[=value]] ...". This command sets variables. ๐set-var-verbatim"S""V"โ takes"name[=value]" "[name[=value]] ...". Same as above, but the attributes are read verbatim.set-var-xโ takes"name=variable-name". This command assigns a variable the value of the body of the command. It's especially useful when the variable values contain newlines and/or quotes. ๐
Note that the variable can not be indexed with this command. Also note that this command behaves likeset-var-verbatim: the body isn't expanded until the variable is shown withget-var.
get-var"S"โ takes"name" "[name] ...". Shows the variable contents. If a numeric value inside square brackets is appended to a variable name, it's the index of an array. The first index of arrays is0by convention. โญ
Source:
<set-var version="0.10.1" />
This is version <get-var version />
<set-var-x name="osversion">Operating system is
"<include command="uname" />"</set-var-x>
<get-var osversion />
Output:
This is version 0.10.1
Operating system is
"Linux
"
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"โ takes"name" "[name] ...". Same as above, but the 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"โ takes"name" "[name] ...". All variables are global, bestie โ there is no variable or macro scope. That's exactly why a stack is used to preserve variables. When this command is invoked, the arguments are the names of variables whose values get put at the top of the stack, and the variables are reset to an empty string. ๐ซถrestore"S"โ takes"name" "[name] ...". This is the opposite: the arguments are the names of variables that get set to the value found at the top of the stack, and then 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"โ takes"name" "[name] ...". Undefines variables. bye bye ๐var-exists"S"โ takes"name". Returns"true"when this variable exists.increment"S"โ takes"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"โ takes"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"โ takes"src""dest". Copies a variable into another one. ๐ฆ
Source:
<set-var i=10 />
<copy-var i j />
<get-var j />
Output:
10
defvar"S"โ takes"name""value". If this variable isn't defined, or is defined to an empty string, then it gets 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"โ takes"name". Shows info on symbols. If it's a variable name, the"STRING"word gets printed, plus the number of lines contained within that variable. ๐
If it's a macro name, one of these messages gets 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:
STRING
5
PRIM TAG
PRIM COMPLEX
USER TAG
USER COMPLEX
โจ String Functions โจ
okay bestie, this is the fun part โ all the little macros that play with text ๐
string-length "S"โ feed it a"string"and it tells you how long that string is. that's it. that's the whole vibe. โจ
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"โ takes a"string"and makes it all lowercase letters. soft launch energy ๐
Source:
<downcase "Does it work?" />
Output:
does it work?
upcase "S"โ takes a"string"and SHOUTS IT. uppercase everything, main character behaviour ๐
Source:
<upcase "Does it work?" />
Output:
DOES IT WORK?
capitalize "S"โ takes a"string"and turns it into a title, with a capital letter at the beginning of every word. very proper, very demure ๐
Source:
<capitalize "Does it work?" />
Output:
Does It Work?
substring "S"โ slices a piece out of a string. the arguments go"string" "[start [end]]": first one is the original string, then start index, then end index. and by convention the first character has a null index (so it's zero-based, iykyk). ๐
Source:
<set-var foo="abcdefghijk" />
<substring <get-var foo /> 4 />
<substring <get-var foo /> 4 6 />
Output:
efghijk
ef
string-eq "S"โ args are"string1" "string2" "[caseless=true]". it hands you back"true"if the first two arguments are equal. periodt. โจ
Source:
1:<string-eq "aAbBcC" "aabbcc" />
2:<string-eq "aAbBcC" "aAbBcC" />
Output:
1:
2:true
caseless=true โ the comparison is case insensitive.
Source:
1:<string-eq "aAbBcC" "aabbcc" caseless=true />
2:<string-eq "aAbBcC" "aAbBcC" caseless=true />
Output:
1:true
2:true
string-neq "S"โ args are"string1" "string2" "[caseless=true]". returns"true"if the first two arguments are not equal. the little hater of the string world ๐
Source:
1:<string-neq "aAbBcC" "aabbcc" />
2:<string-neq "aAbBcC" "aAbBcC" />
Output:
1:true
2:
caseless=true โ the 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"โ args are"string1" "string2" "[caseless=true]". it compares two strings and returns one ofless,greaterorequaldepending on how they stack up against each other. vibe check central ๐ฅ
Source:
1:<string-compare "aAbBcC" "aabbcc" />
2:<string-compare "aAbBcC" "aAbBcC" />
Output:
1:less
2:equal
caseless=true โ the comparison is case insensitive.
Source:
1:<string-compare "aAbBcC" "aabbcc" caseless=true />
Output:
1:equal
char-offsets "S"โ args are"string" "character" "[caseless=true]". it prints an array with all the indexes where that character shows up in the string. like a little map of where your fave letter is hiding ๐ฆ
caseless=true โ the 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"โ args are"format" "string" "[string ...]". it prints according to a given format. right now only the%sflag character is recognized, and the"$"extension is supported so you can change the order of the arguments. fancyyy ๐
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 ๐ธ
bestie, regex support is provided by the PCRE (Perl Compatible Regular Expressions) library package, which is open source software, copyright by the University of Cambridge. it's genuinely a very nice piece of software, and the latest versions live at ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/. iconic. ๐
before version 1.0.6, POSIX regular expressions were what got implemented. so for that reason, the following macros recognize two attributes:caseless=trueandsingleline=true|false. but Perl gives you so much better control over regular expressions with these things called modifiers, which get passed to the newreflagsattribute. 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, which means whitespaces, newlines and comments are removed from the regular expression before processing.
Note: the attributesingleline=trueis a synonym for thesmodifier, whereassingleline=falseis a synonym for themmodifier. this behaviour was different up to mp4h 1.0.6, so don't get it twisted ๐
subst-in-string "S"โ args are"string" "regexp" "[replacement]" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]". it replaces a regular expression inside a string with a replacement text. glow-up for your text fr fr โจ
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"โ args are"name" "regexp" "[replacement]" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]". it performs substitutions inside variable content. same energy as above but it lives in your variable ๐ซถmatch "S"โ args are"string" "regexp" "[caseless=true]" "[singleline=true|false]" "[reflags=[imsx]]" "[action=report|extract|delete|startpos|endpos|length]". this one is the multi-tool and I'm obsessed ๐action=reportโ prints"true"if the string contains the regexp.action=extractโ prints the expression matching the regexp in the string.action=deleteโ prints the string without the expression matching the regexp in the string.action=startposโ prints the first char of the expression matching the regexp in the string. if there is no match, it returns"-1".action=endposโ prints the last char of the expression matching the regexp in the string. if there is no match, it returns"-1".action=lengthโ prints the length of the expression matching the regexp in the 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 ๐ฆ
ngl bestie, with mp4h you can easily deal with string arrays. variables can be treated as a single value or as a newline separated list of strings. so after defining
<set-var digits="0
1
2
3" />
you can view its content, or one of those values:
Source:
<get-var digits />
<get-var digits[2] />
Output:
0
1
2
3
2
array-size "S"โ takes a"name"and returns the array size, which is the number of lines present in the variable. โจ
Source:
<array-size digits />
Output:
4
array-push "S"โ args are"name" "value". it adds a value (or more than one, if that value contains newlines) at the end of an array. ๐ซถ
Source:
<array-push digits "10\n11\n12" />
<get-var digits />
Output:
0
1
2
3
10
11
12
array-pop "S"โ takes a"name", removes the toplevel value of an array, and returns that string. bye bestie ๐array-topvalue "S"โ takes a"name"and prints the last entry of an array. no cap ๐
Source:
<array-topvalue digits />
Output:
12
array-add-unique "S"โ args are"name" "value" "[caseless=true]". it adds a value at the end of an array only if that value isn't already chillin' in the variable. gatekeeping, but make it polite ๐
Source:
<array-add-unique digits 2 />
<get-var digits />
Output:
0
1
2
3
10
11
12
caseless=true โ the comparison is case insensitive.
array-concat "S"โ args are"name1" "[name2] ...". it concatenates all arrays into the first one. squad goals, everyone joins the first array ๐
Source:
<set-var foo="foo" />
<set-var bar="bar" />
<array-concat foo bar /><get-var foo />
Output:
foo
bar
array-member "S"โ args are"name" "value" "[caseless=true]". if the value is contained in the array, it returns its index โ otherwise it returns-1. it's giving "do I know you?" ๐
Source:
<array-member digits 11 />
Output:
5
caseless=true โ the comparison is case insensitive.
array-shift "S"โ args are"name" "offset" "[start=start]". it shifts an array. if the offset is negative, the indexes below 0 are lost. if the offset is positive, the first indexes get filled with empty strings. ๐ฆ
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 the origin of the shifts (default is 0).
Source:
<array-shift digits -2 start=2 /><get-var digits />
Output:
2
3
12
sort "S"โ args are"name" "[caseless=true]" "[numeric=true]" "[sortorder=reverse]". it sorts the lines of an array in place. the default is to sort lines alphabetically. put it in order, bestie โจ
Source:
<sort digits /><get-var digits />
Output:
12
2
3
caseless=true โ the comparison is case insensitive.
numeric=true โ sort lines numerically
Source:
<sort digits numeric=true /><get-var digits />
Output:
2
3
12
sortorder=reverse โ reverse the sort order
Source:
<sort digits numeric=true sortorder=reverse />;;;
<get-var digits />
Output:
12
3
2
Numerical operators ๐ข
Okay bestie, these ones do math. That's it, that's the vibe. When every operand is an integer, you get an integer back โ the second anything's a float, you get a float. Honestly they explain themselves. No cap.
add "S"โ adds everything together ๐
"number1" "number2" "[number3] ..."
substract "S"โ takes them away from each other (yes, it's spelledsubstractin here, that's not a typo on my end, that's canon)
"number1" "number2" "[number3] ..."
multiply "S"โ multiplies โจ
"number1" "number2" "[number3] ..."
divide "S"โ divides ๐
"number1" "number2" "[number3] ..."
min "S"โ smallest one wins ๐
"number1" "number2" "[number3] ..."
max "S"โ biggest one wins ๐
"number1" "number2" "[number3] ..."
Source:
<add 1 2 3 4 5 6 />
<add 1 2 3 4 5 6. />
Output:
21
21.000000
See that second one? One little . and boom, float era. ๐ญ
Source:
<define-tag factorial whitespace=delete>
<ifeq %0 1 1 <multiply %0 "<factorial <substract %0 1 /> />" /> />
</define-tag>
<factorial 6 />
Output:
720
Not me showing you a whole recursive factorial in a manpage ๐ iconic behaviour fr.
modulo "S"โ the remainder, the leftovers, the crumbs
"number1" "number2"
Unlike the functions up above, modulo is a strict little diva: it cannot handle more than 2 arguments, and both of those arguments must be integers. No exceptions, no float shenanigans.
Source:
<modulo 345 7 />
Output:
2
Now, these next ones compare two numbers and hand you back "true" when the comparison holds up. But heads up โ if one of the arguments isn't a number at all, the comparison is just false. Vibe check failed, moving on. ๐
gt "S"โ returns"true"if the first argument is greater than the second.
"number1" "number2"
lt "S"โ returns"true"if the first argument is lower than the second.
"number1" "number2"
eq "S"โ returns"true"if the arguments are equal.
"number1" "number2"
neq "S"โ returns"true"if the arguments are not equal.
"number1" "number2"
Relational operators ๐
not "S"โ returns"true"if the string is empty, otherwise it hands you back an empty string. She's giving reverse psychology. ๐ฆ
"string"
and "S"โ returns the last argument if every single argument is non-empty. All of them have to show up, no ghosting.
"string" "[string] ..."
or "S"โ returns the first non-empty argument. First one through the door wins. โจ
"string" "[string] ..."
Flow functions ๐
group "S""V"โ smooshes a bunch of statements into one single statement, like a little friend group holding hands ๐ซถ
"expression" "[expression] ..." "[separator=string]"
Some examples of this show up below with the conditional operations, so stay with me.
There's a less obvious but super useful trick with this macro: it preserves newlines when whitespace=delete is in play. That's the whole point, bestie.
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
Notice how the newlines get suppressed in text2 and the result is, uhโฆ absolutely not what anyone wanted. ๐ญ That's the cautionary tale. text1 keeps its newlines, text3 uses group to keep them on purpose, and text2 is just out here suffering.
compoundโ likegroup, except this tag is complex. ๐
"expression" "[expression] ..." "[separator=string]"
separator=string By default the arguments are just put aside. This attribute defines a separator that gets inserted between the arguments.
disjoin "S"โ does the exact opposite job ofgroup: its argument stops being treated as a single object when another command processes it. Breaking up the group chat. ๐
"expression"
noexpand "S""V"โ prints its arguments with no expansion whatsoever. They will never ever be expanded unless theexpandtag comes along to cancel thisnoexpandtag. Frozen in time, periodt. ๐ง
"command" "[command] ..."
expand "S"โ cancels thenoexpandtag. The un-freezing. ๐ฅ
"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 the string is non-empty, the second argument gets evaluated; otherwise the third one does. โจ
"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 the first two arguments are identical strings, the third argument is evaluated, otherwise the fourth is.
"string1" "string2" "then-clause" "[else-clause]"
ifneq "S""V"โ if the first two arguments are not identical strings, the third argument is evaluated, otherwise the fourth is.
"string1" "string2" "then-clause" "[else-clause]"
whenโ when the argument isn't empty, its body gets evaluated. ๐ฆ
"string"
while "V"โ while the condition stays true, the body function keeps getting evaluated. Looping era, no escape. ๐
"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โ basically theforeachmacro from Perl, but make it mp4h. A variable loops over the array values and the function body is evaluated once for each value. ๐
"variable" "array" "[start=start]" "[end=end]" "[step=pas]"
The first argument is a generic variable name, and the second one 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 the 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 once the 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 Changes the index increment (default is 1). And if the step is negative, the array gets treated in reverse order โ walking backwards through the group chat like a menace. ๐
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"โ runs a whole stack of conditions off a single instruction. One tag, many outcomes, main character energy. ๐
"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. Get me out of here. ๐โโ๏ธ
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"โ immediately exits the innermost macro, and you can slip a message in too.
"[up=number]" "string"
โ ๏ธ But listen โ this macro changes token parsing, so using it may become very hazardous in some situations. Like, actually be careful, bestie. ๐
up=number This attribute decides how many levels have to be exited. By default only one level is skipped. With a null value, all current macros get exited from. A negative value does the same thing, and additionally stops processing the current file.
warning "S"โ prints a warning on standard error. Just a lil heads up. ๐
"string"
exit "S"โ immediately exits the program. Bye. โ๏ธ
"[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 one's special: its content is stored and will be expanded after the end of input. A little delayed surprise. ๐
File functions ๐
directory-contents "S"โ returns a newline separated list of the files inside a given directory. ๐
"dirname" "[matching=regexp]"
Source:
<directory-contents . matching=".*\\.mp4h$" />
Output:
mp4h.mp4h
real-path "S"โ resolves all symbolic links, extra ``/'' characters and references to/./and/../in the pathname, and expands into the resulting absolute pathname. โจ
"patname=pathname"
โ ๏ธ Every component of the pathname except the last one has to exist when real-path is called. No cap.
This tag is especially handy when you're comparing whether file or directory names are identical. ๐
Source:
<real-path pathname=<__file__/> />
Output:
/build/mp4h-MsMTo_/mp4h-1.3.1/doc/mp4h.mp4h
file-exists "S"โ returns"true"if the file exists. Simple, honest, reliable. ๐ซถ
"filename"
get-file-properties "S"โ returns an array of information about the file. And that info is: size, type, ctime, mtime, atime, owner and group. The whole 411. ๐
"filename"
Source:
<get-file-properties <__file__/> />
Output:
68604
FILE
1530057894
1530057894
1530057894
root
root
include "S"โ inserts the contents of a file from the file system into the input stream (if thefileattribute is given), or the output from executing a system command into the input stream (if thecommandattribute is given). ๐ฆ
"file=filename | command=command-line" "[alt=action]" "[verbatim=true]"
For backwards compatibility, if neither the file nor the command attribute is given, the first argument is taken as a file to include.
file=filename The given file is read and inserted into the input stream. This attribute cannot be combined with the command attribute.
Files are searched first in the current directory, then in the directories specified on the command-line with the -I option, next in the 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.1:/usr/local/share/mp4h by default). That's the full search order, in order, don't skip a step. ๐
command=command-line The given command line is executed on the operating system, and its output is inserted into 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 can't be found, this is the backup plan that gets handled instead. Noaltand no file? Then bestie, that's an error, periodt. Heads up tho: this attribute is fully inactive if you also passedcommand. It just does nothing there. ๐verbatim=trueโ the file's content gets dropped in with zero expansion. Basically the same energy as using them4undivertmacro with a filename as its argument. ๐
Source:
<include command="uname -s" />
Output:
Linux
use "S""name=package"
Load definitions from a package file. ๐ฆ
- comment โ this tag is literally doing nothing and it ate that. Its whole body just gets thrown away. ๐
set-eol-comment "S""[string]"
Change comment characters. โ๏ธ
set-quotes "S""[string string]""[display=visible]"
By default, everything sitting between an <@[ and a ]@> pair gets read with no parsing at all. Call it with no argument and this macro switches that whole feature off. Call it with two arguments and it redefines the begin and end delimiters. The begin delimiter has to start with a left-angle bracket, and the end delimiter has to end with a right-angle bracket. No negotiating on that. ๐
display=visible
The delimiters also get written into the output. ๐
๐ Diversion functions ๐
Diversions are how you temporarily stash output somewhere. At any moment, mp4h's output can get diverted into a temp file, and then later be reinserted โ undiverted โ back into the output stream. So chic. โจ
Numbered diversions count from 0 upward, and diversion number 0 is the normal output stream. The number of simultaneous diversions is limited mostly by the memory it takes to describe them, because mp4h tries to keep diversions in memory. But โ there's a ceiling on the total memory all diversions can eat together. When that max is about to get blown, a temporary file is opened to receive the contents of the biggest diversion still in memory, which frees that memory up for the other diversions. So theoretically, the number of diversions could end up limited by how many file descriptors you have available. ๐ซ
divert "S""[ divnum=diversion-number ]"
Output gets diverted with this tag, where diversion-number is the diversion you're sending it to. Leave the divnum attribute out and diversion-number is assumed to be zero. Divert output into a diversion that doesn't exist and it's just... gone. Discarded. Which honestly is a great way to suppress output you don't want. Peep the example below. ๐
When all mp4h input has been processed, every existing diversion is automatically undiverted, in numerical order.
Calling divert several times with the same argument does not overwrite the previous diverted text โ it appends to it. ๐ง
Source:
<divert divnum="-1"/>
This is sent nowhere...
<divert/>
This is output.
Output:
This is sent nowhere...
This is output.
undivert "S""[ divnum=diversion-number ]"
This tag explicitly undiverts diverted text saved in the diversion with the specified number. Don't give it a divnum attribute and all diversions get undiverted, in numerical order. ๐ฆ
When diverted text gets undiverted, mp4h does not reread it โ it's copied straight to the current output. Which means it is absolutely not an error to undivert into a diversion. Go off. ๐
Unlike m4, the mp4h undivert tag does not let you pass a file name as an argument. You can do the same thing with the include tag plus the verbatim="true" attribute instead. ๐
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.
divnum "S"
This tag expands to the number of the current diversion. ๐ข
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 your constructs get complicated, debugging them can be a whole ordeal. The functions below are genuinely so useful when you're sitting there like "why is this not working." Fair warning tho: these functions are not perfect yet and they need to be improved in future releases. ๐
function-def "S""name"
Prints the replacement text of a user defined macro. For instance, the macro used to generate all the examples in 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 />
" />
debugmode "S""string"
This command acts like the -d flag, except it can be changed ynamically. (That's the original's spelling, bestie, not mine โ I'm keeping it.) โจ
debugfile "S""filename"
Picks a file where debugging messages get diverted to. If this filename is empty, debugging messages go back to standard error, and if it's set to -, those messages are just discarded. ๐๏ธ
Note: there is no way to print these debugging messages into the document being processed. Nope. Not happening. ๐
debugging-on "S""name" "[name] ..."
Declares these macros as traced, meaning info about them gets printed if the -d flag or the debugmode macro is being used. ๐
debugging-off "S""name" "[name] ..."
These macros are no longer traced. ๐ซ
โจ Miscellaneous โจ
__file__ "S""[name]"
With no argument, this macro prints the current input filename. With an argument, it sets the string that future invocations of this macro will return. ๐
__line__ "S""[number]"
With no argument, this macro prints the current line number in the input file. With an argument, it sets the number that future invocations of this macro will return. ๐ข
Source:
This is <__file__/>, line <__line__/>.
Output:
This is ./mp4h.mp4h, line 2201.
If you look closely at the source code, you'll notice this number is wrong. Indeed, the number line is the end of the entire block containing this instruction. Not a vibe, but that's how it is. ๐ญ
__version__ "S"
Prints the version of mp4h. ๐ท๏ธ
dnl "S"
Discards all characters until a newline is reached. This macro makes sure the following string counts as a comment and does not depend on the value of the comment characters. ๐ฎ
Source:
<dnl/>This is a comment
foo
<dnl/>This is a comment
bar
Output:
foo
bar
date "S""[epoch]"
Prints local time according to the epoch passed as argument. No argument? Then the current local time gets 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:
Wed Jun 27 00:04:54 2018
Wed Jun 27 00:04:54 2018
2018-06-27 00:04:54
timer "S"
Prints the time spent since the last call to this macro. The printed value is the number of clock ticks, so it's dependent on your CPU. โฑ๏ธ
Source:
<timer/>
The number of clock ticks since the beginning of the parsing of
this example by &mp4h; is:
<timer/>
Output:
user 23
sys 23
The number of clock ticks since the beginning of the parsing of
this example by mp4h is:
user 23
sys 23
mp4h-l10n "S""name=value"
Set locale-specific variables. By default, the portable "C" locale is selected. Since locales have different names on different platforms, you have to check your system documentation to find which values suit your system. ๐
mp4h-output-radix "S""number"
Changes the output format of floats by setting how many digits come 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 ๐ฆ
It is possible to include external files with the include command. Files get searched in this order, bestie, so pay attention: first in the current directory, then in directories specified on the command line with the -I option, next in directories listed in the MP4HLIB environment variable (fun fact: it used to be MP4HPATH for versions prior to 1.3), and last under the compile-time location, which by default is /usr/local/lib/mp4h/1.3.1:/usr/local/share/mp4h. ๐
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. ๐
๐ธ MACRO EXPANSION ๐ธ
This part describes the internal mechanism of macro expansion. It's gotta be as precise and exhaustive as possible, so contact me at <URL:mailto:barbier@linuxfr.org> if you have any suggestion. ๐ซถ
๐ Basics ๐
Let's start 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
%namegets replaced by the command name. - Attributes are numbered from 0. In the replacement text,
%0gets replaced by the first argument,%1by the 2nd, etc. Since there's no limitation on the number of arguments,%20is the 21st argument and not the third followed by the0letter. Don't get it twisted. โจ
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
%%gets replaced by%, which is useful in nested definitions. ๐ช
Source:
<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:
...and that's exactly where this page cuts off, bestie โ mid-example, mid-vibe, no warning. The rest is coming in the next part. ๐ญ
outer, # attributes: 2
inner1, # attributes: 2
inner2, # attributes: 5
- the
%attributessequence gets swapped out for the space-separated list of attributes. slay.
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
%bodygets swapped out for the body of a complex macro. iconic behaviour tbh.
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>
- both of the forms above can take modifiers btw. when you use
%Aattributesor%Abody, you get a newline-separated list of attributes printed instead. ๐
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
- and there's another alternate form, where you swap the
"A"for a"U". in that case the text gets replaced but it does not get expanded. ngl this only actually makes sense when the macro was defined withattributes=verbatim, otherwise the attributes get expanded before the replacement even happens.
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
- the
"A"and"U"modifiers can be combined. yes bestie, stack them.
note: input expansion is completely different in Meta-HTML versus in mp4h. in Meta-HTML you sometimes have to reach for other constructs like%xbodyand%qbody. to make compatibity with Meta-HTML better, those constructs are recognized and interpreted just like%body. another compat feature: for simple tags,%bodyand%attributesare equivalent. these features exist in the current mp4h version but they might disappear in future releases. so don't get too attached. ๐ญ
๐ Attributes ๐
Attributes are separated by spaces, tabulations or newlines, and every single attribute has to be a valid mp4h entity. so like, with the definitions above, "<bar>" can not be an attribute, because it has to be finished off by "</bar>". but this one is valid:
<foo <foo/> />
or even:
<foo <foo name=src url=ici /> />
in both those examples, the "foo" tag has only one argument.
sometimes you gotta group multiple statements into a single one, lowkey necessary. you do that with double quotes, or with the "group" primitive, like:
<foo "This is the 1st attribute"
<group and the second /> />
note: unlike HTML, single quotes can not replace double quotes for this purpose. not the vibe, apparently. ๐
if double quotes show up inside an argument, they gotta be escaped with 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)
- whether attributes are expanded or not
- function type (primitive or user defined macro)
- for primitives, the address of the corresponding code in memory, and for user defined macros, the replacement text
Characters get read on input until a left angle bracket shows up. then the macro name gets read. after that, attributes get read โ verbatim or not, depending on how this macro was defined. and if the macro is complex, its body is read verbatim. when all that's done, some special sequences in the replacement text get replaced (like %body, %attributes, %0, %1, etc.) and the resulting text gets put on the input stack so it can be rescanned. periodt.
note: by default, attributes are evaluated before any replacement. iykyk.
ok consider this next one, for changing text into typewriter font:
<define-tag text-tt endtag=required whitespace=delete>
<tt>%body</tt>
</define-tag>
ngl 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>
and we would like the inner tags to be removed. ๐
first idea: use an auxiliary variable to know whether we're still 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 the next section, bestie, stay with me).
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, like in the example below, our definition just... doesn't seem to work. ๐ญ that's because attributes get expanded before they get put into the 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>
so if we wanna prevent this problem, we gotta 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 wanna embed some server-side scripting language in your pages, you run into some weird problems, like in:
<a href=<%= $url %>>Hello</a>
the question is: how does mp4h know that this input has some extra delimiters? and the answer is that mp4h should not try to handle some special delimiters, because it literally cannot handle all of them (there's ASP, ePerl, PHP,... and some of them are customizable). so remember: mp4h is a macro-processor, not an XML parser. so we focus on macros, and we format our input file so it can be parsed without any problem. the previous example can be written like:
<a href="<%= $url %>">Hello</a>
because quotes stop the inner right-angle bracket from closing the "a" tag. ๐
another common problem: when we need to print only a begin or an end tag alone. for instance it's very desirable to define your own headers and footers with:
<define-tag header>
<html*>
<head>
... put here some information ....
</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 they're complex HTML tags but used as simple tags inside mp4h, because otherwise the tags wouldn't be well nested.
this asterisk is called the ``trailing star'', and it shows up at the end of the tag name.
sometimes HTML tags just aren't parsable, like in this javascript code:
...
document.write('<*img src="foo.gif"');
if (text) document.write(' alt="'+text+'"');
document.write('>');
...
the ``leading star'' is an asterisk sitting between the left-angle bracket and the tag name, and it stops this tag from being parsed. ๐ซถ
ok so with all that said, we can finally understand what the --expansion flag is for. it controls how expansion is performed by mp4h. it's followed by an integer, which is a bit sum of these values:
- 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.
- 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).
- 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.
- 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.
- interpret backslashes as printf. when set, backslashes before non special characters are removed. when unset, they are preserved.
- remove trailing slash in tag attributes. when set, remove trailing slash in tag attributes on output. when unset, they are preserved.
- do not remove trailing star in tag name. when set, trailing star after tag name are preserved on output. when unset, they are removed.
- do not remove leading star in tag name. when set, leading star before tag name are preserved on output. when unset, they are removed.
- 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 the default value. the current value matches HTML syntax, and it'll tend toward zero when XHTML syntax becomes more familiar. ๐
๐ AUTHOR ๐
Denis Barbier "<URL:mailto:barbier@linuxfr.org>"
Mp4h has its own homepage "<URL:http://mp4h.tuxfamily.org/>".
๐ THANKS ๐
Sincere thanks to Brian J. Fox for writing Meta-HTML and Rene Seindal for maintaining this wonderful macro parser called GNU m4. absolute legends, no cap. ๐ธ
and that's the whole page bestie, you made it to the end ๐ this one's from HTML Tools, dated 2018-06-27, and it lives at MP4H(1). go forth and macro-process, queen. ๐
โจ๐ฆ
๐ฆ where this page comes from
- ubuntu jammy: mp4h 1.3.1-17 ยท GPL-2.0-or-later
- ubuntu noble: mp4h 1.3.1-17 ยท GPL-2.0-or-later
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms