Subscribe to Dr. Macro's XML Rants

NOTE TO TOOL OWNERS: In this blog I will occasionally make statements about products that you will take exception to. My intent is to always be factual and accurate. If I have made a statement that you consider to be incorrect or innaccurate, please bring it to my attention and, once I have verified my error, I will post the appropriate correction.

And before you get too exercised, please read the post, date 9 Feb 2006, titled "All Tools Suck".

Tuesday, February 02, 2016

Rethinking DITA "Custom" Attributes

I had a bit of an epiphany today as regards the definition of "custom" attributes and their DITA conformance or lack thereof.

To date I think I have been consistent in saying "You can't define attributes for individual elements in a way that is a conforming DITA specialization."

But in discussing this today with some colleagues I realized I have been too limited in my thinking. In particular, I now think that @base is an appropriate specialization base for any "custom" attribute and can be combined with constraint modules to limit the appearance of those attributes to specific element types.

If the initial requirement is, for example, "I want the attribute @foo on table" then I think you can make this a conforming @base specialization as follows:

1. Declare a normal attribute domain module for your new attribute, specializing it from @base:

me_fooAttDomain.ent:

<!-- @foo attribute domain module: -->
<!ENTITY % me_fooAtt-d-attribute "foo CDATA #IMPLIED">

<!ENTITY me_fooAtt-d-att "a(base foo)" >
<!-- End of domain module -->

2. Define a constraint module that allows @foo on the elements you want it to be allowed on:

me_fooAttributeConstraint.mod:

<!-- @foo attribute constraint module: -->

<!ENTITY me_footAttTableOnly-constraints
"(topic me_fooAttTableOnly-c)"
>

<!ATTLIST table foo CDATA #IMPLIED >
<!-- End of constraint module. -->

3. In your shell, include the domain module but *do not* add it to the @base overrides (the not including it is a constraint and needs to be declared as such on the @domains attribute, which our separate constraint module will do for us):

<!ENTITY % me_fooAtt-d-dec
      PUBLIC "urn:pubid:example.com:dita:attributes:me_fooAttDomain.ent"
      "me_fooAttDomain.ent"
>%me_fooAtt-d-doc;
...
<!-- Constraint: Not including @foo in base attribute extensions: -->
<!ENTITY % base-attribute-extensions
""
>
4. In your shell, include the constraint module:

<!-- ============================================================= -->
<!-- DOMAIN CONSTRAINT INTEGRATION -->
<!-- ============================================================= -->

<!ENTITY % me_fooAttributeConstraint.def
    PUBLIC "urn:pubid:example.com:dita:constraints:me_fooAttributeConstraint.mod"
           "me_fooAttributeConstraint.mod"
>%me_fooAttributeConstraint.def;

5. In your shell, add the constraint domains contribution to the @domains attribute:

<!ENTITY included-domains
    "&concept-att;
     ...
     &me_footAttTableOnly-constraints;
    "
>

You've now declared a specialization of @base but then constrained it to only be allowed on <table>, as defined in the separate constraint module.

The 1.3 specification says this about the @base attribute:

"A generic attribute that has no specific purpose. It is intended to act as a base for specialized attributes that have a simple value syntax like the conditional processing attributes (one or more alphanumeric values separated by whitespace), but is not itself a filtering or flagging attribute. The attribute takes a space-delimited set of values. However, when acting as a container for generalized attributes, the content model will be more complex; see Attribute generalization <http://docs.oasis-open.org/dita/v1.2/os/spec/archSpec/attributegeneralize. html> for more details."
-- http://docs.oasis-open.org/dita/v1.2/os/spec/common/select-atts.html#select-atts

As long as your attribute's values will satisfy the requirement that "The attribute takes a space-delimited set of values." then your attributes will be conforming instances of @base.

An interesting implication of using @base is that normal generalization processing will convert @foo="bar" to base="foo(bar)". Which means you can also author @base attributes using that syntax (just as you can author @props using the same syntax, e.g. props="mycondition(myvalue)", which is equivalent to having a @props specialization named "@mycondition" with the value "myvalue"). In particular, this provides a standard way to interchange documents in terms of the OASIS-defined vocabulary without loss of information by generalizing specialized attributes to their @base and @props bases (which is part of the point of attribute specialization in the first place).

The result of all this is that you have an attribute this is a conforming specialization of @base and it's limited, via constraint, to only those places that you want to allow it. Your intent as a grammar designer is clear: I want this attribute to be limited to these element types.

If the constraint module isn't used (but the attribute domain is in the normal way), all your existing documents that have @foo on <table> will continue to be valid, which is one test for the correctness of a constraint. If they are generalized so @foo becomes base="foo(bar)" they will also continue to be valid.

So I reverse my earlier position on the appropriateness and conformance of element-type-specific attributes: the @base attribute combined with the constraint mechanism allows it without having to play any semantic tricks. Interchange is preserved through generalization, everyone is happy, and peace and prosperity reigns across the land.

Labels: , , ,