lQuery is a DOM manipulation library written in Common Lisp, inspired by and based on the jQuery syntax and functions. It uses Plump and CLSS as DOM and selector engines. The main idea behind lQuery is to provide a simple interface for crawling and modifying HTML sites, as well as to allow for an alternative approach to templating.
Load lQuery with ASDF or Quicklisp.
(ql:quickload :lquery)
First, lQuery needs to be initialized with a document to work on:
(lquery:$ (initialize #p"/path/to/input.html"))
After that, you can use the $ function to select and manipulate the DOM:
(lquery:$ "article")
(lquery:$ "article"
(add-class "fancy")
(attr "foo" "bar"))
To render the HTML to a string use serialize. If you want to save it to a file directly, there's also write-to-file.
(lquery:$ (serialize))
(lquery:$ (write-to-file #p"/path/to/output.html"))
So a quick file manipulation could look something like this:
(lquery:$ (initialize #p"/path/to/input.html")
"article"
(append-to "content")
(add-class "foo")
(write-to-file #p"/path/to/output.html"))
Aside from using selectors as the first step, it's also possible to use any other variable or list and operate on it.
2.0: Literal function calls need to be added with INLINE.
Note that the result of the inline function will be used as if literally put in place. For example, an inlined function that evaluates
to a string will result in a CSS-select.
(lquery:$ (inline (list node-a node-b))
"article"
(serialize))
Selectors can come at any point in the sequence of lQuery operations and will always act on the current set of elements.
If an operation evaluates to a list, array, vector or a single node, the current set of elements is set to this result.
(lquery:$ "a"
(text "Link")
(inline (lquery:$ "p"))
(text "Paragraph"))
This is equivalent to the following:
(lquery:$ "a"
(text "Link"))
(lquery:$ "p"
(text "Paragraph"))
Functions in the argument list will be translated to a function invocation with the current list of elements as their argument.
(lquery:$ "a"
#'(lambda (els) (aref els 0)))
lQuery2.0 also supports compile-time evaluation of forms, whose results are then put in place of their function calls:
(lquery:$ (eval (format NIL "~a" *selector*)))
Keep in mind that the lexical environment is not the same at compile-time as at run-time.
Often times you'll also want to retrieve multiple, different values from your current set of nodes. To make this more convenient, you can use the COMBINE form:
(lquery:$ "a"
(combine (attr :href) (text))
(map-apply #'(lambda (url text) (format T "[~a](~a)" text url))))
lQuery uses vectors internally to modify and handle sets of nodes. These vectors are usually modified instead of copied to avoid unnecessary resource allocation. This however also means that lQuery functions are possibly side-effecting. If you pass an adjustable vector into lQuery through INLINE or similar, it will not be copied and therefore side-effects might occur. lQuery will automatically copy everything else that isn't an adjustable vector through ENSURE-PROPER-VECTOR. If you do want to pass in an adjustable vector, but make sure it doesn't affect it, use COPY-PROPER-VECTOR.
To ensure that functions are at least somewhat stable in their behaviour, lQuery includes a test suite. You can load this through Quicklisp/ASDF with
(ql:quickload :lquery-test)
Running the suit is simply a matter of invoking RUN
(lquery-test:run)
The tests are rather loose, but should cover all functions to at least behave mostly according to expectation.
lQuery allows extension in a couple of ways. The most important of which are node functions themselves, which come in two flavours: lquery-funs and lquery-list-funs.
Any lquery function resides in the package LQUERY-FUNCS, which is automatically scanned by the $ macro. The two macros responsible for defining new lquery functions automatically
place the resulting operations in this package for you.
(define-lquery-function name (node-name &rest arguments) &body body)
(define-lquery-list-function name (vector-name &rest arguments) &body body)
Any function generated by these macros can be called either with a single node or a vector of nodes. In the case of a regular node operation, if it receives a vector of nodes, the
function is called once for each node and the results are collected into a vector, which is then returned. If it receives a single node, only a single result is returned. In the
case of a node list function, the return value can be either a vector or a single value, depending on what the goal of the operation is. It is expected that node list functions will
modify the given vector directly in order to avoid unnecessary copying.
Some constructs would be very cumbersome to write as functions, or would simply be more suited in the form of a macro. To allow for this, lQuery3.1 includes a mechanism of $ local macros. The previously mentioned forms like INLINE are handled through this system. Just like lquery functions, lquery macros reside in their own package LQUERY-MACROS. The responsible macro for defining new lquery macros will automatically place it in there for you.
(define-lquery-macro name (previous-form &rest arguments) &body body)
The $ macro itself can be extended as well by providing additional argument- or value-handlers. The following two macros make this possible:
(define-argument-handler type (argument-name operator-name) &body body)
Argument handlers transform the arguments at compile-time. For example, this would allow an extension to turn literal arrays into lists so they can be processed as well.
(define-value-handler type (variable-name operator-name) &body body)
Value handlers on the other hand determine the action at run-time. This is mostly useful for defining special actions on certain variable values.
3.1.0
Renamed DEFINE-NODE-* macros into more sensible DEFINE-LQUERY-*.
Added macro system, new standard COMBINE macro.
3.0.0
Complete rewrite of everything. This version is compatibility breaking. While the node functions themselves perform just the same as before (with one or two exceptions), lQuery now uses vectors instead of lists internally. If you ever relied on lQuery return values, this version will most likely break your code. Effort has been made to keep upgrading as simple as possible though; passing lists into an lQuery chain automatically transforms it for example.
Thanks to the change to Plump, lQuery is now also able to parse almost any kind of X/HT/ML document, which was not well possible previously. And thanks to switching to CLSS, lQuery is now much faster at selecting nodes from the DOM.
2.0.0
Added extension system and INLINE, EVAL handling. Revamped base macros to be more stable and simple.
lQuery is a sub-project of TyNETv5 ("Radiance"), licensed under the Artistic License 2.0 and ©2013 TymoonNET/NexT, Nicolas Hafner.
This library can be obtained via git on https://github.com/Shinmera/lquery.git. For questions, patches or suggestions, please contact me via email or write a github issue.
lQuery Package Function Index
-
EXTERNAL MACRO
(&BODY ACTIONS)
Performs lQuery operations on the current document.
Each argument is executed in sequence. The arguments are evaluated according to the defined argument-handlers. By default, the following cases are handled:
* STRING Translates to a CLSS:QUERY on the current elements.
* FUNCTION Translates to a function call with the list of nodes as argument.
* SYMBOL Delegates to the value handlers.
* LIST Lists are transformed according to their first element, which must be a symbol. If the symbol's name corresponds to a function found in the LQUERY-MACROS package, The form is assembled according to that function. Otherwise if it corresponds to an LQUERY-FUNCS function, it is expanded into a call to that function. If the symbol cannot be found in either package, it is put back in place, but the call itself is assembled like so: (FUNCTION PREVIOUS-RESULT ARGUMENT*)
Values are handled at runtime according to the defined variable-handlers. By default, the following cases are handled at run time:
* STRING Performs a CLSS:QUERY on the current elements.
* DOM:NODE Replaces the current set of nodes with just this node.
* FUNCTION Calls the given function with the current set of nodes as argument.
* LIST Lists are transformed into a proper vector.
* ARRAY Arrays are transformed into a proper vector.
* VECTOR Vectors that are not adjustable are transformed into a proper vector.
* T Any other value simply replaces the current list of nodes.
-
The master document used at the beginning of a chain.
-
EXTERNAL GENERIC
(SEQUENCE &KEY TRANSFORM (TRANSFORM #'IDENTITY))
Copies the sequence into a new proper vector.
-
EXTERNAL MACRO
(TYPE (ARGUMENT-NAME OPERATOR-NAME) &BODY BODY)
Defines a new argument handler that decides what to do with a certain type of argument at compile-time.
TYPE --- A type or EQL specifier.
ARGUMENT-NAME --- Symbol bound to the argument.
OPERATOR-NAME --- Symbol bound to the object being operated on.
BODY ::= form*
-
EXTERNAL MACRO
(NAME (NODE-NAME &REST ARGUMENTS) &BODY BODY)
Defines a new node function. This is the main mechanism by which node manipulations are defined.
All lquery functions are automatically created in the lquery-funcs package.
NAME --- A symbol naming the lquery function. Automatically interned in the LQUERY-FUNCS package.
NODE-NAME --- Symbol bound to the current node.
ARGUMENTS --- A lambda-list specifying the arguments for the function.
BODY ::= form*
-
EXTERNAL MACRO
(NAME (VECTOR-NAME &REST ARGUMENTS) &BODY BODY)
Defines a new function that operates on the current node array instead of individual elements.
All lquery functions are automatically created in the lquery-funcs package.
NAME --- A symbol naming the lquery function. Automatically interned in the LQUERY-FUNCS package.
VECTOR-NAME --- Symbol bound to the node vector.
ARGUMENTS --- A lambda-list specifying the arguments for the function.
BODY ::= form*
-
EXTERNAL MACRO
(NAME (PREVIOUS-FORM &REST ARGUMENTS) &BODY BODY)
Define a new lquery local macro.
All lquery macros are automatically created in the lquery-macros package.
NAME --- A symbol naming the lquery macro. Automatically interned in the LQUERY-MACROS package.
PREVIOUS-FORM --- Symbol bound to the so far assembled form, the previous value so to speak.
ARGUMENTS --- A lambda-list specifying the arguments for the macro (note that this must be a standard lambda-list).
BODY ::= form*
-
EXTERNAL MACRO
(TYPE (VARIABLE-NAME OPERATOR-NAME) &BODY BODY)
Defines a new symbol handler that decides what to do with a certain type of symbol at run-time (variable type).
TYPE --- A type or EQL specifier.
VARIABLE-NAME --- Symbol bound to the argument.
OPERATOR-NAME --- Symbol bound to the object being operated on.
BODY ::= form*
-
Ensure that the variable is a proper vector.
-
EXTERNAL FUNCTION
(DOCUMENT)
Sets the *lquery-master-document* variable to the provided document.
-
EXTERNAL FUNCTION
(FILE-OR-STRING)
Load the given file or string into a HTML DOM.
-
EXTERNAL FUNCTION
(&KEY (SIZE 0) INITIAL-ELEMENT INITIAL-CONTENTS (FILL-POINTER T))
Creates a new proper vector.
-
Build the given string into DOM objects related to the master document.
lQuery-Macros Package Function Index
-
EXTERNAL FUNCTION
(NODES &REST CALLS)
COMBINES multiple lquery function calls into one by gathering them into a list for each element.
($ (combine (text) (attr :a))) would be equivalent to
($ (map #'(lambda (node) (list (lquery-funcs:text node) (lquery-funcs:attr node :a)))))
This construct is especially useful in combination with MAP-APPLY.
-
EXTERNAL FUNCTION
(NODES FORM)
Evaluates the form at compile-time and puts its resulting value in place.
-
EXTERNAL FUNCTION
(NODES NAME)
Macro to allow #'foo to be used in lquery chains.
-
EXTERNAL FUNCTION
(NODES &REST INIT-CALLS)
See lquery function INITIALIZE.
This is merely a performance macro to avoid the unnecessary default allocation of a vector.
-
EXTERNAL FUNCTION
(NODES FORM)
Treats the form as if the evaluated value was put literally in place.
See DETERMINE-VALUE.
lQuery-Funcs Package Function Index
-
EXTERNAL FUNCTION
(WORKING-NODES SELECTOR-OR-NODES)
Add elements to the set of matched elements.
-
EXTERNAL FUNCTION
(NODE &REST CLASSES)
Adds the specified class(es) to the set of matched elements.
-
EXTERNAL FUNCTION
(NODES HTML-OR-NODES)
Insert content (in html-string or node-list form) after each element.
-
EXTERNAL FUNCTION
(WORKING-NODES)
Find the common ancestor of all elements.
-
EXTERNAL FUNCTION
(NODES HTML-OR-NODES)
Insert content (in html-string or node-list form) to the end of each element.
-
EXTERNAL FUNCTION
(WORKING-NODES SELECTOR-OR-NODES)
Insert every element to the end of the target(s).
-
EXTERNAL FUNCTION
(NODE &REST PAIRS)
Retrieve or set attributes on a node
-
EXTERNAL FUNCTION
(NODES HTML-OR-NODES)
Insert content (in html-string or node-list form) before each element.
-
Returns the index of the element within its parent, also counting text nodes. See index() otherwise.
-
EXTERNAL FUNCTION
(NODES &OPTIONAL SELECTOR)
Get the children of each element, optionally filtered by a selector.
-
Create a deep copy of the set of matched elements.
-
EXTERNAL FUNCTION
(NODE SELECTOR)
For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
If no matching element can be found the root is entered instead.
-
EXTERNAL FUNCTION
(NODES STRING)
Select all elements that contain the specified text.
-
EXTERNAL FUNCTION
(NODES)
Get the children of each element, including text and comment nodes.
-
EXTERNAL FUNCTION
(NODE &REST PAIRS)
Retrieve or set css style attributes on a node.
-
EXTERNAL FUNCTION
(NODE &REST PAIRS)
Retrieve or set data attributes on a node. This is a convenience method and uses attr in the back.
-
Returns the innermost (left-bound) child element.
-
EXTERNAL FUNCTION
(NODES FUN &KEY REPLACE)
Execute the specified function on each element until NIL is returned or all elements have been processed. The original set of elements is returned if replace is NIL.
-
Remove all child nodes from the set of matched elements.
-
Check if the node contains no children and/or only empty (whitespace) text nodes. If it is empty, T is returned, otherwise NIL.
-
EXTERNAL FUNCTION
(WORKING-NODES INDEX)
Reduce the set of matched elements to the one at the specified index
-
EXTERNAL FUNCTION
(WORKING-NODES)
Selects even elements, 1-indexed
-
EXTERNAL FUNCTION
(NODES SELECTOR-OR-FUNCTION)
Reduce the set of matched elements to those that match the selector or pass the function's test.
-
EXTERNAL FUNCTION
(NODES SELECTOR-OR-FUNCTION &KEY (TEST-SELF NIL))
Get the descendants of each element filtered by selector or function.
-
EXTERNAL FUNCTION
(WORKING-NODES)
Reduce the set of matched elements to the first in the set.
-
EXTERNAL FUNCTION
(WORKING-NODES INDEX)
Select all elements at a greater than index(0) within the matched set.
-
EXTERNAL FUNCTION
(NODES SELECTOR-OR-NODES)
Reduce the set of matched elements to those that have a descendant that matches the selector or element.
-
EXTERNAL FUNCTION
(WORKING-NODES CLASS)
Determine whether any of the matched elements are assigned to the given class.
-
EXTERNAL FUNCTION
(WORKING-NODES)
Hide the matched elements (short for (css "display" "none")).
-
EXTERNAL FUNCTION
(NODE &OPTIONAL NEW-CONTENT)
Get the HTML contents of the elements or set the HTML contents of every matched element.
-
EXTERNAL FUNCTION
(WORKING-NODES PATHNAME)
Read an HTML file and insert its contents into each element.
-
Find the index of the node within its parent.
-
EXTERNAL FUNCTION
(WORKING-NODES DOCUMENT)
Re-initializes lQuery with a new page.
-
EXTERNAL FUNCTION
(WORKING-NODES SELECTOR-OR-NODES)
Insert every element after the target.
-
EXTERNAL FUNCTION
(WORKING-NODES SELECTOR-OR-NODES)
Insert every element before the target.
-
EXTERNAL FUNCTION
(WORKING-NODES SELECTOR-OR-NODES)
Check the current elements against a selector or list of elements and return true if at least one of them matches.
-
Check if the node contains no children and/or only empty (whitespace) text nodes. If it is empty, T is returned, otherwise NIL.
Alias of EMPTY-P
-
EXTERNAL FUNCTION
(WORKING-NODES)
Reduce the set of matched elements to the final one in the set.
-
EXTERNAL FUNCTION
(WORKING-NODES)
Returns the number of elements in the list.
-
EXTERNAL FUNCTION
(WORKING-NODES INDEX)
Select all elements at an index less than the index within the matched set.
-
EXTERNAL FUNCTION
(WORKING-NODES FUNCTION)
Pass each element through a function (which has to accept one argument, the node), returning the list of all results.
-
EXTERNAL FUNCTION
(WORKING-NODES FUNCTION)
Pass each element through a function by apply, returning the vector of all results.
This is commonly useful in combination with COMBINE.
-
EXTERNAL FUNCTION
(NODES &OPTIONAL SELECTOR)
Get the immediately following sibling of each element (if there is one). If a selector is provided, the sibling is only included if it matches.
-
EXTERNAL FUNCTION
(NODES &OPTIONAL SELECTOR)
Get all following siblings of each element. If a selector is provided, the sibling is only included if it matches.
-
EXTERNAL FUNCTION
(NODES SELECTOR-OR-NODES)
Get all following silings of each element up to (excluding) the element matched by the selector or node list.
-
EXTERNAL FUNCTION
(WORKING-NODES &OPTIONAL (N 0))
Return the specified node (default first) directly, without encompassing it into a list.
-
EXTERNAL FUNCTION
(WORKING-NODES SELECTOR-OR-NODES)
Remove matching elements from the working elements.
-
Check if the node contains no children and/or only empty (whitespace) text nodes. If the node is effectively empty NIL is returned, otherwise T
-
EXTERNAL FUNCTION
(WORKING-NODES)
Select all odd elements from the current set, 1-indexed.
-
EXTERNAL FUNCTION
(NODES &OPTIONAL SELECTOR)
Get the parent of each element, optionally filtered by a selector.
-
EXTERNAL FUNCTION
(NODES &OPTIONAL SELECTOR)
Get the ancestors of each element, optionally filtered by a selector. Closest parent first.
-
EXTERNAL FUNCTION
(NODES SELECTOR-OR-NODES)
Get the ancestors of each element, up to (excluding) the element matched by the selector or node list. Closest parent first
-
EXTERNAL FUNCTION
(NODES HTML-OR-NODES)
Insert content, specified by the parameter, to the beginning of each element.
-
EXTERNAL FUNCTION
(WORKING-NODES SELECTOR-OR-NODES)
Insert every element to the beginning of the target(s).
-
EXTERNAL FUNCTION
(NODES &OPTIONAL SELECTOR)
Get the immediately preceding sibling of each element (if there is one). If a selector is provided, the sibling is only included if it matches.
-
EXTERNAL FUNCTION
(NODES &OPTIONAL SELECTOR)
Get all preceeding siblings of each element. If a selector is provided, the sibling is only included if it matches.
-
EXTERNAL FUNCTION
(NODES SELECTOR-OR-NODES)
Get all preceeding silings of each element down to (excluding) the element matched by the selector or node list.
-
EXTERNAL FUNCTION
(NODE &OPTIONAL SELECTOR)
Remove the set of matched elements from the DOM.
-
EXTERNAL FUNCTION
(NODE &REST ATTRIBUTES)
Remove attributes from each element.
-
EXTERNAL FUNCTION
(NODE &REST CLASSES)
Remove classes from each element.
-
EXTERNAL FUNCTION
(NODE &REST DATA)
Remove data attributes from each element. This is a convenience method and uses remove-attr in the back.
-
EXTERNAL FUNCTION
(WORKING-NODES SELECTOR-OR-NODES)
Replace each in the set of matched elements with the current nodes.
-
EXTERNAL FUNCTION
(WORKING-NODES HTML-OR-NODES)
Replace each element with the provided new content and return the set of elements that was removed.
-
EXTERNAL FUNCTION
(NODE &OPTIONAL (STREAM NIL))
Serialize the node into a string.
-
EXTERNAL FUNCTION
(WORKING-NODES)
Display the matched elements (short for (css :display 'block'))
-
EXTERNAL FUNCTION
(NODES &OPTIONAL SELECTOR)
Get the siblings of each element, optionally filtered by a selector.
-
EXTERNAL FUNCTION
(WORKING-NODES)
Return the number of elements in the list.
-
EXTERNAL FUNCTION
(WORKING-NODES START &OPTIONAL END)
Reduce the set of matched elements to a subset specified by a range of indices
-
EXTERNAL FUNCTION
(NODE &OPTIONAL (TEXT NIL T-S-P))
Get the combined text contents of each element, including their descendants. If text is set, all text nodes are removed and a new text node is appended to the end of the node. If text is NIL, all direct text nodes are removed from the node.
-
EXTERNAL FUNCTION
(NODE &REST CLASSES)
Add or remove one or more classes from each element, depending on their presence within the element.
-
Remove the parents of the set of matched elements from the DOM, inserting the parents children in place of it.
-
EXTERNAL FUNCTION
(NODE &OPTIONAL (VALUE NIL V-P))
Get the current values or set the value of every matched element.
-
EXTERNAL FUNCTION
(NODES HTML-OR-NODES)
Wrap an HTML structure around each element. Note that always the first node of the structure to wrap is chosen.
-
EXTERNAL FUNCTION
(WORKING-NODES HTML-OR-NODES)
Wrap an HTML structure around all elements and put it in place of the first element, removing all other elements from their position.
-
EXTERNAL FUNCTION
(NODES HTML-OR-NODES)
Wrap an HTML structure around the contents of each element.
-
EXTERNAL FUNCTION
(WORKING-NODES FILE &KEY (IF-DOES-NOT-EXIST CREATE) (IF-EXISTS SUPERSEDE))
Write the serialized node to the file. Note that always only the first element is written.