Pages: 1
Print
Author Topic: Highlighting and coloring text  (Read 9162 times)
AHM
Member

Posts: 5


« on: March 10, 2010, 05:43:30 AM »

Hello,

I try to implement a text color / text highlight feature in an XMetaL customization (for XMetaL 4.5 through 5.5).
It's a simple toggle element which stores the color code returned by the ColorChooser dialog in an attribute.

My question is: Is there a way to make this freely selectable color appear in the editor?
As far as I know, the attr() function in CSS 2 does not work with any property other than content and I have found no other way to do that.

Thanks,
Andreas
Logged
Su-Laine Yeo
Solutions Consultant
Member

Posts: 260


« Reply #1 on: March 10, 2010, 07:11:39 PM »

It is possible in the version of CSS that XMetaL uses to to apply a background color to an element based on the value of an attribute. Can you give us more details on what you're trying to do and what hasn't worked?
Logged

Su-Laine Yeo
Solutions Consultant
JustSystems Canada, Inc.
AHM
Member

Posts: 5


« Reply #2 on: March 11, 2010, 02:34:33 AM »

For example, if I have the following XML fragment:

Code:
<paragraph>A paragraph with <colorFont color="#003FC8">colored words</colorFont>.</paragraph>
<paragraph>Another paragraph with words in a <colorFont color="#7A2700">different color</colorFont>.</paragraph>

I need the font to be exactly the color defined in the color attribute.
I think what you mean is that I could define something like:

Code:
colorFont[color="#003FC8"] {
   color: #003FC8;
}
colorFont[color="#7A2700"] {
   color: #7A2700;
}

The problem is that the users are free to select any of the 16.7 million colors from the Color Chooser.
So what I would like to be able to do is something more dynamic like:

Code:
colorFont {
   color: attr(color);
}

Unfortunately, this does not work in CSS, but maybe there is another way of doing it.

Thanks,
Andreas
Logged
mag3737
XMetaL Evangelist
Administrator
Member

Posts: 117

I even use XMetaL to write my business letters.


« Reply #3 on: March 11, 2010, 06:13:52 PM »

Hi Andreas,

You could do this with some script.  The useful API is ContainerStyle(), which is a property of the Selection/Range object.  ContainerStyle is a string containing CSS style declarations -- a sequence of property:value pairs separated by semicolons -- the stuff that would be between the { } in a CSS descriptor.  When you set this property, it applies those styles to the container of the current selection or range.  They are added to any existing styles from the stylesheet already being used.

E.g. The following line of code would apply a yellow background to the container of your current selection:
Code:
Selection.ContainerStyle = "background-color:yellow"

Styles applied with this method will last as long as the document remains open in either Normal or Tags On view.  If you switch to Plain Text view, or close and reopen the document, all of these script-applied styles are wiped out (back to the base CSS stylesheet).

The second part of the problem is when/how you should execute this script.

You could, for example, put this code in a user macro that used a range object to traverse all the <colorFont> nodes in turn, grabbed the "color" attribute value of each one, and set this property.  E.g. you could name the macro something like "Refresh all fontColor elements".

More likely, you would want the background colors to change automatically whenever the user adds a <colorFont> element or changes an attribute value.  There are several places where you might/might not need to repeat the script logic that applies these styles, including:  On_Insert script for whenever the <colorFont> element is added; On_Update_UI macro that detects if changes have been made to a color attribute; and On_View_Change macro where you can detect if the user is "changing back" from Plain Text View.
Logged

Tom Magliery
JustSystems Canada, Inc.
Derek Read
Program Manager (XMetaL)
Administrator
Member

Posts: 2621



WWW
« Reply #4 on: March 12, 2010, 07:39:09 PM »

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:

<MACRO name="On_Macro_File_Load" hide="true" lang="JScript"><![CDATA[
function setCSSFromColorAttribute() {
   var rng = ActiveDocument.Range;
   var defaultColor = "black";
   var colorAttrName = "color";
   rng.MoveToDocumentStart();
   while(rng.MoveToElement()) {
      if (rng.ContainerNode.hasAttribute(colorAttrName)) {
         rng.ContainerStyle = "color:" + rng.ContainerAttribute(colorAttrName);
      }
      else {
         rng.ContainerStyle = "color:" + defaultColor;
      }
   }
}

...other functions if required...
]]></MACRO>


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:

<MACRO name="On_ElementList_Insert" hide="true" lang="JScript"><![CDATA[
   Selection.InsertWithTemplate(Application.ElementList.SelectedName);
   if (Application.ElementList.SelectedName == "colorFont") {
      //call function defined in On_Macro_File_Load event
      setCSSFromColorAttribute();
   }
]]></MACRO>


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"] {
   color: #003FC8;
}
colorFont[color="#7A2700"] {
   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.
« Last Edit: March 13, 2010, 12:02:32 PM by Derek Read » Logged
Derek Read
Program Manager (XMetaL)
Administrator
Member

Posts: 2621



WWW
« Reply #5 on: March 13, 2010, 12:46:55 PM »

One more thing that would make the function in my last post more efficient. If you only every need to set ContainerStyle for a specific element, pass that element's name in as a parameter of the MoveToElement() method, so in my script and assuming your element name "colorFont":

rng.MoveToElement("colorFont");
Logged
AHM
Member

Posts: 5


« Reply #6 on: March 15, 2010, 04:20:11 AM »

Thank you guys. Works like a charm.


Just a couple hints (in case anyone else wants to do the same thing):

For the macros which process the whole document (On_Document_Open_Complete and On_View_Change) it is a good idea to use the MoveToDocumentStart() function in the end again. Otherwise the user sees the last colored element rather than the document's beginning.


In the On_View_Change macro is is useful to check not only the current view but also the previous one.
As Tom pointed out the colors remain active when the user switches between Normal View and TagsOn View.
Processing the document in this situation would not only have a negative impact on performance (depending on size of document and amount of processed elements) but would unnecessarily move the selection away from where the user put it.

Code:
if(ActiveDocument.PreviousViewType >= 2 && ActiveDocument.ViewType <= 1) {
    SetColors("colorFont", "color");
    SetColors("colorBack", "background-color");
}

Logged
Pages: 1
Print
Jump to: