Warning!

This file is an editor's draft and should not be considered final.

Arbitrary XML Rendering Specification

1 The syntax of HSS

1.1 Introduction

HSS stands for Hierarchical Style Sheets and it is the language used to instruct the AXR renderer how the various elements in the document should look like, how they are laid out and, to some extent, how they behave. The syntax of this language is inspired by CSS, but extending and altering it in various ways, to achieve powerful new ways of creating a web document. Therefore, HSS is not compatible with existing CSS files.

HSS inherits the simplicity and clarity of CSS‌' syntax, but stops being a static language, bearing more similarity in some areas to other programming languages. The primary premise for this is the realization that a web page isn‌'t a simple hyperlinked text document anymore, but an interactive software, which presents itself to the user through an interface and performs some logic, which may or not be displaying a text document. With this in mind, and the environment in which the document will be accessed (a computing device with an arbitrary display size), it is clear that some sort of interface programming language is needed which is able to express relationships and behaviors, and allows advanced interactivity and layout.

HSS files are plain-text files, and thus can be edited by any text editor on any platform, although some of its features are better put to use through a visual editing tool, since using code in those cases is less intuitive.

1.2 The primary building blocks

1.2.1 Rules

One of the most basic concepts in HSS is the notion of rules. A rule is the combination of a selector and a block, which starts with an opening curly brace { and ends with a closing one }, and may contain property definitions and/or other rules.

selector
{
	property: value;
	selector { … }
}

1.2.2 Selector basic principles

Selectors are used to point to certain elements of the document, so that the properties defined inside of the block get applied to them. You define a search pattern and all applicable elements will be searched to see if the conditions match. If so, the property definitions are applied.

Simple selectors are the element names, the universal selector and reference objects, combined with one or several appended filters, flags or splitters. A series of simple selectors joined by combinators is called the selector (or selector chain) and reads from left to right.

Some examples:

//select elements with name "document"
document { }
//select the first element with name "book"
book:first { }
//select the elements with name "content" that occur after "title"
//elements, which are descendants of "book" elements, which themselves
//are direct children of "books" elements
books book .. title + content { }

1.2.3 Properties

Each element in the document has a certain amount of properties, which you can define using property definitions. They are contained within the block that follows a selector chain.

A property declaration is composed of the name of the property followed by optional whitespace, a colon, and finally, preceded by optional whitespace, comes either a literal value, an expression, a function or an object. More whitespace can come afterwards, as well. If it is not the last property declaration of the block, it must be ended by a semicolon, to denote that it finishes on that place, and that what follows is something else.

Here are some examples:

document
{
    //this makes it 150 points wide
    width: 150;
    //this makes the element 10 points less high than its parent's innerHeight
    height: 100% - 10;
    //this aligns the element to the center of its parent element
    alignX: 50%;
    alignY: 50%;
}

//in this example the semicolon was omitted, since that
//property immediately precedes the closing brace
document { alignX: 50% }

1.2.4 Objects

Objects represent values that are more complex in their nature. Similar to rules, objects have properties which accept values, defined inside a block. Objects are identified using the object sign @ prefixed to their object type.

document
{
    //this makes the element have rounded corners
    shape: @roundedRect {
        corners: 5;
    };
    //this adds a 3 points wide black border
    border: @stroke {
        size: 3;
        color: #000;
    };
}

1.2.5 HSS points

#TODO Issue 68

1.2.6 The negator

You can negate simple selectors by prefixing them with !, meaning that all elements in the current scope that don't match the selector will be selected.

//this selects all elements except note ones
!note { }

Likewise you can negate filters and flags.

//this selects any note element which is not the first one
note!:first { }
//this selects any note element which is not being hovered
note!::hover { }

To negate an entire selector (taking into account its filters and flags) you have to use grouping brackets.

//this matches all elements except the last note(s)
![note:last] {}

The negator can also be prepended to values.

note
{
    //using a reference, this ensures that the element
    //is not targetable when it is draggable
    //and targetable when it's not draggable
    targetable: !ref(draggable);
}

1.3 Selectors

Consider the following XML snippet:

<note>
   <to>Peter</to>
   <from>James</from>
   <subject>Reminder</subject>
   <message>Please don't forget to bring some <important>wine</important>…</message>
</note>

In the following examples we will be using rules, but without property definitions for clarity. We will assume that this xml snippet could be inserted anywhere: the note element doesn't necessarily represent the root of the xml document.

1.3.1 Element name

You can target an element by its tag name.

//match every note element (within the current scope) and apply
//the style rules declared within the block (in this case none)
note { }

1.3.2 Hierarchy & Scope

Usually what the HSS author does is following the document tree and applying styles to most elements. Rules can be nested inside other rules which will only match children of the elements matched by the parent selector. This is what gives HSS its name.

The scope refers to the elements targeted by the selector. In the following example, only message elements that are immediate children of note can be selected, because the scope is restricted to the children of the note element.

note
{
    //match every message element which is a child
    //of the previously selected note element
    message { }
}

Similarly to avoid repetition you can nest the this object affixed with filters or flags.

note
{ @::hover { } } //which is equivalent to note
{ @this::hover { } } //which is equivalent to note { } note::hover { }

1.3.3 Grouping

You can group multiple selectors together which is convenient when they share the same properties (or rules).

//match the note's children of type subject and message
note subject, note message { }

Selector grouping helps getting rid of unnecessary repetition.

//hierarchical grouping
note {
        child1, child2 {
                        element1, element2 { }
        }
}
//which is equivalent to
note { }
note child1, note child2 { }
note child1 element1, note child1 element2,
note child2 element1, note child2 element2 { }

Through the use of grouping brackets, several consecutive parts of a selector chain can be evaluated as one.

//inline grouping is also available
note [child1, child2] [element1, element2] { }
//which is equivalent to
note child1 element1, note child1 element2,
note child2 element1, note child2 element2 { }

//this could match B elements which precede A elements
//(which themselves are followed by C elements)
[A + C] - B { }
//which is equivalent to
A + C - B { }
//this couldn't: the B elements would have to come after an A
//and before a C
A + [C - B] { }

You can also apply filters, flags or splitters to the aggregate resulting from the combined collections.

//matches the odd element(s) of the collection of A1 and A2 elements
[A1, A2]:odd { }
//which is not equivalent to
A1:odd, A2:odd { }

Grouping brackets can also be used to join collections of a simple selector.

//this would join the collections of odd elements of each type
//and then out of that match the odd ones
[*/type:odd]:odd { }

1.3.4 The universal selector

The universal selector * matches every element in the current scope:

//matches every element that is a child of note
note
{
    * { }
}

Reminder: Since the initial scope is restricted to the root element, simply using the universal selector in the outermost rule set would only select one element, unlike what would happen in CSS. To traverse all scopes you need to start the selector chain with the descendant combinator.

//matches only the root element
* { }
//matches all elements under the root element
* .. * { }
//matches all elements
.. * { }

If the universal selector is not the only part of the selector, like when it has a filter, for example, it can be omitted. It may be omitted as well if placed between matching combinators (except the child and descendant combinators).

//the following statements are equivalent
*:first { }
:first { }
//and so are these
* > * > * { }
* >> * { }

1.3.5 Selecting the subject

Using the subject selector $ you can match an element anywhere in the selector chain thus turning everything that follows it into a condition.

//matches all note elements which have at least one comment element as a child
$note comment { }
//matches the note element which has an important
//descendant currently being hovered
$note .. important::hover { }

1.3.6 Regex selector

You can use a regular expression to select elements based on their names.

//matches every element in the current scope whose name starts with a
/^a/ { }

//matches every element in the current scope whose name starts with a or A
//i stands for insensitive
/^a/i { }

//matches every element in the current scope whose name starts with fi or 
//l stands for ligature
/^fi/l { }

//matches every element in the current scope whose name starts with a, à, â, etc.
//d stands for diacritic
/^a/d { }

1.3.7 Reference Objects

The this object always refers to the nearest container(s); independently of each other (if there are several matches). It can be used in a selector or a property's value.

//using the object dot notation, this would set the width
//to the element's current computed height value
element
{
    height: 100%;
    width:  @this.height;
}

The self object refers to the current object which immediately contains it. It is especially useful when it targets anonymous objects.

//sets the file name as the window's title
@window
{
    title: @self.file;
}

The root object refers to the topmost element (AKA document element), the one that encloses all others in the document.

//this sets the width of the section element(s)
//to the same value as the width of the root element
//if it is named "document" (using the is filter)
.. section
{
    width: ref(width of @root:is(document));
}

Note: You can turn a generic element into a root element towards its descendants using the sandbox property.

The parent object refers to the parent of the preceding element in the selector chain or (if there's none before) to the parent of the element which is currently affected by the styling rule containing @parent.

//set the messages' width to a third of their own parents' height
.. message
{
    width: ref(height of @parent) / 3;
}
//this reference would return nothing since
//the headmost root element is devoid of any ancestor
@root
{
    height: ref(height of @parent);
}
//in case @parent starts the selector chain
//it relates to the current @this
.. message
{
    @parent { }
    //resolves to
    @this @parent { }
}

The these object refers to the nearest container(s) as a whole. It is particularly useful in case the rightmost selector is unknown.

//to be later used by the isA property
@container loftiest
{
    //this sets the height of the elements matched
    //to the height of whichever is currently the tallest
    height: ref(max height of @these);
}

The owner object refers to the object which immediately contains the current object. It's designed to retrieve values from varying or multiple owners.

#TODO example

Reminder: elements are objects of type container.

The window object holds the properties pertaining to the window.

@window
{
    title: "This is my custom title.";
}

1.4 Combinators

Combinators allow you to select an element based on its relationship to another in the hierarchy. Multiple simple selectors can be chained together, forming the selector chain. Usually this is done when you want to "skip" the elements which leads to the one you want to style. In the case of a single childhood relationship, it is more convenient to use the child combinator than to nest rules; in other cases, combinators are mandatory regardless.

//even in a hierarchical context
//you can start a selector chain by a combinator
note
{
    //this matches important elements which are descendants of note elements
    .. important { }
}
//which is equivalent to
note { }
note .. important { }

Keep in mind that these examples only select elements in the current scope, and therefore will select elements depending on where in the hierarchy you are using them.

1.4.1 Child combinator

Children—or more explicitly level one descendants—may be selected using the child combinator (whitespace)  .

Note: This is different from CSS, where the whitespace represents the descendant combinator. The reasoning behind this is that authors are inherently lazy, and thus end up using the descendant combinator when they really should be using the child combinator instead, because it's just much more convenient.

//matches every subject element which is a child of note
note subject { }

1.4.2 Descendant combinator

If you want to select elements which are descendants (could be direct children of the element, or more generations down), the descendant combinator .. is used.

//matches every subject element which is a descendant of note
note .. subject { }

1.4.3 Siblings combinator

Sibling elements can be accessed via the siblings combinator =:

//matches all the subject elements that are siblings of
//the message element(s)

message = subject { }

1.4.4 Next and previous siblings combinators

Use the next siblings combinator + and previous siblings combinator - if you want to access elements following or preceding (in the current tree order) a specified element in the same scope.

//this will match the message element(s) following a from sibling
from + message { }
//this will match the subject element(s) preceding a message sibling
message - subject { }

Note: For further instructions on how to select amongst the following or preceding elements read the filters, flags and splitters chapter.

1.4.5 Accessing elements laid out in lines

Containers that are in flow might be laid out in various lines and subjacent lines of elements. You can target them by the use of the line combinator. By placing a filter or the universal selector after the pipe you can select lines of elements in a container.

//this will select all the lines of elements inside of the
//document element
document | * { }
//this will select only the first line
document | :first { }

To select elements inside the matched line(s) you have to use a second pipe, like so:

//this selects the last element in the first line of elements
document | :first | :last { }

You can drill down into the secondary lines (hierarchically) that have been formed according to the layout algorithm using the child combinator.

//this will select all the subjacent lines inside of the first
//line of elements
document | :first * { }
#TODO 37

Since each line is a somewhat abstract concept - there is no element representing each line, if you create new elements in the scope of a line, only one element per line will be created, and not one per selected element, and it will be attached to the parent element. On the other hand, properties applied in the scope of the line will be applied to all elements in that line.

The parent object will refer to the parent line, in case of a subjacent line, or the parent element, in case of a line.

1.4.6 Content text combinator

Many times you will want to access a part of an element's content text—a word, or a particular line, for example—rather than its container.

Consider this snippet of XML code:

<document>
    <paragraph>This is a line of dummy text. Lorem ipsum
    dolor sit amet, consectetur adipiscing elit.</paragraph>
</document>

First, we have to understand what happens with the content text when it is rendered. An element is represented by a container, think of it as an amount of space in the rendered document and inside it the content text and the containing elements. The default height of the container is the height of its content.

When the width of the container is not wide enough to accommodate all its content text, wrapping occurs. This means that the text is split in various lines of text, and therefore the height increases. Now there are two or more blocks stacked over each other.

Conceptually, the text can be further split into elements. Each word is preceded, followed, or surrounded by whitespace. Furthermore, each character (letter, kanji…) of that word is a separate glyph you can refer to.

Graphic explaining the concept of lines, words and glyphs

To select portions of text, the content text combinator is used. After the combinator, you write a string to specify what part of the text you want to select.

//this will select any occurrence of "dummy text" and set
//its height to 15 points
.. * > "dummy text" { height: 15 }
//this will only match the first one
.. * > "dummy text":first { height: 15 }

The universal selector permits to select text elements by type (lines, words or glyphs):

//this will select all lines (the entire content text)
document paragraph > * { }
//this will select the first line of text
document paragraph > *:first {}

To access a word, you use the content text combinator and universal selector again, after selecting a portion of the text:

document
{
    //this will select the last word of the content text
    paragraph > * > *:last { }
    //this is equivalent, since the universal selector can be
    //omitted in this case
    paragraph >> :last { }
}

You repeat the same pattern again to select a glyph:

document
{
    //this will select the first letter of the content text
    paragraph > * > * > *:first { }
    //this is equivalent
    paragraph >>> :first { }
}

The each splitter permits the separation of each text elements (either lines, words or glyphs) that would otherwise be joined in the same selection:

document
{
    //this will select each line of text and give it vertical margins,
    //increasing the line spacing
    paragraph > */each { margin: @{ top, bottom: 5 }; }
    //this will select the first three letters of "dummy", each separately
    paragraph >> "dummy" > :nth(1, 3)/each { }
}

1.4.7 Ancestor combinator

Ancestors can be selected using the ancestor combinator ^^. If the elements matched previously don't share the same ancestors, the combined collection of ancestors—and not the common ones—will be returned.

//matches every ancestors of important elements
.. important ^^ * { }

1.5 Filters, flags and splitters

Filters are used to reduce or alter the current selection. Each individual element of the selection is checked to see if the condition(s) match(es).

Always remember that if used alone, the omitted universal selector is actually assumed. Filters can be appended to any other selector even allowing multiple affixed filters. For example, you could do the following:

//this matches the first occurrence of the note element,
//if it has a child
note:first:parent { }

Keep in mind that the order of the filters will alter the outcome, so if you put the filters of the previous example in reverse order, it will select the first element that has a child, instead of the first element, but only if it has a child.

//this matches the first of the note elements that have a child
note:parent:first { }

Using flags you can apply certain HSS declarations depending on an element's event state. Flags are preceded by :: unlike filters which use :.

Note: System flags are automatically activated and deactivated: the user has no control over their default behaviour.

This XML sample will be used to illustrate examples throughout this chapter.

<notes>
	<note>
		<to>Peter</to>
		<from>James</from>
		<subject>Reminder</subject>
		<message>The keys are under the doormat.</message>
		<message>Please don't forget to bring some wine.</message>
	</note>
	<note>
		<to>Paul</to>
		<from>Julia</from>
		<subject>Rendezvous</subject>
		<message>Meet me at the inn.</message>
	</note>
</notes>

1.5.1 Position filters

The following filters are used to select a subset of elements, depending on their position in the current tree order.

In the case of several matched parents the filters refer to the combined collection returned whereas their Child variant to the children's position inside their own parent.

The first and firstChild filters match the first element(s).

//this matches the first to element in our example
notes note :first { }
//this matches the first to elements of each note element in our example
notes note :firstChild { }
//which is equivalent to appending the each splitter to the former
notes note /each:first { }

The last and lastChild filters match the last element(s).

//this matches the last element in the second note element
notes note :last { }
//this matches the last elements of each note element
notes note :lastChild { }

The even and evenChild filter select elements whose index is even (the count starts with 1).

//in our example this matches the from element in the first note element
//but not the one in the second note element (7 isn't even)
notes note from:even { }
//in our example this matches the fourth message elements of the note elements
notes note message:evenChild { }

The odd and oddChild filters select elements whose index is odd (the count starts with 1).

//this matches the 1st to element in our example
notes note to:odd { }
//this matches the 1st to elements of each note element in our example
notes note to:oddChild { }

The nth and nthChild filters select elements which index matches a number or expression, meets a condition or falls within a given range.

//this matches the fourth element in the collection
:nth(4) { }
//if there are at least 6 elements currently in the collection,
//it matches the element after every 5th one; if not it matches the 5th one
:nth(5n + 1 | 5) { }
//this matches elements whose index is ≥ 3 in the collection
:nth(>= 3) { }
//this matches elements whose index is 5, 10 or 15 in the collection
:nth(5n, 15) { }
//this matches elements whose index is 1, 2, 3 and ≥ 8 in the collection
:nth(1, 3 & 8, *) { }

1.5.2 Hierarchy filters

You can filter elements by their relationship with the other elements in the tree.

The parent filter matches every element that is a parent, which means that it has at least one child. If the element is an ancestor it will always also be a parent.

//this matches childless elements
!:parent { }

The has filter matches elements which have certain children, descendants, ancestors or siblings.

//this matches elements that are parent of at least one message element
:has(message) { }
//this matches elements that are an ancestor of a message element
:has(.. message) { }
//this matches elements that are a descendant of a message element
:has(^^ message) { }
//this matches elements which have at least one message sibling
:has(= message) { }
//this matches elements which have at least one message sibling following them
:has(+ message) { }
//this matches elements which have at least one message sibling preceding them
:has(- message) { }

1.5.3 System flags

You usually want to provide some kind of feedback to the user when he/she is using your site. Targeting elements using flags is an easy way to add interactivity to the site, to create a compelling user experience.

The hover flag targets only elements that are lying under the cursor.

//matches every element which has the mouse over it (even overlaid ones)
::hover { }

The press flag targets only elements which are lying under the mouse pointer while the primary button is pressed (usually the left one) or during an hold event (an unreleased touch event).

//matches every element which is being pressed
::press { }

The drag flag targets only elements which are being dragged (multiple elements can be dragged at the same time using a multi-touch interface device).

//matches every element that is being dragged
::drag { }

The target flag targets elements which are a potential landing point for the currently dragged element(s).

//matches all elements that are actual dragging destination
//for the currently dragged element(s)
::target { }
//which is equivalent to
::target(*) { }
//this will match while a file sibling is being dragged
::target(file) { }

Note: If you want to match a dragging destination which is currently hovered by a dragged element you just need to append the hover flag: the drop target will be matched when any part of the combined boundary of the dragged element and the contact area of the input device touches it.

//matches every dragging destination elements when a dragged
//element is over it
::target::hover { }

The focus flag matches the element that currently receives user input (through tabbing for example).

//matches every element which receives user input
::focus { }

The select flag matches either elements, lines, words or characters that are currently selected. It ceases if a click or a tap occurs afterward.

//matches elements which are currently selected
::select { }
//matches the characters that are currently selected
* >>> ::select { }

1.5.4 The attribute filter

You can filter elements based on their attributes using the attribute filter.

//matches elements which have a title attribute
:[title] { }
//matches elements which have a title AND an href attribute
:[title]:[href] { }
//matches elements which have a title OR an href attribute
:[title, href] { }
//which is equivalent to
:[title], :[href] { }

1.5.5 Text matching filters

These filters select elements which text—either the content text or the value of the attribute(s)—matches a given string, condition or pattern.

The contains filter matches elements that contain a given string in their texts.

//matches every element whose content text contains "reminder"
//regardless of the case
:contains("reminder") { }
//matches every element whose title attribute contains "reminder"
:[title:contains("reminder")] { }

The equals filter matches any element whose text is a given string.

//matches every element whose content text is equal to "reminder"
:equals("reminder") { }

The startsWith filter matches elements whose texts start with a given string.

//matches every element whose content text starts with "reminder"
:startsWith("reminder") { }

The endsWith filter matches elements whose texts end with a given string.

//matches every element whose content text ends with "reminder"
:endsWith("reminder") { }

The match filter matches elements whose texts match a given regular expression.

//matches every element whose content text starts with a number
:match(/^[0-9]/) { }

The empty filter matches elements which have no text nor children or whose attributes' values are empty.

//matches elements which have at least one attribute set to ""
:[*:empty] { }

The length filter use a criterion to match elements or compares the length of the text against a given condition.

//matches every element whose content text's length is greater
//than 3 characters
:length(> 3) { }
//matches the element(s) whose content text has the most characters
:length(max) { }
//matches the element(s) whose content text has the less characters
:length(min) { }

1.5.6 The property filter

Conditions are used to test whether an element is matched based on its properties. The computed values are tested against, not the declared ones. For example if you set the height of a container to 50%, the value will be dynamically returned in points. The properties are on the left side of the comparator, the values on the right.

//matches image elements (@container has a type property)
*:(@image) { }
//matches the elements whose title attribute's value is AXR
*:(attr.title = "AXR") { }
//matches all elements with a background
*:(background) { }
//which is equivalent to
*:(background != no) { }
//similarly, this matches elements which were taken out of the flow
*:(!flow) { }
//which is equivalent to
*:(flow = no) { }
//matches all elements which are using a font object named serif
*:(font = serif) { }
//matches all elements that are taller than 300 and not wider than 400
*:(height > 300 & width < 400) { }
//matches any element which surface is square shaped
*:(height = ref(width)) { }

Note: If recursion occurences are detected the condition(s) will be evaluated in one pass thus the value(s) returned will always be the same.

1.5.7 Splitters

Splitters are preceded by / and directly act on the current selection outputting one or more collections in result. The sorting of the returned collections is determined by the elements' order in the source.

The each splitter separates each element that would otherwise be joined in a selection. Apart from being a content text splitter, it is particularly useful in association with sibling combinators, collection filters or other splitters.

Using the type splitter you can rearrange the elements of a collection by types. Put simply the type of an element is its tag's name.

notes
{
	//this will match the children of each note elements which
	//are the second of their type; in our example it would select
	//the second message element from the first note
	note/each /type:nth(2) { }
}

The attribute splitter allows the splitting of elements into separate collections in relation to either an attribute's name or an attribute's value.

<matches>
   <match0 louis="won" schmeling="lost" name="Fight of the Century" />
   <match1 ali="lost" frazier="won" name="Fight of the Century" />
   <match2 ali="won" frazier="lost" />
   <match3 ali="won" frazier="lost" name="Thrilla in Manila" />
</matches>
//the splitter returns 3 collections in this order: louis, frazier and ali
//this selects the first element of each boxer collection:
//match0 (for Louis), match1 (for Frazier) and match2 (for Ali)
matches /attr("won"):first { }

//this returns 2 collections: "Fight of the Century" and "Thrilla in Manila"
//the first one contains 2 elements and the second only 1
//here we are selecting the first element of each collection: match0 and match3
matches /attr(name):first { }

The level splitter permits to split the elements matched into one or several collections depending on their current depth level relative to the root element.

<planets>
   <terrestrials>
	<mercury />
	<venus />
	<earth />
	<mars />
   </terrestrials>
   <jovians>
	<neptune />
	<uranus />
	<saturn />
	<jupiter />
   </jovians>
   <dwarves>
	<pluto />
   </dwarves>
</planets>
//matches the third element of each level
//in our example it would be dwarves and earth
.. */level:nth(3) { }
//matches the third element of the collection resulting
//from the consolidation of the levels' collections
//in our example it would be dwarves (earth is 6th)
.. [*/level]:nth(3) { }

Note: You can't negate a splitter.

1.5.8 Collection Filters

The amount filter matches elements depending on their current number in the returned collection(s). If a number or a condition is given, the amount of elements is compared.

//this matches if the sibling collection has exactly 3 elements
:amount(3) { }
//this matches if the collection contains 4 or more elements
:amount(>= 4) { }
//this matches the type collections which exactly return 4 elements
/type:amount(4) { }

The is filter lets you filter the returned collection(s) by a particular element type.

notes
{
	//this will only match the first siblings following from elements
	//under each note elements provided that these siblings are message elements
	note/each from + :first:is(message) { }
	//this matches, inside each note elements separately,
	//the first message elements following a from element
	//these elements could be positioned anywhere in their siblings' collections
	note/each from + message:first { }

	//this matches the fourth element out of the combined collection
	//of notes' children if it's not a message element
	note :nth(4)!:is(message) { }
	//if the message element is either the 1st, 2nd, 3rd or 4th element
	//and then removed from the returned collection (!message)
	//it would have an impact on the position of the remaining elements
	//in these cases the following example wouldn't match the
	//same element(s) than the previous example
	note !message:nth(4) { }
}

Using the oneIs filter you can match a collection on the condition that it contains a certain element.

notes
{
	//this matches the children of the first note element
	//if one of them is a from element
	note:first :oneIs(from) { }
}

1.5.9 Custom macros

In HSS it is possible to create your own set of macros using the #macro instruction. They can also be imported from an external file, as any other part of your HSS code, which enables the creation of custom libraries.

//used to style the first element out of the ones which have a black background
#macro firstBlack { :(background = black):first }
//this creates an alias for :startsWith()
#macro sw(myArg) { :startsWith(myArg) }
//creates a macro which selects the first or the last element
#macro limit { :first, :last }
//macros are reusable inside other macros' definitions
#macro wideLimit { :(width > ref(height)):::limit }
//flags are accepted as well
#macro dock { ::target::hover }

:::firstBlack { }
:::sw() { }
:::limit { }
:::wideLimit { }
:::dock { }

1.5.10 User flags

You can create or reactivate a custom flag using the flag() function. Once created you can deactivate it using its counterpart, unflag(). toggleFlag() conveniently combine both aforesaid functions. Finally we have the takeFlag() function which deactivates an existing flag to activate it on the current element(s). Evidently all system flags are reserved names.

example
{
    //activates the custom flag if the element is clicked on
    on: @click { flag(custom) };
    //which is equivalent to
    on: @click { action: flag(custom of @this); };
    
    //if the test flag is currently activated on at least one of the elements
    //matched by the selector, this will activate the flag on example and
    //deactivate it on its siblings whenever a click is registered on this element
    on: @click { takeFlag(test) };
    //which is equivalent to
    on: @click { action: takeFlag(test of *); };
}
example::custom { }
example::test { }

Note: As with macros you can use the resulting flags before their activating function.

1.6 Properties

1.6.1 Literal values

Literal values are keywords, numbers and strings.

Special keywords

Special keywords are considered valid values for all HSS properties.

The default value of a property is used to (re)set a value to its default value.

element
{
    height: 50;
    width:  100;
    @::hover
    {
        //using grouping this resets the height and width
        //to their default values (respectively content and 100%)
        height, width: default;
    }
}

The inherit value of a given property will correspond to the current value of the element's parent matching property. If the value returned is inherit it will use the property of the parent's own parent, and so on, until it picks up a non-inherit value.

parent
{
    width: content;
    //the child inherits the content value (not its computed value)
    child { width: inherit }
}

Note: The inherit value is not allowed on properties set on the root element. You can't separately inherit one value from multi-value properties because their values don't have a fixed order. To retrieve the computed value of a property you can use a reference.

Using the current value you can retrieve the current value(s) of a property whether it has been explicitly set earlier or not. If a property has multiple values set it will refer them as a whole.

//assuming the background property has currently "object1, object2" as values
background: current, object3;
//would be equivalent to
background: object1, object2, object3;

1.6.2 Expressions

You can write an expression anywhere a numerical value is accepted, and it is generally only useful when used with a variable value. If all the values are fixed numbers, the result will be calculated at parse time and it would be interpreted as if the result had been written. The available operations are: sum (+), subtraction (−), multiplication (×) and division (∕). You can also use parentheses to designate the order of the operations, in an inside-out order. Otherwise, multiple operations are executed following the mathematical order (first multiply and divide, then sum and subtract).

//this element will always be twice as wide as its current height
element { width: 2 * ref(height); }

//using the count property, this permits to perfectly fit
//the child element(s) in their parent to prevent wrapping
parent child { width: 100% / @these.count; }

1.6.3 Functions

Functions take some parameters and create an output. The result of the function is what is assigned to the property.

Retrieval functions

References

You can take values from other elements to apply them to the properties of the currently selected element(s).  The value returned is always the computed value of that property, not the declared one. The syntax is ref(propertyName of selector) and can be used anywhere a value is accepted.

Consider the following XML snippet:

<parent>
    <child1>dynamically generated content text, changes in height</child1>
    <child2>static content text</child2>
</parent>

Suppose that in your layout each child elements is a column, taking half of the horizontal width. If you wanted the second child element to always have the same height as the first one, since the first changes in height, you could not use any static value. Instead, you would reference the first child from the second one.

parent child2
{
    //this gives both columns the same height
    height: ref(height of child1);
    //since the property referenced is the same
    //as the one we are setting, this is equivalent
    height: ref(of child1);
    //using the max function, you can prevent
    //the child2's height from dropping below its content's height
    height: max(content, ref(of child1));
}

As you can see, the scope of the specified selector is the scope of the element on which you are declaring the reference. To select elements outside of that scope, the @parent or @root objects are used.

In case you are referencing another property of the element(s) on which you are declaring the reference, a shorthand exists:

parent
{
    height: ref(width of @this);
    //which is equivalent to
    height: ref(width);
}

Using the sum, avg, max and min modifiers you can respectively do a summation, get an average and retrieve the maximum or minimum.

parent *
{
    //this sets the width of the children of each parent element
    //to the width of whichever is currently the widest child
    width: ref(max width of *);
}

Normally if several elements, which don't share the same value for the referenced property, are matched by the inner selector, the reference, expecting only one value, will return nothing. Setting modifiers or using certain reference objects as the selector permit to bypass this rule.

parent
{
    child1 { height: 150 }
    child2 { height: 50 }
    child3 { height: 100 }

    //this reference won't return anything
    height: ref(height of @this *);
    //granted that the parent elements only have these 3 children
    //this one returns 300
    height: ref(sum height of @this *);
}
The attribute function

The syntax is attr(attributeName of selector) and can be used anywhere a string is accepted. Analogous to references, the function won't return anything if the elements matched don't share the same attribute's value.

element
{
    //this would replace the content text of the element
    //by the value of its href attribute
    text: attr(href);
    //which is equivalent to
    text.value: attr(href of @this);
}
The selector function

The syntax is sel(selector) and can be used anywhere selectors are accepted.

Type functions

The object function

It can be used anywhere objects are accepted. Since keywords takes precedence in the event of a name clash between keywords and object names, this function permits to enforce the type of the value. It can also be used to convert a string to an object name (granted that the outputted name is valid).

element
{
    //sets the background to black using a keyword
    background: black;
    //sets the background to an object named black (assuming it has been defined)
    background: obj(black);
    //since background doesn't have a bg keyword
    //this sets the background to an object named bg (assuming it has been defined)
    background: bg;
    //which is equivalent to
    background: obj(bg);
}
The keyword function

It can be used anywhere keywords are accepted. It is mainly used to convert single-word strings to keywords. It also filters what's passed to it by discarding numbers, objects and multi-word strings.

element
{
    //assuming we have a dir attribute set on the element
    //its value would be converted to a keyword
    //it doesn't ensure it will be valid on this property
    direction: kwd(attr(dir));
}
The string function

str() can be used anywhere strings are accepted. It stringifies what's passed to it.

The number function

num() can be used anywhere numbers are accepted. It converts strings into numbers (provided that they contain precisely one number). It also filters what's passed to it by discarding keywords and objects.

Arithmetic functions

The bounding functions

The max and min functions return respectively the largest and smallest value in a given list of numbers.

element
{
    //if 20% of the parent's innerWidth represents less than 100
    //this sets the width to 100 and 20% otherwise
    width: max(100, 20%);
    //similarly, if 50% of the parent's innerWidth represents 300 or more
    //this sets the width to 300 and defaults to 50% in other cases
    width: min(300, 50%);
}

Using the bound function you can constrain numeric values to certain limits.

element
{
    //this sets a lower and an upper limit of respectively
    //200 for the minimum and 400 for the maximum
    width: bound(200, 30%, 400);
    //which is equivalent to
    width: max(200, min(30%, 400));
}

Note: the third argument of the bound function must always be greater than the first one.

The rounding functions
//346
round(346.238)
//346.24
round(346.238, 2)
//300
round(346.238, -2)
//347
round(346.238, odd)
//which is equivalent to
round(346.238, 0, odd)
//346
round(346.238, even)
//which is equivalent to
round(346.238, 0, even)
//-346 (default half up)
round(-346.5)
//-347
round(-346.5, down)
//which is equivalent to
round(-346.5, 0, down)
//347
ceil(346.238)
//-346
ceil(-346.238)
//346.24
ceil(346.238, 2)
//400
ceil(346.238, -2)
//346
floor(346.238)
//-347
floor(-346.238)
//346.23
floor(346.238, 2)
//300
floor(346.238, -2)

1.6.4 Color

For convenience and compatibility with common graphic edition programs, color values in the sRGB color space can be written as instructions. It is composed of a hash (number sign) # followed by a number in hexadecimal notation of one to eight digits. If omitted the alpha channel defaults to FF.

//this would set the background to white
background: #FFFFFF;
	    //which can be alternatively be written
     〃	    #FFF;
	    //or
     〃	    #FF;
	    //or
     〃	    #F;

//sets the alpha channel to CC using the #RRGGBBAA format
background: #552211CC;
	    //which can be alternatively be written
     〃	    #552211C;
	    //or
     〃	    #521CC;
	    //or
     〃	    #521C;

Besides instructions, you can also use objects (@rgb, @cmyk and @hsb) and keywords (black, white and transparent) as color values.

1.6.5 Property grouping

You can group two or more properties which share the same value(s).

element
{
    width, height: 100%;
    //which is equivalent to
    width: 100%;
    height: 100%;
}

1.6.6 The value selector

Certain properties accept multiple objects as values, in these cases the value selector [] permits to select them either by name, index or using position filters (minus their Child variants).

element {
    //supposing these container objects were defined elsewhere
    isA: objectA, objectX, objectY;
    //this would (re)set objectY's width to 50 for this isA property
    isA[objectY].width: 50;
    //which is equivalent to
    isA[:nth(3)].width: 50;
    //and
    isA[3].width: 50;
}

1.6.7 Boolean casting

Using the negator you can convert a value to a boolean.

//this sets the property to no for any value
//except !no which returns yes
property: !<value>;
//this sets the property to yes for any value
//except !!no which returns no
property: !!<value>;

1.6.8 Concatenation

Concatenating strings is achieved by apposing them next to each other. Optional whitespace can be inserted between the strings to improve readability.

//if the attribute's value were example
//the resulting string would be "The example"
property: "The " attr(attribute of selector);

1.6.9 Custom properties

#TODO Issue 94

1.7 Objects

Objects have a hierarchy, in which objects can descend from others, hence inheriting all the properties defined in their owner. This enables great flexibility and modularization of the code.

Objects can be set to be of a certain type, placing an object type immediately after the at sign, or automatically deriving it from the context. It is followed by whitespace, and optionally an object name and more optional whitespace, followed by a block. Inside the block, its properties are defined.

1.7.1 Object dot notation

1.7.2 Named objects

1.7.3 Anonymous objects

1.7.4 Shorthand notation

1.7.5 Grouping and inheritance

1.8 Instructions

table of contents