Name | xpath-kit JSON |
Version |
0.3.5
JSON |
| download |
home_page | None |
Summary | A toolkit for convenient and expressive XPath operations based on lxml. |
upload_time | 2025-09-03 10:50:46 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.11 |
license | None |
keywords |
xpath
lxml
xml
html
parser
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# xpath-kit
[](https://pypi.org/project/xpath-kit/)
[](https://opensource.org/licenses/MIT)
[](https://pypi.org/project/xpath-kit/)
**xpath-kit** is a powerful Python library that provides a fluent, object-oriented, and Pythonic interface for building and executing XPath queries on top of `lxml`. It transforms complex, error-prone XPath string composition into a highly readable and maintainable chain of objects and methods.
Say goodbye to messy, hard-to-read XPath strings:
`div[@id="main" and contains(@class, "content")]/ul/li[position()=1]`
And say hello to a more intuitive and IDE-friendly way of writing queries:
`E.div[(A.id == "main") & A.class_.contains("content")] / E.ul / E.li[1]`
---
## ✨ Features
- **🐍 Fluent & Pythonic Interface**: Chain methods and operators (`/`, `//`, `[]`, `&`, `|`, `==`, `>`) to build complex XPath expressions naturally using familiar Python logic.
- **💡 Smart Builders**: Use `E` (elements), `A` (attributes), and `F` (functions) for a highly readable syntax with excellent IDE autocompletion support.
- **📖 Superb Readability & Maintainability**: Complex queries become self-documenting. It's easier to understand, debug, and modify your selectors.
- **💪 Powerful Predicate Logic**: Easily create sophisticated predicates for attributes, text, and functions. Gracefully handle multi-class selections with `any()`, `all()`, and `none()`.
- **🔩 Convenient DOM Manipulation**: The result objects are powerful wrappers around `lxml` elements, allowing for easy DOM traversal and manipulation (e.g., `append`, `remove`, `parent`, `next_sibling`).
- **🔒 Fully Type-Hinted**: The entire library is fully type-hinted for an unmatched developer experience and static analysis with modern IDEs.
- **⚙️ HTML & XML Support**: Seamlessly parse both document types with `html()` and `xml()` entry points.
---
## 🚀 Installation
Install `xpath-kit` from PyPI using pip:
```bash
pip install xpath-kit
```
The library requires `lxml` as a dependency, which will be installed automatically.
---
## 🏁 Quick Start
Here's a simple example of how to use `xpath-kit` to parse a piece of HTML and extract information.
```python
from xpathkit import html, E, A, F
html_content = """
<html>
<body>
<div id="main">
<h2>Article Title</h2>
<p>This is the first paragraph.</p>
<ul class="item-list">
<li class="item active">Item 1</li>
<li class="item">Item 2</li>
<li class="item disabled">Item 3</li>
</ul>
</div>
</body>
</html>
"""
# 1. Parse the HTML content
root = html(html_content)
# 2. Build a query to find the <li> element with both "item" and "active" classes
# XPath: .//ul[contains(@class, "item-list")]/li[contains(@class, "item") and contains(@class, "active")]
query = E.ul[A.class_.contains("item-list")] / E.li[A.class_.all("item", "active")]
# 3. Execute the query and get a single element
active_item = root.descendant(query)
# Print its content and attributes
print(f"Tag: {active_item.tag}")
print(f"Text: {active_item.string()}")
print(f"Class attribute: {active_item['class']}")
# --- Output ---
# Tag: li
# Text: Item 1
# Class attribute: item active
# 4. Build a more complex query: find all <li> elements whose class does NOT contain 'disabled'
# XPath: .//li[not(contains(@class, "disabled"))]
query_enabled = E.li[F.not_(A.class_.contains("disabled"))]
# 5. Execute the query and process the list of results
enabled_items = root.descendants(query_enabled)
item_texts = enabled_items.map(lambda item: item.string())
print(f"\nEnabled items: {item_texts}")
# --- Output ---
# Enabled items: ['Item 1', 'Item 2']
```
---
## 📚 Core Concepts
### 1. Parsing Entrypoints
Use the `html()` or `xml()` functions to start. They accept a string, bytes, or a file path.
```python
from xpathkit import html, xml
# Parse an HTML string
root_html = html("<div><p>Hello</p></div>")
# Parse an XML file
root_xml = xml(path="data.xml")
```
### 2. The Smart Builders (E, A, F)
These are the heart of `xpath-kit`, making expression building effortless.
- **`E` (Element)**: Builds element nodes. E.g., `E.div`, `E.a`, or custom tags `E["my-tag"]`.
- **`A` (Attribute)**: Builds attribute nodes within predicates. E.g., `A.id`, `A.href`, or custom attributes `A["data-id"]`.
- **`F` (Function)**: Builds XPath functions. E.g., `F.contains()`, `F.not_()`, `F.position()`, or any custom function: `F["name"](arg1, ...)`.
*Note*: Since `class` and `for` are reserved keywords in Python, use a trailing underscore: `A.class_` and `A.for_`.
### 3. Path Selection (`/` and `//`)
Use the division operators to define relationships between elements.
- `/`: Selects a direct child.
- `//`: Selects a descendant at any level.
```python
# Selects a <p> that is a direct child of a <div>
# XPath: div/p
query_child = E.div / E.p
# Selects an <a> that is a descendant of the <body>
# XPath: body//a
query_descendant = E.body // E.a
```
You can also use a string directly after an element for simple cases:
```python
# Equivalent to E.div / E.span
query = E.div / "span"
```
This is convenient for simple queries without predicates or attributes.
### 4. Predicates (`[]`)
Use square brackets `[]` on an element to add filtering conditions. This is where `xpath-kit` truly shines.
#### Attribute Predicates with `A`
```python
# Find a div with id="main"
# XPath: //div[@id="main"]
query = E.div[A.id == "main"]
# Find an <a> that has an href attribute
# XPath: //a[@href]
query_has_href = E.a[A.href]
# Find an <li> whose class contains "item" but NOT "disabled"
# XPath: //li[contains(@class,"item") and not(contains(@class,"disabled"))]
query = E.li[A.class_.contains("item") & F.not_(A.class_.contains("disabled"))]
```
#### Text/Value Predicates
To query against the string value of a node (`.`), import the `dot` class.
```python
from xpathkit import dot
# Find an <h1> whose text is exactly "Welcome"
# XPath: //h1[.="Welcome"]
query = E.h1[dot() == "Welcome"]
# Find a <p> whose text contains the word "paragraph"
# XPath: //p[contains(., "paragraph")]
query_contains = E.p[dot().contains("paragraph")]
```
#### Functional Predicates with `F`
Use `F` to call any standard XPath function inside a predicate.
```python
# Select the first list item
# XPath: //li[position()=1]
query_first = E.li[F.position() == 1]
# Select the last list item
# XPath: //li[last()]
query_last = E.li[F.last()]
```
#### Combining Predicates with `&` and `|`
- `&`: Logical `and`
- `|`: Logical `or`
```python
# Find an <a> with href="/home" AND a target attribute
# XPath: //a[@href="/home" and @target]
query_and = E.a[(A.href == "/home") & A.target]
# Find a <div> with id="sidebar" OR class="nav"
# XPath: //div[@id="sidebar" or contains(@class,"nav")]
query_or = E.div[(A.id == "sidebar") | A.class_.contains("nav")]
```
**Important:** Due to Python's operator precedence, it's highly recommended to wrap combined conditions in parentheses `()`.
#### Positional Predicates
Use integers (1-based) or negative integers (from the end) directly.
```python
# Select the second <li>
# XPath: //li[2]
query = E.li[2]
# Select the last <li> (equivalent to F.last())
# XPath: //li[last()]
query_last = E.li[-1]
```
### 5. Working with Results
- `.child()`/`.descendant()` return a single `XPathElement`.
- `.children()`/`.descendants()` return an `Union[XPathElementList, str, float, bool, List[str]]`.
#### `XPathElement` (Single Result)
- `.tag`: The element's tag name (e.g., `'div'`).
- `.attr`: A dictionary of all attributes.
- `element['name']`: Access an attribute directly.
- `.string()`: Get the concatenated text of the element and all its children (`string(.)`).
- `.text()`: Get a list of only the element's direct text nodes (`./text()`).
- `.parent()`: Get the parent element.
- `.next_sibling()` / `.prev_sibling()`: Get adjacent sibling elements.
- `.xpath(query)`: Execute a raw string or a constructed query within the context of this element.
#### `XPathElementList` (Multiple Results)
- `.one()`: Ensures the list contains exactly one element and returns it; otherwise, raises an error.
- `.first()` / `.last()`: Get the first or last element; raises an error if the list is empty.
- `len(element_list)`: Get the number of elements.
- `.filter(func)`: Filter the list based on a function.
- `.map(func)`: Apply a function to each element and return a list of the results.
- Can be iterated over directly: `for e in my_list: ...`
- Supports slicing and indexing: `my_list[0]`, `my_list[-1]`
### 6. DOM Manipulation
Modify the document tree with ease.
```python
from xpathkit import XPathElement, E, A
# Assuming 'root' is a parsed XPathElement
# Find the <ul> element
ul = root.descendant(E.ul)
# Create and append a new <li>
new_li = XPathElement.create("li", attr={"class": "new-item"}, text="Item 4")
ul.append(new_li)
# Remove an element
item_to_remove = ul.child(E.li[A.class_.contains("disabled")])
if item_to_remove:
ul.remove(item_to_remove)
# Print the modified HTML
print(root.tostring())
```
---
## 📄 License
This project is licensed under the MIT License. See the `LICENSE` file for details.
Raw data
{
"_id": null,
"home_page": null,
"name": "xpath-kit",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": "xpath, lxml, xml, html, parser",
"author": null,
"author_email": "Kabxx <2201174299@qq.com>",
"download_url": "https://files.pythonhosted.org/packages/c2/ca/dfca1edd91bec3d8fbcbd1289463f6f1d42cae32f6619ef97914d754c451/xpath_kit-0.3.5.tar.gz",
"platform": null,
"description": "# xpath-kit\r\n\r\n[](https://pypi.org/project/xpath-kit/)\r\n[](https://opensource.org/licenses/MIT)\r\n[](https://pypi.org/project/xpath-kit/)\r\n\r\n**xpath-kit** is a powerful Python library that provides a fluent, object-oriented, and Pythonic interface for building and executing XPath queries on top of `lxml`. It transforms complex, error-prone XPath string composition into a highly readable and maintainable chain of objects and methods.\r\n\r\nSay goodbye to messy, hard-to-read XPath strings:\r\n\r\n`div[@id=\"main\" and contains(@class, \"content\")]/ul/li[position()=1]`\r\n\r\nAnd say hello to a more intuitive and IDE-friendly way of writing queries:\r\n\r\n`E.div[(A.id == \"main\") & A.class_.contains(\"content\")] / E.ul / E.li[1]`\r\n\r\n---\r\n\r\n## \u2728 Features\r\n\r\n- **\ud83d\udc0d Fluent & Pythonic Interface**: Chain methods and operators (`/`, `//`, `[]`, `&`, `|`, `==`, `>`) to build complex XPath expressions naturally using familiar Python logic.\r\n- **\ud83d\udca1 Smart Builders**: Use `E` (elements), `A` (attributes), and `F` (functions) for a highly readable syntax with excellent IDE autocompletion support.\r\n- **\ud83d\udcd6 Superb Readability & Maintainability**: Complex queries become self-documenting. It's easier to understand, debug, and modify your selectors.\r\n- **\ud83d\udcaa Powerful Predicate Logic**: Easily create sophisticated predicates for attributes, text, and functions. Gracefully handle multi-class selections with `any()`, `all()`, and `none()`.\r\n- **\ud83d\udd29 Convenient DOM Manipulation**: The result objects are powerful wrappers around `lxml` elements, allowing for easy DOM traversal and manipulation (e.g., `append`, `remove`, `parent`, `next_sibling`).\r\n- **\ud83d\udd12 Fully Type-Hinted**: The entire library is fully type-hinted for an unmatched developer experience and static analysis with modern IDEs.\r\n- **\u2699\ufe0f HTML & XML Support**: Seamlessly parse both document types with `html()` and `xml()` entry points.\r\n\r\n---\r\n\r\n## \ud83d\ude80 Installation\r\n\r\nInstall `xpath-kit` from PyPI using pip:\r\n\r\n```bash\r\npip install xpath-kit\r\n```\r\n\r\nThe library requires `lxml` as a dependency, which will be installed automatically.\r\n\r\n---\r\n\r\n## \ud83c\udfc1 Quick Start\r\n\r\nHere's a simple example of how to use `xpath-kit` to parse a piece of HTML and extract information.\r\n\r\n```python\r\nfrom xpathkit import html, E, A, F\r\n\r\nhtml_content = \"\"\"\r\n<html>\r\n <body>\r\n <div id=\"main\">\r\n <h2>Article Title</h2>\r\n <p>This is the first paragraph.</p>\r\n <ul class=\"item-list\">\r\n <li class=\"item active\">Item 1</li>\r\n <li class=\"item\">Item 2</li>\r\n <li class=\"item disabled\">Item 3</li>\r\n </ul>\r\n </div>\r\n </body>\r\n</html>\r\n\"\"\"\r\n\r\n# 1. Parse the HTML content\r\nroot = html(html_content)\r\n\r\n# 2. Build a query to find the <li> element with both \"item\" and \"active\" classes\r\n# XPath: .//ul[contains(@class, \"item-list\")]/li[contains(@class, \"item\") and contains(@class, \"active\")]\r\nquery = E.ul[A.class_.contains(\"item-list\")] / E.li[A.class_.all(\"item\", \"active\")]\r\n\r\n# 3. Execute the query and get a single element\r\nactive_item = root.descendant(query)\r\n\r\n# Print its content and attributes\r\nprint(f\"Tag: {active_item.tag}\")\r\nprint(f\"Text: {active_item.string()}\")\r\nprint(f\"Class attribute: {active_item['class']}\")\r\n\r\n# --- Output ---\r\n# Tag: li\r\n# Text: Item 1\r\n# Class attribute: item active\r\n\r\n# 4. Build a more complex query: find all <li> elements whose class does NOT contain 'disabled'\r\n# XPath: .//li[not(contains(@class, \"disabled\"))]\r\nquery_enabled = E.li[F.not_(A.class_.contains(\"disabled\"))]\r\n\r\n# 5. Execute the query and process the list of results\r\nenabled_items = root.descendants(query_enabled)\r\nitem_texts = enabled_items.map(lambda item: item.string())\r\nprint(f\"\\nEnabled items: {item_texts}\")\r\n\r\n# --- Output ---\r\n# Enabled items: ['Item 1', 'Item 2']\r\n\r\n```\r\n\r\n---\r\n\r\n## \ud83d\udcda Core Concepts\r\n\r\n### 1. Parsing Entrypoints\r\n\r\nUse the `html()` or `xml()` functions to start. They accept a string, bytes, or a file path.\r\n\r\n```python\r\nfrom xpathkit import html, xml\r\n\r\n# Parse an HTML string\r\nroot_html = html(\"<div><p>Hello</p></div>\")\r\n\r\n# Parse an XML file\r\nroot_xml = xml(path=\"data.xml\")\r\n```\r\n\r\n### 2. The Smart Builders (E, A, F)\r\n\r\nThese are the heart of `xpath-kit`, making expression building effortless.\r\n\r\n- **`E` (Element)**: Builds element nodes. E.g., `E.div`, `E.a`, or custom tags `E[\"my-tag\"]`.\r\n- **`A` (Attribute)**: Builds attribute nodes within predicates. E.g., `A.id`, `A.href`, or custom attributes `A[\"data-id\"]`.\r\n- **`F` (Function)**: Builds XPath functions. E.g., `F.contains()`, `F.not_()`, `F.position()`, or any custom function: `F[\"name\"](arg1, ...)`.\r\n\r\n*Note*: Since `class` and `for` are reserved keywords in Python, use a trailing underscore: `A.class_` and `A.for_`.\r\n\r\n### 3. Path Selection (`/` and `//`)\r\n\r\nUse the division operators to define relationships between elements.\r\n\r\n- `/`: Selects a direct child.\r\n- `//`: Selects a descendant at any level.\r\n\r\n```python\r\n# Selects a <p> that is a direct child of a <div>\r\n# XPath: div/p\r\nquery_child = E.div / E.p\r\n\r\n# Selects an <a> that is a descendant of the <body>\r\n# XPath: body//a\r\nquery_descendant = E.body // E.a\r\n```\r\n\r\nYou can also use a string directly after an element for simple cases:\r\n\r\n```python\r\n# Equivalent to E.div / E.span\r\nquery = E.div / \"span\"\r\n```\r\n\r\nThis is convenient for simple queries without predicates or attributes.\r\n\r\n### 4. Predicates (`[]`)\r\n\r\nUse square brackets `[]` on an element to add filtering conditions. This is where `xpath-kit` truly shines.\r\n\r\n#### Attribute Predicates with `A`\r\n\r\n```python\r\n# Find a div with id=\"main\"\r\n# XPath: //div[@id=\"main\"]\r\nquery = E.div[A.id == \"main\"]\r\n\r\n# Find an <a> that has an href attribute\r\n# XPath: //a[@href]\r\nquery_has_href = E.a[A.href]\r\n\r\n# Find an <li> whose class contains \"item\" but NOT \"disabled\"\r\n# XPath: //li[contains(@class,\"item\") and not(contains(@class,\"disabled\"))]\r\nquery = E.li[A.class_.contains(\"item\") & F.not_(A.class_.contains(\"disabled\"))]\r\n```\r\n\r\n#### Text/Value Predicates\r\n\r\nTo query against the string value of a node (`.`), import the `dot` class.\r\n\r\n```python\r\nfrom xpathkit import dot\r\n\r\n# Find an <h1> whose text is exactly \"Welcome\"\r\n# XPath: //h1[.=\"Welcome\"]\r\nquery = E.h1[dot() == \"Welcome\"]\r\n\r\n# Find a <p> whose text contains the word \"paragraph\"\r\n# XPath: //p[contains(., \"paragraph\")]\r\nquery_contains = E.p[dot().contains(\"paragraph\")]\r\n```\r\n\r\n#### Functional Predicates with `F`\r\n\r\nUse `F` to call any standard XPath function inside a predicate.\r\n\r\n```python\r\n# Select the first list item\r\n# XPath: //li[position()=1]\r\nquery_first = E.li[F.position() == 1]\r\n\r\n# Select the last list item\r\n# XPath: //li[last()]\r\nquery_last = E.li[F.last()]\r\n```\r\n\r\n#### Combining Predicates with `&` and `|`\r\n\r\n- `&`: Logical `and`\r\n- `|`: Logical `or`\r\n\r\n```python\r\n# Find an <a> with href=\"/home\" AND a target attribute\r\n# XPath: //a[@href=\"/home\" and @target]\r\nquery_and = E.a[(A.href == \"/home\") & A.target]\r\n\r\n# Find a <div> with id=\"sidebar\" OR class=\"nav\"\r\n# XPath: //div[@id=\"sidebar\" or contains(@class,\"nav\")]\r\nquery_or = E.div[(A.id == \"sidebar\") | A.class_.contains(\"nav\")]\r\n```\r\n**Important:** Due to Python's operator precedence, it's highly recommended to wrap combined conditions in parentheses `()`.\r\n\r\n#### Positional Predicates\r\n\r\nUse integers (1-based) or negative integers (from the end) directly.\r\n\r\n```python\r\n# Select the second <li>\r\n# XPath: //li[2]\r\nquery = E.li[2]\r\n\r\n# Select the last <li> (equivalent to F.last())\r\n# XPath: //li[last()]\r\nquery_last = E.li[-1]\r\n```\r\n\r\n### 5. Working with Results\r\n\r\n- `.child()`/`.descendant()` return a single `XPathElement`.\r\n- `.children()`/`.descendants()` return an `Union[XPathElementList, str, float, bool, List[str]]`.\r\n\r\n#### `XPathElement` (Single Result)\r\n\r\n- `.tag`: The element's tag name (e.g., `'div'`).\r\n- `.attr`: A dictionary of all attributes.\r\n- `element['name']`: Access an attribute directly.\r\n- `.string()`: Get the concatenated text of the element and all its children (`string(.)`).\r\n- `.text()`: Get a list of only the element's direct text nodes (`./text()`).\r\n- `.parent()`: Get the parent element.\r\n- `.next_sibling()` / `.prev_sibling()`: Get adjacent sibling elements.\r\n- `.xpath(query)`: Execute a raw string or a constructed query within the context of this element.\r\n\r\n#### `XPathElementList` (Multiple Results)\r\n\r\n- `.one()`: Ensures the list contains exactly one element and returns it; otherwise, raises an error.\r\n- `.first()` / `.last()`: Get the first or last element; raises an error if the list is empty.\r\n- `len(element_list)`: Get the number of elements.\r\n- `.filter(func)`: Filter the list based on a function.\r\n- `.map(func)`: Apply a function to each element and return a list of the results.\r\n- Can be iterated over directly: `for e in my_list: ...`\r\n- Supports slicing and indexing: `my_list[0]`, `my_list[-1]`\r\n\r\n### 6. DOM Manipulation\r\n\r\nModify the document tree with ease.\r\n\r\n```python\r\nfrom xpathkit import XPathElement, E, A\r\n\r\n# Assuming 'root' is a parsed XPathElement\r\n# Find the <ul> element\r\nul = root.descendant(E.ul)\r\n\r\n# Create and append a new <li>\r\nnew_li = XPathElement.create(\"li\", attr={\"class\": \"new-item\"}, text=\"Item 4\")\r\nul.append(new_li)\r\n\r\n# Remove an element\r\nitem_to_remove = ul.child(E.li[A.class_.contains(\"disabled\")])\r\nif item_to_remove:\r\n ul.remove(item_to_remove)\r\n\r\n# Print the modified HTML\r\nprint(root.tostring())\r\n```\r\n\r\n---\r\n\r\n## \ud83d\udcc4 License\r\n\r\nThis project is licensed under the MIT License. See the `LICENSE` file for details.\r\n",
"bugtrack_url": null,
"license": null,
"summary": "A toolkit for convenient and expressive XPath operations based on lxml.",
"version": "0.3.5",
"project_urls": null,
"split_keywords": [
"xpath",
" lxml",
" xml",
" html",
" parser"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "1257c35f15c39de812046c9dc84609446db3a7cb4885e2b8964b997b601fb2bc",
"md5": "6a3b0829957a1bebb14ea678bb45f8d0",
"sha256": "06b689b48b3a06ce5563afffe67aea219638f4629b5bd4468a59355c8e0dc86d"
},
"downloads": -1,
"filename": "xpath_kit-0.3.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "6a3b0829957a1bebb14ea678bb45f8d0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 14230,
"upload_time": "2025-09-03T10:50:45",
"upload_time_iso_8601": "2025-09-03T10:50:45.262267Z",
"url": "https://files.pythonhosted.org/packages/12/57/c35f15c39de812046c9dc84609446db3a7cb4885e2b8964b997b601fb2bc/xpath_kit-0.3.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c2cadfca1edd91bec3d8fbcbd1289463f6f1d42cae32f6619ef97914d754c451",
"md5": "3ac03eb98a7ff19b37725fe03fd611ec",
"sha256": "f643bc4ed9b80de1a319fc12bd41a35783f6205d25c7fccf72c0356c73588f29"
},
"downloads": -1,
"filename": "xpath_kit-0.3.5.tar.gz",
"has_sig": false,
"md5_digest": "3ac03eb98a7ff19b37725fe03fd611ec",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 19872,
"upload_time": "2025-09-03T10:50:46",
"upload_time_iso_8601": "2025-09-03T10:50:46.919785Z",
"url": "https://files.pythonhosted.org/packages/c2/ca/dfca1edd91bec3d8fbcbd1289463f6f1d42cae32f6619ef97914d754c451/xpath_kit-0.3.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-03 10:50:46",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "xpath-kit"
}