(define+provide+safe(attr-reftxkey[failure-result(λ_(raise(make-exn:fail:contract(format"attr-ref: no value found for key ~v"key)(current-continuation-marks))))])
@ -50,35 +50,33 @@ It's an X-expression with the following grammar:
[element xexpr?]
]
A txexpr is a list with a symbol in the first position— the @italic{tag} — followed by a series of @italic{elements}, which are other X-expressions. Optionally, a txexpr can have a list of @italic{attributes} in the second position.
A tagged X-expression — @italic{txexpr} for short — is a list with a symbol in the first position— the @italic{tag} — followed by a series of @italic{elements}, which are other X-expressions. Optionally, a txexpr can have a list of @italic{attributes} in the second position.
The last one is a common mistake. Because the key–value pair is not enclosed in a @racket[list], it's interpreted as a nested txexpr within the first txexpr, as you may not find out until you try to read its attributes:
@margin-note{There's no way of eliminating this ambiguity, short of always requiring an attribute list — empty if necessary —in your txexpr. See also @racket[xexpr-drop-empty-attributes].}
The last one is a common mistake. Because the key–value pair is not enclosed in a @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{list}, it's interpreted as a nested txexpr within the first txexpr, as you may not find out until you try to read its attributes:
After converting to and from HTML, we get back the original X-expression. Well, almost. The brackets turned into parentheses — no big deal, since they mean the same thing in Racket. Also, per its usual practice, @racket[string->xexpr] added an empty attribute list after @racket[em]. This is also benign.
After converting to and from HTML, we get back the original X-expression. Well, almost. Per its usual practice, @racket[string->xexpr] added an empty attribute list after @racket[em]. This is benign —an empty attribute list can be omitted with no change in meaning, or vice versa.
@section{Why not just use @exec{match}, @exec{quasiquote}, and so on?}
@ -90,7 +88,8 @@ If you prefer those, please do. But I've found two benefits to using module func
The programming is trivial, but the annoyance is real.
@section{Interface}
@section{Predicates}
@deftogether[(
@defproc[
@ -154,24 +153,7 @@ boolean?]
[v any/c])
boolean?]
)]
Shorthand for @code{(listof txexpr-tag?)}, @code{(listof txexpr-attr?)}, and @code{(listof txexpr-element?)}.
@defproc[
(validate-txexpr
[possible-txexpr any/c])
txexpr?]
Like @racket[txexpr?], but raises a descriptive error if @racket[_possible-txexpr] is invalid, and otherwise returns @racket[_possible-txexpr] itself.
Predicates equivalent to a list of @code{txexpr-tag?}, @code{txexpr-attr?}, or @code{txexpr-element?}, respectively.
@ -186,63 +168,64 @@ boolean?]
(can-be-txexpr-attr-value?
[v any/c])
boolean?]
)]
Predicates for input arguments that are trivially converted to an attribute @racket[_key] or @racket[_value]…
Predicates for input arguments that can be trivially converted to an attribute @racket[_key] or @racket[_value] with the associated conversion functions.
@examples[#:eval my-eval
(can-be-txexpr-attr-key? 'symbol)
(can-be-txexpr-attr-key? "string-val")
(can-be-txexpr-attr-key? (list 1 2 3))
(can-be-txexpr-attr-value? 'symbol)
(can-be-txexpr-attr-value? "string-val")
(can-be-txexpr-attr-value? (list 1 2 3))
]
@deftogether[(
@defproc[
(->txexpr-attr-key
[v can-be-txexpr-attr-key?])
txexpr-attr-key?]
@defproc[
(->txexpr-attr-value
[v can-be-txexpr-attr-value?])
txexpr-attr-value?]
)]
…with these conversion functions.
(can-be-txexpr-attrs?
[v any/c])
boolean?]
Predicate for functions that handle @racket[_txexpr-attrs]. Covers values that are easily converted into pairs of @racket[_attr-key] and @racket[_attr-value]. Namely: single @racket[_xexpr-attr]s, lists of @racket[_xexpr-attr]s (i.e., what you get from @racket[get-attrs]), or interleaved symbols and strings (each pair will be concatenated into a single @racket[_xexpr-attr]).
Convert @racket[_x] to an HTML string. Better than @racket[xexpr->string] because consistent with the HTML spec, it will not escape text that appears within @code{script} or @code{style} blocks. For convenience, this function will take any X-expression, not just tagged X-expressions.
(txexpr
[tag txexpr-tag?]
[attrs txexpr-attrs? @empty]
[elements txexpr-elements? @empty])
txexpr?]
Assemble a @racket[_txexpr] from its parts. If you don't have attributes, but you do have elements, you'll need to pass @racket[empty] (or @racket[null] or @racket['()]) as the second argument. Note that unlike @racket[xml->xexpr], if the attribute list is empty, it's not included in the resulting expression.
Assemble a @racket[_txexpr] from its parts. If you don't have attributes, but you do have elements, you'll need to pass @racket[empty] as the second argument. Note that unlike @racket[xml->xexpr], if the attribute list is empty, it's not included in the resulting expression.
Predicate for functions that handle @racket[_txexpr-attrs]. Covers values that are easily converted into pairs of @racket[_attr-key] and @racket[_attr-value]. Namely: single @racket[_xexpr-attr]s, lists of @racket[_xexpr-attr]s (i.e., what you get from @racket[get-attrs]), or interleaved symbols and strings (each pair will be concatenated into a single @racket[_xexpr-attr]).
(txexpr->list
[tx txexpr?])
(list txexpr-tag?
txexpr-attrs?
txexpr-elements?)]
)]
Dissolve a @racket[_txexpr] into its components. @racket[txexpr->values] returns the components as multiple values; @racket[txexpr->list] returns them in a list.
Convert @racket[_attrs] to an immutable hash, and back again.
Convert @racket[_attrs] to an immutable hash, and back again. Following the convention specified for @link["https://www.w3.org/TR/xml/#attdecls"]{XML parsers}, the @italic{first} appearance of an attribute name binds the value —later attributes with the same name are ignored. If you prefer the typical @racket[hash] behavior where later values override earlier ones, set @racket[#:hash-style?] to @racket[#t].
@ -340,42 +322,36 @@ Returns @racket[#t] if the @racket[_attrs] contain a value for the given @racket
[attrs (or/c txexpr-attrs? txexpr?)]
[other-attrs (or/c txexpr-attrs? txexpr?)])
boolean?]
Returns @racket[#t] if @racket[_attrs] and @racket[_other-attrs] contain the same keys and values, @racket[#f] otherwise. The order of attributes is irrelevant.
Return @racket[#t] if @racket[_attrs] and @racket[_other-attrs] contain the same keys and values, @racket[#f] otherwise. The order of attributes is irrelevant. (If order matters to you, use good old @racket[equal?] instead.)
Given a @racket[_key], look up the corresponding @racket[_value] in the attributes of a @racket[_txexpr]. Asking for a nonexistent key produces an error.
Given a @racket[_key], return the corresponding @racket[_value] from the attributes of a @racket[_txexpr]. By default, asking for a nonexistent key produces an error. But if a value or procedure is provided as the @racket[_failure-result], evaluate and return that instead.
@examples[#:eval my-eval
(attr-ref tx 'class)
(attr-ref tx 'id)
(attr-ref tx 'nonexistent-key)
(attr-ref tx 'nonexistent-key "forty-two")
(attr-ref tx 'nonexistent-key (λ _ (* 6 7)))
]
@defproc[
(attr-ref*
[tx txexpr?]
[key can-be-txexpr-attr-key?])
(listof can-be-txexpr-attr-value?)]
Like @racket[attr-ref], but returns a recursively gathered list of all the @racket[_value]s for that key within @racket[_tx]. Asking for a nonexistent key produces @racket[null].
@examples[#:eval my-eval
(define tx '(div [[class "red"]] "Hello" (em ([class "blue"]) "world")))
(attr-ref* tx 'class)
(attr-ref* tx 'nonexistent-key)
]
@deftogether[(
@defproc[
(attr-set
@ -383,14 +359,7 @@ Like @racket[attr-ref], but returns a recursively gathered list of all the @rack
[key can-be-txexpr-attr-key?]
[value can-be-txexpr-attr-value?])
txexpr?]
Given a @racket[_txexpr], set the value of attribute @racket[_key] to @racket[_value]. Return the updated @racket[_txexpr].
@ -398,67 +367,54 @@ Given a @racket[_txexpr], set the value of attribute @racket[_key] to @racket[_v
[key can-be-txexpr-attr-key?]
[value can-be-txexpr-attr-value?] ... ... )
txexpr?]
Like @racket[attr-set], but accepts any number of keys and values.
)]
Set the value of attribute @racket[_key] to @racket[_value] in @racket[_txexpr]. Return the updated @racket[_txexpr]. Duplicate attributes, if they exist, are resolved using @racket[attr->hash]. @racket[attr-set] only accepts one key and one value; @racket[attr-set*] accepts any number.
Given a @racket[_txexpr], append the value of attribute @racket[_key] with @racket[_value]. Return the updated @racket[_txexpr].
Given a @racket[_txexpr], append attribute @racket[_key] with @racket[_value]. Return the updated @racket[_txexpr]. If @racket[_key] doesn't already exist, then add a new attribute (i.e., behave like @racket[attr-set]).
@examples[#:eval my-eval
(define tx '(div [[class "red"]] "Hello"))
(define tx '(div ((class "red")) "Hello"))
(attr-join tx 'class "small")
(attr-join tx 'klass "small")
]
@defproc[
(merge-attrs
[attrs (listof can-be-txexpr-attrs?)] ...)
txexpr-attrs?]
Combine a series of attributes into a single @racket[_txexpr-attrs] item. This function addresses three annoyances that surface in working with txexpr attributes.
@itemlist[#:style 'ordered
@item{You can pass the attributes in multiple forms. See @racket[can-be-txexpr-attrs?] for further details.}
@item{Attributes with the same name are merged, with the later value taking precedence (i.e., @racket[hash] behavior). }
@item{Attributes are sorted in alphabetical order.}]
Recursively apply @racket[_proc] to all elements, leaving tags and attributes alone. Using plain @racket[map] will only process elements at the top level of the current @racket[_txexpr]. Usually that's not what you want.
Recursively apply @racket[_proc] to all elements, leaving tags and attributes alone. Using plain @racket[map] will only process elements at the top level of @racket[_tx]. Usually that's not what you want.
@ -467,7 +423,7 @@ Recursively apply @racket[_proc] to all elements, leaving tags and attributes al
(map-elements upcaser tx)
]
In practice, most @racket[_xexpr-element]s are strings. But woe befalls those who pass string procedures to @racket[map-elements], because an @racket[_xexpr-element] can be any kind of @racket[xexpr?], and an @racket[xexpr?] is not necessarily a string.
In practice, most @racket[_txexpr-element]s are strings. But it's unwise to pass string-only procedures to @racket[map-elements], because an @racket[_txexpr-element] can be any kind of @racket[xexpr?], and an @racket[xexpr?] is not necessarily a string.
@ -476,30 +432,6 @@ In practice, most @racket[_xexpr-element]s are strings. But woe befalls those wh
(map-elements upcaser tx)
]
@defproc[
(map-elements/exclude
[proc procedure?]
[tx txexpr?]
[exclude-test (txexpr? . -> . boolean?)])
txexpr?]
Like @racket[map-elements], but skips any @racket[_txexprs] that evaluate to @racket[#t] under @racket[_exclude-test]. The @racket[_exclude-test] gets a whole txexpr as input, so it can test any of its parts.
Be careful with the wider consequences of exclusion tests. When @racket[_exclude-test] is true, the @racket[_txexpr] is excluded, but so is everything underneath that @racket[_txexpr]. In other words, there is no way to re-include (un-exclude?) elements nested under an excluded element.
@ -526,17 +458,19 @@ Ordinarily, the result of the split operation is to remove the elements that mat
@deftogether[(
@defproc[
(findf*-txexpr
(findf-txexpr
[tx txexpr?]
[pred procedure?])
(or/c #f (listof txexpr-element?))]
(or/c #f txexpr-element?)]
@defproc[
(findf-txexpr
(findf*-txexpr
[tx txexpr?]
[pred procedure?])
(or/c #f txexpr-element?)]
(or/c #f (listof txexpr-element?))]
)]
Like @racket[splitf-txexpr], but only retrieve the elements that match @racket[_pred]. @racket[findf*-txexpr] retrieves all results; @racket[findf-txexpr] only the first. In both cases, if there are no matches, you get @racket[#f].
@ -552,6 +486,25 @@ Like @racket[splitf-txexpr], but only retrieve the elements that match @racket[_
]
@section{HTML conversion}
@defproc[
(xexpr->html
[x xexpr?])
string?]
Convert @racket[_x] to an HTML string. Better than @racket[xexpr->string] because consistent with the HTML spec, it will skip the content of @code{script} or @code{style} blocks. For convenience, this function will take any X-expression, not just tagged X-expressions.