omniJS – Style enumeration values can't be compared or tested?


#1

To start with a particular example – the default underline style is UnderlineStyle.None,
but at present the following test seems to evaluate to a false negative:

Style.Attribute.UnderlineStyle.defaultValue === UnderlineStyle.None

More generally, human-readable names of omniJS Style Attribute enum values can already be used for imperatively setting styles, but if we want to read styles and conditionally branch the script behaviour accordingly (e.g. for custom exports and clipboards etc), we seem to get tripped and blocked by some obstacles in current builds (OO 5.2 test (v184 r294214)) of the omniJS interface to OmniOutliner.

  1. We don’t seem to be able to serialise style enum values (as returned by Style.get or Style.Attribute.defaultValue) to human-readable or JSON-friendly name strings.
  2. We can’t (I think) test OO omniJS enum values (returned by Style.get or Style.Attribute.defaultValue) for equality using JavaScript equivalence operators (=== for simple fast equivalence, or == for slower equivalence after coercion)

For example, selecting a singly-underlined topic like:

The Automation Console returns the UnderlineStyle value as:

[object UnderlineStyle: 1]

The omniJS Console also evaluates the omniJS expression

UnderlineStyle.Single

to what looks like the same value:

So we might hope that

selectedTopicUnderline === singleUnderline

or

selectedTopicUnderline == singleUnderline

Would evaluate to true, and that we could make good use of this in a script.

In fact, however:

(The built-in JavaScript equivalence operators do not return true for two objects which simply have the same pattern of keys and values – in contrast with the AppleScript example of:

In JavaScript, while pairs of atomic values of types like Bool, Float, and String may be equivalent, two Arrays, Dictionaries or other compound values, however similar, are still two different (non-equivalent) objects in terms of the ( === / == ) operators.

( In JavaScript only two references to the identical instance of an Array or Dictionary object satisfy === )

We would be able to test, compare and branch on Style.Attribute enum values if they serialized to distinct strings (had some kind of string .identifier property, for example), but unfortunately the values which they return to .toString() are not distinct.

The members of the underlineStyle enum, for example, are accessible (for writing) through the 4 distinct internal keys:

Object.getOwnPropertyNames(UnderlineStyle).slice(2)
-> ['None', 'Single', 'Thick', 'Double']

But these keys are not available for testing or reading – simple coercion of the enum values to strings preserves no distinctions among them
Object.getOwnPropertyNames(UnderlineStyle).slice(2).map(k => UnderlineStyle[k].toString())

-> [ "[object UnderlineStyle]", "[object UnderlineStyle]", "[object UnderlineStyle]", "[object UnderlineStyle]" ]

and the enum value objects have no methods or properties which enable a script to distinguish between them:

JSON.stringify(UnderlineStyle['Double']) -> "{}"

while

Object.getOwnPropertyNames(UnderlineStyle['Double']) -> []


To summarise:

What we need is to be able to write code analogous to:

if (fontWeight > 8 || underlineStyle !== none)  { etc. etc. }

and to easily serialise enum values to corresponding name strings which are:

  1. human-legible
  2. usable as JSON keys or JS identifiers
  3. comparable in terms of === and !==

[details=Test code] (() => {
‘use strict’;

    const selectedTopicUnderline = document.editors[0]
        .selectedNodes[0]
        .valueForColumn(this.outlineColumn)
        .style.get(Style.Attribute.UnderlineStyle);

    const singleUnderline = UnderlineStyle.Single;

    return [
        selectedTopicUnderline,
        singleUnderline,
        selectedTopicUnderline === singleUnderline,
        selectedTopicUnderline == singleUnderline
    ];
})();

[/details]


#2

PS the contrast between the OmniOutliner and OmniGraffle omniJS APIs may shed some light:

In the OmniGraffle API, the enum values are comparable with === and !==

For example, where the value of g is an orthogonal line graphic:

Perhaps in the OmniGraffle case, each of the two appearances of [object LineType: 2] is a reference to the same underlying object, and therefore usefully comparable by a calling script ?

Could it be that in the current OmniOutliner builds, enum name constants and Text property return values are returning references to separately generated instances (hence always returning false to === comparisons) ?


#3

Ah … partial progress:

using

Style.localValueForAttribute()

in lieu of

Style.get()

does return a human-legible and JSON friendly serialization.

(but all the equivalence tests still fail)

(() => {
    'use strict';

    const strKey = 'underline-style';

    const selnStyle = document.editors[0]
        .selectedNodes[0]
        .valueForColumn(this.outlineColumn)
        .style;

    const topicUnderlineByGet = selnStyle.get(
        Style.Attribute.UnderlineStyle
    );

    const topicUnderlineLocalValue = selnStyle.localValueForAttribute(
        Style.Attribute.UnderlineStyle
    );

    const singleUnderline = UnderlineStyle.Single;

    return show(2, {
        topicUnderlineByGet: topicUnderlineByGet,
        topicUnderlineLocalValue: topicUnderlineLocalValue,
        singleUnderline: singleUnderline,
        asString: [topicUnderlineByGet.toString(),
            topicUnderlineLocalValue.toString(),
            singleUnderline.toString(),
        ],
        EQ: [topicUnderlineByGet === singleUnderline,
            topicUnderlineLocalValue === singleUnderline,
            topicUnderlineLocalValue === UnderlineStyle.Single
        ]
    });
})();

Result:

{
  "topicUnderlineByGet": {},
  "topicUnderlineLocalValue": "single",
  "singleUnderline": {},
  "asString": [
    "[object UnderlineStyle]",
    "single",
    "[object UnderlineStyle]"
  ],
  "EQ": [
    false,
    false,
    false
  ]
}

#4

It also seems a bit unexpected, from a reading of the docs, which suggest that the two functions should have the same return type,

that .get returns an enum value object,

while .localValueForAttribute returns a string

Conceivably another clue to the comparison failures under === and !== ?