DOM Examples

This location provides examples of using the Common LISP DOM implementation. The examples can be used with Interaction, Emilé, XPublish and other software that supports the interface.

Getting a Paragraph

Using DOM it takes just a few lines of code to get the content of a paragraph from a document. The GET-ELEMENTS-BY-TAG-NAME function returns a list of all nodes in the document that matches a given tag name. The ITEM function selects any node from the list based on an index. Here is how to combine these to get the first (zero'th) paragraph element of the document for the homepage of the site:

(with-open-dom (homepage "home:documents;index.html")
  (dom:item (dom:get-elements-by-tag-name homepage "P") 0))

The form first opens a DOM interface to the "index.html" file in the documents folder, and assigns it to the variable called homepage. Next, it get a list of all "P" (paragraph) elements of the homepage. This list is used as argument to the ITEM function, which returns the first node in the list (the one with 0 as index).

This simple example can be used say to make an entity for Interaction or XPublish that provides the latest update of the introduction paragraph of the site. Just paste the script above into the editor for a Function Entity or make a component containing the following code:

(define-entity homepage.introduction ()
  "The introduction paragraph of the homepage"
  (with-open-dom (homepage "home:documents;index.html")
    (dom:item (dom:get-elements-by-tag-name homepage "P") 0)))

Navigating an HTTP Resource

You are not limited to content on the server computer. DOM can also be used with content on other sites. The following opens an HTTP resource on another site (ours), returning all of its paragraph as plain text.

(defun write-element (element out)
  "Writes a DOM element as plain text"
  (dom:do-nodes (node element)
    (case (dom:node-type node) 
      (#.dom:text-node 
        (write-string (dom:data node) out))
      (#.dom:element-node
        (write-element node out)))))

(with-open-http-resource (in "http://www.in-progress.com")  
  (with-open-dom (document in)
    (with-output-to-string (out)
      (dom:do-nodes (paragraph (dom:get-elements-by-tag-name document "P"))
        (write-element paragraph out)
        (write-char #\newline out)
        (write-char #\newline out)))))

Generating HTML from Text

A common task is to generate markup from another format. The following example processes a text file where paragraphs are separated by empty lines and first paragraph is header. The result is an HTML document. Before running, create a text file called "plain.txt" in the same folder as the application, and add to it a number of plain text paragraphs separated by a blank line. This is a verbose edition using only official DOM constructs and COMMON LISP. See below for a more compact version that eliminates most reduncancy by using our extensions to DOM.

(with-open-file (in "home:plain.txt" :if-does-not-exist :create))
  (with-open-dom (document "home:result.html" :direction :output :if-exists :supersede :if-does-not-exist :create)
    (let ((html-element (dom:create-element document "HTML"))
          (head-element (dom:create-element document "HEAD"))
          (title-element (dom:create-element document "TITLE"))
          (body-element (dom:create-element document "BODY"))
          (heading-element (dom:create-element document "H1"))) 
      (dom:append-child document html-element)
      (dom:append-child html-element head-element)
      (dom:append-child html-element body-element)
      (dom:append-child head-element title-element)
      (dom:append-child body-element heading-element)
      (dom:append-child title-element
        (dom:create-text-node document (pathname-name in)))
      (loop
        for line = (read-line in)
        until (equal line "")
        do (dom:append-child heading-element
             (dom:create-text-node document line)))
      (loop
        while (peek-char T in NIL)
        for paragraph = (dom:create-element document "P")
        do (loop
             for line = (read-line in NIL)
             while line
             until (equal line "")
             do (dom:append-child paragraph
                  (dom:create-text-node document line)))
        do (dom:append-child body-element paragraph)))
     document))

The following does the same job, but uses our BUILD-ELEMENT extension to DOM for more compact code. BUILD-ELEMENT creates a new element, appends it to the surrounding element, and executes the enclosed code.

(with-open-file (in "home:plain.txt" :if-does-not-exist :create)
  (with-open-dom (document (pathname "home:result2.html") :direction :output :if-exists :supersede :if-does-not-exist :create)
    (dom:build-element (html "HTML" document)
      (dom:build-element (head)
        (dom:build-element (title)
          (append-text title (pathname-name in))))
      (dom:build-element (body)
        (dom:build-element (h1)
          (append-read-paragraph-text h1 in))
        (loop while (peek-char T in NIL) do
          (dom:build-element (p)
            (append-read-paragraph-text p in))))
      (dom:normalize html))))

(defun append-text (node text)
  "Append a text string to a node"
  (dom:append-child node
    (dom:create-text-node (dom:owner-document node) text)))

(defun append-read-paragraph-text (element stream)
  "Reads a paragraph from a text file and appends it to the element"
  (loop for line = (read-line stream NIL)
        until (or (null line) (equal line ""))
        do (append-text element line)))

MODIFYING A DOCUMENT

This demonstrates inserting and deleting random text content to a document. Note the use of the DO-NODES construct, which is a custom extension of DOM that binds a variable to each of the nodes.

(with-open-dom (document "home:Documents;foo3.html" :direction :io :if-exists :supersede :if-does-not-exist :create)
  (let* ((root (or (dom:document-element document)
                   (dom:append-child document
                     (dom:create-element document "HTML"))))
         (body (or (dom:item (dom:get-elements-by-tag-name root "BODY") 0)
                   (dom:append-child root
                     (dom:create-element document "BODY")))))
    (dom:append-child body (dom:create-element document "P"))
    (dom:do-nodes (paragraph (dom:get-elements-by-tag-name body "P"))
      (let ((p-content (dom:child-nodes paragraph)))
        (when p-content
          (when (eq (random 3) 1)
            (dom:delete-data (dom:item p-content 0) 0 1))
          (dotimes (i (random 5))
            (dom:append-data (dom:item p-content 0) 
              (string (character (+ 64 (random 64)))))))
        (dom:append-child paragraph 
          (dom:create-text-node document "abc"))))))