cssx

package
v1.0.0-rc.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 1, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package cssx compiles and validates CSSX expressions for HTML drivers.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CollectCallArgs

func CollectCallArgs(step cssx.Op) ([]any, error)

func Compile

func Compile(input string) (cssx.Pipeline, error)

func ValidateCallArgs

func ValidateCallArgs(exp Expression, step cssx.Op) error

Types

type CompiledOp

type CompiledOp struct {
	Kind     OpKind `json:"kind"`
	Selector string `json:"selector,omitempty"`
	Name     string `json:"name,omitempty"`
	Args     []any  `json:"args,omitempty"`
	Arity    int    `json:"arity,omitempty"`
	Index    int    `json:"index"`
}

func CompileOps

func CompileOps(input string) ([]CompiledOp, error)

func CompilePipeline

func CompilePipeline(pipeline cssxpipeline.Pipeline) ([]CompiledOp, error)

type Expression

type Expression string

Expression is a cssx "extended CSS" operation.

In regular CSS, a selector string (e.g. ".section .item") can only select nodes. cssx extends CSS with Ferret-style pseudo functions (called "expressions") that can be nested and combined to post-process selection results.

Expressions are written inside a cssx string using a leading ':' name and optional arguments:

:first(h1)                 // select nodes with CSS, then take first
:text(:first(h1))          // take first <h1>, then read its text
:count(.product)           // count matching nodes
:attr("href", :first(a))   // take first <a>, read href attribute

Expressions are NOT CSS pseudo-classes like ":nth-child(2)". They are an extension layer evaluated by Ferret (e.g. compiled into a pipeline and executed in the browser via JS or in an in-memory DOM engine).

Usage examples (Ferret-style):

LET nodes = el[~ css`:take(5)`]                    // -> list of nodes
LET title = el[~ css`:text(:first(h1))`]           // -> string
LET n = el[~ css`:count(.product)`]                // -> number
LET href = el[~ css`:attr("href", :first(a.cta))`] // -> string|null

Note: expressions can return different kinds of values (node lists, a single node, scalars). Node-returning expressions are suitable for "query" operations; scalar- returning expressions require "eval" (or equivalent) depending on your runtime API.

var (
	// ExpressionFirst selects the first node from a node set, or the first node matching
	// a selector under the current root.
	//
	// Examples:
	//   :first(.item)          -> node|null
	//   :text(:first(h1))      -> string|null
	ExpressionFirst Expression = ":first"

	// ExpressionLast selects the last node from a node set (or last match for selector).
	//
	// Example:
	//   :last(.item)           -> node|null
	ExpressionLast Expression = ":last"

	// ExpressionNth selects the nth node from a node set (indexing convention is up to you:
	// 0-based or 1-based - document it and keep it consistent).
	//
	// Examples:
	//   :nth(2, .item)         -> node|null
	//   :text(:nth(0, h2))     -> string|null
	ExpressionNth Expression = ":nth"

	// ExpressionTake keeps only the first N nodes from a node set.
	//
	// Example:
	//   :take(5)               -> nodes (from the current node set)
	//   :take(5, .item)        -> nodes (first 5 matches of .item)
	ExpressionTake Expression = ":take"

	// ExpressionSkip skips the first N nodes from a node set.
	//
	// Example:
	//   :skip(10, .item)       -> nodes
	ExpressionSkip Expression = ":skip"

	// ExpressionSlice selects a subrange from a node set (offset, limit).
	//
	// Example:
	//   :slice(10, 5, .item)   -> nodes (items 10..14)
	ExpressionSlice Expression = ":slice"

	// ExpressionWithin evaluates a selector/expression within a scoped subtree.
	// Use this to avoid ambiguity when composing queries across multiple sections.
	//
	// Example:
	//   :within(.product, :text(:first(h2))) -> string|null
	ExpressionWithin Expression = ":within"

	// ExpressionParent returns the parent of the first node in the set.
	//
	// Example:
	//   :parent(:first(.item)) -> node|null
	ExpressionParent Expression = ":parent"

	// ExpressionClosest finds the closest ancestor (including self depending on your semantics)
	// matching a selector, starting from the first node in the set.
	//
	// Example:
	//   :closest(.card, :first(.title)) -> node|null
	ExpressionClosest Expression = ":closest"

	// ExpressionChildren returns children of the first node (optionally filtered by selector).
	//
	// Example:
	//   :children(li, :first(ul.menu)) -> nodes
	ExpressionChildren Expression = ":children"

	// ExpressionNext returns the next sibling of the first node (optionally filtered by selector).
	//
	// Example:
	//   :next(.value, :first(.label)) -> node|null
	ExpressionNext Expression = ":next"

	// ExpressionPrev returns the previous sibling of the first node (optionally filtered by selector).
	//
	// Example:
	//   :prev(.label, :first(.value)) -> node|null
	ExpressionPrev Expression = ":prev"

	// ExpressionExists returns true if the selector/expression yields at least one node.
	//
	// Example:
	//   :exists(form#captcha)  -> bool
	ExpressionExists Expression = ":exists"

	// ExpressionEmpty returns true if the selector/expression yields zero nodes.
	//
	// Example:
	//   :empty(.results)       -> bool
	ExpressionEmpty Expression = ":empty"

	// ExpressionHas returns true if the first node in the set has a descendant matching selector.
	//
	// Example:
	//   :has(.price, :first(.product)) -> bool
	ExpressionHas Expression = ":has"

	// ExpressionMatches returns true if the first node in the set matches the selector.
	//
	// Example:
	//   :matches(.active, :first(li)) -> bool
	ExpressionMatches Expression = ":matches"

	// ExpressionCount returns the number of nodes produced by selector/expression.
	//
	// Examples:
	//   :count(.item)          -> number
	//   :count(:take(5, .item))-> number (5)
	ExpressionCount Expression = ":count"

	// ExpressionIndexOf returns index of a target node within a node set (optional / advanced).
	//
	// Example:
	//   :indexOf(.item, :first(.selected)) -> number
	ExpressionIndexOf Expression = ":indexOf"

	// ExpressionLen returns length of a list or string (optional convenience alias).
	//
	// Examples:
	//   :len(:texts(.tag))     -> number
	//   :len(:text(h1))        -> number
	ExpressionLen Expression = ":len"

	// ExpressionText extracts textContent from the first node of selector/expression.
	//
	// Example:
	//   :text(:first(h1))      -> string|null
	ExpressionText Expression = ":text"

	// ExpressionTexts extracts textContent from all nodes of selector/expression.
	//
	// Example:
	//   :texts(.tag)           -> []string
	ExpressionTexts Expression = ":texts"

	// ExpressionOwnText extracts only direct text nodes (no descendant text).
	//
	// Example:
	//   :ownText(.price)       -> string|null
	ExpressionOwnText Expression = ":ownText"

	// ExpressionNormalize normalizes whitespace (trim + collapse internal whitespace).
	//
	// Example:
	//   :normalize(:text(h1))  -> string
	ExpressionNormalize Expression = ":normalize"

	// ExpressionTrim trims leading/trailing whitespace.
	//
	// Example:
	//   :trim(:text(h1))       -> string
	ExpressionTrim Expression = ":trim"

	// ExpressionJoin joins a list of strings using a separator.
	//
	// Example:
	//   :join(", ", :texts(.tag)) -> string
	ExpressionJoin Expression = ":join"

	// ExpressionAttr reads an attribute from the first node.
	//
	// Example:
	//   :attr("href", :first(a)) -> string|null
	ExpressionAttr Expression = ":attr"

	// ExpressionAttrs reads an attribute from all nodes.
	//
	// Example:
	//   :attrs("href", a)      -> []string
	ExpressionAttrs Expression = ":attrs"

	// ExpressionProp reads a DOM property from the first node (value, checked, etc.).
	//
	// Example:
	//   :prop("value", :first(input[name="q"])) -> any
	ExpressionProp Expression = ":prop"

	// ExpressionHTML returns innerHTML of the first node.
	//
	// Example:
	//   :html(:first(.content)) -> string|null
	ExpressionHTML Expression = ":html"

	// ExpressionOuterHTML returns outerHTML of the first node.
	//
	// Example:
	//   :outerHtml(:first(.content)) -> string|null
	ExpressionOuterHTML Expression = ":outerHtml"

	// ExpressionValue is a convenience for form-field value (often maps to property "value").
	//
	// Example:
	//   :value(:first(input[name="q"])) -> string|null
	ExpressionValue Expression = ":value"

	// ExpressionAbsURL resolves a relative URL (usually from href/src) into an absolute URL,
	// using document.baseURI / page URL (execution-time detail).
	//
	// Example:
	//   :absUrl(:attr("href", :first(a))) -> string|null
	ExpressionAbsURL Expression = ":absUrl"

	// ExpressionURL reads a URL from a named attribute and resolves it to absolute.
	//
	// Example:
	//   :url("href", :first(a)) -> string|null
	ExpressionURL Expression = ":url"

	// ExpressionParseURL parses a URL string into a structured object (optional).
	//
	// Example:
	//   :parseUrl(:url("href", :first(a))) -> object
	ExpressionParseURL Expression = ":parseUrl"

	// ExpressionFilter filters a node set (advanced; exact predicate language is up to your runtime).
	//
	// Example (one possible meaning):
	//   :filter(:has(.price), .product) -> nodes
	ExpressionFilter Expression = ":filter"

	// ExpressionWithAttr keeps nodes that have a given attribute.
	//
	// Example:
	//   :withAttr("href", a)   -> nodes
	ExpressionWithAttr Expression = ":withAttr"

	// ExpressionWithText keeps nodes whose text matches a substring/regex (advanced).
	//
	// Example:
	//   :withText("Sale", .product) -> nodes
	ExpressionWithText Expression = ":withText"

	// ExpressionDedupeByAttr deduplicates a node set by attribute value.
	//
	// Example:
	//   :dedupeByAttr("href", a) -> nodes
	ExpressionDedupeByAttr Expression = ":dedupeByAttr"

	// ExpressionDedupeByText deduplicates a node set by normalized text.
	//
	// Example:
	//   :dedupeByText(.tag)    -> nodes
	ExpressionDedupeByText Expression = ":dedupeByText"

	// ExpressionReplace replaces text using a pattern (regex or substring; define at runtime).
	//
	// Example:
	//   :replace("\\s+", " ", :text(h1)) -> string
	ExpressionReplace Expression = ":replace"

	// ExpressionRegex extracts a regex match/group from a string.
	//
	// Example:
	//   :regex("(\\d+(?:\\.\\d+)?)", 1, :text(.price)) -> string|null
	ExpressionRegex Expression = ":regex"

	// ExpressionToNumber converts a string to number (after cleanup).
	//
	// Example:
	//   :toNumber(:regex("(\\d+)", 1, :text(.price))) -> number|null
	ExpressionToNumber Expression = ":toNumber"

	// ExpressionToDate converts a string to date/time using a layout/hint (optional).
	//
	// Example:
	//   :toDate("2006-01-02", :text(time)) -> date|null
	ExpressionToDate Expression = ":toDate"
)

func ResolveSelector

func ResolveSelector(selector string) (Expression, error)

type OpKind

type OpKind string
const (
	OpSelect OpKind = "select"
	OpCall   OpKind = "call"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL