Applying more than one text (character) style to a portion
I am using LibreOffice 4.1 on OS X 10.8.
It is indeed possible to apply more than one character style to a given portion of text. Take the following example with the two styles you mention (Source Text and Emphasis):
<text:p text:style-name="P1">This is what
<text:span text:style-name="T1">emphasized </text:span>
<text:span text:style-name="Source_20_Text">
<text:span text:style-name="Emphasis">code</text:span></text:span>
looks like.</text:p>
In this first example, I have added the string emphasized
afterwards, so that it is enclosed in its own span element with an automatic text style T1
. Reminder: styles that apply to text strings are called character styles in LibO and text styles in the ODF spec. The T1
style is defined thus:
<style:style style:name="T1" style:family="text">
<style:text-properties officeooo:rsid="000c8b60"/>
</style:style>
The only defined attribute is an officeooo:rsid
, which shows that this style’s only purpose is document comparison (this makes me grumpy). Apart from that, we can see that it is quite possible to apply two character styles to the same portion. In fact, there are two ways to say it:
- LibO speak: It is possible to apply multiple different character styles to one text portion;
- Spec speak: It is possible to enclose a given text node in multiple nested
span
elements with different text:style-name
attributes.
Remark on LibO’s behaviour: the effects are cumulative, so that the word code
in this example is displayed in monospaced oblique type (for a monospaced font, the proper term is oblique, as there is no italic shape to speak of, but of course LibO does what is expected and chooses the oblique font).
Remark on implementation: according to the ODF 1.2 spec (part 1, section 19.770), a text:class-names
attribute exists for the purpose of applying more than one text style to a node:
A text:class-names
attribute specifies a white space separated list of style names. The referenced styles are applied in the order they are contained in the list.
If both text:style-name
and text:class-names
are present, the style referenced by the text:style-name
attribute is applied before the styles referenced by text:class-names
attribute. If a conditional style is specified together with a text:class-names
attribute, but without a text:style-name
attribute, the text:style-name
attribute is assumed to have the value of the first style name in the list defined by the text:class-name
attribute.
I find that very useful and desirable. One potential use case is to apply a style that defines the visual presentation of the element with a text:style-name
attribute (say, "Emphasis"
to denote a change in tone with italic type) and also apply other styles with a purely semantic meaning and without any particular visual distinction, with a text:class-names
attribute (e.g. "Archaic Eponym"
). Perhaps that sounds convoluted, but it’s the kind of thing I have to do in my work and I am sure I am not alone.
Unfortunately, in my experience, LibO does not write text:class-names
attributes in the ODF it produces. Perhaps I just haven’t experienced enough, though.
Let us try with user-defined styles now. Here is the second paragraph in my document:
<text:p text:style-name="P2">I like
<text:span text:style-name="Plant">
<text:span text:style-name="Drink">tea</text:span></text:span>
very much.</text:p>
I have defined two character styles in LibO for this document: Plant and Drink. Plant makes the text green and Drink makes it italicized. I have applied them both to the word tea
because tea is the name for both the plant and the drink made from the plant ;-). Again, the effects are cumulative: the word tea
in my document is now both green and italicized, but again we have two span
elements instead of one element with both a text:style-name
and a text:class-names
attribute. Note that I applied Plant first and then Drink and, accordingly, the span
with Plant
encloses the one with Drink
(the span
with the Plant
attribute comes first in the document’s hierarchy).
Remark on the paragraph element in this second example: it has a different style (P2
), even though the first and the second paragraph have the same level, the same appearance and the same purpose. Makes me grumpy. For people who want to produce structured and semantic documents, it is a pain. But this will change again in a future LibreOffice version, won’t it?
Now, let us try to understand how LibreOffice represents this document to itself.
How do we inquire about the character styles applied to a text portion?
A distinction that must be made clearly here is that, much like most applications that consume some kind of XML, LibreOffice does not use XML data structures “natively”. It is still true that ODF is LibO’s native format in a certain sense, but any application that supports many document formats needs some kind of internal representation. In our case, we could call it a StarWriter document object or something like that (but that might summon some old ghosts, so I’m not going to insist on that).
In order to give a full account, I would have to define concepts which are specific to OpenOffice/LibreOffice, but this is beyond my means. Suffice it to say that in the application, the text document contains objects that have properties and methods and that the objects that “wrap” others together are called services. The OpenOffice Developer’s Guide (a resource of very uneven quality) has a page that distinguishes further in Appendix A. To put it relatively simply, the structure of text paragraphs is so:
-
Paragraph
s are services that include a TextContent
service.
- The actual text is stored in
TextPortion
services. The description of the TextPortion
service in the API documentation is very unclear about the actual status of the service and its relation to the Paragraph
service.
- In any case, the
TextPortion
is not the thing that has a style: the TextPortion
service includes a TextRange
service. This service is the one that has the actual CharacterProperties
service.
- In the
CharacterProperties
service, there are two properties that are relevant to our inquiry: CharStyleName
and CharStyleNames
. Both are optional. The data type of the CharStyleName
property is “string”, while the data type of the CharStyleNames
property is a sequence of strings.
Logically, the CharStyleName
property should correspond to the text:style-name
ODF attribute and the CharStyleNames
property should correspond to text:class-names
. This is more or less the case, except that the description of the CharStylesnames
property says:
It is not guaranteed that the order in the sequence reflects the order of the evaluation of the character style attributes.
If the ODF spec says that “[t]he referenced styles are applied in the order they are contained in the list”, then why is that? I have no answer…
However, now we know what properties we must inquire about. Here is a very dumb subroutine that only works in a very specific situation. When selecting a single styled word in the document, the subroutine prints the values of the style properties. Actually, it generates an error if the selection only has one style applied. The only purpose of this lousy code is to print the styles of a portion which we know has two styles:
sub print_char_styles_of_viewcursor()
dim CurrentDoc as object
dim FirstStyleofSelection as string
dim StyleListofSelection(2) as string
CurrentDoc = ThisComponent.CurrentController
FirstStyleofSelection = CurrentDoc.getViewCursor().CharStyleName
StyleListofSelection() = CurrentDoc.getViewCursor().CharStyleNames()
print "The values of the selected portion’s character style properties are: “"; _
FirstStyleofSelection; "” for CharStyleName; “"; _
StyleListofSelection(0); ", "; StyleListofSelection(1); "” for CharStyleNames."
end sub
Remarks on the code:
- Even though I said that character styles actually apply to text ranges, here I am using StarBasic to access properties, I am working on portions and I do get the character styles.
- I mentioned that in the API (IDL reference), the
CharStyleNames
property is defined as a sequence of strings. Here, from the perspective of StarBasic, it is an array of strings. My assignment of StyleListofSelection
to the value of CurrentDoc.getViewCursor().CharStyleNames()
is basically the assignment of an array to another array. When you do this in real life, you need to be careful.
When I select the word tea
and then run this subroutine, the following message is printed:
The values of the selected portion’s character style properties are: “Drink” for CharStyleName; “Plant, Drink” for CharStyleNames.
When I select the word code
, this message is printed:
The values of the selected portion’s character style properties are: “Emphasis” for CharStyleName; “Source Text, Emphasis” for CharStyleNames.
In both cases, the value of the CharStyleName
property is the style that was applied last, but the order of the styles in CharStyleNames
corresponds to the order in which they were applied. This suggests that the order is indeed not respected when it comes to deciding which style has priority.
This is all very interesting, but the question remains: how do we accurately control the assignment of character properties? When writing the ODF, LibO does respect the order of assignment, but this is not reflected in the document’s data structures inside the application. I think a bug should be raised about the specific issue of the character styles’ priority. If the CharStyleNames
property was mapped to the text:class-names
attribute, that would be a big improvement.