cascading properties sheets

Dave Crossland edited this page Jun 5, 2015 · 3 revisions

Demo of CPS:

Metapolator is a parametric font tool. To make working with parameters familiar to web developers, we use the language of Cascading Style Sheets.

This file is a work in progress. It will lead us through the development of our CPS Tools and eventually become the documentation thereof.

CPS is not CSS

CPS is not CSS and this name conveys that you can't use the properties that you know from CSS. However the syntax is very much the same and in fact we use a CSS Parser program to read CPS.

How CPS is similar to CSS:

  • CSS syntax means you can write CPS in a familiar way, with a familiar way of thinking
  • CSS selectors allow us to specify rules for a set of elements, or for just one distinct element
  • CSS cascading so that when more than one rule can apply to an element, the most specific one wins

How CPS is not like CSS:

  • CSS properties are different, as we have our own structure, MOM
  • CSS media-queries are different, with our own @-rules
  • CSS selectors are only partially available today, so selecting the glyph after the glyph 'a' with glyph#a + glyph is not yet possible
  • You can define CPS properties with CPS - it is self-extensible!

Why is this a good thing?

  • If you know CSS, you will feel comfortable with CPS
  • CPS has broad possibilities, so we hope other applications can use it


Interoperability: We don't delete!

To create the maximum of interoperability of CPS tools we try to preserve as much information found when parsing CPS as possible. That means: We keep what we don't understand and just output it again when serializing CPS

This matters because CPS is self extensible, so when new properties and selectors are defined, they will always be loaded without being lost. You can even mix CSS and CPS. But that is for the future. Today a practical use of this is for loading CPS from another Metapolator Project that defines other properties, and being able to operate on the available ones: We can load properties from Lobster into Libre Baskerville.

The Broker

We are planning to support multi-user scenarios, where a lot of people can edit one project simultaneously. Therefore we need to restrict the access to the data-model using special APIs. Since our planning for multiuser editing is not finished yet, we have approached the problem by setting some rules, that in the best case will just hold true when we encounter the real problem and in the worst case shields us from having a lot to rewrite.

So far, the plan is that we are round-tripping a change from the UI to the model and beyond. The UI will commission changes on the data, but the authority to change that data will be in the responsibility of other code, namely, a concept that we call the "Broker" for now. The Model, from the perspective of the UI will be an API of the Broker. The Broker, will inform the UI of changes on the model (using an event/message like system probably). There should be no difference from where the change was made, either from the local computer or from one far away. So when the UI changes a parameter, the value will be in an "unresolved" state, unless the broker sets it to its new value. This must be, because we don't want to reset the display of the value after we submitted the change-request, this would only cause confusion. This, however, implies that we should lock the value until we receive the answer, otherwise the user might be confused by the control element moving autonomously.

CPS Object Model

In general the items in a CPS Object Model are divided into two big categories:

  • the ones we understand
  • the ones we don't (even try to) understand, represented by the one single object-type, the GenericCPSNode.

The items we don't understand will have a string representation, that resembles how the rule displayed in the source CSS. So we can use this for the interface, or let the user choose to hide these pieces. We keep these items around, so that we can just reinsert them in new serializations of the CPS.

The items we understand will provide all the APIs we need to create and change the parameters of Metapolator.

Current State

The base for the CPS Object-Model is established in app/lib/models/parameters

What we can do

  • parse a CPS/CSS string into the object structure as outlined below
  • serialize the object structure back into an equivalent CPS string (formatting differs, some comments may have another position, but at the element where they originally where found)
  • the object structure is ready to be filled with useful methods ;-)
  • we have ways to just keep unknown data around

What we can NOT do

  • understand properties, parse ParameterValue-objects or Selector-Objects
  • query the ParameterCollection for rules that apply for a certain object, this will need the MOM (Metapolator Object Model) for interaction anyways.
  • change data in the object structure (we'll add APIs for that of course)
  • receive values from the structure

The Parser

After some tests the Gonzales CSS Parser was chosen. Not only because the name implies that it is fast ;-) Also, because the Abstract Syntax Tree (AST) that it creates looks like a very complete CSS implementation and it allows us to keep the parts we don't interpret around, so we can obey the "We don't delete!" rule. First tests showed, that we might even be able to restore the line numbers from the AST (which it sadly does not provide by itself). That would be a great help with debugging the CPS, the method used, however, still has to be tested against real-life situations.

The parsing of CPS/CSS with gonzales, reading the AST into the CPS-Object-Model and serializing the result again can currently be seen in http://localhost:8000/playground.html.

Example roundtrip

Besides some reformatting into a much prettier form, the important information here is that the output block is the result of a complete roundtrip: string -> gonzales AST -> CPS-Object-Model -> string


  background-color /*property comment*/: #fff;
  margin: 0 auto;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='a.png',sizingMethod='scale')}/* And a comment */ strange>.selector/* selector comment 
 with linebreak */,and another onemissing.delim >{ any-param
: unheard-of(style + css);/* a comment within a block */another-one: def-unheard-of(style + css)/*this param has a comment*/;}@media whatever{ hi{name:val} }


body {
    background-color /*property comment*/:  #fff;
    margin:  0 auto;

/* And a comment */

strange>.selector/* selector comment 
 with linebreak */,
and another onemissing.delim > {
    any-param:  unheard-of(style + css);
    /* a comment within a block */
    another-one: /*this param has a comment*/  def-unheard-of(style + css);

@media whatever{ hi{name:val} }

The most intersting part happens in app/lib/models/parameters/factories.js. Where all the logic for the conversion from AST to CPS-Object-Model is contained. The code is richly documented, so it may be worth a look.

One of the best things with the factory approach here is, that we could change the parser without touching the CPS-Object-Model. We should work in that direction and reduce all touching points with the AST of gonzales to the factories module--this is not fully the case at the moment, because some Object Model classes still receive GenericCPSNode-objects where there should be processable values instead.


To parse CPS into the object model, the module app/lib/models/parameters/factories provides the methods rulesFromString and rulesFromAST. Internally rulesFromString uses gonzales to parse the string and then invoke rulesFromAST with the AST from gonzales. The resulting object is an instance of ParameterCollection.

All objects used in the CPS Object-Model so far are:

  • Source every _Node has one, so we can track the origin of it.

  • _Node at the moment all items in the CPS-Object-Model inherit from _Node. _Node itself inherits from _BaseModel. _Node has the Source and line-number information of each CPS Object-Model item.

  • ParameterCollection consists at the highest level of a list of Comment, GenericCPSNode and Rule-objects.

    GenericCPSNode-objects that only contain whitespace (type == "s") are dumped.

    equivalent gonzales AST item: stylesheet

  • Comment can occur at different positions in the CPS. So far, nodes aware of comments are:

    • ParameterCollection
    • Selector
    • ParameterDict
    • ParameterName
    • ParameterValue

    equivalent gonzales AST item: comment

  • GenericCPSNode This node stores the parts of the AST that we don't interpret. It's the basic concept to achieve the "We don't delete!" rule. A GenericCPSNode can be converted back to a CPS-string without loss of information. GenericCPSNodes occur at different positions in the Collection:

    • ParameterCollection for example @-Rules as in @media screen {} are not yet interpreted, they are just stored as GenericCPSNodes and recreated upon serialization
    • ParameterDict unknown parameters are/will be kept as GenericCPSNodes

    Also at the moment:

    • Selector
    • ParameterValue

    Both keep their contents as a mixed list GenericCPSNodes and Comments, but this will change as soon as we start to interpret their values for our uses.

    equivalent gonzales AST item: (anything else)

  • Rule Container for one SelectorList and one ParameterDict

    equivalent gonzales AST item: ruleset

  • SelectorList Container for a list of Selector-objects

    equivalent gonzales AST item: selector

  • Selector one CPS selector. The value of the selector is currently not further processed and consists of GenericCPSNode-objects and Comments. Whitespace is not removed because that could change the meaning of the selector. We'll have to interpret the value of a Selector further, waiting for the first use case.

    equivalent gonzales AST item: simpleselector

  • ParameterDict a list of Parameter, Comment and GenericCPSNode And it will provide a dictionary like access to the Parameter-objects

    equivalent gonzales AST item: block

  • Parameter a container for the key-value pair of ParameterName and ParameterValue

    equivalent gonzales AST item: declaration

  • ParameterName the name of a Parameter and a list of Comment-objects (that's possible in CSS). The list of comments can be empty of course.

    equivalent gonzales AST item: parameter

  • ParameterValue the value of a Parameter, currently list of GenericCPSNode-objects and a list of Comment-objects. This current state with the GenericCPSNode-objects will change when we start to interpret the value. Also, at the moment, this results in an unfortunate situation when serializing, because we have to keep the whitespace information around. When the values are interpreted, the whitespace situation will resolve.

    equivalent gonzales AST item: value

Undocumented CPS elements
  • AtRuleName
  • AtRuleCollection
  • AtNamespaceCollection
  • Namespaces in general

The CPS interface

I suggest 2 stages: The first as a pure display of the model. The second stage would add the ability to change values. There a more features that we are going to add subsequently.

We will start with a very simple viewer, that takes a CascadedParameters object and displays its values. We will differentiate between raw AST items and items that we understand.

  • The Generic AST-Items (and others that we don't touch, if there is more) will have a text only representation, and may be relocatable and deletable manually by the user.
  • Plugins will define the available parameters AND how to display and change them, this includes information like: value ranges, kind of value, tooltips, further documentation etc. So we can, in the end, have interfaces like sliders, switches, text-fields etc.

Two Modes

Both Modes Change RuleSets (CRUD), Reorder stuff, Edit Comments (CRUD)

Both modes load using a CascadedParameters object, the CascadedParameters may expose useful API-methods, so that we can keep knowledge away from the UI wherever possible.

File Mode Display one "file" or more exact "source" of CPS. This will make it possible to change the order of RuleSets.

Entity Mode This will display all Rulesets that apply to one entity, pretty much like Firebug does when selecting one single element. The RuleSets will be expose including their information about their source (name and line number). This will, similar like in Firebug, display which parameters are responsible for the final value and where overridden. The ordering will not be created by the UI, but by Model API, as well as the selection of the applying rules are made in Model-land.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.