Repository navigation
XML Plus Compiler
xmlinc reads a skin source file that mixes ordinary enigma2 XML with a small set of extra constructs, resolves all of it, and writes out a single plain XML file with nothing left in it that enigma2 wouldn't understand natively.
-
Hierarchical includes —
<xmlinc file="name" .../>pulls inname.xmlinc(or any file with an explicit extension) in place, recursively. Included files are searched in order next to the source file, in its parent directory, in a shared "common" directory, and in that common directory's parent — so a plugin can override a shared building block just by placing a same-named file closer to its own skin. -
Parameters on includes, as
$vars— every extra attribute on an<xmlinc>tag becomes a$namevariable visible inside the included file (size=additionally exposes$width/$height). This is what makes an include reusable rather than a copy-paste target:<!-- Example.xml --> <xmlinc file="test" source="title"/>
<!-- test.xmlinc --> <widget ... source=$source .../>
compiles to:
<widget ... source="title" .../>
-
Declared parameters, with defaults — a file can say which parameters it takes, and what they are when the includer does not give them, with
<param name="x" default="y"/>at its top (YAML:- param: {name: "x", default: "y"}). A parameter without a default is required.<!-- screenpart_Pager.xmlinc --> <param name="connection"/> <!-- required --> <param name="pager_max_pages" default="10"/> <!-- optional --> <widget addon="Pager" connection="$connection" maxPages="$pager_max_pages" .../>
<xmlinc file="screenpart_Pager" connection="list"/> <!-- 10 --> <xmlinc file="screenpart_Pager" connection="list" pager_max_pages="15"/> <!-- 15, for this one only -->
What this gives: the default is in the file that uses the parameter, not in a global somewhere else; a missing required parameter is reported by name (
ERROR: include screenpart_Pager: parameter connection is required), an attribute that is no parameter (a typo) is aWARNING, and a declared parameter is put back when the include is done, so a value given to one include does not stay behind for the next one. The attributes of an<xmlinc>to a file that declares no parameters are globals from then on, as before (a screen can handdialog_widthto its frame include and use it for the rest of the screen;<local>is the cleaner way to do that). -
Relative positioning — a
position="x,y"attribute on an<xmlinc>tag is added to every widget position inside that include, recursively through nested includes. A sharedbuttons.xmlincblock of four pixmaps at0,0/300,0/600,0/900,0becomes a single reusable "button bar" you place anywhere just by choosing where to<xmlinc>it:<xmlinc file="buttons" position="100,200"/>
places the first button at
100,200, the second at400,200, and so on. -
An include's own size is exposed back to its parent — every
<xmlinc>sets$child_width/$child_heightto the bounding box (furthest right/bottom edge) of the content it just pulled in, measured in that file's own local 0,0-based coordinates, once its content is fully processed but before its ownposition=is resolved — so the include can reference its own just-measured size to place itself, with nothing hand-computed or hardcoded:<xmlinc file="screenpart_PRSPluginBody.xmlinc" position="eval(($screen_width-$child_width)/2),150"/>
centers that include horizontally, whatever width its content actually adds up to. The name is fixed, not per-file, so each
<xmlinc>overwrites it — only reliable for the include that was just processed (this one, on its ownposition=, or the very next thing after it), not an earlier sibling. -
Frames — a file whose root
<screen>hasframe="true"is a frame: a window drawn on the screen. An include of it can sayposition="center"(orcenter,N/N,center): the compiler centres the include's bounding box ($child_width/$child_height) on the screen. The origin of the frame placed by an include is remembered, and everything else in the screen that places it (widgets, labels, includes of ordinary parts) is positioned relative to that origin, so a screen never repeats the frame's offset in its own coordinates:<screen name="CronTimers" position="fill"> <xmlinc file="screenpart_Screen_Menu1.xmlinc" position="center"/> <!-- the frame, centred --> <widget name="list" position="26,137" size="1130,405"/> <!-- relative to the frame -->This also works through other screens: a screen that includes a screen that places a frame (an alias, with its own extra widgets) gets the same origin, and includes it at
0,0. A frame can also be put at a fixed position (position="252,111"); the content follows it. An element that really belongs to the screen, not to the frame (it lies outside it), saysorigin="screen": its position is then used as written, and the attribute is removed from the output. Without any frame in the screen nothing changes. -
Screen families — screens that differ only in their name (and title) share one body instead of one file each. In
skin.yml:screenfamilywith the screen attributes they have in common,body(a part or screen, included atbodyPosition, default0,0) and onescreenper member:- screenfamily: backgroundColor: "md_transparent" body: "screenpart_ConfigScreen.ymlinc" flags: "wfNoBorder" position: "fill" children: - screen: name: "Setup" title: "Setup" - screen: name: "ScanSetup"Each member is compiled to a complete
<screen>(attributes merged and sorted, body included), exactly as if it had a file of its own.A member can differ in more than its name:
content="part"is not a screen attribute but the$contentof the body, so a family can be a frame and a body that every member chooses for itself. The body is a screen file of its own that places the frame and then includes$content(a name without extension: the file name is looked up as for any include):# screenpart_ScreenLarge.ymlinc screen: name: "ScreenLarge" children: - xmlinc: {file: "screenpart_Window.ymlinc", position: "center", size: "$window_large_width,$window_large_height", window_inset_x: "$window_large_body_x"} - xmlinc: {file: "$content", position: "$window_large_body_x,$window_large_body_y", size: "$window_large_body_width,$window_large_body_height"} # skin.yml - screenfamily: backgroundColor: "md_transparent" body: "screenpart_ScreenLarge.ymlinc" flags: "wfNoBorder" position: "fill" children: - screen: {name: "LogManager", content: "screenpart_LogManagerBody"} - screen: {name: "TextBox", content: "screenpart_TextBoxBody", title: "Text"}This replaces one
screen_*file per screen (a frame include and a body include) by one line inskin.yml. -
Local variables —
<local name="x" value="y"/>(YAML:- local: {name: "x", value: "..."}) defines$xfor the rest of the file it is written in, and nothing else: not the files it includes, not the file that included it, and it is gone when the file is done. It shadows a global of the same name, and its value can use globals, earlier locals and formulas (<local name="row" value="$window_medium_list_height/10"/>). To hand a value to an included part, give it as an attribute of the<xmlinc>, the way a part receivesclock_font. -
Global variables —
<global name="x" value="y"/>defines$x;<screen size="w,h" .../>implicitly defines$screen_width/$screen_heightfor the whole file; a screen without a size (position="fill") gets the skin's<resolution xres yres>. The size of the output is also$output_width/$output_height(1920 and 1080 until a<resolution>is read): the window that fills the display is sized from them inframes.ymlinc, where no screen has been processed yet (see the Skin design guide). A screen's ownposition=is resolved against that output size, not against its ownsize=.$screen_centeris the centre along the axis it is written on: in anx,ypair the first one is$screen_width/2, the second$screen_height/2(position="$screen_center-300,45"). These replace thee(end) andc(centre) anchors, and thecenterkeyword is$screen_center-<half the element's size, rounded up>(what enigma2 computes:(parent - size) // 2), so the position stays a plain number that includes can offset.$varsdon't need to be their own token —picon$indexsubstitutes just the$indexpart, so variables can be embedded in literals. -
Font heights, checked at compile time — a widget with
font="Family;Size"(or a font alias) and asizeis warned about (WARNING: screen=… widget=… size: 30 < font: 34) if its height is less than one line of the font. A line isSize × scale/100 × (ascender − descender + line gap) / unitsPerEmhigh: the scale is the one the skin declares for the family infonts.xmlinc(<font name="Regular" filename="OpenSans-Regular.ttf" scale="89"/>), the metrics are read from the font file (looked for in the skin directories and theirfontsfolders,$SKINFORGE_FONTS, enigma2'sdata/fontsand/usr/share/fonts; a built-in table of the common fonts if the file isn't found). A family the skin doesn't declare keeps the old per-family constants. Heights of 0 (sized by code) are not checked. -
Colors, checked at compile time — any
...Color="name"attribute is validated against colors declared via<color name="x" value="y"/>(normally collected from a sharedcolors.xmlinc) -
Default widgets — a
<defaults>block (normally in a shareddefaults.xmlincthat is included near the top ofskin.xml, alongsidecolors/fonts) holds a default for each kind of element, written like the element it is for. The compiler fills in every attribute that an element does not set itself; an attribute the element does set always wins. This is for a uniform look across plugins: change a value in one shared file instead of on every widget in every screen.<defaults> <widget zPosition="1" transparent="1"/> <!-- every widget --> <widget if="itemCornerRadius" scrollbarMode="showNever" valign="center"/> <!-- every widget that has an itemCornerRadius --> <widget if="render=Listbox" selectionFrame="none" enableWrapAround="1"/> <!-- every widget with render="Listbox" --> <widget if="render=Listbox and scrollbarMode!=showOnDemand" scrollbarWidth="0"/> <screen if="name" flags="wfNoBorder"/> <!-- every screen that has a name --> </defaults>
The
ifsays when a default applies; all the other attributes are the values to fill in. A condition isattribute(the element has it, whatever its value - it does not have to),not attribute,attribute=valueorattribute!=value(also true when the element does not have the attribute);andjoins several, all of which have to be met. Conditions are about what the element itself sets in its source, not about what other defaults fill in, so the result does not depend on the order in which the defaults are applied. Several defaults can apply to one element: the one with more conditions wins over the one with fewer, and between equally specific ones the one declared last wins. A default only takes effect once the file declaring it has been reached via an<xmlinc>include, and it is matched by element name only, with no per-screen targeting. Files withSummaryin their name get no defaults. A<defaults>block never appears in the compiled output; instead the compile log ends with a report of what the defaults filled in (==> defaults: widget[itemCornerRadius] valign: 329), so a value that is not in the source can be traced to the rule that put it there. A condition that cannot be read is reported as an error and that default is left out.The older form
<default tag="widget[render=Listbox]" .../>(andtag="widget[itemHeight]") is still read, as the same thing. In YAML the same defaults are written more compactly, with nestedifblocks and anelse, see Authoring in YAML. -
Formula evaluation —
eval(...)runs the enclosed expression as real arithmetic, so positions and sizes can be computed instead of hand-calculated:eval(($width-100)/2)centers a 100px-wide element. Division is automatically treated as integer (floor) division since pixel coordinates can't be fractional; if a formula still produces a float (e.g. from a scaling ratio) the result is rounded to the nearest pixel rather than truncated. -
Font/size sanity check — every widget with both a
fontand asizeis checked against a minimum-line-height heuristic; asizetoo short for itsfontproduces a warning identifying the screen, widget, font variable, and both values, catching text that would otherwise render clipped on the actual device. -
Verbatim passthrough — an include named
applet_*is inlined as raw text without any of the above processing, for embedding pre-rendered or foreign XML snippets unchanged.
xmlinc <source-file> <destination-file> <destination-dir> <common-dir>
-
source-file— the hierarchical skin source (e.g.skin.xml, or a screen's own.xml) -
destination-file— where the fully-resolved, flat XML is written -
destination-dir— the directoryxmlinctreats as the plugin's own skin directory when searching for includes -
common-dir— a shared directory of reusable.xmlincbuilding blocks (buttons, title bars, colors, ...) used across plugins
xmlcompile/ymlcompile call this per skin variant as part of a full build; run it directly only when working on a single file in isolation.
Two options are for maintaining the sources:
-
--marksmakes every element of the converted.ymlinccarry an_src="path:line"that points back to its line. -
--redundant <file>(with--marks) writes a report of which attributes the sources set that are exactly what the default widgets would fill in anyway: one line per source element and attribute,path:line, attribute, how often it was compiled, how often it was redundant. An attribute that is redundant in every compile can be deleted from the source without changing the compiled skin; run it for every skin that includes the part, as a part of Common is compiled into all of them. Attributes that a default depends on (the ones in anif) are never reported. The text must be the same as the text of the default, as$varscan mean something else in another skin.
A default resolution for screens counts for the compiler as if the screen said it: $screen_width/$screen_height come from it, so a screen can leave resolution out.