# XEXPR module – handling XML as scheme s-expressions

Writing and searching x-expressions

Scheme s-expressions are a convenient way of wrangling XML, known as x-expressions. The functions in this module will search the content of, and write out, such x-expressions. There are a couple of ways that x-expressions have been defined, in the Scheme world, in the past. I have followed Racket.

The following grammar describes expressions that create X-expressions:

    xexpr = string
      | (list symbol (list (list symbol string) ...) xexpr ...)
      | (cons symbol (list xexpr ...))
      | symbol
      | valid-char?
      | cdata
      | misc

For example:

    (xexpr-write/xml!
       '(p "Here is a " (a ((href "http://foo")) "link")
           " with " (br ((clear "right")))
           ". This" amp "that" 33))

produces

    <p>Here is a <a href="http://foo">link</a> with <br clear="right" />. This&amp;that&#x21;</p>

Above, a ‘string’ is literal data, either as a `"string"` or `#[ustring]`. When converted to an XML stream, the characters of the data will be escaped as necessary.

A pair represents an element, optionally with attributes. Each attribute’s name is represented by a symbol, and its value is represented by a string.

A symbol represents an entity reference. For example, the symbol `'nbsp` represents `&nbsp;`. These are serialised as such, unless `:expand-entities?` is `#t`, in which case a subset of entities are expanded into the nominated character. It is unspecified just what entities are included in this ‘subset’, but this list is reasonable.

A `valid-char?` represents a numeric entity. For example, `#x20` represents `&#x20;`.

Right now, I don't implement cdata or misc.

There is not at present any support for parsing XML into xexprs.

## Writing

The functions `xexpr-write/md!`, `xexpr-write/python!`, `xexpr-write/sexp!`, `xexpr-write/xhtml!`, `xexpr-write/xml!` will write out an xexpr in one or other format (the function `xexpr-write/xhtml!` is generally the most useful).

## Examining

The function `xexpr-path-search` implements a basic path searcher.

Given an xexpr, functions `xexpr-disassemble`, `xexpr-get-attribute` and `xexpr-text` make it convenient to inspect the contents.

## Functions

Index:

- xexpr-disassemble
- xexpr-get-attribute
- xexpr-path-search
- xexpr-search-path?
- xexpr-text
- xexpr-write/md!
- xexpr-write/python!
- xexpr-write/sexp!
- xexpr-write/xhtml!
- xexpr-write/xml!

### xexpr-disassemble

(xexpr-disassemble xe) : Given an xexpr representing an element, return its name, attributes and contents as multiple values. If there are no attributes in the xexpr, then we return a null list for that part of the result.

### xexpr-get-attribute

(xexpr-get-attribute xe att) : Given an xexpr `xe`, return the attribute `att` (a symbol) if it's present, or #f if not.

### xexpr-path-search

(xexpr-path-search search-path xexpr) : search for a path in an xexpr.

The `search-path` is a list of element names as symbols, predicates, or one final keyword.

Returns a list comprised of the lists within `xexpr` which match. A symbol matches an element name, a predicate is true if it returns true when applied to an xexpr, and a trailing keyword matches an attribute name.

Thus `'(p em)` would find all `em` elements contained within a `p` element, `'(p class:)` would find the `class` attribute of `p` elements (ie, XPath `p/@class`), and `(list 'p (lambda (e) (xexpr-get-attribute e 'a1)) 'q)` would match `p` elements which have an `a1` attribute and contain a `q` element (ie, XPath `p[@a1]/q`).

### xexpr-search-path?

(xexpr-search-path? x) : The search path specification for XEXPR-PATH-SEARCH.

A sequence of symbols followed by an optional keyword. The prefix of symbols specifies a path of tags from the leaves with an implicit any sequence to the root. The final, optional keyword specifies an attribute.

### xexpr-text

(xexpr-text xe) -> string? : Given an xexpr, extract all and only the text content. Any entity references (ie, symbols) are deemed to be 'text' in this context, and expanded to the corresponding string.

### xexpr-write/md!

(xexpr-write/md! xexprs) : Convert xexprs to Markdown. The argument XEXPRS is a list of xexpr?. Writes the result to the current output port. This is preliminary/best-efforts, rather than being at all thorough.

### xexpr-write/python!

(xexpr-write/python! body) : Write the xexpr expression in a form which is readable by Python. Specifically, it's the format wrangled by https://pypi.org/project/listxml/

### xexpr-write/sexp!

`(xexpr-write/sexp! el :dumb?)` : write an xexpr to the stdout as a sexpr (ie, this is just a very basic prettyprinter).

If the keyword argument `:dumb?` is `#t`, then this uses an alternative writer which is less pretty but more robust.

### xexpr-write/xhtml!

(xexpr-write/xhtml! body :metadata) : Write an xexpr to the stdout as XHTML (ie, adding head and body elements). If METADATA is present, then it is taken to be a list of lists, `'(("key" "value") ("key" "value") ...)` (specifically, the result from parse-markdown/metadata), which is searched for an item with key `"title"`.

### xexpr-write/xml!

(xexpr-write/xml! el :expand-entities?) : write an xexpr to the stdout as XML.

If keyword `:prettyprint?` is present and `#t`, then prettyprint the XML output. At present, this is very basic, and consists only of adding newlines after end-tags. For the keyword `:expand-entities?`, see below.

An xexpr is defined as follows; this is copying the specification in Racket.

The following grammar describes expressions that create X-expressions:

    xexpr = string
      | (list symbol (list (list symbol string) ...) xexpr ...)
      | (cons symbol (list xexpr ...))
      | symbol
      | valid-char?
      | cdata
      | misc

For example:

    (xexpr-write/xml!
       '(p "Here is a " (a ((href "http://foo")) "link")
           " with " (br ((clear "right")))
           ". This" amp "that" 33))

produces

    <p>Here is a <a href="http://foo">link</a> with <br clear="right" />. This&amp;that&#x21;</p>

Above, a string is literal data, either as a `"string"` or `#"ustring"`. When converted to an XML stream, the characters of the data will be escaped as necessary.

A pair represents an element, optionally with attributes. Each attribute’s name is represented by a symbol, and its value is represented by a string.

A symbol represents an entity reference. For example, the symbol `'nbsp` represents `&nbsp;`. These are serialised as such, unless `:expand-entities?` is `#t`, in which case a subset of entities are expanded into the nominated character. It is unspecified just what entities are included in this ‘subset’, but this list is reasonable.

A `valid-char?` represents a numeric entity. For example, `#x20` represents `&#x20;`.

Right now, I don't implement cdata or misc.

Norman
2026 August 02