by Paul Prescod
DSSSL is the Document Style Semantics and Specification Language and is meant to work with SGML, the Standard Generalized Markup Language. Those acronyms are sufficiently complex that you may already be worried that this is Rocket Science and that you will not be able to figure them out. Don't Panic. If you do not know about SGML, start by reading my SGML Introduction and the Text Encoding Initiative's Gentle Introduction to SGML . It is not too complicated.
DSSSL is an international standard for associating processing with SGML documents. As you know, SGML itself is intended to allow the complete separation of the content of a document (text, structure, links), from the processing to be associated with it (usually formatting). So where a Word for Windows, Tex, or even LaTeX document would describe what a document looks like (in other words how a printer should "process" it), SGML documents would only describe the structure. Using DSSSL you can describe the processing of documents in a standard way. Since the two most common forms of document processing are formatting and transformation, DSSSL standardised these two processes first. Others may follow as they are needed. The first two are very powerful and many believe that DSSSL will "transform" (sorry) the world of SGML document processing.
DSSSL is divided into many different parts. This means that even if a part of the standard does not meet your current needs, the rest might.
The style language provides a standardized, powerful language for describing the formatting of SGML documents. The DSSSL processors that I will discuss in this document implement the style language. You just download them from the Web, create an SGML document (with DTD) and a stylesheet, and execute the DSSSL processor. It will either create a formatted document in whatever wordprocessor or typesetting formats it supports, or else display the formatted result immediately. This tutorial covers only the style language. Transformation is creating a document that conforms to one DTD from one that conforms to another or changing the structure of a document within a single DTD.
Flow objects provide a standard mechanism for describing the layout of a document. They represent layout constructs such as page sequences, paragraphs, hyperlinks and artwork. Flow objects have characteristics, such as a page sequence's margins, a paragraph's font size, a hyperlink's destination and a picture's height and width.
Usually you will create flow objects from within a style specification. But it might make sense to use the same layout model (the same set of flow objects) for systems that did not use the style language at all, for instance in formatting processes driven by a programming language like Perl or C++, or in word processors or browsers that did not support the full DSSSL Expression Language. If publishing tools share a common layout model, moving information from one tool to another may be an easier process in the future, even in environments that are not using SGML.
The Transformation Language is a standard language for transforming SGML documents marked up according to one DTD into another. You could, for instance, transform a TEI document into DocBook or HTML. The Transformation Language is similar to the Style Language because they have a large common subset called the Expression Language.
DSSSL shares a document model with the recently modified HyTime standard. This model describes a document or a set of documents as "nodes" organized into a "grove". You can probably imagine how the elements in an SGML document could be construed as forming a tree, with larger, containing elements as the branches and smaller, contained elements as the leaves. The DSSSL/HyTime grove model extends this concept. Groves are trees of trees. Things like attribute values are expressed in trees attached to the main element tree and the DTD is a tree attached to the SGML document tree. Each branch or leaf in a tree is called a "node". Do not worry if you do not really understand all of that. DSSSL use does not depend on it. There are resources on the Web where you can learn more about groves if you are interested.
The Query Language is for selecting and returning document components, just as SQL selects and returns particular rows from a database. The Query Language returns nodes from the DSSSL/HyTime document model described above. The Query Language is only available in DSSSL processors that support the "Query Feature".
You will probably want to get a DSSSL processor to try it out. There are three complementary processors available. They run on different platforms and have different strengths and weaknesses. The three of them form a three-prong SGML blitzkreig on the publishing world. Profuse thanks to the people and organizations that are creating them to donate them to us. There is a nobility in these acts of generosity.
Jade was the first DSSSL processor released. It is a fast C++ based processor that is available for Win32 and Unix platforms. It was created by James Clark, the free software hero that has been steadily slaying the dragon of high prices by turning out fantastically reliable and powerful free SGML source code and products. Jade is very fast. It implements only a subset of the Style Language, but it is a very usable subset. It does not implement any of the Transformation Language. It currently outputs Microsoft's Rich Text Format (RTF does not support all of the features of the DSSSL layout model, however), TeX (under development), the Web's HTML (through a non-standard DSSSL extension) and a non-standard "FOT" Flow Object Tree format that describes the flow objects as SGML elements. Despite all of those caveats, Jade is an excellent product and worth a good hard look. If everyone in the SGML software community pitches in by contributing code, documentation or tests, Jade will become a free standard tool that we can all depend upon for robust, fast performance.
Copernican Solutions is developing a DSSSL implementation for the Java Virtual Machine called Seng . This implementation will have the benefit of working anywhere the JVM has been ported. It sounds like it will also be more complete than Jade when it is released. Here is information from Alex Milowski (slightly edited from an email):
Seng will be a commercial product. It is more than a DSSSL processor--it is a DSSSL architecture based on components written in Java. We will support both the style language and transformation language in the final product.
For example, grove building is a component that is abstracted from the other DSSSL engine components. Thus, regardless of how the processor receives the grove and what technology the grove is built in, it will be able to process the grove and produce the desired results.
We also have been working on a set of abstract interfaces for DSSSL in Java such that they could be *standardized*. The idea is the we are trying to bootstrap an API standardization process such that developers can write DSSSL *applications* in Java and deliver them to arbitrary browsers or processors.
We currently have Seng integrated with the Jigsaw Java-based web server. Thus, Seng is not only for browsing, but for manipulating SGML via the web. We also plan to support XML in the final product.
Emacs is a popular text editor, word processor, web browser, mail program and news reader -- why not a DSSSL processor too? William Perry , the man behind the Web browser in Emacs (Emacs-W3) is working on a DSSSL processor for Emacs. The Elisp programming language that Emacs is programmed in is not far from the DSSSL Expression Language, so it should be a fairly natural fit. He is actually planning to implement the Web's Cascading Style Sheet support in his web browser through a real-time conversion of CSS code to DSSSL scripts in future versions of Emacs-W3. This will allow you to display HTML or SGML documents with their associated styleshets in the Emacs-W3 browser. Once again, this promises to be an amazing free software product when it is complete. Please consider contributing code or documentation.
Most people starting with DSSSL just want to print out SGML documents or publish them to the Web. This seems like a natural place to start learning about the components of the DSSSL Style Language.
Every style sheet language consists of a series of statements that map structural elements (from the source document) into formatting objects. Even the "Style" menu in Word for Windows consists of such a mapping. It takes "paragraph" elements and "maps" them to fonts, colours and other typographic effects. LaTeX users and professional publishers are probably quite comfortable with this idea of a stylesheet.
In the DSSSL Style Language, the syntax for setting up this mapping is called a construction rule because it "constructs" a formatted document from an SGML document. This statement informs the DSSSL application that when it comes across a "para" element in the input SGML document, it should produce a "paragraph flow object" in the output formatted flow object tree:
(element para (make paragraph font-size: 12pt )) ; everything after a semicolon is a comment
A flow object is really just a formatting object -- a typographic construct like a paragraph, a table, an area, a mathematical formula, etc. Flow objects have characteristics. I have specified the "font-size" characteristic for the paragraph flow object. It determines (surprise!) the font size of the paragraph. We will see this pattern many times throughout our stylesheets. Construction rules and flow objects are at the heart of DSSSL Style specifications.
It is also possible for DSSSL engine implementors to add flow objects. Jade adds one designed for publishing specifically to SGML. This can be used to do SGML transformations, such as TEI to HTML. Note that this style language extension for doing transformations is very different than the actual DSSSL transformation language! The "real" transformation language uses a different flow of control and makes different DSSSL features available. The style language is not optimal for SGML transformations, even with the added flow objects, but it is certainly reasonable compared to alternatives like Perl or C++. Using these flow objects, DSSSL (or at least Jade) is as useful for publishing on the Web as in print.
(element para (make element gi: "P")) ;creates an HTML paragraph
This maps the para element in the input SGML document to a "P" element in HTML.
As you can see, getting started with DSSSL is quite simple. You were worried this might be difficult! There is a lot going on behind the scenes, and we will learn that we can naturally go from these simple construction rules to quite sophisticated style specifications as we move from chapter to chapter. Right now, DSSSL does much of the work for us, later on, we will take control of a few things that it is doing automatically.
The technical term for a DSSSL style sheet is a "DSSSL specification". The more technical term captures the fact that a "DSSSL specification document" could actually have multiple style sheets in it, and could even have other components that are unrelated to styles (such as transformations).
DSSSL specifications are real SGML documents. SGML comments and entities work fine in them. Of course they must also conform to a DTD. For our purposes, you just need to get style-sheet.dtd and put it in the same directory as your stylesheet. You can also refer to it through the public identifier: "-//James Clark//DTD DSSSL Style Sheet//EN". DSSSL style specifications are actually a document architecture, so more than one DTD can conform, but James Clark's is as easy to use as any.
The Style Language is sufficiently easy that it is possible to make simple style sheets for simple DTDs just by learning the names of a few flow objects and listing their mapping to HTML and print through construction rules. Here is a simple DTD, sample document and style sheet that does just that.
As you will see, it is a subset of HTML. We have chosen HTML because it is such a well known DTD. We will start with a tiny subset of HTML and work towards all of it. We will treat HTML as if it were an ordinary DTD used for publishing documents and make a style sheet for printing it out. We will also look at a more structured DTD later on. It does not make much sense to create a stylesheet for transforming HTML into HTML, so we will ignore that Jade extension for now, and concentrate on print. Most of the principles are the same.
Here is a small stylesheet, based on a much more comprehensive HTML stylesheet by Jon Bosak:
|Small but complete document and DTD
<!DOCTYPE HTMLLite [ <!ELEMENT HTMLLite O O (H1|P)* > <!ELEMENT (H1|P) - - (#PCDATA|EM|STRONG)* > <!ELEMENT (EM|STRONG) - - (#PCDATA)> ]> <HTMLLite> <H1>This is a heading</H1> <P>This is text</P> <P>This is <em>bold</em></P> <P>This is <strong>strong</strong></P> </HTMLLite>
|Small but complete stylesheet
<!DOCTYPE style-sheet system "style-sheet.dtd" > <!-- you must have James Clark's style-sheet.dtd for this parse to correctly. --> (element HTMLLite (make simple-page-sequence)) (element H1 (make paragraph font-family-name: "Times New Roman" font-weight: 'bold font-size: 20pt line-spacing: 22pt space-before: 15pt space-after: 10pt start-indent: 6pt first-line-start-indent: -6pt quadding: 'center keep-with-next?: #t)) (element P (make paragraph font-family-name: "Times New Roman" font-size: 12pt line-spacing: 13.2pt space-before: 6pt start-indent: 6pt quadding: 'start)) (element EM (make sequence font-posture: 'italic)) (element STRONG (make sequence font-weight: 'bold))
Notice that there is no special "heading" flow object so we use a paragraph flow object instead. This practice of defining headings as a special style of paragraph is common in desktop publishing and wordprocessing software. Headings are just a special form of paragraph flow object. This is one of the reasons that Jade provides the special HTML flow object described above for converting into HTML. If you are converting into HTML from another DTD, you need to be able to accurately label headings as headings for proper HTML display.
The "main" flow objects that contain all of the rest must be simple-page-sequence or page-sequence flow objects for print, and scroll flow objects for online.
Most of the characteristic names are pretty obvious. Here is a quick summary of a few of them:
See the DSSSL specification for the complete list. The single quote character indicates that the string following (such as start) is a symbol. You cannot use end a symbol with a close quote. We will discuss symbols more later. For more information an any of these you should check the DSSSL specification.
You may actually test out this stylesheet with the sample document. Our next step is to expand this style sheet with more heading types (H2 through H6) to support a slightly larger subset of HTML. In doing so, we will discuss the mechanisms for reusing stylesheet code in DSSSL.
We want to expand our printable HTML subset to include some more heading levels. The obvious way to do that is to type out copies of the definition for H1 for H2 through H6, with changes to font size, quadding etc. But DSSSL has features that allow us to do this much more elegantly than that. For instance, we want a change to the font of one to change them all. We should not have to change them all independently. We want to be able to reuse definitions of font-family-name and some other characteristics. We could use the fact that DSSSL style sheets are SGML documents to reuse code using entities, as we would in a typical SGML document. We shall find, however, that DSSSL definitions are more flexible and powerful than that.
|A reusable definition
(define *heading-font* "Times New Roman") (define *heading-weight* 'bold) (define *heading-posture* 'italic) (element H1 (make paragraph font-family-name: *heading-font* font-weight: *heading-weight* font-posture: *heading-posture* font-size: 20pt quadding: 'center)) (element H2 (make paragraph font-family-name: *heading-font* font-weight: *heading-weight* font-posture: *heading-posture* font-size: 18pt quadding: 'left))
In technical terms, the first definition "binds" the "identifier" *heading-font* to the string "Times New Roman". This may remind you of SGML entities. I have used variable names with asterisks at the start and end just to draw attention to the fact that these things may be named anything. They are arbitrary names of variables and you can bind values to them. Now you can see why it is important to precede symbols with a single quote character. Otherwise they would be interpreted as variables. The DSSSL processor would try to look them up as variables and find that they have no value, which is an error.
You may also have encountered something like it in programming languages or in high school mathematics. In high school, you would have said: "let x = 5". That would bind the value 5 to the identifier x so that you can now refer to x and really mean 5. The goal of definitions is to reduce code duplication. Instead of typing out "Times New Roman", we can just type the identifier that is bound to it. Now a change to the font-family-name: in one place will change all of the headings at once.
But what about the font-size:? Although we do not want every heading to have the same font-size:, there is surely a relationship between them. We need to be able to express that relationship. Before we can do that, we must explore the DSSSL concept of expressions.
You have seen expressions before, but may not recall them. You were probably introduced to them way back in high school math:
We have seen many expressions in our examples already. We can examine one:
(element EM (make sequence font-posture: 'italic))
In this construction rule there are several expressions.
You may recall that math expressions may be nested arbitrarily deeply (as deeply as you need them to). So may DSSSL expressions. There is one big difference between DSSSL expressions and math expressions, however. In math, the operator, (such as "+", "-", "*") usually goes between the things it operates on. This is called "infix notation". So in "5 + 5", the "+" goes between the two fives. Infix is fine for regular math, but it demands relatively sophisticated typographic techniques to display large expressions. You have seen the complicated expressions that use a very wide line to represent the division of the top and the bottom, and a "+" in the middle of the page to add two complicated expressions on either side. We do not have the luxury of fancy typography in creating DSSSL specifications. You are not supposed to need special expression-editing software just to make complex expressions. So DSSSL uses something called "parenthesized prefix notation". And people who create DSSSL specifications use a special form of layout to show the structure of the expression without special software. Here are some examples of infix expressions translated to prefix according to the formatting conventions used by DSSSL users:
|Infix and prefix
Once you get used to it, DSSSL's syntax may actually be more comfortable to you than the traditional math notation. When you are creating an expression like the last one above, you probably think about it in terms of "I want to define something. I want its name to be x. I want its value to be the sum of two complicated expressions. I want the first expression to be a sum..." DSSSL supports this way of thinking about operations first and operands later. On the other hand, people who love the popular and expensive HP calculators prefer "postfix" notation which is the complete opposite of DSSSL's. That would indicate that notations are just arbitrary and you can get used to any of them. You can mentally turn most prefix operators into their infix equivalents by simply imagining that the operator was placed between its arguments ("operands").
You may wonder how mainstream programming languages solve this problem of complex nested expressions. Clearly the infix notation is fairly unwieldy for large expressions. In "procedural" infix programming languages like C, C++, Basic, or Java, when an expression gets complicated, you are expected to assign part of it to one variable and another part of it to another variable and then build up the complicated expression through assignments and expressions involving variables. The expression language is the part of the style language that allows you to create and nest expressions. As you can see, it is convenient for you to build complicated expressions without variables in the expression language.
Back to our sample stylesheet then! Now that we know how to define variables and how to create expressions, we can express the idea that the headings are identical except for their size, and that their sizes are related.
|A reusable font-size: definition
(define document-font-size 10pt) (define heading-font "Times New Roman") (define heading-weight 'bold) (define heading-posture 'italic) (element p (make paragraph font-size: document-font-size) ) (element H1 (make paragraph font-family-name: heading-font font-weight: heading-weight font-posture: heading-posture font-size: (* 2 document-font-size) ; double the document font size quadding: 'center)) (element H2 (make paragraph font-family-name: heading-font font-weight: heading-weight font-posture: heading-posture font-size: (* 1.8 document-font-size) ; a little smaller than H1 quadding: 'left))
We have put it all together. We used an expression to specify the font-size: characteristic. We based the expression on a variable we defined to represent the document's font size. And we used the standard DSSSL prefix notation to multiply the predefined font size by a different multiplier for each heading type. We have already done many things that are difficult or impossible in other style sheet languages, but DSSSL is just getting warmed up. We'll discuss some more advanced tools for manipulating flow objects, and then figure out how to allow these headings to share even more code.
Up to this point, we have been dealing with flow objects like paragraphs and sequences. What I did not tell you is that we have also been working with sosofos. If you are a reasonable English-language speaker, you are wondering: what in the world is a sosofo? Well, believe it or not, it is an acronym (only the computer industry or the military could come up with that one). But it is actually a very reasonable acronym. It stands for "Specification Of a Sequence Of Flow Objects." That's a mouthful, but at least it is very descriptive, unlike some other computer industry acronyms. We can look at a sosofo and see how it is a "Specification Of Sequence Of Flow Objects." Here is code to create perhaps the world's simplest sosofo:
(make paragraph font-size: 12pt )
Earlier I said that this code creates a flow object. That was a little bit of a simplification. Actually, this code creates a specification for a flow object. You can think of it as creating a flow object blueprint. We could also modify the text that goes within the paragraph and even attach a "label" to the flow object. When the construction rule is finished executing, the DSSSL engine interprets the sosofo as a blueprint for a paragraph and creates the paragraph. This distinction between blueprint (the sosofo) and creation (the flow object) will become more important later when we learn about the more powerful "functional" features of DSSSL, when it will become possible to create, duplicate and ignore sosofos based on complex logic, just as you could create, photocopy and ignore physical blueprints to a building. You can't duplicate or destroy flow objects, because they are only created after your DSSSL style sheet is finished executing!
You will note that the code above, termed a "make expression", seems to generate a specification for a very short sequence of flow objects: 1! But actually all of the characters in the paragraph are processed automatically by the DSSSL engine, and they return sosofos that specify character flow objects. Sub-elements (such as <em> elements) of the paragraph are processed automatically also. We can take control of that situation, however. DSSSL automatically processes these "child elements" for us, but we can do it ourselves:
(make paragraph font-size: 12pt (process-children))
Inside a make expression, after the keyword argument list, you can specify a "content expression" that tells the DSSSL processor what to put in the flow object. In this case, we have asked the DSSSL processor to process the child nodes (usually children elements and individual character nodes) of this element, and use them for the content of the paragraph flow object. If you do not specify a content expression, DSSSL will use (process-children) automatically, unless the flow object being created cannot have children. Such a flow object is called an "atomic flow object." A character flow object is an example. It may not have any content. The character itself is a "char:" characteristic.
We have, thus far, been letting DSSSL do the work. It has been filling in the (process-children) content expression for us. But we can take control over child node processing. Why would we want to do that? Perhaps we want to surpress the processing of some children, or only process particular ones. Or perhaps we want to introduce some flow objects that do not correspond directly to elements in the original document.
For instance, in this next example, we want to handle a "note" element which, in our imaginary DTD, represents a warning to the reader.
<NOTE>Platform shoes are back!</NOTE>
It will be displayed like this:
Warning: Platform shoes are back!
As you will recall, a make expression has several parts. Here is the full definition from the DSSSL specification:
make-expression = (make flow-object-class-name keyword-argument-list content-expression*)
The asterisk at the end means that you can have as many content-expressions as you want. We will use this feature in our example.
We will make a list of characters that display the word "Warning:". We will wrap them in a "sequence" flow object that will just make them bold. We will also have a content expression that processes the actual contents of the note element. We will wrap both of them in a paragraph.
(element note (make paragraph font-size: 12pt (make sequence font-weight: 'bold (literal "Warning:")) (process-children)))
The "literal" procedure creates a sosofo specifying a sequence of character flow objects representing the characters of the string. In this case it would create a sosofo specifying a sequence of several character flow objects :"W","a","r","n","i","n", "g", and ":". Because this literal is within the make expression for a sequence flow object with the font-weight: 'bold characteristic, these characters are bolded.
Because the make expression for the bolded sequence containing the literal precedes the (process-children) procedure, the sequence will precede the other content generated by the children of the paragraph. It is also possible to append ("glue together") sosofos without creating a new paragraph. This next example will create three paragraphs. The first with the word "Note:" the second with the actual contents of the note, and a third with the ominous warning: "You have been warned!".
(element note (make sequence (make paragraph font-size: 14pt font-weight: 'bold (literal "Note:")) (make paragraph font-size: 12pt font-weight: 'medium (process-children-trim)) (make paragraph font-size: 14pt font-weight: 'medium (literal "You have been warned!"))))
The sequence makes a flow object that concatenates the first sosofo (the bolded word "Note:"), the second sosofo (the children of the note element) and the sentence at the end.. As you can see, DSSSL's ability to nest expressions nicely is becoming very useful.
(process-children-trim) is a simple variant on (process-children). It chops off whitespace (usually spaces, newlines, tabs, etc.) at the beginning and end of the content of the element.
The (empty-sosofo) content expression returns an empty sosofo. You can use this to supress an element:
(element hidden (empty-sosofo))
It is possible that you do not want to process all of an element's children. For instance, suppose that you were writing a stylesheet that generates a table of contents for a document. In that case, you want a more advanced content expression that would allow you to specify exactly which children to display (chapter titles) and not display (everything else). You do this using the process-matching-children procedure.
The parameter for the process-matching-children procedure is called a pattern. Any child elements that match the pattern will be processed. In this case, that will be all section elements. The construction rule used is the same one that is usually used. That means, of course, that the child elements of title will be processed if the title has a (process-children) construction rule, or has no explicit construction rule (in which case (process-children) is implied).
Note that the element name, "section", is represented by a symbol: it is preceded by a single quote character ('). If we did not include that quote character, the DSSSL interpreter would try to look up "section" as a variable.
The process-matching-children procedure can take more than one pattern. It will try each one and process any child that matches either. This example processes all section and appendix child elements.
(process-matching-children 'section 'appendix)
It is also possible to work your way do
There is another procedure that is somewhat similar to process-matching-children, called process-first-descendant. You can imagine that it flattens the tree back into a linear SGML document and then searches for the first element whose name is the same as its argument:
This will check if the current element's first child is a title, then check if the first child has a descendant that is a title. If not, it will check if the current element's second child is a title, then check if the second child has a descendant that is a title, and so forth.
It is also possible to process an element with a particular unique identifier, no matter where the identifier occurs in the current document. This would most often be used in processing cross-references. For instance, you might want the text from the referenced element (such as a title). Imagine this document:
|A Cross Reference
<document> <section> <title id="Intro">Getting Started</title> <p>... <p>... </section> <section> <title>Finishing Up</title> <p>As I mentioned in <title-ref idref="Intro">, ... </section>
We want to get the content out of the title at the point of the "title-ref". The "title-ref" attribute that tells us what title to reference is called "idref" in this DTD (often it will be called "target", or simply "id").
(element title-ref (process-element-with-id (attribute-string "idref")))
This will then process the title at the point where the title-ref occurs.
There are two things that are rather contrived about this example. The first is that there is a pretty good chance that the ID attribute would actually occur on the parent element, the section, rather than on the title. It is, after all, the section we are identifying, not the title. The second thing is that there is a very good chance that the construction rule for the title actually makes a large, bolded, centered paragraph. That is not what we want to insert at the point of the title-ref. We will correct both of these problems when we study modes next.
If you want to process a section (or all) of a document using some different construction rules, DSSSL provides a simple way of doing so, called a "processing mode". For example, perhaps you want to deal with the reference problem that we discussed before. You want to process references to sections, but you want to only print out the title of the section, and with no special title formatting. "Just the title, ma'am."
You do this by grouping the special construction rules into something called a mode construction rule group. Here is an example mode construction rule group:
(element title (make paragraph font-size: '20pt quadding: 'center)) (mode section-reference (element section (make sequence (literal "Section ") (literal (format-number (child-number) "1")) (process-first-descendant 'title))) (element title (process-children)))
Note that the child-number procedure returns 1 if the element being processed is the first element of a particular type in its parent element, 2 if it is the second, and so forth. The "format-number" procedure turns a number into a string that can be used as the content of a literal. The second argument specifies the format to be used (arabic, alphabetic, roman). We have chosen arabic. So this section construction rule prints out the word "Section" and its child-number. It then looks for and processes its first title element.
Since the title is being processed with the section-reference mode, it uses the construction rule specified within that mode, rather than the one in the "initial mode" (the construction rule that is outside of any particular named mode). If the construction rule in the named mode had not existed, the element would have been processed instead with the construction rule from the initial mode. You could say that modes "borrow" (or "inherit") the construction rules from the initial mode. So it is important that we force the section to only process its first descendant. Otherwise the content from all of the other descendants would be returned according to their construction rules in the initial mode. Your "section reference" will contain all of the contents of the section!
The mode name is basically arbitrary. Like any other arbitrary name (such as variable names or procedures), it is easier to name a mode something you will remember. To use the mode you create a with-mode expression that uses the mode's name:
(element section-ref (with-mode section-reference (process-element-with-id (attribute-string "idref"))))
This processes the section at the other end of the idref with the section-reference mode. You could also imagine that we could create a single "reference" element that could refer to many different kinds of elements. We could change the name of the section-reference mode to be more intuitive (perhaps "any-reference") and add construction rules for each reference target.
Processing modes are even more powerful when you use them to create an alternate formatting for an entire document or document section. For example, imagine that you want to generate a table of contents for the titles of each top-level section. What you would do is set up a mode that prints out only top-level section headings. It would be very much like our any-reference mode, but for each section it would output a paragraph contain the title of the section rather than just the title of the section itself. Otherwise our table of contents would be one long line!
(mode toc (element section (make paragraph (literal "Section ") (literal (format-number (child-number) "1")) (process-first-descendant 'title))) (element title (process-children)))
You use this mode much as you would have the reference mode, but use (process-children) rather than (process-element-with-id). Presume that the document's root element (the element that contains all others) is called "document".
(element document (make sequence (process-matching-children 'title) (make paragraph font-size: 18pt (literal "Table of Contents:")) (with-mode toc (process-matching-children 'section)) (process-children) ))
This construction rule first prints out the title of the document. Next it prints out the text "Table of Contents". Next it processes each section in the document in our any-reference mode to get just the title text. Finally it processes the entire document in the "initial" (regular) mode. We could make also make a mode that builds a "recursive" table of contents, one with all sections, subsections, sub-subsections and so forth. There is a DSSSL feature called "labelled sosofos" that accomplishes this task a little bit easier, but that feature is not covered by the current tutorial.
Readers knowledgable about SGML may be wondering how to handle more complex structures than relatively flat HTML documents. We can look, instead, at a sample nested-section example DTD, based on the TEI DTD:
|An example of a hierarchical DTD
<!DOCTYPE root [ <!ELEMENT root - - (head, div+)> <!ELEMENT div - - (head, (div+ | p+)) > <!ELEMENT head - - (#PCDATA)> <!ELEMENT p - - (#PCDATA)> ]> <root> <head>Document Title</head> <div> <head>Top level section title</head> <div> <head>Second level section</head> </div> <div> <head>Another Second level section</head> </div> <div> <head>Yet Another Second level section</head> <div> <head>A third level section</head> </div> </div> </div> <div><head>Another top level section</head> </root>
For this DTD, we need a way to make titles at different levels look visually different, despite the fact that the same element type "head" is used for all of them. We could write conditional DSSSL code that checks the depth of a heading, before creating a sosofo for it, but we do not need to do this. The DSSSL standard provides a simple, elegant mechanism for doing this. For instance, if we want to make the document heading (HEAD in ROOT) look different from a section heading (HEAD in DIV), we can do it this way:
(element (DIV HEAD) (make ...) ) (element (ROOT HEAD) (make ...) )
The first is a construction rule for HEAD elements directly within DIV elements. The second matches HEAD elements directly within ROOT elements. We can make this chain of ancestors as long as we want. To cover subsections, sub-subsections, etc., we can make rules like this:
(element (DIV DIV HEAD) (make ...) ) (element (DIV DIV DIV HEAD) (make ...) ) (element (DIV DIV DIV DIV HEAD) (make ...) )
It might seem more elegant to have one rule that covered all HEADs, with conditional code to differentiate them. We do have that option, but section heads and sub-section heads are often so radically different that they would not share much code, other than that which can already be shared through definitions. We will look into this more later.
Note that unlike the (process-matching-children) procedure, construction rules cannot be selected based on attribute values. If you want to do more complicated element differentiation, you must use a DSSSL implementation that supports the query feature. Using a query construction rule, you can do sophisticated queries based on attributes, properties of the parent, existance of children and so forth. Jade does not support the query feature. However, you can use conditional expressions to do basically the same things with more work. We will get to that later.
As you will recall, flow objects are the formatting constructs that result from the application of your style sheet to an SGML document. So far, we have only used three flow objects, paragraph, sequence, simple-page-sequence. We have also mentioned the "scroll" flow objects that are used on-line in place of simple-page-sequence flow objects and the page-sequence flow object that allows more complicated layout than the simple-page-sequence flow objects would. See the DSSSL specification for more information on any of these.
You can do some very reasonable style sheets with only those flow objects. Many style sheet languages do not have many more than that. But DSSSL has a very rich toolbox of flow objects, and some interesting ways of combining them. I will touch briefly on a few others that may be useful to you. See the DSSSL specification for more information:
The line-field flow object creates a fixed-width "field" in a line of text. You might use it for numbers in a numbered list, where the start of the first character after the number or bullet should not depend on the size of the number or bullet.
<!DOCTYPE style-sheet system "style-sheet.dtd" > (root (make simple-page-sequence)) (element item (make paragraph (make line-field field-width: 12pt (literal (format-number (child-number) "1")) (literal ".")) (process-children)))
The display-group flow object concatenates ("glues together") other flow objects in the same way that the sequence flow object does, but it creates a new "display area". A display area is an area that is not "inline". Inline areas are displayed in the same line as the areas that precede and follow them. Display areas start on a new line and are followed by another display area on a new line. A figure or list might be a display-group, because you could set them off from the rest of the document with the space-before: and space-after: characteristics, or use the "keep:" characteristic to force all of the items to lie on the same page.
Jade has a proprietary flow object specifically for HTML output. It is called a formatting-instruction. It is used like this:
(element P (make sequence (make formatting-instruction data: "<P>") (process-children) (make formatting-instruction data: "</P>")))
The formatting-instruction copies the characters in the "data" characteristic (in this case "<P>" and "</P>") into the scroll flow object that makes up an HTML file. It is well documented on the Jade page. Do not forget to put your formatting-instructions within a scroll flow object instead of a page-sequence.
DSSSL's "expression language" is a full featured programming language that can do most of the things other programming languages can do. It is, however, a side-effect free language. That means that you cannot read or write files, open or close windows, assign to variables or do anything other than transform or format an SGML document.
In the expression language, #t means "true" and #f means "false". So the expression to determine if 2 is less than 3, "(< 2 3)", evaluates to #t. The expression to determine if 3 is less than 2, "(< 3 2)", evaluates to #f. We call the values "#t" and "#f" booleans. DSSSL experts call operators that return booleans "predicates", from the "predicate logic" that you might have taken in a university philosophy or math course.
Note that the ">" operator returns a boolean: true (#t) if the first argument is numerically larger than the second, false (#f) otherwise. Other predicates check numeric equality ("="), oddness ("odd") and evenness ("even") and things that have nothing to do with numbers at all, such as whether a particular element is the first in a series ("first-sibling?").
Conditional expressions are expressions that execute one sub-expression or another based on a condition. If you have programmed in any other programming language, you will find that the expression language's "if" construct will be very like that other language's "if" or "if/then/else". In the expression language, "if" looks like this:
(if test consequent alternate)
The test is evaluated first. If it yields a true value, then the consequent is evaluated and returned. Otherwise the alternate is evaluated and returned. Note that in the expression language, any expression other than the token "#f" evalutes to true, including (but not limited to!) the empty list, '(), the number 0, the string "false" and the symbol 'false. Programmers familiar with languages where this is not the case should take careful note.
Here are some examples of conditionals in action, and their return values (some taken right from the DSSSL standard):
(if #t 'yes 'no) => yes (if #f 'yes 'no) => no (if (> 3 2) 'yes 'no) => yes (if (> 2 3) 'yes 'no) => no (if "Elvis is alive" "He is alive!" "He is dead!") => "He is alive!". (if (> 3 2) (- 3 2) (+ 3 2)) => 1
The final example above shows how to use more complicated expressions as the components of the conditional expression. We are going to use this feature to make some very powerful and complex structures. Here is an obvious use: presume that a DTD is designed so that the first paragraph in a series has no space before it, but other paragraphs would have space before them (for inter-paragraph space).
The obvious way to do it might be to have two make expressions nested within a conditional expression:
(element p (if (first-sibling?) (make paragraph first-line-start-indent: 0pt) (make paragraph first-line-start-indent: paragraph-indent)))
The first-sibling? procedure returns true if the current element being processed is not preceded by another element of the same type (with the same "generic identifier"). Chapter 10 of the DSSSL standard defines many useful procedures like this. You should become familiar with them as you create more complex DSSSL style sheets.
This works, but there is a more elegant way. A conditional expression is an expression. It can go anywhere an ordinary expression can go. This is a very powerful and useful feature of the DSSSL expression language.
(element p (make paragraph first-line-start-indent: (if (first-sibling?) 0pt paragraph-indent)))
Note that every conditional expression has an "if part" (the consequent) and an "else part" (the alternate). In many programming languages, you can skip the alternate, because they view code as a series of instructions that can either be executed or not. The expression language views code as an extended expression: an expression must always return a value. After all, what would this expression evaluate to:
(if #f 'yes)
It cannot evaluate to 'no because we did not specify that. It cannot evaluate to anything! It is just logically incomplete. The solution is to always return a value: if you want to return a sosofo that has no actual contents, and takes up no display or print space, you can return the empty sofofo: (emtpy-sosofo) like this:
(element p (if (not (equal? (attribute-string "SECURITY") "HIDDEN")) (make paragraph) (empty-sosofo)))
Now hidden paragraphs will just disappear in the print document.
You may also want to use conditional expressions in conjunction with definitions to organize your DSSSL script so that one changed "flag" at the top triggers changes throughout your document:
(define BIG-TEXT #t) (element p (make paragraph font-size: (if BIG-TEXT 20pt 12pt))) (element (report head) (make paragraph font-size: (if BIG-TEXT 30pt 20pt)))
In order to productively use conditional expressions, you should also know how to use and-expressions and or-expressions. and-expressions take 0 or more expression arguments and returns a true value if all of them are true. If any of the expressions are false the and-expression returns #f. Usually you will use and-expressions with two arguments, but it is possible to use them with 0, in which case it just returns #t, or more than two.
(if (and (= 2 2) (> 2 1)) 'yes 'no) => 'yes (if (and (= 2 2) (< 2 1)) 'yes 'no) => 'no (if (and 1 2 'c '(f g)) 'yes 'no) => 'yes (if (and) 'yes 'no) => 'yes
or-expressions take 0 or more arguments and returns a true value if any are true (anything other than #f). If all are false, the or-expression returns #f.
(if (or (= 2 2) (> 2 1)) 'yes 'no) => 'yes (if (or (= 2 2) (< 2 1)) 'yes 'no) => 'no (if (or #f #f #f) 'yes 'no) => 'no
The not procedure takes a single argument and returns #t if it is false (#f), and #f if it is true (any other value).
(not #t) => #f (not 3) => #f (not (list 3)) => #f (not #f) => #t (not '()) => #f (not (list)) => #f (not 'nil) => #f
Remember that the DSSSL expression language considers any value that is not #f to be true. So really "(not anything)" is #f, unless anything is "#f".
Now that we know how to build up relatively complex expressions with conditional expressions, nesting, and definitions, you might wonder if there is a way that we can reuse expressions so that we do not have to type them out every time. The DSSSL expression language allows this through a construct called procedures. A full description of the features and power of procedures are well beyond the scope of this tutorial, but simple procedures can be done easily, and you will often see them in DSSSL style sheets.
We have already worked with procedures: "built-in" procedures. For instance "+" is a procedure that adds two numbers. "*" is a procedure that multiplies them. "=", "<", and ">" are procedures that compare numbers.
One reasonably complicated expression we have seen before is the one we used to create a title. This wouldn't be a problem, but the code must be duplicated over and over for each different kind of title (document title, section title, sub-section title, etc.) A stylesheet for HTML would have 6. A stylesheet for another DTD might have more, depending on how many levels of titles the stylesheet writer decides to differentiate. It would be nice to take that complicated expression and turn it into a "template" or an "abstraction".
We can do this using a procedure definition, which works much like the data definitions that we examined earlier. There is, however, one large difference. We do not want headings of all types to be exactly the same, only mostly the same. In particular, the font-size and quadding (alignment) should change from heading to heading, but the font-family-name will not, and the line-spacing, line-before, line-after and other spacing attributes can be either fixed for all headings, or based on the font-size. What we need to do is "parameterize" the definition. In other words, we need a definition that takes "parameters" or "arguments". Here is such a definition:
(define (heading-procedure heading-font-size heading-quadding) (make paragraph font-family-name: "Times New Roman" font-weight: 'bold font-size: heading-font-size space-before: (* 0.5 heading-font-size) start-indent: 6pt first-line-start-indent: -6pt quadding: heading-quadding keep-with-next?: #t))
Note that immediately following the keyword define is a "procedure prototype", "(heading-procedure font-size quadding)". It serves a couple of roles. First, it shows the way this procedure will be called. This first token in the parentheses is the procedure name, in this case "heading-procedure", just as in the expression "(+ 5 3)", the "+" is the procedure name. The other tokens in the procedure prototype are arguments. This procedure takes two arguments. One is called heading-font-size and the other heading-quadding. You can call your arguments just about anything you want. You cannot use keywords like "define" or "if", of course, but most other names are fine. Inside the procedure, we use heading-font-size and heading-quadding as if they were defined with a define statement. They are variables, but "local" variables. They only exist inside this procedure. Here is how you use heading-procedure:
(element h1 (heading-procedure 24pt 'center)) (element h2 (heading-procedure 18pt 'start))
These are named procedure calls. You may already understand how this works. For each procedure call, imagine two cut-and-paste operations. The first makes a copy of the heading-procedure defined above, and pastes it "on top of" the procedure call. The second copies the arguments from the procedure call "on top of" each occurence of the corresponding arguments in the definition. Here is what these procedure calls would look like if it were fully "expanded":
(element h1 (make paragraph font-family-name: "Times New Roman" font-weight: 'bold font-size: 24pt space-before: (* 0.5 24pt) start-indent: 6pt first-line-start-indent: -6pt quadding: 'center keep-with-next?: #t)) (element h2 (make paragraph font-family-name: "Times New Roman" font-weight: 'bold font-size: 18pt space-before: (* 0.5 18pt) start-indent: 6pt first-line-start-indent: -6pt quadding: 'left keep-with-next?: #t))
Cut-and-paste is one way to think of procedure calls, but you can also think of them as "black box" machines that take input (arguments) and produce output (a "return value"). "+" is a black box that takes in numbers and returns the sum of the numbers. heading-procedure is a black box that takes in a quantity representing a font size and a symbol representing quadding and returns a sosofo. If you think back to high school math, you may remember creating "user-defined" functions, such as f(x)=x+5 or f(x,y)=x*y. The expression language works the same.
Note that there is another syntax for procedures that does much the same thing:
(define heading-procedure (lambda (heading-font-size heading-quadding) (make paragraph font-family-name: "Times New Roman" font-weight: 'bold font-size: heading-font-size line-spacing: 22pt space-before: 15pt space-after: 10pt start-indent: 6pt first-line-start-indent: -6pt quadding: heading-quadding keep-with-next?: #t))
This syntax emphasizes other characteristics of procedures, and also points to the theoretic computer-science background of the expression language. These important features of the expression language are beyond the scope of this tutorial, but I point the other syntax out so that you will recognize it if you see it.
I have barely scratched the surface of the things that can be done with procedures. Procedures can call procedures, and can even call themselves (this is called recursion). Procedures can be arguments to procedures and return values from procedures (this is called "higher-order functions"). If you want to make large, complex stylesheets, you should learn about these features of DSSSL.
The DSSSL expression language is very closely modelled on a programming language called "Scheme". If you buy books on Scheme, you will find the information in them is directly applicable to the expression language, with only a few small exceptions. Two good ones are R. Kent Dybvig, The Scheme Programming Language, (Prentice Hall, 1996, ISBN 0-13-454-646-6) and and Hal Abelson's, Jerry Sussman's and Julie Sussman's Structure and Interpretation of Computer Programs (MIT Press, 1984; ISBN 0-262-01077-1), also known as the "Wizard Book". I recommend both highly.
You can practice Scheme programming in a very comfortable environment like MIT Scheme or PLT Scheme. Do a web search to get one of these excellent tools for learning the language that the DSSSL expression language is based upon. The benefit of these tools over existing DSSSL tools is that they allow you to test out expressions while you are editing them, so that you do not have to waste time writing and testing the entire stylesheet to test a small procedure or expression. While you are learning, this is a very useful feature.
Daniel M. Germán has a tutorial that covers some of the more sophisticated uses of DSSSL.
The DSSSL standard itself is also fairly readable, though intricate and large. The more you learn, or attempt to learn, from the spec, the more comfortable you will be with finding things in it. Give it a try!
Copyright Paul Prescod, 1997