Strokes and Fills in Flex CSS through ActionScript injection

Flex CSS, while a fine idea, is riddled with limitations. Most notable is probably its lack of proper cascading, but equally unfortunate is that it’s a system defined by programmatic ActionScript APIs. There is a textual way of defining styles, but the Flex compiler, mxmlc, is used to turn this style sheet language into ActionScript, which is in turn compiled into a style module you can load at runtime.

The textual stylesheet language is the black sheep of the family, supporting only a limited set of the features available with runtime Flex styles. For example, take the LineSeries from the data visualization package that comes with FlexBuilder Pro. It defines a number of styles in terms of IFill or IStroke instances. How do you specify IFill or IStroker instances in Flex CSS, so that you can configure look and feel separately from application logic, or alter styles without redeploying the entire application? You don’t. You curse the mxmlc designers for not allowing richer CSS compilation. You can only specify numbers; Strings; embedded images, sounds, and fonts; and references to classes through the ClassReference directive. No instances of any other objects.

Well, what if you’re mad as hell and you won’t take it anymore?

I’m working on a visualization framework with some dynamic styling, and being able to specify “default” styles externally through standard Flex CSS is really, really important. It makes the model much simpler. I was banging my head against the Flex CSS wall and then the scales fell from my eyes:

  1. Mxmlc compiles Flex CSS files to ActionScript.
  2. That ActionScript must simply be building standard mx.styles.CSSStyleDeclaration classes.
  3. Mxmlc has a keep-generated-actionscript attribute.

We take global.css:

AxisRenderer {
    labelAlign: "center";
}

compile with keep-generated-actionscript=true, and we get:

core_properties.as
global.as
global_mx_core_FlexModuleFactory
styles_properties.as

Looking at these, only global.as is interesting. It has a lot of boilerplate code, but then something familiar:

        ...
        var style:CSSStyleDeclaration;
        var keys:Array;
        var selector:String;
        var effects:Array;
        var addedEffects:Array;

        selector = "AxisRenderer";

        style = StyleManager.getStyleDeclaration(selector);

        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration(selector, style, false);
            selectors.push(selector);
        }

        keys = overrideMap[selector];

        if (keys == null)
        {
            keys = [];
            overrideMap[selector] = keys;
        }

        style.mx_internal::setStyle('labelAlign', "center");
        keys.push("labelAlign");
        ...

This seems fairly routine. It builds a CSSStyleDeclaration programmatically, and then adds values according to our source CSS file. And the style values seem to be just passed along. If only we could somehow “unquote” that style value, we could inject arbitrary ActionScript expressions that would be evaluated before being assigned as style values.

“Unquoting” has a venerable history in programming, so it’s not too crazy an idea. Bash does it (echo "hello $user"), PHP does it (though it’s been a while since my PHP days and I don’t remember the syntax–something along the lines of bash if memory serves), and of course Lisp does it ((let ((user 'maciek)) `(hello ,user)). The tricky part is the implementation.

Fortunately, it’s not really that tricky. With the keep-generated-actionscript flag, mxmlc does most of the work for us. All we need to do is a little post-processing, and re-build the ActionScript into a swf, ignoring the intermediate output swf. Now, everybody stand back. I know regular expressions (not well, mind you, but I get by).

Since Flex has ant task wrappers for its compilers, the easiest way to approach this was to actually build a custom ant filter. Filters can be applied when moving or copying files and they can perform line-by-line transformations on a file. Looking at the generated ActionScript above, we try to find lines matching the calls to setStyle, look for an arbitary special escape sequence that we introduce in String style values (I chose a leading comma, in homage to Lisp), and strip quotes from values matching that escape sequence, to let them execute as arbitrary ActionScript code.

import org.apache.tools.ant.filters.TokenFilter;

public class StyleFilter implements TokenFilter.Filter {
    public String filter(String token) {
        return token.
            replaceAll("(?<=style\\.mx_internal::setStyle\\('[^']{1,100}', )\",([^\"]+)\"(?=\\);)",
                "$1").replaceAll("^import mx\\.core\\.mx_internal;$",
                "import mx.core.mx_internal;\nimport mx.graphics.*;");
    }
}

The regular expression is a little daunting. It can probably be optimized for both performance and correctness (I know I miss edge cases), but it works for now. Briefly,

  1. "(?<=style\\.mx_internal::setStyle\\('[^']{1,100}', )\",([^\"]+)\"(?=\\);)": positive lookbehind for the setStyle() call we see above–everything up to and including the comma after the style name. Note that lookbehind won’t let us use an unbounded number of characters due to regex mechanics, so I arbitrarily cap it at 100. I’m sure there are better ways around this, but you’ll seldom see 100-character style names in the wild. Note also that we need to escape the period and parentheses, and escape the backslashes escaping them, since these are Java Strings.
  2. "(?<=style\\.mx_internal::setStyle\\('[^']{1,100}', )\",([^\"]+)\"(?=\\);)": the actual quoted value. This expression won’t work on escaped embedded quotes, but since ActionScript supports both single and double quotes, you can typically use single quotes instead. Note that we match the whole quoted value, with the leading comma that signifies the value is to be “de-quoted”, but we only capture the unquoted value, sans leading comma.
  3. "(?<=style\\.mx_internal::setStyle\\('[^']{1,100}', )\",([^\"]+)\"(?=\\);)": We make sure the line ends as we expect with positive lookahead. Not really necessary, but a nice sanity check

We replace this whole thing with the first captured group.

Note also the second replace. That’s there to add an import statement for the mx.graphics package, where Stroke and SolidFill, the classes I am most interested in live. Unfortunately, Flex requires import statements even for fully qualified class names (whereas in, e.g., Java, you can say something like System.out.println instead of having to import it explicitly). With this, a style like:

AxisRenderer {
    labelAlign: center;
    axisStroke: ',new Stroke(0x0000ff, 2)';
}

will look like this before the processing step:

        style.mx_internal::setStyle('labelAlign', "center");
        keys.push("labelAlign");
        style.mx_internal::setStyle('axisStroke', ",new Stroke(0x0000ff, 2)");
        keys.push("axisStroke");

and this after processing:

        style.mx_internal::setStyle('labelAlign', "center");
        keys.push("labelAlign");
        style.mx_internal::setStyle('axisStroke', new Stroke(0x0000ff, 2));
        keys.push("axisStroke");

This means you can put arbitrary ActionScript expressions in your style values in CSS. Compiling this yields a standard Flex style module that can be loaded like any other.

Does the special treatment for the mx.graphics package mean that you need to extend the filter with more imports if you need another package? Not necessarily. ActionScript has anonymous functions and imports can be done locally, so you can have CSS values that would give any self-respecting designer a heart attack:

AxisRenderer {
    axisStroke: ',function():Object { import com.example.stroke.CustomStroke; return new CustomStroke(0x0000ff, 2); }()';
}
About these ads

4 Responses to “Strokes and Fills in Flex CSS through ActionScript injection”

  1. James Ward Says:

    Wow! This is freaking cool. Thanks!

    • deafbybeheading Says:

      Thanks (and sorry about the late reply)–your RIA census was one of the reasons we decided to go with Flex back in the day–it’s nice to be able to give back. Did you actually end up going with this approach? Any feedback?

      • James Ward Says:

        That’s great to hear about Census. I’m really excited about the new version. Hopefully it will be done in the next month.

        I actually ended up just creating a custom AxisRenderer that exposes the styles I needed. I posted it on StackOverflow. It was no where near as cool as your approach though. :)

        Thanks for helping!

        -James

  2. deafbybeheading Says:

    Yep, that’s generally a more sensible approach. We needed something fully generic, so this works for us, but if you can constrain the problem, it’s generally possible to have a saner solution.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: