COMPOUND DOCUMENTS

What could be better than ASCII?

Lowell Williams

Lowell is a senior software engineer in Digital's electronic publishing systems engineering group. He can be contacted at 603-884-1241.


Marshall McLuhan was never more right than in the post-Macintosh era. The media really is the message. Images, artwork, and presentation style have become as important as text for conveying meaning in computer-generated documents. As the amount of information represented by nontext features increases, so too does the need to interchange complete documents, not just document text. Because it is strictly a text format, ASCII is no longer adequate to fulfill the role it is continually called upon to play--that of universal document-interchange standard. Programmers need, and need to learn how to use, an encoding model that can interchange complete documents the way ASCII has been used to interchange text.

Let's say, for example, that we wish to encode the newspaper USA Today so that we will be able to interchange text, photos, layout, and other information between editorial, printing, and sales offices around the world. At these locations, different applications perform various operations on the paper, such as news retrieval, printing, and regional advertising insertion. These applications need to process a combination of document objects, such as galleys, photos, artwork, and stylized headlines, in addition to plain vanilla ASCII. A universal document-encoding model would ensure that each application, regardless of vendor, could see and therefore work with these objects, just as ASCII allows current applications to see text.

While most programmers know how to process ASCII, few know how to process documents from applications for which they don't have source code. One problem, of course, is a lack of equivalents to ASCII for encoding compound documents--that is, documents that contain a mix of content types. But a bigger problem may be adjusting to the idea of multidimensional primitives. An ASCII primitive (a 7-bit character) expresses meaning in one dimension: content. A compound ASCII-equivalent primitive (whatever that might be) may express meaning in three (or more) dimensions: content, structure, and presentation style. Yet the two situations are parallel. Files of ASCII primitives encode revisable text. Files of compound-document primitives encode revisable documents. The question is: What rules for constructing and using primitives would allow applications to interchange and revise each other's compound documents in all required dimensions?

The Problem and Some Solutions

Most documents contain a mix of different content types, and are therefore called compound documents. These content types might include text, line art, raster graphics (images), tabular data, and eventually even audio and video. Frequently, a compound document also contains a mix of layout and content presentation styles. Layout is the way content is arranged on a page, such as in columns, footnotes, headings, and so on. Presentation styles affect the rendering of the content itself, such as with boldface, italics, and underlining. A compound ASCII equivalent, therefore, would faithfully encode mixed-content, layout, and presentation information across applications.

In addition to providing the rules (called semantics) to encode documents, a compound ASCII equivalent might also provide the process (runtime system) to encode documents. Whether a standards body should be in the business of providing software in addition to an encoding format is a subject of debate. Experience indicates, however, that even though two applications follow the same encoding rules, minor differences can still occur when two programs attempt to reproduce the same document. Having common software perform the encoding greatly reduces this possibility.

What makes ASCII so popular, despite its limitations, is that it is interpreted consistently by applications that support ASCII and is universally accessible to developers. Developers currently have four candidates available to them as a potential compound ASCII equivalent: private formats, the Open Document Architecture (ODA) standard, the Standard Generalized Markup Language (SGML) standard, and Digital's Compound Document Architecture (CDA).

Private formats rely entirely on application-specific semantics and process for features. If any interchange or cooperation happens, it results from homogeneous applications and platforms using a pair-wise agreement on what the data format is. The upside of this approach is that the developer can build whatever functionality he or she wants into software, without worrying about achieving consensus with others. The downside is that there may be fewer applications with which to exchange documents and that time is wasted reinventing features already present in incompatible software.

The other three solutions are "public" in the sense that the specifications are published and that documentation and software are available at nominal cost. Each solution, however, brings a significantly different approach to encoding revisable compound documents. SGML (ISO 8879:1986) and ODA (ISO 8613:1989) are both entirely in the public domain. To date, however, ODA has yet to be implemented in real products. Also, ODA does not include runtime software for encoding documents that comply with ODA semantics, leaving the door open to slightly inconsistent encodings of the same document by different ODA-compliant products.

Unlike ODA, SGML is widely supported by many applications, although it, too, does not specify a particular encoding process. Nor, in fact, does SGML provide a standard of semantics to encode documents. Rather it is a standard of semantics to encode the semantics to encode documents. SGML-compliant semantics are called document type definitions (DTDs). In order to interchange revisable compound documents, two applications must comply with the same DTD, of which there are hundreds. A developer may define his or her own DTD or use one of many public DTD libraries, the most popular of which is CALS (Computer-aided Acquisition and Logistics Support), a DOD specification (MIL-M-28001).

Like ODA, CDA provides a consistent set of semantics with which to encode documents. Like SGML, CDA is also widely implemented. Currently, CDA is supported by over 200 applications from a list of over 50 vendors, including Microsoft, Lotus, and WordPerfect. Unlike both ODA and SGML, CDA provides a set of runtime services which obey CDA-complaint semantics and ensure universally interchangeable documents.

CDA semantics are called DDIF (Digital Document Interchange Format). The runtime services, appropriately called the CDA Run Time Services, translate DDIF documents into a machine-readable format called Digital Document Interchange Standard (DDIS), a subset of ASN.1 (ISO 8824) encoding. ASN.1 is an ISO application-level binary format for information interchange between heterogeneous computer systems. Currently, CDA Run Time Services are available under VAX/VMS, RISC/Ultrix, OS/2, and Windows. (DDIF specifications and documentation are available from Digital.)

Programming in DDIF

DDIF is like ASCII in two important respects. First, both models define classes of objects (characters, in the case of ASCII) that are encoded consistently, regardless of application or platform. Second, programmers use both ASCII and DDIF to define objects within documents. The two models are not equivalent, however, in that ASCII does not provide semantics that let developers specify how objects are presented and laid out. As far as ASCII is concerned, an ASCII "A" is an ASCII "A." But in DDIF, a Times 14 bold ASCII "A" can consistently be a Times 14 bold ASCII "A." Furthermore, the DDIF document might also define the "A" to be part of a higher-level object, such as a paragraph or a title, and that maintains a specified position within a still higher-level object, such as a frame or a page.

Developers typically define DDIF document objects using C, although other high-level languages are also supported. Object definitions are specified according to the semantics of DDIF. These blocks result in the ASN.1 (DDIS) fields that are the primitives of object encodings. As such, they are analogous to the 7-bit ASCII primitives that define characters.

There are two kinds of DDIF/DDIS primitives--aggregates and items. In each document segment there are three kinds of aggregates: segment root, attribute, and content. A segment is a part of a document (for instance, a footnote) that has different structure, presentation style, or content from surrounding parts. A segment is encoded as a linked list of aggregates attached to the segment's root aggregate. A document is encoded as a list of segment aggregates linked to the document's root aggregate.

Items are attached to aggregates. An item supplies either an attribute (to an attribute aggregate) or content (to a content aggregate). An attribute aggregate may have multiple items attached to it that define various attributes of the segment's layout and presentation style, while a content aggregate may only have content. Inheritance applies. An attribute value (say, a font style) specified for a parent segment, applies to a child segment, unless overruled.

Example 1(a) is one of several text items linked to Example 1(b), a content aggregate, while Example 1(c) is one of several attributes (a galley frame) linked to Example 1(d) , an attribute aggregate.

Example 1: Examples of DDIF primitives.

  (a)

  aggregate_item = DDIF$_TXT_CONTENT
  status = CdaStoreItem ( root_aggregate_handle
              aggregate_handle_stack [ahs_index],
              aggregate_item,
              txt_content_length,
              txt_content,
              0, 0)

  (b)
  aggregate_type = DDIF$_TXT;
  status = CdaCreateAggregate ( root_aggregate_handle,
              aggregate_type,
              &aggregate_handle_stack [ahs_index]

  (c)

  aggregate_item = DDIF$_SGA_FRM_POSITION_C:
  interger_value = DDIF$K_FRAME_GALLEY;
  status = CdaStoreItem ( root_aggregate_handle
              aggregate_handle_stack [ahs_index],
         aggregate_item,
              integer_length,
              & integer_value,
              0, 0)

  (d)

  aggregate_type = DDIF$_SGA;
  status = CdaCreateAggregate (  root_aggregate-handle
              aggregate_handle_stack [ahs_index],
  aggregate_item = DDIF$_TYD_ATTRIBUTES;
  status = CdaStoreItem ( root_aggregate_handle
              aggregate_handle_stack [ahs_index-1],
              aggregate_item,
              aggregate_handle_length,
              & aggregate_handle_stack [ahs_index],
              0, 0)

DDIF-capable modules process DDIF primitives in a way somewhat analogous to that in which ASCII-capable modules process ASCII primitives. Program A, for example, can process primitives written by Program B. What Program A decides to do with those primitives is up to its developer. Program A may simply recreate some or all of Program B's primitives on the local system. It may combine Program A's primitives with those from other programs; or it may simply scan the primitives for information.

The essential differences between the two models are as follows: An ASCII primitive always represents just one class of object (content) and just one particular category of content, a character. An ASCII primitive also contains no information about how to present the content (for example, a font size) or where the content belongs within a higher-order object, such as a table, graphic, or document. A DDIF primitive can represent either content or attributes (layout/presentation). In addition, the DDIF primitive provides information, via the linked list, that allows a processing application to understand and/or assert where a primitive belongs within a segment and document. Figure 1 illustrates key differences between ASCII and DDIF. Note in this figure that both models encode revisable documents from primitives that are interchangeable between heterogeneous applications. ASCII primitives are predetermined to be the 128 ASCII character set. DDIF primitives are defined by C modules employing predetermined DDIF semantics.

The semantics that define particular DDIF primitives are interpreted by routines within the CDA Run Time Services and are included in programs that process the primitives. This is analogous to a compiler supplying ASCII 7-bit codes to a program, so that the source code doesn't have to. Document content may be defined as some literal content or as space assigned to hold the literal content (as in Example 1). Definitions may also reference data generated in other programs via external pointers called live links. When the value of the linked item changes in a remote document (say, a spreadsheet), that change is reflected in the local document (a bar chart, for example).

Programmers can define DDIF documents within their applications by handcoding each and every primitive directly or by using scripting tools supplied with CDA Run Time Services, called High Level Routines. As with any scripting language, the High Level Routines streamline encoding by allowing programmers to replace commonly used and lengthy routines with a single call. A few examples provide a flavor of what it's like to define a DDIF document using these routines. For instance, Listing One (page 101) shows the syntax that would write the title at the top of Figure 2(a). The title is defined to be some literal text that is black, highlighted, underlined, and centered at a specific location on the page.

Note that each High Level Routine uses the hlr_ prefix. The color and font style were taken from arrays created earlier in the program from which the following statements were taken: fonts [0] fontname = "-Adobe-Helvetica-Bold- R-Normal--*-100-*-*-p-*-*IS08859-1" and colors[3].red=0; colors[3].green=0; colors[3].blue=0:. The three rectangles are drawn with the routine in Listing Two (page 101).

The text in Figure 2(b) illustrates how to use DDIF to create galleys, which specify an area of space in a document in which text is allowed to flow. For example, a galley might contain a newspaper story, the contents of which appear on various pages. In the figure, Galley 0 is the paragraph at the top of the page. The other galleys are shown as nine boxes (bordered) at the bottom. Galley 0 has no border. The syntax in Listing Three (page 101) defines the galleys. The code in Listing Four (page 101) inserts text in each of the galleys and employs a previously defined paragraph style, paragraph (2).

As these examples illustrate, defining a document is a straightforward process, directly analogous to creating a document on a high-quality word processor. First, the high-level structures (segments such as footnotes, figures, galleys, and so forth) are declared. Next, the writer declares the overall attributes of those objects (segment layout, fonts, color, and so on). Finally the programmer provides (or points to) the content for the various segments (artwork, text, images, and so on). Prior to document definition itself, the developer may wish to create arrays that define font styles, paragraph styles, colors, recurring text strings, or other features to be referenced within the definition.

At some point, the thought of using ASCII to interchange revisable documents may seem as obsolete as the thought of using character-cell terminals does now for creating documents. When that happens, programmers will take for granted that file primitives can be interchanged among heterogeneous applications without necessarily belonging to a static set of predefined bit patterns. Employing these new primitives requires standardized semantics and possibly standardized runtime software. Given the promise of universal document interchange, that's not a lot to get used to.



_COMPOUND DOCUMENTS_
by Lowell Williams


[LISTING ONE]


/* specify the page in the document */
setup_subtitle_page 1
{
/* Change the color to black and font to helvetica */
status = hlr_set_color (3)
status = font (0)

/* Turn on highlighting and underlining */
status = hlr_set_text_rendition(hlr_c-highlight);
status = hlr_set_text_rendition(hlr_c-underline);
/* draw a page title */
status = hlr_draw_text{
             1800,     /* upper left x */
             12500     /* upper left y */
             1200*5    /* line length  */
             300       /* text height  */
             RSAMPLE TEST - continued  R,      /* text string  */
             hlr_c_text_center                 /* center text  */
             };
}






[LISTING TWO]



{
/* Set the line width */
status = hlr_set_line_width (25)
/* draw three rectangles */
status = hlr_draw_rectangle (3600, 8000, 5200, 9700);
status = hlr_draw_rectangle (3800, 8400, 5400, 10000);
status = hlr_draw_rectangle (4000, 8800, 5600, 10400);
return;
}






[LISTING THREE]



i = 0
status = define_galls (galls4, "gal0", i, 1200, 10800, 8400, 12000, "gal1",
                                           hlr_c_default, hlr_c_no_border_on);
status = define_galls (galls4, "gal1", ++i, 1200, 5500, 3600, 7000, "gal",
                                           hlr_c_default, hlr_c_no_border_on);
status = define_galls (galls4, "gal2", ++i, 3601, 5500, 6000, 7000, "gal3",
                                           hlr_c_default,hlr_c_no_border_on);
status = define_galls (galls4, "gal3", ++i, 5*1201, 5500, 7*1200, 7000, "gal4",
                                           hlr_c_default, hlr_c_no_border_on);
status = define_galls (galls4, "gal4", ++i, 1201, 4000, 3600, 5500, "gal5",
                                           hlr_c_default,hlr_c_no_border_on);
status = define_galls (galls4, "gal5", ++i, 3*1201, 4000, 5*1200, 5500, "gal6",
                                           hlr_c_default, hlr_c_no_border_on);
status = define_galls (galls4, "gal6", ++i, 5*1201, 4000, 7*1200, 5500, "gal7",
                                           hlr_c_default, hlr_c_no_border_on);
status = define_galls (galls4, "gal7", ++i, 1201, 2500, 3600, 4000, "gal",
                                           hlr_c_default, hlr_c_no_border_on);
status = define_galls (galls4, "gal8", ++i, 3*1201, 2500, 5*1200, 4000, "gal9",
                                           hlr_c_default, hlr_c_no_border_on);
status = define_galls (galls4, "gal9", ++i, 5*1201, 2500, 7*1200, 4000,"gal5a",
                                           hlr_c_default, hlr_c_no_border_on);






[LISTING FOUR]


int create_page_1
status = hlr_set_galley (4,0)
status = hlr_set_paragraph (2);
status = hlr_write_text ("this is the first paragraph of the first galley
                        containing the first flow for this sample program.  ");
status = hlr_write_text ("The text is justified and is displayed above the
                        path-based text that is displayed above the table.");
status = hlr_set_new_ galley(1)
status = hlr_set_paragraph (2);
for (i=1; i < 9; i++)  {
sprintf (temp1 , "%s%d%s", "This is flow ",i,". ");
status = hlr_write_text (temp1);
/* change galleys */
if (i < 9)
    status = hlr_set_new_galley (1)
    status = hlr_set_paragraph (2)
    }

Example 1: Examples of DDIF primitives


(a)

aggregate_item = DDIF$_TXT_CONTENT
status = CdaStoreItem ( root_aggregate-handle
            aggregate_handle_stack[ahs_index],
            aggregate_item,
            txt_content_length,
            txt_content,
            0, 0)

(b)

aggregate_type = DDIF$_TXT;
status = CdaCreateAggregate (   root_aggregate_handle,
           aggregate_type,
           &aggregate_handle_stack[ahs_index]


(c)

aggregate_item = DDIF$_SGA_FRM_POSITION_C:
integer_value = DDIF$K_FRAME_GALLEY;
status = CdaStoreItem ( root_aggregate_handle
           aggregate_handle_stack[ahs_index],
       aggregate_item,
           integer_length,
           & integer_value,
           0, 0)

(d)

aggregate_type = DDIF$_SGA;
status = CdaCreateAggregate (   root_aggregate-handle
           aggregate_handle_stack[ahs_index],
aggregate_item = DDIF$_TYD_ATTRIBUTES;
status = CdaStoreItem ( root_aggregate_handle
           aggregate_handle_stack[ahs_index-1],
           aggregate_item,
           aggregate_handle_length,
           & aggregate_handle_stack[ahs_index],
           0, 0)

Copyright © 1993, Dr. Dobb's Journal