Home Forums General XMetaL Discussion Highlighting and coloring text Reply To: Highlighting and coloring text

Derek Read

Reply to: Highlighting and coloring text

The CSS that XMetaL Author supports is pretty much that which is defined by the W3C, and does not currently allow for the extraction of an attribute's value for injection into CSS properties other than 'content'.

Basically, what this means is the only place the attr() construct is supported is within the 'content' property of CSS selector containing a :before or :after pseudo-selector, like so:

elementName:before {
content: attr(attrName);

As previously noted, there are several solutions to your problem. One of them requires 6.0 however, so as you are working with pre-6.0 releases you are stuck with the ones recommended by Tom and Su-Laine. I'm listing them in order of what I believe to be your best option:

ContainerStyle Solution: (more detail on Tom's idea)
Create a function inside an event macro called On_Macro_File_Load that does something like this:

function setCSSFromColorAttribute() {
var rng = ActiveDocument.Range;
var defaultColor = "black";
var colorAttrName = "color";
while(rng.MoveToElement()) {
if (rng.ContainerNode.hasAttribute(colorAttrName)) {
rng.ContainerStyle = "color:" + rng.ContainerAttribute(colorAttrName);
else {
rng.ContainerStyle = "color:" + defaultColor;
} ...other functions if required...

Then in every event where these colors might need to be refreshed call that function. Events I can think of:

On_Document_Open_Complete (to style the document after loading it)

On_View_Change (inside which you will want to detect by using an if…then statement whether ViewType is TagsOn or Normal view and if PreviousViewType was not TagsOn or Normal view, before executing, this is necessary because CSS set this way is lost when you move out of TagsOn or Normal view)

On_After_Set_Attribute_From_AI (to style the document after the user makes a change to a particular color attribute value)

And for each of the following events you may want to override the standard behavior by adding a call to the function.

On_ElementList_Insert (called when the Insert radio button is selected)
On_ElementList_Change (called when the Change radio button is selected, may not be needed if the user cannot change from any other element)
On_ElementList_Surround (called when the Insert radio button is selected and the user’s selection is not empty)
On_ElementList_Insert_NoRequired (this would likely not be needed in most cases)

Refer to the Programmers Guide to see how to trigger the standard behavior in each of the events listed above for On_ElementList_* as without those an element will not be inserted.

Example for when elements are inserted:

if (Application.ElementList.SelectedName == "colorFont") {
//call function defined in On_Macro_File_Load event

It is tempting to call your function from On_Update_UI because it will fire in all the cases listed above. The problem is that it fires in a lot more cases than just those listed above so that may slow editing down too much.

The alternative to doing all of this (for various events) would be to simply define a macro containing the script from the function above and allow the user to trigger it manually (button, menu item, shortcut key), or via some other common event that they regularly use like On_After_Document_Validate which is triggered with F9, the menu item Validate, and when saving. The advantage to this is that the user can opt to choose when they wish the styling to be updated themselves.

Finally, if you tend to work with extremely large documents the execution speed of this function might be improved by checking the property ChangedNodes and only re-styling those nodes that have had attributes changed. However, for most people this should not be necessary (assuming fairly small documents with perhaps a few thousand nodes and a modern computer). Checking and keeping track of ChangedNodes can be complex however, so if possible I'd try to get by with the suggestion as it is here. The one benefit to this is that you might get away with calling such a function from within the On_Update_UI event (only testing would tell for sure).

RefreshCSSStyle() Solution:
A similar solution to the one above could be written that would write out a bunch of CSS selectors that look like the following to a file that would then be imported using a CSS @import statement in your main CSS file (that references a secondary file you might call colorFont.css. However, this will add complexity to the problem because then you need to worry about where to write out the colorFont.css file and whether or not the user has permission to write to that location.

colorFont {
   color: #003FC8;
colorFont {
   color: #7A2700;

However, in order to build this list such a script would still need to walk through your document and pull out every “color” attribute value (almost identical to the solution above) by visiting each element and so execution time would likely be very similar, plus the overhead of writing to disk. It doesn't really save any coding either (and in fact adds to it) as you would still need to trigger the function on all the same events as above.

XMetaL Author 6.0 “Appending CSS In-Memory” Solution:
This is almost exactly the same as the solution immediately above. The only difference being that instead of having to write out CSS settings to disk, in 6.0 you can append CSS to the end of all other CSS directly in memory using some new APIs. However, as above, the main function would still need to walk the entire document to obtain the attribute values and so, again, I don't believe this would be any more efficient.