{"id":13516612,"url":"https://github.com/gajus/surgeon","last_synced_at":"2025-05-16T18:10:24.719Z","repository":{"id":52996839,"uuid":"79125156","full_name":"gajus/surgeon","owner":"gajus","description":"Declarative DOM extraction expression evaluator. 👨‍⚕️","archived":false,"fork":false,"pushed_at":"2020-06-05T18:11:27.000Z","size":729,"stargazers_count":696,"open_issues_count":17,"forks_count":30,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-05-04T08:36:24.481Z","etag":null,"topics":["css-selector","parser","scraper","subroutines"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gajus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"gajus","patreon":"gajus"}},"created_at":"2017-01-16T14:14:34.000Z","updated_at":"2025-01-26T10:55:58.000Z","dependencies_parsed_at":"2022-08-26T13:31:39.057Z","dependency_job_id":null,"html_url":"https://github.com/gajus/surgeon","commit_stats":null,"previous_names":[],"tags_count":63,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fsurgeon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fsurgeon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fsurgeon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fsurgeon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gajus","download_url":"https://codeload.github.com/gajus/surgeon/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254283339,"owners_count":22045140,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css-selector","parser","scraper","subroutines"],"created_at":"2024-08-01T05:01:24.104Z","updated_at":"2025-05-16T18:10:24.698Z","avatar_url":"https://github.com/gajus.png","language":"JavaScript","funding_links":["https://github.com/sponsors/gajus","https://patreon.com/gajus"],"categories":["JavaScript"],"sub_categories":[],"readme":"# Surgeon\n\n[![GitSpo Mentions](https://gitspo.com/badges/mentions/gajus/surgeon?style=flat-square)](https://gitspo.com/mentions/gajus/surgeon)\n[![Travis build status](http://img.shields.io/travis/gajus/surgeon/master.svg?style=flat-square)](https://travis-ci.org/gajus/surgeon)\n[![Coveralls](https://img.shields.io/coveralls/gajus/surgeon.svg?style=flat-square)](https://coveralls.io/github/gajus/surgeon)\n[![NPM version](http://img.shields.io/npm/v/surgeon.svg?style=flat-square)](https://www.npmjs.org/package/surgeon)\n[![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)\n[![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social\u0026label=Follow)](https://twitter.com/kuizinas)\n\nDeclarative DOM extraction expression evaluator.\n\nPowerful, succinct, composable, extendable, declarative API.\n\n```yaml\narticles:\n- select article {0,}\n- body:\n  - select .body\n  - read property innerHTML\n  imageUrl:\n  - select img\n  - read attribute src\n  summary:\n  - select \".body p:first-child\"\n  - read property innerHTML\n  - format text\n  title:\n  - select .title\n  - read property textContent\npageName:\n- select .body\n- read property innerHTML\n\n```\n\n\u003e Not succinct enough for you? Use [aliases](#declare-subroutine-aliases) and the [pipe operator (`|`)](#the-pipe-operator-) to shorten and concatenate the commands:\n\u003e\n\u003e ```\n\u003e articles:\n\u003e - sm article\n\u003e - body: s .body | rp innerHTML\n\u003e   imageUrl: s img | ra src\n\u003e   summary: s .body p:first-child | rp innerHTML | f text\n\u003e   title: s .title | rp textContent\n\u003e pageName: s .body | rp innerHTML\n\u003e\n\u003e ```\n\nHave you got suggestions for improvement? [I am all ears](https://github.com/gajus/surgeon/issues).\n\n---\n\n* [Configuration](#configuration)\n* [Evaluators](#evaluators)\n  * [`browser` evaluator](#browser-evaluator)\n  * [`cheerio` evaluator](#cheerio-evaluator)\n* [Subroutines](#subroutines)\n  * [Built-in subroutines](#built-in-subroutines)\n    * [`append` subroutine](#append-subroutine)\n    * [`closest` subroutine](#closest-subroutine)\n    * [`constant` subroutine](#constant-subroutine)\n    * [`format` subroutine](#format-subroutine)\n    * [`match` subroutine](#match-subroutine)\n    * [`nextUntil` subroutine](#nextuntil-subroutine)\n    * [`prepend` subroutine](#prepend-subroutine)\n    * [`previous` subroutine](#previous-subroutine)\n    * [`read` subroutine](#read-subroutine)\n    * [`remove` subroutine](#remove-subroutine)\n    * [`select` subroutine](#select-subroutine)\n      * [Quantifier expression](#quantifier-expression)\n    * [`test` subroutine](#test-subroutine)\n  * [User-defined subroutines](#user-defined-subroutines)\n  * [Inline subroutines](#inline-subroutines)\n  * [Built-in subroutine aliases](#built-in-subroutine-aliases)\n* [Expression reference](#expression-reference)\n  * [The pipe operator (`|`)](#the-pipe-operator-)\n* [Cookbook](#cookbook)\n  * [Extract a single node](#extract-a-single-node)\n  * [Extract multiple nodes](#extract-multiple-nodes)\n  * [Name results](#name-results)\n  * [Validate the results using RegExp](#validate-the-results-using-regexp)\n  * [Validate the results using a user-defined test function](#validate-the-results-using-a-user-defined-test-function)\n  * [Declare subroutine aliases](#declare-subroutine-aliases)\n* [Error handling](#error-handling)\n* [Debugging](#debugging)\n\n## Configuration\n\n|Name|Type|Description|Default value|\n|---|---|---|---|\n|`evaluator`|[`EvaluatorType`](./src/types.js)|HTML parser and selector engine. See [evaluators](#evaluators).|[`browser` evaluator](#browser-evaluator) if `window` and `document` variables are present, [`cheerio`](#cheerio-evaluator) otherwise.|\n|`subroutines`|[`$PropertyType\u003cUserConfigurationType, 'subroutines'\u003e`](./src/types.js)|User defined subroutines. See [subroutines](#subroutines).|N/A|\n\n## Evaluators\n\n[Subroutines](#subroutines) use an evaluator to parse input (i.e. convert a string into an object) and to select nodes in the resulting document.\n\nThe default evaluator is configured based on the user environment:\n  * [`browser` evaluator](#browser-evaluator) is used if `window` and `document` variables are defined; otherwise\n  * [`cheerio`](#cheerio-evaluator)\n\n\u003e Have a use case for another evaluator? [Raise an issue](https://github.com/gajus/surgeon/issues).\n\u003e\n\u003e For an example implementation of an evaluator, refer to:\n\u003e\n\u003e * [`./src/evaluators/browserEvaluator.js`](./src/evaluators/browserEvaluator.js)\n\u003e * [`./src/evaluators/cheerioEvaluator.js`](./src/evaluators/cheerioEvaluator.js)\n\n### `browser` evaluator\n\nUses native browser methods to parse the document and to evaluate CSS selector queries.\n\nUse `browser` evaluator if you are running Surgeon in a browser or a headless browser (e.g. PhantomJS).\n\n```js\nimport {\n  browserEvaluator\n} from './evaluators';\n\nsurgeon({\n  evaluator: browserEvaluator()\n});\n\n```\n\n### `cheerio` evaluator\n\nUses [cheerio](https://github.com/cheeriojs/cheerio) to parse the document and to evaluate CSS selector queries.\n\nUse `cheerio` evaluator if you are running Surgeon in Node.js.\n\n```js\nimport {\n  cheerioEvaluator\n} from './evaluators';\n\nsurgeon({\n  evaluator: cheerioEvaluator()\n});\n\n```\n\n## Subroutines\n\nA subroutine is a function used to advance the DOM extraction expression evaluator, e.g.\n\n```js\nx('foo | bar baz', 'qux');\n\n```\n\nIn the above example, Surgeon expression uses two subroutines: `foo` and `bar`.\n\n`foo` subroutine is invoked without additional values. `bar` subroutine is executed with 1 value (\"baz\").\n\nSubroutines are executed in the order in which they are defined – the result of the last subroutine is passed on to the next one. The first subroutine receives the document input (in this case: \"qux\" string).\n\nMultiple subroutines can be written as an array. The following example is equivalent to the earlier example.\n\n```js\nx([\n  'foo',\n  'bar baz'\n], 'qux');\n\n```\n\nThere are two types of subroutines:\n\n* [Built-in subroutines](#built-in-subroutines)\n* [User-defined subroutines](#user-defined-subroutines)\n\n\u003e Note:\n\u003e\n\u003e These functions are called subroutines to emphasise the cross-platform nature of the declarative API.\n\n### Built-in subroutines\n\nThe following subroutines are available out of the box.\n\n#### `append` subroutine\n\n`append` appends a string to the input string.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|tail|Appends a string to the end of the input string.|N/A|\n\nExamples:\n\n```js\n// Assuming an element \u003ca href='http://foo' /\u003e,\n// then the result is 'http://foo/bar'.\nx(`select a | read attribute href | append '/bar'`);\n\n```\n\n#### `closest` subroutine\n\n`closest` subroutine iterates through all the preceding nodes (including parent nodes) searching for either a preceding node matching the selector expression or a descendant of the preceding node matching the selector.\n\nNote: This is different from the jQuery [`.closest()`](https://api.jquery.com/closest/) in that the latter method does not search for parent descendants matching the selector.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|CSS selector|CSS selector used to select an element.|N/A|\n\n#### `constant` subroutine\n\n`constant` returns the parameter value regardless of the input.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|`constant`|Constant value that will be returned as the result.|N/A|\n\n#### `format` subroutine\n\n`format` is used to format input using [printf](https://en.wikipedia.org/wiki/Printf_format_string).\n\n|Parameter name|Description|Default|\n|---|---|---|\n|format|[sprintf format](https://www.npmjs.com/package/sprintf-js) used to format the input string. The subroutine input is the first argument, i.e. `%1$s`.|`%1$s`|\n\nExamples:\n\n```js\n// Extracts 1 matching capturing group from the input string.\n// Prefixes the match with 'http://foo.com'.\nx(`select a | read attribute href | format 'http://foo.com%1$s'`);\n\n```\n\n#### `match` subroutine\n\n`match` is used to extract matching [capturing groups](https://www.regular-expressions.info/refcapture.html) from the subject input.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|Regular expression|Regular expression used to match capturing groups in the string.|N/A|\n|Sprintf format|[sprintf format](https://www.npmjs.com/package/sprintf-js) used to construct a string using the matching capturing groups.|`%s`|\n\nExamples:\n\n```js\n// Extracts 1 matching capturing group from the input string.\n// Throws `InvalidDataError` if the value does not pass the test.\nx('select .foo | read property textContent | match \"/input: (\\d+)/\"');\n\n// Extracts 2 matching capturing groups from the input string and formats the output using sprintf.\n// Throws `InvalidDataError` if the value does not pass the test.\nx('select .foo | read property textContent | match \"/input: (\\d+)-(\\d+)/\" %2$s-%1$s');\n\n```\n\n#### `nextUntil` subroutine\n\n`nextUntil` subroutine is used to select all following siblings of each element up to but not including the element matched by the selector.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|selector expression|A string containing a selector expression to indicate where to stop matching following sibling elements.|N/A|\n|filter expression|A string containing a selector expression to match elements against.|\n\n#### `prepend` subroutine\n\n`prepend` prepends a string to the input string.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|head|Prepends a string to the start of the input string.|N/A|\n\nExamples:\n\n```js\n// Assuming an element \u003ca href='//foo' /\u003e,\n// then the result is 'http://foo/bar'.\nx(`select a | read attribute href | prepend 'http:'`);\n\n```\n\n#### `previous` subroutine\n\n`previous` subroutine selects the preceding sibling.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|CSS selector|CSS selector used to select an element.|N/A|\n\nExample:\n\n```html\n\u003cul\u003e\n  \u003cli\u003efoo\u003c/li\u003e\n  \u003cli class='bar'\u003e\u003c/li\u003e\n\u003cul\u003e\n```\n\n```js\nx('select .bar | previous | read property textContent');\n// 'foo'\n\n```\n\n#### `read` subroutine\n\n`read` is used to extract value from the matching element using an [evaluator](#evaluators).\n\n|Parameter name|Description|Default|\n|---|---|---|\n|Target type|Possible values: \"attribute\" or \"property\"|N/A|\n|Target name|Depending on the target type, name of an attribute or a property.|N/A|\n\nExamples:\n\n```js\n// Returns .foo element \"href\" attribute value.\n// Throws error if attribute does not exist.\nx('select .foo | read attribute href');\n\n// Returns an array of \"href\" attribute values of the matching elements.\n// Throws error if attribute does not exist on either of the matching elements.\nx('select .foo {0,} | read attribute href');\n\n// Returns .foo element \"textContent\" property value.\n// Throws error if property does not exist.\nx('select .foo | read property textContent');\n\n```\n\n#### `remove` subroutine\n\n`remove` subroutine is used to remove elements from the document using an [evaluator](#evaluators).\n\n`remove` subroutine accepts the same parameters as the `select` subroutine.\n\nThe result of `remove` subroutine is the input of the subroutine, i.e. previous `select` subroutine result.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|CSS selector|CSS selector used to select an element.|N/A|\n|Quantifier expression|A [quantifier expression](#quantifier-expression) is used to control the expected result length.|See [quantifier expression](#quantifier-expression).|\n\nExamples:\n\n```js\n// Returns 'bar'.\nx('select .foo | remove span | read property textContent', `\u003cdiv class='foo'\u003ebar\u003cspan\u003ebaz\u003c/span\u003e\u003c/div\u003e`);\n\n```\n\n#### `select` subroutine\n\n`select` subroutine is used to select the elements in the document using an [evaluator](#evaluators).\n\n|Parameter name|Description|Default|\n|---|---|---|\n|CSS selector|CSS selector used to select an element.|N/A|\n|Quantifier expression|A [quantifier expression](#quantifier-expression) is used to control the shape of the results (direct result or array of results) and the expected result length.|See [quantifier expression](#quantifier-expression).|\n\n##### Quantifier expression\n\nA *quantifier expression* is used to assert that the query matches a set number of nodes. A quantifier expression is a modifier of the [`select` subroutine](#select-subroutine).\n\nA *quantifier expression* is defined using the following syntax.\n\n|Name|Syntax|\n|---|---|\n|Fixed quantifier|`{n}` where `n` is an integer `\u003e= 1`|\n|Greedy quantifier|`{n,m}` where `n \u003e= 0` and `m \u003e= n`|\n|Greedy quantifier|`{n,}` where `n \u003e= 0`|\n|Greedy quantifier|`{,m}` where `m \u003e= 1`|\n\nA *quantifier expression* can be appended a node selector `[i]`, e.g. `{0,}[1]`. This allows to return the first node from the result set.\n\n\u003e If this looks familiar, its because I have adopted the syntax from regular expression language. However, unlike in regular expression, a quantifier in the context of Surgeon selector will produce an error (`SelectSubroutineUnexpectedResultCountError`) if selector result length is out of the quantifier range.\n\nExamples:\n\n```js\n// Selects 0 or more nodes.\n// Result is an array.\nx('select .foo {0,}');\n\n// Selects 1 or more nodes.\n// Throws an error if 0 matches found.\n// Result is an array.\nx('select .foo {1,}');\n\n// Selects between 0 and 5 nodes.\n// Throws an error if more than 5 matches found.\n// Result is an array.\nx('select .foo {0,5}');\n\n// Selects 1 node.\n// Result is the first match in the result set (or `null`).\nx('select .foo {0,}[0]');\n\n```\n\n#### `test` subroutine\n\n`test` is used to validate the current value using a regular expression.\n\n|Parameter name|Description|Default|\n|---|---|---|\n|Regular expression|Regular expression used to test the value.|N/A|\n\nExamples:\n\n```js\n// Validates that .foo element textContent property value matches /bar/ regular expression.\n// Throws `InvalidDataError` if the value does not pass the test.\nx('select .foo | read property textContent | test /bar/');\n\n```\n\nSee [error handling](#error-handling) for more information and usage examples of the `test` subroutine.\n\n### User-defined subroutines\n\nCustom subroutines can be defined using [`subroutines` configuration](#configuration).\n\nA subroutine is a function. A subroutine function is invoked with the following parameters:\n\n|Parameter name|\n|---|\n|An instance of [Evaluator].|\n|Current value, i.e. value used to query Surgeon or value returned from the previous (or ancestor) subroutine.|\n|An array of values used when referencing the subroutine in an expression.|\n\nExample:\n\n```js\nconst x = surgeon({\n  subroutines: {\n    mySubroutine: (currentValue, [firstParameterValue, secondParameterValue]) =\u003e {\n      console.log(currentValue, firstParameterValue, secondParameterValue);\n\n      return parseInt(currentValue, 10) + 1;\n    }\n  }\n});\n\nx('mySubroutine foo bar | mySubroutine baz qux', 0);\n\n```\n\nThe above example prints:\n\n```\n0 \"foo\" \"bar\"\n1 \"baz\" \"qux\"\n\n```\n\nFor more examples of defining subroutines, refer to:\n\n* [Validate the results using a user-defined test function](#validate-the-results-using-a-user-defined-test-function).\n* [Source code](./src/subroutines) of the the built-in subroutines.\n\n## Inline subroutines\n\nCustom subroutines can be inlined into [pianola](https://github.com/gajus/pianola) instructions, e.g.\n\n```js\nx(\n  [\n    'foo',\n    (subject) =\u003e {\n      // `subject` is the return value of `foo` subroutine.\n\n      return 'bar';\n    },\n    'baz',\n  ],\n  'qux'\n);\n\n```\n\n## Built-in subroutine aliases\n\nSurgeon exports an alias preset is used to reduce verbosity of the queries.\n\n|Name|Description|\n|---|---|\n|`ra ...`|Reads Element attribute value. Equivalent to `read attribute ...`|\n|`rdtc ...`|Removes any descending elements and reads the resulting `textContent` property of an element. Equivalent to `remove * {0,} | read property ... textContent`|\n|`rih ...`|Reads `innerHTML` property of an element. Equivalent to `read property ... innerHTML`|\n|`roh ...`|Reads `outerHTML` property of an element. Equivalent to `read property ... outerHTML`|\n|`rp ...`|Reads Element property value. Equivalent to `read property ...`|\n|`rtc ...`|Reads `textContent` property of an element. Equivalent to `read property ... textContent`|\n|`sa ...`|Select any (sa). Selects multiple elements (0 or more). Returns array. Equivalent to `select \"...\" {0,}`|\n|`saf ...`|Select any first (saf). Selects multiple elements (0 or more). Returns single result or `null`. Equivalent to `select \"...\" {0,}[0]`|\n|`sm ...`|Select many (sm). Selects multiple elements (1 or more). Returns array. Equivalent to `select \"...\" {1,}`|\n|`smo ...`|Select maybe one (smo). Selects one element. Returns single result or `null`. Equivalent to `select \"...\" {0,1}[0]`|\n|`so ...`|Select one (so). Selects a single element. Returns single result. Equivalent to `select \"...\" {1}[0]`.|\n|`t {name}`|Tests value. Equivalent to `test ...`|\n\n\u003e Note regarding `s ...` alias. The CSS selector value is quoted. Therefore, you can write a CSS selector that includes spaces without putting the value in the quotes, e.g. `s .foo .bar` is equivalent to `select \".foo .bar\" {1}`.\n\u003e\n\u003e Other alias values are not quoted. Therefore, if value includes a space it must be quoted, e.g. `t \"/foo bar/\"`.\n\nUsage:\n\n```js\nimport surgeon, {\n  subroutineAliasPreset\n} from 'surgeon';\n\nconst x = surgeon({\n  subroutines: {\n    ...subroutineAliasPreset\n  }\n});\n\nx('s .foo .bar | t \"/foo bar/\"');\n\n```\n\nIn addition to the built-in aliases, user can [declare subroutine aliases](#declare-subroutine-aliases).\n\n## Expression reference\n\nSurgeon subroutines are referenced using expressions.\n\nAn expression is defined using the following pseudo-grammar:\n\n```\nsubroutines -\u003e\n    subroutines _ \"|\" _ subroutine\n  | subroutine\n\nsubroutine -\u003e\n    subroutineName \" \" parameters\n  | subroutineName\n\nsubroutineName -\u003e\n  [a-zA-Z0-9\\-_]:+\n\nparameters -\u003e\n    parameters \" \" parameter\n  | parameter\n\n```\n\nExample:\n\n```js\nx('foo bar baz', 'qux');\n\n```\n\nIn this example, Surgeon query executor (`x`) is invoked with `foo bar baz` expression and `qux` starting value. The expression tells the query executor to run `foo` subroutine with parameter values \"bar\" and \"baz\". The expression executor runs `foo` subroutine with parameter values \"bar\" and \"baz\" and subject value \"qux\".\n\nMultiple subroutines can be combined using an array:\n\n```js\nx([\n  'foo bar baz',\n  'corge grault garply'\n], 'qux');\n\n```\n\nIn this example, Surgeon query executor (`x`) is invoked with two expressions (`foo bar baz` and `corge grault garply`). The first subroutine is executed with the subject value \"qux\". The second subroutine is executed with a value that is the result of the parent subroutine.\n\nThe result of the query is the result of the last subroutine.\n\nRead [user-defined subroutines](#user-defined-subroutines) documentation for broader explanation of the role of the parameter values and the subject value.\n\n### The pipe operator (`|`)\n\nMultiple subroutines can be combined using the pipe operator.\n\nThe following examples are equivalent:\n\n```js\nx([\n  'foo bar baz',\n  'qux quux quuz'\n]);\n\nx([\n  'foo bar baz | foo bar baz'\n]);\n\nx('foo bar baz | foo bar baz');\n\n```\n\n## Cookbook\n\nUnless redefined, all examples assume the following initialisation:\n\n```js\nimport surgeon from 'surgeon';\n\n/**\n * @param configuration {@see https://github.com/gajus/surgeon#configuration}\n */\nconst x = surgeon();\n\n```\n\n### Extract a single node\n\nUse [`select` subroutine](#select-subroutine) and [`read` subroutine](#read-subroutine) to extract a single value.\n\n```js\nconst subject = `\n  \u003cdiv class=\"title\"\u003efoo\u003c/div\u003e\n`;\n\nx('select .title | read property textContent', subject);\n\n// 'foo'\n\n```\n\n### Extract multiple nodes\n\nSpecify [`select` subroutine](#select-subroutine) `quantifier` to match multiple results.\n\n```js\nconst subject = `\n  \u003cdiv class=\"foo\"\u003ebar\u003c/div\u003e\n  \u003cdiv class=\"foo\"\u003ebaz\u003c/div\u003e\n  \u003cdiv class=\"foo\"\u003equx\u003c/div\u003e\n`;\n\nx('select .title {0,} | read property textContent', subject);\n\n// [\n//   'bar',\n//   'baz',\n//   'qux'\n// ]\n\n```\n\n### Name results\n\nUse a [`QueryChildrenType`](./src/types.js) object to name the results of the descending expressions.\n\n```js\nconst subject = `\n  \u003carticle\u003e\n    \u003cdiv class='title'\u003efoo title\u003c/div\u003e\n    \u003cdiv class='body'\u003efoo body\u003c/div\u003e\n  \u003c/article\u003e\n  \u003carticle\u003e\n    \u003cdiv class='title'\u003ebar title\u003c/div\u003e\n    \u003cdiv class='body'\u003ebar body\u003c/div\u003e\n  \u003c/article\u003e\n`;\n\nx([\n  'select article',\n  {\n    body: 'select .body | read property textContent'\n    title: 'select .title | read property textContent'\n  }\n]);\n\n// [\n//   {\n//     body: 'foo body',\n//     title: 'foo title'\n//   },\n//   {\n//     body: 'bar body',\n//     title: 'bar title'\n//   }\n// ]\n\n```\n\n### Validate the results using RegExp\n\nUse [`test` subroutine](#test-subroutine) to validate the results.\n\n```js\nconst subject = `\n  \u003cdiv class=\"foo\"\u003ebar\u003c/div\u003e\n  \u003cdiv class=\"foo\"\u003ebaz\u003c/div\u003e\n  \u003cdiv class=\"foo\"\u003equx\u003c/div\u003e\n`;\n\nx('select .foo {0,} | test /^[a-z]{3}$/');\n\n```\n\nSee [error handling](#error-handling) for information how to handle `test` subroutine errors.\n\n### Validate the results using a user-defined test function\n\nDefine a [custom subroutine](#user-defiend-subroutines) to validate results using arbitrary logic.\n\nUse `InvalidValueSentinel` to leverage standardised Surgeon error handler (see [error handling](#error-handling)). Otherwise, simply throw an error.\n\n```js\nimport surgeon, {\n  InvalidValueSentinel\n} from 'surgeon';\n\nconst x = surgeon({\n  subroutines: {\n    isRed: (value) =\u003e {\n      if (value === 'red') {\n        return value;\n      };\n\n      return new InvalidValueSentinel('Unexpected color.');\n    }\n  }\n});\n\n```\n\n### Declare subroutine aliases\n\nAs you become familiar with the query execution mechanism, typing long expressions (such as `select`, `read attribute` and `read property`) becomes a mundane task.\n\nRemember that subroutines are regular functions: you can partially apply and use the partially applied functions to create new subroutines.\n\nExample:\n\n```js\nimport surgeon, {\n  readSubroutine,\n  selectSubroutine,\n  testSubroutine\n} from 'surgeon';\n\nconst x = surgeon({\n  subroutines: {\n    ra: (subject, values, bindle) =\u003e {\n      return readSubroutine(subject, ['attribute'].concat(values), bindle);\n    },\n    rp: (subject, values, bindle) =\u003e {\n      return readSubroutine(subject, ['property'].concat(values), bindle);\n    },\n    s: (subject, values, bindle) =\u003e {\n      return selectSubroutine(subject, [values.join(' '), '{1}'], bindle);\n    },\n    sm: (subject, values, bindle) =\u003e {\n      return selectSubroutine(subject, [values.join(' '), '{0,}'], bindle);\n    },\n    t: testSubroutine\n  }\n});\n\n```\n\nNow, instead of writing:\n\n```yaml\narticles:\n- select article\n- body:\n  - select .body\n  - read property innerHTML\n\n```\n\nYou can write:\n\n```yaml\narticles:\n- sm article\n- body:\n  - s .body\n  - rp innerHTML\n```\n\nThe aliases used in this example are available in the aliases preset (read [built-in subroutine aliases](#built-in-subroutine-aliases)).\n\n## Error handling\n\nSurgeon throws the following errors to indicate a predictable error state. All Surgeon errors can be imported. Use `instanceof` operator to determine the error type.\n\n\u003e Note:\n\u003e\n\u003e Surgeon errors are non-recoverable, i.e. a selector cannot proceed if it encounters an error.\n\u003e This design ensures that your selectors are capturing the expected data.\n\n|Name|Description|\n|---|---|\n|`ReadSubroutineNotFoundError`|Thrown when an attempt is made to retrieve a non-existent attribute or property.|\n|`SelectSubroutineUnexpectedResultCountError`|Thrown when a [`select` subroutine](#select-subroutine) result length does not match the quantifier expression.|\n|`InvalidDataError`|Thrown when a subroutine returns an instance of `InvalidValueSentinel`.|\n|`SurgeonError`|A generic error. All other Surgeon errors extend from `SurgeonError`.|\n\nExample:\n\n```js\nimport {\n  InvalidDataError\n} from 'surgeon';\n\nconst subject = `\n  \u003cdiv class=\"foo\"\u003ebar\u003c/div\u003e\n`;\n\ntry {\n  x('select .foo | test /bar/', subject);\n} catch (error) {\n  if (error instanceof InvalidDataError) {\n    // Handle data validation error.\n  } else {\n    throw error;\n  }\n}\n\n```\n\nReturn `InvalidValueSentinel` from a subroutine to force Surgeon throw `InvalidDataError` error.\n\n## Debugging\n\nSurgeon is using [`roarr`](https://www.npmjs.com/package/roarr) to log debugging information.\n\nExport `ROARR_LOG=TRUE` environment variable to enable Surgeon debug log.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgajus%2Fsurgeon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgajus%2Fsurgeon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgajus%2Fsurgeon/lists"}