Advanced Customization Tutorial - Document Type Associations

<oXygen/> Author is highly customizable. Practically you can associate an entire class of documents (grouped logically by some common features like namespace, root element name or filename) to a bundle consisting of a CSS stylesheets, validation schemas, catalog files, templates for new files, transformation scenarios and even custom actions. This is called a Document Type Association.

 Creating the Basic Association

In this section a Document Type Association will be created for a set of documents. As an example a light documentation framework will be created, similar to DocBook and create a complete customization of the Author editor.

You can find the complete files that were used in this tutorial in the Example Files Listings.

 First step. XML Schema.

Our documentation framework will be very simple. The documents will be either articles or books, both composed of sections. The sections may contain titles, paragraphs, figures, tables and other sections. To complete the picture, each section will include a def element from another namespace.

The first schema file:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    targetNamespace="http://www.oxygenxml.com/sample/documentation"
    xmlns:doc="http://www.oxygenxml.com/sample/documentation"
    xmlns:abs="http://www.oxygenxml.com/sample/documentation/abstracts"
    elementFormDefault="qualified">

    <xs:import namespace=
    "http://www.oxygenxml.com/sample/documentation/abstracts" 
     schemaLocation=
    "abs.xsd"/>
                    

The namespace of the documents will be http://www.oxygenxml.com/sample/documentation. The namespace of the def element is http://www.oxygenxml.com/sample/documentation/abstracts.

Now let's define the structure of the sections. They all start with a title, then have the optional def element then either a sequence of other sections, or a mixture of paragraphs, images and tables.

<xs:element name="book" type="doc:sectionType"/>
<xs:element name="article" type="doc:sectionType"/>
<xs:element name="section" type="doc:sectionType"/>
    
<xs:complexType name="sectionType">
    <xs:sequence>
        <xs:element name="title" type="xs:string"/>
        <xs:element ref="abs:def" minOccurs="0"/>
        <xs:choice>
            <xs:sequence>
                <xs:element ref="doc:section" maxOccurs="unbounded"/>
            </xs:sequence>    
            <xs:choice maxOccurs="unbounded">
                <xs:element ref="doc:para"/>
                <xs:element ref="doc:image"/>
                <xs:element ref="doc:table"/>                
            </xs:choice>
        </xs:choice>
    </xs:sequence>
</xs:complexType>

The paragraph contains text and other styling markup, such as bold (b) and italic (i) elements.

<xs:element name="para" type="doc:paragraphType"/>
    
<xs:complexType name="paragraphType" mixed="true">
    <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="b"/>
        <xs:element name="i"/>
    </xs:choice>
</xs:complexType>

The image element has an attribute with a reference to the file containing image data.

<xs:element name="image">
    <xs:complexType>
        <xs:attribute name="href" type="xs:anyURI" use="required"/>
    </xs:complexType>
</xs:element>

The table contains a header row and then a sequence of rows (tr elements) each of them containing the cells. Each cell has the same content as the paragraphs.

 <xs:element name="table">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="header">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="td" maxOccurs="unbounded" 
                            type="doc:paragraphType"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="tr" maxOccurs="unbounded">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="td" type="doc:tdType" 
                             maxOccurs="unbounded"/>                                
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>


<xs:complexType name="tdType">
    <xs:complexContent>
        <xs:extension base="doc:paragraphType">
            <xs:attribute name="row_span" type="xs:integer"/>
            <xs:attribute name="column_span" type="xs:integer"/>
        </xs:extension>            
    </xs:complexContent>
</xs:complexType>    

The def element is defined as a text only element in the imported schema abs.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    targetNamespace=
     "http://www.oxygenxml.com/sample/documentation/abstracts">
    <xs:element name="def" type="xs:string"/>
</xs:schema>

Now the XML data structure will be styled.

 Second step. The CSS.

If you read the Simple Customization Tutorial then you already have some basic notions about creating simple styles. The example document contains elements from different namespaces, so you will use CSS Level 3 extensions supported by the <oXygen/> layout engine to associate specific properties with that element.

[Note]Note

Please note that the CSS Level 3 is a standard under development, and has not been released yet by the W3C. However, it addresses several important issues like selectors that are namespace aware and values for the CSS properties extracted from the attributes of the XML documents. Although not (yet) conforming with the current CSS standard these are supported by the <oXygen/> Author.

 Defining the General Layout.

Now the basic layout of the rendered documents is created.

Elements that are stacked one on top of the other are: book, article, section, title, figure, table, image. These elements are marked as having block style for display. Elements that are placed one after the other in a flowing sequence are: b, i. These will have inline display.

/* Vertical flow */
book,
section,
para,
title,
image,
ref {
    display:block;
}

/* Horizontal flow */
b,i {
    display:inline;
}

[Important]Important

Having block display children in an inline display parent, makes <oXygen/> Author change the style of the parent to block display.

 Styling the section Element.

The title of any section must be bold and smaller than the title of the parent section. To create this effect a sequence of CSS rules must be created. The * operator matches any element, it can be used to match titles having progressive depths in the document.

title{
    font-size: 2.4em;
    font-weight:bold;    
}
* * title{
    font-size: 2.0em;
}
* * * title{
    font-size: 1.6em;
}
* * * * title{
    font-size: 1.2em;
}

[Note]Note

CSS rules are combined as follows:

  • All the rules that match an element are kept as a list. The more specific the rule is, the further it will be placed to the end of the list.

  • If there is no difference in the specificity of the rules, they are placed in the list in the same order as they appear in the CSS document.

  • The list is then iterated, and all the properties from the rules are collected, overwriting the already collected values from the previous rules. That is why the font-size is changed depending on the depth of the element, while the font-weight property remains unchanged - no other rule is overwriting it.

It's useful to have before the title a constant text, indicating that it refers to a section. This text can include also the current section number. The :before and :after pseudo elements will be used, plus the CSS counters.

First declare a counter named sect for each book or article. The counter is set to zero at the beginning of each such element:

book, 
article{
    counter-reset:sect;
}
                        

The sect counter is incremented with each section, that is the a direct child of a book or an article element.

book > section,
article > section{
    counter-increment:sect;
}   

The "static" text that will prefix the section title is composed of the constant "Section ", followed by the decimal value of the sect counter and a dot.

book > section > title:before,
article > section > title:before{
    content: "Section " counter(sect) ". ";
}

To make the documents easy to read, you add a margin to the sections. In this way the higher nesting level, the larger the left side indent. The margin is expressed relatively to the parent bounds:

section{
    margin-left:1em;
    margin-top:1em;
}

 

Figure 8.3. A sample of nested sections and their titles.

A sample of nested sections and their titles.

In the above screenshot you can see a sample XML document rendered by the CSS stylesheet. The selection "avoids" the text that is generated by the CSS "content" property. This happens because the CSS generated text is not present in the XML document and is just a visual aid.

 Styling the table Element.

There are standard CSS properties used to indicate what elements are tables, table rows and table cells. What CSS is missing is the possibility to indicate the cell spanning. <oXygen/> Author offers support for adding an extension to solve this problem. This will be presented in the next chapters.

The table in this example is a simple one. The header must be formatted in a different way than the ordinary rows, so it will have a background color.

table{
    display:table;
    border:1px solid navy;
    margin:1em;
    max-width:1000px;
    min-width:150px;
}

table[width]{
  width:attr(width, length);
}

tr, header{
    display:table-row;
}

header{
    background-color: silver;
    color:inherit
}

td{
  display:table-cell;
  border:1px solid navy;
  padding:1em;
}

[Note]Note

Children elements with block or table-caption display placed at the beginning or the end of an element displayed as a table, will be grouped and presented as blocks at the top or the bottom of the table.

[Note]Note

Mixing elements having table-cell, table-group, table-row, etc.. display type with others that have block or inline display or with text content breaks the layout of the table. In such cases the table is shown as a block.

[Note]Note

Having child elements that do not have table-cell or table display in a parent with table-row display breaks the table layout. In this case the table display is supported for the children of the table-row element in order to allow sub-tables in the parent table.

[Note]Note

<oXygen/> Author can automatically detect the spanning of a cell, without the need to write a Java extension for this.

This happens if the span of the cell element is specified using the colspan and rowspan attributes, just like in HTML, or cols and rows attributes.

For instance, the following XML code:

  <table>
        <tr>
            <td>Cell 1.1</td>
            <td>Cell 1.2</td>
            <td>Cell 1.3</td>
        </tr>
        <tr>
            <td>Cell 2.1</td>
            <td colspan="2" rowspan="2">
            Cell spanning 2 rows and 2 columns.
            </td>
        </tr>
        <tr><td>Cell 3.1</td></tr>
  </table>

using the CSS:

table{
   display: table;
}
tr{
   display: table-row;
}
td{
   display: table-cell;
}

is rendered correctly:

 

Table 8.1. Built-in Cell Spanning

Cell 1.1Cell 1.2Cell 1.3
Cell 2.1Cell spanning 2 rows and 2 columns
Cell 3.1


Because in the schema the td tag has the attributes row_span and column_span that are not automatically recognized by <oXygen/> Author, a Java extension will be implemented which will provide information about the cell spanning. See the section Configuring a Table Cell Span Provider.

Because the column widths are specified by the attributes width of the elements customcol that are not automatically recognized by <oXygen/> Author, it is necessary to implement a Java extension which will provide information about the column widths. See the section Configuring a Table Column Width Provider.

 Styling the Inline Elements.

The "bold" style is obtained by using the font-weight CSS property with the value bold, while the "italic" style is specified by the font-style property:

b {
    font-weight:bold;
}

i {
    font-style:italic;
}

 Styling Elements from other Namespace

In the CSS Level 1, 2, and 2.1 there is no way to specify if an element X from the namespace Y should be presented differently from the element X from the namespace Z. In the upcoming CSS Level 3, it is possible to differentiate elements by their namespaces. <oXygen/> Author supports this CSS Level 3 functionality. For more information see the Namespace Selectors section.

To match the def element its namespace will be declared, bind it to the abs prefix, and then write a CSS rule:

@namespace abs "http://www.oxygenxml.com/sample/documentation/abstracts";

abs|def{
    font-family:monospace;
    font-size:smaller;    
}
abs|def:before{
    content:"Definition:";
    color:gray;
}

 Styling images

The CSS 2.1 does not specify how an element can be rendered as an image. To overpass this limitation, <oXygen/> Author supports a CSS Level 3 extension allowing to load image data from an URL. The URL of the image must be specified by one of the element attributes and it is resolved through the catalogs specified in <oXygen/>.

[Note]Note

<oXygen/> Author recognizes the following image file formats: JPEG, GIF, PNG and SVG. The oXygen Author for Eclipse does not render the SVG files.

image{
    display:block;
    content: attr(href, url);
    margin-left:2em;
}

Our image element has the required attribute href of type xs:anyURI. The href attribute contains an image location so the rendered content is obtained by using the function:

attr(href, url)
[Important]Important

The first argument is the name of the attribute pointing to the image file. The second argument of the attr function specifies the type of the content. If the type has the url value, then <oXygen/> identifies the content as being an image. If the type is missing, then the content will be the text representing the attribute value.

[Important]Important

<oXygen/> Author handles both absolute and relative specified URLs. If the image has an absolute URL location (e.g: "http://www.oasis-open.org/images/standards/oasis_standard.jpg") then it is loaded directly from this location. If the image URL is relative specified to the XML document (e.g: "images/my_screenshot.jpg") then the location is obtained by adding this value to the location of the edited XML document.

An image can also be referenced by the name of a DTD entity which specifies the location of the image file. For example if the document declares an entity graphic which points to a JPEG image file:

<!ENTITY graphic SYSTEM "depo/keyboard_shortcut.jpg" NDATA JPEG>

and the image is referenced in the XML document by specifying the name of the entity as the value of an attribute:

<mediaobject>
    <imageobject>
        <imagedata entityref="graphic" scale="50"/>
    </imageobject>
</mediaobject>

The CSS should use the functions url, attr and unparsed-entity-uri for displaying the image in the Author mode:

[Note]Note

Note that the scale attribute of the imagedata element will be considered without the need of a CSS customization and the image will be scaled accordingly.

imagedata[entityref]{
    content: url(unparsed-entity-uri(attr(entityref))); 
}

To take into account the value of the width attribute of the imagedata and use it for resizing the image, the CSS can define the following rule:

imagedata[width]{
  width:attr(width, length);
}
 

Figure 8.4. Samples of images in Author

Samples of images in Author

 Marking elements as foldable

You can specify what elements are collapsible. The collapsible elements are rendered having a small triangle icon in the top left corner. Clicking on this icon hides or shows the children of the element. The section elements will be marked as foldable. You will leave only the title child elements visible.

section{
    foldable:true;
    not-foldable-child: title;
}

 

Figure 8.5. Folded Sections

Folded Sections

 Marking elements as links

You can specify what elements are links. The text content specified in the :before pseudo element will be underlined. When hovering the mouse over that content the mouse pointer will change to indicate that it can follow the link. Clicking on a link will result in the referred resource being opened in an editor. The link elements will be marked as links with the href attribute indicating the referred location.

link[href]:before{
    display:inline;
    link:attr(href);
    content: "Click to open: " attr(href);
}

[Note]Note

If you plan to use IDs as references for links, the value of the link property should start with a sharp sign(#). This will ensure that the default link target reference finder implementation will work and clicking on the link will send you to the indicated location in the document. For more details about the link target reference finder read the section Configuring a Link target reference finder.

 

Example 8.1. IDs as references for links

link[linkend]:before{
    display:inline;
    link: "#" attr(linkend);
    content: "Click to open: " attr(linkend);
}

 Third Step. The Association.

After creating the XML Schema and the CSS stylesheet for the documents that will be edited a distributable framework package can be created for content authors.

 

Figure 8.6. The Document Type Dialog

The Document Type Dialog


 Organizing the Framework Files

First create a new folder called sdf (from "Simple Documentation Framework") in {oXygen_installation_directory}/frameworks. This folder will be used to store all files related to the documentation framework. The following folder structure will be created:

oxygen
  frameworks
     sdf
       schema
       css

[Important]Important

The frameworks directory is the container where all the oXygen framework customizations are located.

Each subdirectory contains files related to a specific type of XML documents: schemas, catalogs, stylesheets, CSSs, etc.

Distributing a framework means delivering a framework directory.

[Important]Important

It is assumed that you have the right to create files and folder inside the oXygen installation directory. If you do not have this right, you will have to install another copy of the program in a folder you have access to, the home directory for instance, or your desktop. You can download the "all platforms" distribution from the oXygen website and extract it in the chosen folder.

To test your framework distribution you will need to copy it in the frameworks directory of the newly installed application and start oXygen by running the provided start-up script files.

You should copy the created schema files abs.xsd and sdf.xsd, sdf.xsd being the master schema, to the schema directory and the CSS file sdf.css to the css directory.

 Association Rules

You must specify when <oXygen/> should use the files created in the previous section by creating a document type association. Open the Document Type dialog by following the procedure:

 
  1. Open the Options Dialog, and select the Document Types Association option pane.

  2. Select the Developer user role from the User role combo box at the top of the dialog. This is important, because it will allow us to save the document type association in a file on disk, instead of <oXygen/> options.

  3. Click on the New button.

In the displayed dialog, fill in the following data:

Name

Enter SDF - This is the name of the document type.

Description

Enter Simple Documentation Framework - This is a short description helping the other users understand the purpose of the Document Type.

Storage

The storage refers to the place where the Document Type settings are stored. Internal means the Document Types are stored in the default <oXygen/> preferences file. Since you want to share the Document Type to other users, you must select External, and choose a file.

The file must be in the {oXygen_installation_directory}/frameworks/sdf directory. A possible location is /Users/{user_name}/Desktop/oxygen/frameworks/sdf/sdf.framework. The framework directory structure will be:

oxygen
  frameworks
     sdf
       sdf.framework
       schema
         sdf.xsd
       css
         sdf.css
Rules

If a document opened in <oXygen/> matches one of the rules defined for the Document Type, then it is activated.

Press the Add button from the Rules section. Using the newly displayed dialog, you add a new rule that matches documents with the root from the namespace: http://www.oxygenxml.com/sample/documentation. The root name, file name or PublicID are not relevant.

A document matches a rule when it fulfills the conditions imposed by each field of the rule:

Namespace

the namespace of the root element declared in the XML documents of the current document type. A value of ANY_VALUE matches any namespace in an XML document. Value may contain wildcards(*, ?) and editor variables. Multiple values separated by comma(,) are accepted.

Root local name

The local name of the root element of the XML documents of the current document type. A value of ANY_VALUE matches any local name of the root element. Value may contain wildcards(*, ?) and editor variables. Multiple values separated by comma(,) are accepted.

File name

The file name of the XML documents of the current document type. A value of ANY_VALUE matches any file name. Value may contain wildcards(*, ?) and editor variables. Multiple values separated by comma(,) are accepted.

Public ID

The public ID of the XML documents of the current document type (for a document validated against a DTD). A value of ANY_VALUE matches any public ID. Value may contain wildcards(*, ?) and editor variables. Multiple values separated by comma(,) are accepted.

Java class

The full name of a Java class that has access to all root element attributes and the above 4 values in order to decide if the document matches the rule.

 Java API: Rules implemented in Java

An alternative to the rule you defined for the association is to write the entire logic in Java.

 
  1. Create a new Java project, in your IDE.

    Create the lib directory in the Java project directory and copy there the oxygen.jar file from the {oXygen_installation_directory}/lib. The oxygen.jar contains the Java interfaces you have to implement and the available Author API needed to access its features.

  2. Create the class simple.documentation.framework.CustomRule. This class must implement the ro.sync.ecss.extensions.api.DocumentTypeCustomRuleMatcher interface.

    The interface defines two methods: matches, and getDescription.

    1. The matches method is the one that is invoked when the edited document must be checked against the document type association. It takes as arguments the root local name, its namespace, the document location URI, the PublicID and the root element attributes. It must return true when the document matches the association.

    2. The getDescription method returns a description of the rule.

    Here is the implementation of these two methods. The implementation of matches is just a Java equivalent of the rule we defined earlier.

    	public boolean matches(
    			String systemID, 
    			String rootNamespace,
    			String rootLocalName, 
    			String doctypePublicID,
    			Attributes rootAttributes) {
    		
    		return "http://www.oxygenxml.com/sample/documentation"
       .equals(rootNamespace);		
    	}
    
    	public String getDescription() {
    		return "Checks if the current Document Type Association" 
      + " is matching the document.";
    	}

    The complete source code is found in the Example Files Listings, the Java Files section.

  3. Package the compiled class into a jar file. Here is an example of an ANT script that packages the classes directory content into a jar archive named sdf.jar:

    <?xml version="1.0" encoding="UTF-8"?>
    <project name="project" default="dist">    
        <target name="dist">
    			<jar destfile="sdf.jar" basedir="classes">
    				<fileset dir="classes">
    					<include name="**/*"/>
    			  </fileset>
    			</jar>        
        </target>
    </project>
    

  4. Copy the sdf.jar file into the frameworks/sdf directory.

  5. Add the sdf.jar to the Author classpath. To do this select SDF Document Type from the Document Type Association options page and press the Edit button.

    Select the Classpath tab in the lower part of the dialog.

    Press the Add button . In the displayed dialog enter the location of the jar file, relative to the <oXygen/> frameworks directory. If you are in the process of developing the extension actions you can also specify a path to a directory which holds compiled Java classes.

  6. Clear the rules you defined before by using the Remove button.

    Press the Add button from the Rules section.

    Press the Choose button that follows the Java class value. The following dialog is displayed:

     

    Figure 8.7. Selecting a Java association rule.

    Selecting a Java association rule.

To test the association, open the sdf.xml sample and validate it.

 Deciding the initial page

You can decide to impose an initial page for opening files which match the association rules. For example if the files are usually edited in the Author page you can set it as the initial page for files matching your rules.

 Schema Settings

In the dialog for editing the Document Type properties, in the bottom section there are a series of tabs. The first one refers to the schema that is used for validation of the documents that match the defined association Rules.

[Important]Important

If the document refers a schema, using for instance a DOCTYPE declaration or a xsi:schemaLocation attribute, the schema from the document type association will not be used when validating.

Schema Type

Select from the combo box the value XML Schema.

Schema URI

Enter the value ${frameworks}/sdf/schema/sdf.xsd. We should use the ${frameworks} editor variable in the schema URI path instead of a full path in order to be valid for different <oXygen/> installations.

[Important]Important

The ${frameworks} variable is expanded at the validation time into the absolute location of the directory containing the frameworks.

 Author CSS Settings

Select the Author tab from the Document Type edit dialog. By clicking on the CSS label in the right part of the tab the list of associated CSSs is shown.

Here you can also specify how should the CSSs defined in the document type be treated when there are CSSs specified in the document(with xml-stylesheet processing instructions). The CSSs from the document can either replace the CSSs defined in the document type association or merge with them.

Add the URI of the CSS file sdf.css you already defined. You should use the ${frameworks} editor variable in the file path.

 

Figure 8.8. CSS settings dialog

CSS settings dialog

The Title text field refers to a symbolic name for the stylesheet. When adding several stylesheets with different titles to a Document Type association, the content author can select what CSS will be used for editing from the Author CSS Alternatives toolbar.

This combo-box from the toolbar is also populated in case your XML document refers CSSs directly using xml-stylesheet processing instructions, and the processing instructions define titles for the CSSs.

[Note]Note

The CSS settings dialog allows to create a virtual xml-stylesheet processing instructions. The CSSs defined in the Document Type Association dialog and the xml-stylesheet processing instructions from the XML document are processed together, as being all a list of processing instructions.

<oXygen/> Author fully implements the W3C recommendation regarding "Associating Style Sheets with XML documents". For more information see: http://www.w3.org/TR/xml-stylesheet/ http://www.w3.org/TR/REC-html40/present/styles.html#h-14.3.2

 Testing the Document Type Association

To test the new Document Type create an XML instance that is conforming with the Simple Document Format. You will not specify an XML Schema location directly in the document, using an xsi:schemaLocation attribute; <oXygen/> will detect instead its associated document type and use the specified schema.

<book xmlns="http://www.oxygenxml.com/sample/documentation" 
      xmlns:abs="http://www.oxygenxml.com/sample/documentation/abstracts">

    <title>My Technical Book</title>
    <section>
        <title>XML</title>
        <abs:def>Extensible Markup Language</abs:def>
        <para>In this section of the book I will 
           explain different XML applications.</para>
    </section>
</book>

When trying to validate the document there should be no errors. Now modify the title to title2. Validate again. This time there should be one error:

  cvc-complex-type.2.4.a: Invalid content was found starting with element 
  'title2'. One of '{"http://www.oxygenxml.com/sample/documentation":title}' 
  is expected.

Undo the tag name change. Press on the Author button at the bottom of the editing area. <oXygen/> should load the CSS from the document type association and create a layout similar to this:

 Packaging and Deploying

Using a file explorer, go to the <oXygen/> frameworks directory. Select the sdf directory and make an archive from it. Move it to another <oXygen/> installation (eventually on another computer). Extract it in the frameworks directory. Start <oXygen/> and test the association as explained above.

If you create multiple document type associations and you have a complex directory structure it might be easy from the deployment point of view to use an <oXygen/> all platforms distribution. Add your framework files to it, repackage it and send it to the content authors.

[Warning]Warning

When deploying your customized sdf directory please make sure that your sdf directory contains the sdf.framework file (that is the file defined as External Storage in Document Type Association dialog shall always be stored inside the sdf directory). If your external storage points somewhere else <oXygen/> will not be able to update the Document Type Association options automatically on the deployed computers.