List

A list is an ordered collection of zero or more values. Lists are the primary result type for CSS selector expressions and the foundation for most data-processing work in Sessel.

Literal syntax

List literals are written with square brackets. Elements are separated by commas. A trailing comma is allowed.

[]
[1, 2, 3]
["a", "b", "c"]
[true, false, null]

Lists can contain any type, including nested lists and dictionaries:

[[1, 2], [3, 4]]
[{ name: "Widget", price: 42 }, { name: "Gadget", price: 15 }]

Selectors return lists

A CSS selector expression always returns a list, even when only one element matches:

${h1}           // list of all <h1> elements
${div.item}     // list of all elements matching div.item
${#title}       // list — always, even though IDs are unique

Use .first(), .last(), or .at(n) to extract a single element from the list.

Access methods

count()

Returns the number of elements in the list.

${div.item}.count()

Returns: number


isEmpty()

Returns true if the list has zero elements.

[].isEmpty()
// true
${div.item}.isEmpty()

Returns: boolean


first()

Returns the first element in the list, or null if the list is empty.

${h1}.first()

Returns: element or null


last()

Returns the last element in the list, or null if the list is empty.

${p}.last()

Returns: element or null


at(n)

Returns the element at position n (0-indexed), or null if n is out of range. Negative indices count from the end: -1 is the last element, -2 is the second-to-last, and so on.

${li}.at(2)
${li}.at(-1)     // last element
${li}.at(-2)     // second-to-last element

Returns: element or null


slice(start[, end])

Returns a sub-list from position start up to (but not including) position end. If end is omitted, slices to the end of the list. Negative indices count from the end: -1 refers to the last position, -2 to the second-to-last, and so on.

${li}.slice(0, 5)
${li}.slice(2)        // from position 2 to end
${li}.slice(-3, -1)   // third-to-last through second-to-last
${li}.slice(-2)       // last two elements

Returns: list

Aggregation methods

sum()

Sums the values of all elements in the list, parsing each element's value as a number. Non-numeric values are skipped.

${[itemprop="price"]}.sum()

Returns: number


min()

Returns the smallest numeric value among the elements. Non-numeric values are skipped. Returns null if no numeric values are present.

${[itemprop="price"]}.min()

Returns: number or null


max()

Returns the largest numeric value among the elements. Non-numeric values are skipped. Returns null if no numeric values are present.

${[itemprop="price"]}.max()

Returns: number or null

Transformation methods

filter(callback)

Returns a new list containing only the elements for which the callback is true. The callback can accept up to three parameters:

${li}.filter(el => el.text().Integer() > 10)
${[itemprop="price"]}.filter(el => el.value().Number() > 50)

With index and list parameters:

${li}.filter((el, i) => i < 5)
${li}.filter((el, i, list) => i < list.count() / 2)

Returns: list


reject(callback)

The inverse of filter(): returns a new list containing only the elements for which the callback is false. The callback can accept up to three parameters (element, index, list).

${li}.reject(el => el.text() == "")
${li}.reject((el, i) => i < 5)

Returns: list


find(callback)

Returns the first element for which the callback is true, or null if no element matches. The callback can accept up to three parameters (element, index, list).

${li}.find(el => el.text() == "Widget")
${[itemprop="price"]}.find(el => el.value().Number() > 100)

Returns: the matching element, or null


map(callback)

Returns a new list formed by evaluating the callback for each element. The callback can accept up to three parameters:

${[itemprop="item"]}.map(e => e.value())
${li}.map(el => el.text().Integer())

With an index parameter:

${li}.map((el, i) => i.String() + ". " + el.text())

The expression can produce any value type, including dictionaries:

${[itemprop="product"]}.map(p => {
    name: p.text(),
    price: p.attr("data-price")
})

Returns: list


each(callback)

Runs the callback once for each element, for side effects, and returns the original list unchanged. The callback can accept up to three parameters (element, index, list), like the other lambda-taking methods.

@schema Pagelove url("https://pagelove.org/1.0");
${li}.each(el => Pagelove.PUT(el, "/log/" + el.attr("id") + ".html"))

Returns: the original list


takeWhile(callback)

Returns a new list of the leading elements for which the callback is true, stopping at the first element where it's false. The callback can accept up to three parameters (element, index, list).

[1, 2, 3, 10, 4].takeWhile(n => n < 5)
// [1, 2, 3]

Returns: list


dropWhile(callback)

Returns a new list with the leading elements for which the callback is true removed, keeping everything from the first false result onward. The callback can accept up to three parameters (element, index, list).

[1, 2, 3, 10, 4].dropWhile(n => n < 5)
// [10, 4]

Returns: list

Validation methods

all(callback)

Returns true if the callback is true for every element in the list. Returns true for an empty list. The callback can accept up to three parameters (element, index, list).

${div.item}.all(el => el.text().count() > 0)
${li}.all((el, i) => i == 0 || el.text() != "")

Returns: boolean


any(callback)

Returns true if the callback is true for at least one element in the list. Returns false for an empty list. The callback can accept up to three parameters (element, index, list).

${[itemprop="price"]}.any(el => el.value().Number() > 100)

Returns: boolean


unique()

Removes duplicate values (compared by text representation) and returns a new list. Preserves first-occurrence order.

[1, 2, 2, 3].unique()
// [1, 2, 3]
${[itemprop="category"]}.unique()

Returns: list


subset(other)

Returns true if every value in this list (compared as text) also appears in the other list. Returns true if this list is empty.

(${[itemprop="tag"]} from self).subset(${[itemprop="allowed-tag"]} from "/config/tags")

Returns: boolean


disjoint(other)

Returns true if this list and the other list share no common values (compared as text). Returns true if either list is empty.

(${[itemprop="tag"]} from self).disjoint(${[itemprop="tag"]} from "/published/*")

Returns: boolean


contains(value)

Returns true if the list contains the given value. Comparison uses text representation, consistent with unique(), subset(), and disjoint().

["admin", "editor", "viewer"].contains("admin")
// true

Returns: boolean

Ordering methods

sort(callback)

Returns a new list sorted according to the callback. The sort is stable. The callback operates in one of two modes:

Key-extraction mode (1 parameter) -- the callback returns a sort key for each element:

${[itemprop="product"]}.sort(el => el.attr("data-price").Number())
${li}.sort(el => el.text())

Comparator mode (2 parameters) -- the callback receives two elements and returns a negative number, zero, or positive number. The spaceship operator <=> is the idiomatic way to produce this:

${li}.sort((a, b) => a.text() <=> b.text())

Comparator mode enables multi-field sorting:

${[itemprop="product"]}.sort((a, b) => {
  let cat = a.attr("data-category") <=> b.attr("data-category");
  cat != 0 ? cat : a.attr("data-price").Number() <=> b.attr("data-price").Number()
})

Returns: list


reverse()

Returns a new list with the elements in reverse order.

${li}.reverse()
${[itemprop="product"]}.sort(el => el.attr("data-price").Number()).reverse()

Returns: list


flatten()

Recursively flattens nested lists into a single list.

[[1, 2], [3, [4, 5]]].flatten()
// [1, 2, 3, 4, 5]

Useful when map() produces nested lists:

${section}.map(s => ${li} from s).flatten()

Sub-select syntax provides a more concise alternative:

${section}.map(s => s.${ li }).flatten()

Returns: list

String conversion

join(delimiter)

Joins the values in the list into a single string, separated by the given delimiter. Each value is converted to its string representation before joining.

["a", "b", "c"].join(", ")
// "a, b, c"
${[itemprop="tag"]}.map(el => el.text()).join("; ")

Returns: string

Reduction methods

reduce(initial, callback)

Reduces the list to a single value by applying the callback to an accumulator and each element, left to right. The callback receives two parameters: the accumulator and the current element.

[1, 2, 3, 4].reduce(0, (acc, el) => acc + el)
// 10
${li}.reduce("", (acc, el) => acc + el.text() + "\n")

If the list is empty, the initial value is returned unchanged.

The JavaScript argument order reduce(callback, initial) is also accepted, so both of these are equivalent:

[1, 2, 3, 4].reduce(0, (acc, el) => acc + el)   // initial first
[1, 2, 3, 4].reduce((acc, el) => acc + el, 0)   // callback first (JS order)

The callback may also be given alone — reduce((acc, el) => acc + el) — in which case the first element seeds the accumulator (and an empty list is an error).

Returns: any type (determined by the callback and initial value)


reduceRight(initial, callback)

Like reduce(), but processes elements from right to left (last to first).

["a", "b", "c"].reduceRight("", (acc, el) => acc + el)
// "cba"

If the list is empty, the initial value is returned unchanged.

Returns: any type (determined by the callback and initial value)

See also