pax_global_header00006660000000000000000000000064152566722120014522gustar00rootroot0000000000000052 comment=1a28c79b20e191fd26f8318ec0a068c5cea35cb2 rushter-selectolax-240bf6c/000077500000000000000000000000001525667221200157675ustar00rootroot00000000000000rushter-selectolax-240bf6c/.dockerignore000066400000000000000000000002251525667221200204420ustar00rootroot00000000000000.git .gitignore .gitmodules .idea .mypy_cache .pytest_cache .ruff_cache .venv build dist __pycache__ *.pyc *.pyo *.pyd *.so cython_debug docs/_build rushter-selectolax-240bf6c/.github/000077500000000000000000000000001525667221200173275ustar00rootroot00000000000000rushter-selectolax-240bf6c/.github/workflows/000077500000000000000000000000001525667221200213645ustar00rootroot00000000000000rushter-selectolax-240bf6c/.github/workflows/make_release.yml000066400000000000000000000036101525667221200245240ustar00rootroot00000000000000name: Build and upload to PyPI on: release: types: - published jobs: build_wheels: name: Build wheels for ${{ matrix.os }} runs-on: ${{ matrix.runs-on }} strategy: matrix: include: - os: linux-intel runs-on: ubuntu-latest - os: linux-arm runs-on: ubuntu-24.04-arm - os: windows-intel runs-on: windows-latest - os: windows-arm runs-on: windows-11-arm - os: macos-intel runs-on: macos-15-intel - os: macos-arm runs-on: macos-latest steps: - uses: actions/checkout@v4 with: submodules: true - name: Build wheels uses: pypa/cibuildwheel@v3.2.0 env: CIBW_PLATFORM: ${{ matrix.platform || 'auto' }} - uses: actions/upload-artifact@v4 with: name: cibw-wheels-${{ matrix.os }}-${{ matrix.platform}}-${{ strategy.job-index }} path: ./wheelhouse/*.whl build_sdist: name: Build source distribution runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - name: Build sdist run: | pip install -U Cython packaging setuptools wheel python setup.py build_ext --inplace --cython python setup.py sdist - uses: actions/upload-artifact@v4 with: path: dist/*.tar.gz retention-days: 1 name: cibw-sdist upload_pypi: needs: [build_wheels, build_sdist] runs-on: ubuntu-latest environment: release permissions: id-token: write if: github.event_name == 'release' && github.event.action == 'published' steps: - uses: actions/download-artifact@v4 with: pattern: cibw-* path: dist merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 rushter-selectolax-240bf6c/.github/workflows/pythonpackage.yml000066400000000000000000000054741525667221200247560ustar00rootroot00000000000000name: Python package on: pull_request: branches: - master push: branches: - master jobs: test: strategy: max-parallel: 6 matrix: python-version: ["3.11", "3.12", "3.13" ] platform: [ubuntu-24.04, macos-latest] runs-on: ${{ matrix.platform }} timeout-minutes: 6 steps: - uses: actions/checkout@v4 with: submodules: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools pip install -r requirements_dev.txt USE_LEXBOR=1 python setup.py build_ext --inplace --cython - name: Test with pytest run: | USE_LEXBOR=1 pytest tests - name: Test typesafety run: | pytest typesafety test-no-modest: strategy: max-parallel: 6 matrix: python-version: ["3.13"] platform: [ubuntu-24.04] runs-on: ${{ matrix.platform }} timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: submodules: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools pip install -r requirements_dev.txt rm -f selectolax/parser*.so python setup.py build_ext --inplace --cython --disable-modest - name: Verify selectolax.parser is not importable run: | python -c "import importlib.util; assert importlib.util.find_spec('selectolax.parser') is None" - name: Verify selectolax.parser attribute is absent run: | python -c "import selectolax; assert not hasattr(selectolax, 'parser')" - name: Test with pytest run: | pytest tests lint: strategy: max-parallel: 6 matrix: python-version: ["3.13" ] platform: [ ubuntu-24.04 ] runs-on: ${{ matrix.platform }} timeout-minutes: 6 steps: - uses: actions/checkout@v4 with: submodules: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools pip install -r requirements_dev.txt python3 -m pip install types-pyinstaller USE_LEXBOR=1 python setup.py build_ext --inplace --cython - name: Lint using Ruff run: ruff check tests - name: Lint Mypy run: mypy selectolax tests - name: Lint Cython run: cython-lint selectolax/ rushter-selectolax-240bf6c/.gitignore000066400000000000000000000015331525667221200177610ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg .idea # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # pyenv python configuration file .python-version selectolax/parser.c selectolax/lexbor.c # virtual env .venv/ venv/ tmp/ rushter-selectolax-240bf6c/.gitmodules000066400000000000000000000002341525667221200201430ustar00rootroot00000000000000[submodule "modest"] path = modest url = https://github.com/lexborisov/modest [submodule "lexbor"] path = lexbor url = https://github.com/lexbor/lexbor rushter-selectolax-240bf6c/.readthedocs.yaml000066400000000000000000000022631525667221200212210ustar00rootroot00000000000000# Read the Docs configuration file for Sphinx projects # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the OS, Python version and other tools you might need build: os: ubuntu-lts-latest tools: python: latest # You can also specify other tool versions: # nodejs: "20" # rust: "1.70" # golang: "1.20" jobs: pre_build: - git submodule sync - git submodule update --init --recursive - pip install -r requirements_dev.txt - python setup.py develop # Build documentation in the "docs/" directory with Sphinx sphinx: configuration: docs/conf.py # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs # builder: "dirhtml" # Fail on all warnings to avoid broken references # fail_on_warning: true # Optionally build your docs in additional formats such as PDF and ePub # formats: # - pdf # - epub # Optional but recommended, declare the Python requirements required # to build your documentation # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - requirements: requirements_dev.txt rushter-selectolax-240bf6c/CHANGES.md000066400000000000000000000223521525667221200173650ustar00rootroot00000000000000# selectolax Changelog # Version 0.4.13 - Support lexbor-only builds with `--disable-modest` - Importing `selectolax` no longer automatically imports `selectolax.parser` and `selectolax.lexbor` - Update lexbor # Version 0.4.12 - Add `options` parameter to `LexborHTMLParser` to pass Lexbor document parsing options (for example, `LexborDocumentOptions.WO_EVENTS` to disable mutation events). Fixes #221. - Fix `LexborHTMLParser.clone()` losing nodes for empty HTML fragments - Allow empty elements in `text_lexbor`. Fixes #220. - Update lexbor # Version 0.4.11 - Add NULL checks for CSS selectors module to prevent crashes - Fix html5test pretty print serialization mode checks - Update lexbor # Version 0.4.10 - Do not destroy nodes when stripping tags - Allow building selectolax using older lexbor versions (#218) - Update lexbor. Fixes crashes when parsing HTML (#217). # Version 0.4.9 - Add an ability to specify tags and namespace for fragmented parser - Add a new serialization mode when pretty printing: `html5test` - Allow empty HTML fragments - Fix attrs access for non-element nodes - Improve buffer cleanups - Fix duplicate text when doing `text(deep=True)` on a text node - Improve text concatenation performance - Improve attribute handling # Version 0.4.8 - Add Add `html_pretty`, `inner_html_pretty` methods - Enable free-threading - Improve `merge_text_nodes` - Update lexbor # Version 0.4.7 - Fix `.text()` and `iter()` for HTML fragments when there are multiple nodes at the root level. Resolves #209. - Update lexbor. Resolves #212. - Breaking changes: Empty tags are now serialized to `
` instead of `
` ([Commit 4530fed](https://github.com/lexbor/lexbor/commit/4530fed3f3a2b1c3729f7742be4f56131cb8e086)). - Improve `unwrap_tags` and `merge_text_nodes`. # Version 0.4.6 - Fix HTML parsing in fragment parser for `LexborHTMLParser` - Fix memory leak in fragment parser - Improve `skip_empty` parameter for text methods - Add `comment_content` method - Minor performance optimizations - Add `create_tag` method to `LexborHTMLParser` - Fix advanced selector (`.select()`) when attributes are empty. # Version 0.4.5 - Broken release. Not published to PyPi. # Version 0.4.4 - Add `is_fragment` parameter to `LexborHTMLParser` @pygarap - Add the ability to skip empty text nodes for lexbor backend to `.text`, `.iter`, `.traverse` @pygarap - Add new properties to lexbor backend: `is_element_node`, `is_text_node`, `is_comment_node`, `is_document_node`. @pygarap - Update `lexbor` library # Version 0.4.3 - Update `lexbor` library - Fix missing description on PyPi. # Version 0.4.2 - Broken release. Not published to PyPi. # Version 0.4.1 - Fix parsing of CSS selectors that contain Unicode characters. # Version 0.4.0 - Fix incorrect default value in docstrings for strict argument - Fix incorrect exception handling for `any_css_matches` - Fix docstring for `css_first` method - Fix memory leak in `merge_text_nodes` for lexbor backend - Update lexbor backend - Add `.inner_html` property. Allows to get and set inner HTML of a node. - Update various docstrings. - Optimize performance for`css_first` in lexbor backend - Fix segfaults when accessing attributes. Resolves #135. - Add new `.clone` method to lexbor backend. Resolve #117. - Improve unicode handling for malformed text. Resolves #138. - Fix segfaults when doing double `.decompose`. Resolves #179. - Fix sefgaults when doing double `.unwrap`. Resolves #169. - Fix typo for tag names. Clarify available tag names. ## Version 0.3.34 Released - Lexbor backend now supports `:lexbor-contains("abc" i)` CSS pseudo-class to match text nodes. ## Version 0.3.33 Released - Add `merge_text_nodes` to lexbor backend. Fixes #170. @amirshukayev - Performance improvements in Cython code. @Vizonex ## Version 0.3.32 Released - Update lexbor. New version of lexbor fixes bugs with CSS selectors. ## Version 0.3.31 Released - Improve type hints, add docstrings to type hints - Prevent decomposing of the root node - Unpin Cython version and make it Optional - Allow empty attribute values. Fixes #165. ## Version 0.3.30 Released - Update lexbor - Expose `SelectolaxError` exception in lexbor.pyi ## Version 0.3.29 Released - Feat: Add unwrap empty tags functionality. Fixes #159. ## Version 0.3.28 Released - Fix: Update lexbor and improve HTML serialization speed. Fixes #153. - Fix: typo in type annotations. Fixes #147. - Fix: Fix incorrect type annotations for `LexborHTMLParser.__init__`. Fixes #144. ## Version 0.3.27 Released - Fix: Header detected as head ## Version 0.3.26 Released - Improve type hints ## Version 0.3.25 Released - Feat: Add `parse_fragment()` and `create_tag()` - Add missing typing for `Node.insert_child()` - Add `Node.parser` to access the `HTMLParser` to which the node belongs ## Version 0.3.24 Released - Add `Node.insert_child` method to lexbor and modest backends ## Version 0.3.23 Released - Add Python 3.13 wheels - Update lexbor ## Version 0.3.21 Released - ***Breaking change***: `lexbor` backend now includes the root node when querying CSS selectors. Same as `Modest` backend. - Fix `css_matches` and `any_css_matches` methods for `Modest` backend on some compilers ## Version 0.3.20 Released - Fixup for 0.3.19 release - Fix tag order for `lexbor` backend ## Version 0.3.19 Released - Increase maximum HTML size to 2.4GB ## Version 0.3.18 Released - Fix memory leak when using CSS selectors, `lexbor` backend ## Version 0.3.17 Released - Update lexbor - Add Python 3.12 wheels ## Version 0.3.16 Released - Make HTML nodes hashable - Pin Cython version ## Version 0.3.15 Released - Improve typing. Thanks to @nesb1 ## Version 0.3.14 Released - Fix memory leak for `lexbor` backend ## Version 0.3.13 Released - Update `lexbor` ## Version 0.3.12 Released - Update `lexbor` - Add Python 3.11 wheels ## Version 0.3.11 Released - Fix out-of-bounds bug for `merge_text_nodes` method. ## Version 0.3.10 Released This release does not contain any changes. Due to a typo in the version number ([#70](https://github.com/rushter/selectolax/issues/70)), we need to make a new release. ## Version 0.3.9 Released - Remove trailing separator when using `text(deep=True, separator='x')`. - Add a new `merge_text_nodes` method for Modest backend. ## Version 0.3.8 Released - Fix incorrect text handling when using `text(deep=True)` on a text node. ## Version 0.3.7 Released - Fix return type of HTMLParser.tags ## Version 0.3.6 Released - Improve text handling - Add binary builds for Python 3.10 and ARM on MacOS and Linux ## Version 0.3.5 Released - Add type annotations ## Version 0.3.4 Released - Fix `HTMLParser.html` ## Version 0.3.3 Released - Use `document` for the `HTMLParser.html`, `LexborHTMLParser.html` root properties ## Version 0.3.2 Released - Fix `selector` method for lexbor - Improve text extraction for lexbor ## Version 0.3.1 Released - Fix `setup.py` for Windows ## Version 0.3.0 Released - Added `lexbor` backend - Fix cloning for `Modest` backend ## Version 0.2.14 Released - Added advanced Selector (the `select` method) - Improved speed of `strip_tags` - Added `clone` method for the `HtmlParser` object - Exposed `detect_encoding`, `decode_errors`, `use_meta_tags`, `raw_html` attributes for `HtmlParser` - Added `sget` method to the `attrs` property ## Version 0.2.13 Released - Don't throw exception when encoding text as UTF-8 bytes fails ([#40](https://github.com/rushter/selectolax/issues/40)). - Fix Node.attrs.items() causes ([#39](https://github.com/rushter/selectolax/issues/39)). ## Version 0.2.12 Released - Build wheels Apple Silicon ## Version 0.2.11 Released - Fix strip argument is ignored for the root node ([#35](https://github.com/rushter/selectolax/issues/35)). - Fix CSS parser hangs on a bad CSS selector ([#36](https://github.com/rushter/selectolax/issues/36)). ## Version 0.2.10 Released - Fix root node property ([#32](https://github.com/rushter/selectolax/issues/32)). The `root` property now points to the html tag. ## Version 0.2.9 Released - Fix README for PyPI ## Version 0.2.8 Released - Add wheels for Python 3.9 ## Version 0.2.7 Released - Add `raw_value` attribute for `Node` objects ([#22](https://github.com/rushter/selectolax/issues/22)) - Improve node modification operations ## Version 0.2.6 Released - Fix dependency on the source `Node` when inserting to or modifying destination `Node` ## Version 0.2.5 Released - Allow to pass Node instances to `replace_with`, `insert_before` and `insert_after` methods - Added `insert_before` and `insert_after` methods ## Version 0.2.4 Released - Set maximum input size to 80MB - Update modest ## Version 0.2.3 Released - Rebuild PyPi wheels to support Python 3.8 and manylinux2010 ## Version 0.2.2 Released - Fix node comparison ## Version 0.2.1 Released - Add optional `include_text` parameter for the `iter` and `traverse` methods ## Version 0.2.0 Released - Fix `iter()` does not yield text nodes - Switch from TravisCI to Github Actions - Build and ship wheels for Windows, MacOS and Linux using Azure Pipelines - Add `unwrap` and `unwrap_tags` method ([#7](https://github.com/rushter/selectolax/issues/7)) - Add `replace_with` method ([#13](https://github.com/rushter/selectolax/issues/13)) - Add `attrs` property - Add `traverse` method rushter-selectolax-240bf6c/Dockerfile000066400000000000000000000006671525667221200177720ustar00rootroot00000000000000FROM python:3.12-slim RUN apt-get update && apt-get install -y \ gcc \ libc-dev \ make \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements_dev.txt . RUN pip install --no-cache-dir Cython setuptools wheel && \ pip install --no-cache-dir -r requirements_dev.txt COPY . . RUN python setup.py install RUN mkdir /test_run && \ cp test.py *.html /test_run/ WORKDIR /test_run CMD ["python", "test.py"] rushter-selectolax-240bf6c/LICENSE000066400000000000000000000020651525667221200167770ustar00rootroot00000000000000 MIT License Copyright (c) 2018-2026, Artem Golubin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rushter-selectolax-240bf6c/MANIFEST.in000066400000000000000000000011551525667221200175270ustar00rootroot00000000000000include CONTRIBUTING.rst include HISTORY.rst include LICENSE include README.md include CHANGES.md include selectolax/* include selectolax/lexbor/* include selectolax/modest/* include selectolax/lexbor/*.so exclude selectolax/*.so recursive-include modest/source *.c *.h recursive-include modest/include *.h include modest/* include modest/include/* include modest/source/* recursive-include lexbor/source *.c *.h include lexbor/* include lexbor/source/* recursive-include tests * recursive-exclude * __pycache__ recursive-exclude * *.py[co] recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif rushter-selectolax-240bf6c/Makefile000066400000000000000000000054571525667221200174420ustar00rootroot00000000000000.PHONY: clean clean-test clean-pyc clean-build docs help .DEFAULT_GOAL := help define BROWSER_PYSCRIPT import os, webbrowser, sys try: from urllib import pathname2url except: from urllib.request import pathname2url webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) endef export BROWSER_PYSCRIPT define PRINT_HELP_PYSCRIPT import re, sys for line in sys.stdin: match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) if match: target, help = match.groups() print("%-20s %s" % (target, help)) endef export PRINT_HELP_PYSCRIPT BROWSER := python -c "$$BROWSER_PYSCRIPT" help: @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts clean-build: ## remove build artifacts rm -fr build/ rm -fr dist/ rm -rf .eggs/ find . -maxdepth 1 -name '*.egg-info' -exec rm -fr {} + find . -maxdepth 1 -name '*.egg' -exec rm -f {} + clean-pyc: ## remove Python file artifacts find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + clean-test: ## remove test and coverage artifacts rm -f .coverage rm -fr htmlcov/ lint: ## check style with ruff ruff format selectolax tests ruff check --fix selectolax tests cython-lint selectolax/ mypy selectolax tests .PHONY: test test: ## run tests quickly with the default Python pytest tests -s -v .PHONY: test-no-modest test-no-modest: ## build a lexbor-only extension (modest disabled) and run tests rm -f selectolax/parser*.so python setup.py build_ext --inplace --cython --disable-modest python -c "import importlib.util, selectolax; assert not hasattr(selectolax, 'parser'); assert importlib.util.find_spec('selectolax.parser') is None" pytest tests -s -v coverage: ## check code coverage quickly with the default Python coverage run --source selectolax -m pytest coverage report -m coverage html $(BROWSER) htmlcov/index.html docs: ## generate Sphinx HTML documentation, including API docs rm -f docs/selectolax.rst rm -f docs/modules.rst sphinx-apidoc -o docs/ selectolax $(MAKE) -C docs clean $(MAKE) -C docs html $(BROWSER) docs/_build/html/index.html servedocs: docs ## compile the docs watching for changes watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . release: clean ## package and upload a release python setup.py sdist upload python setup.py bdist_wheel upload dist: clean ## builds source and wheel package python setup.py sdist python setup.py bdist_wheel ls -l dist install: clean ## install the package to the active Python's site-packages python setup.py install dev: python setup.py build_ext --inplace --cython --lexbor dev-static: clean-build python setup.py build_ext --inplace --cython --static --disable-modest rushter-selectolax-240bf6c/README.md000066400000000000000000000135321525667221200172520ustar00rootroot00000000000000![selectolax logo](docs/logo.png) --- A fast HTML5 parser with CSS selectors, written in Cython, using [Modest](https://github.com/lexborisov/Modest/) and [Lexbor](https://github.com/lexbor/lexbor) engines. --- [![PyPI - Version](https://img.shields.io/pypi/v/selectolax?logo=pypi&label=Pypi&logoColor=fff)](https://pypi.org/project/selectolax) [![PyPI Total Downloads](https://static.pepy.tech/badge/selectolax)](https://pepy.tech/projects/selectolax) [![CI](https://img.shields.io/github/actions/workflow/status/rushter/selectolax/pythonpackage.yml?branch=master&logo=githubactions&label=CI)](https://github.com/rushter/selectolax/actions/workflows/pythonpackage.yml?query=branch%3Amaster+event%3Apush) [![Python Versions](https://img.shields.io/pypi/pyversions/selectolax?logo=python&logoColor=fff&label=Python)](https://pypi.org/project/selectolax) [![GitHub License](https://img.shields.io/github/license/rushter/selectolax?logo=github&label=License)](https://github.com/rushter/selectolax/blob/master/LICENSE) --- ## Installation From PyPI using pip: ```bash pip install selectolax ``` If installation fails due to compilation errors, you may need to install [Cython](https://github.com/cython/cython): ```bash pip install selectolax[cython] ``` This usually happens when you try to install an outdated version of selectolax on a newer version of Python. Development version from GitHub: ```bash git clone --recursive https://github.com/rushter/selectolax cd selectolax pip install -r requirements_dev.txt python setup.py install ``` How to compile selectolax while developing: ```bash make clean make dev ``` ## Basic examples Here are some basic examples to get you started with selectolax: Parsing HTML and extracting text: ```python In [1]: from selectolax.lexbor import LexborHTMLParser ...: ...: html = """ ...:

Hi there

...:
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
...:
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
...: """ ...: tree = LexborHTMLParser(html) In [2]: tree.css_first('h1#title').text() Out[2]: 'Hi there' In [3]: tree.css_first('h1#title').attributes Out[3]: {'id': 'title', 'data-updated': '20201101'} In [4]: [node.text() for node in tree.css('.post')] Out[4]: ['Lorem Ipsum is simply dummy text of the printing and typesetting industry. ', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'] ``` ### Using advanced CSS selectors ```python In [1]: html = "

link

text

" ...: selector = "div > :nth-child(2n+1):not(:has(a))" In [2]: for node in LexborHTMLParser(html).css(selector): ...: print(node.attributes, node.text(), node.tag) ...: print(node.parent.tag) ...: print(node.html) ...: {'id': 'p1'} p div

{'id': 'p5'} text p div

text

``` #### Using `lexbor-contains` CSS pseudo-class to match text ```python from selectolax.lexbor import LexborHTMLParser html = "

hello

lexbor is AwesOme

" parser = LexborHTMLParser(html) # Case-insensitive search results = parser.css('p:lexbor-contains("awesome" i)') # Case-sensitive search results = parser.css('p:lexbor-contains("AwesOme")') assert len(results) == 1 assert results[0].text() == "lexbor is AwesOme" ``` * [More examples](https://selectolax.readthedocs.io/en/latest/examples.html) ### Available backends Selectolax supports two backends: `Modest` and `Lexbor`. By default, all examples use the `Lexbor` backend. Most of the features between backends are almost identical, but there are some differences. As of 2024, the preferred backend is `Lexbor`. The `Modest` backend is still available for compatibility reasons and the underlying C library that selectolax uses is not maintained anymore. To use `lexbor`, just import the parser and use it in the similar way to the `HTMLParser`. ```python In [1]: from selectolax.lexbor import LexborHTMLParser In [2]: html = """ ...: Hi there ...:
2021-08-15
...: """ In [3]: parser = LexborHTMLParser(html) In [4]: parser.root.css_first("#updated").text() Out[4]: '2021-08-15' ``` ## Simple Benchmark * Extract title, links, scripts and a meta tag from main pages of top 754 domains. See `examples/benchmark.py` for more information. | Package | Time | |-------------------------------|-----------| | Beautiful Soup (html.parser) | 61.02 sec.| | lxml / Beautiful Soup (lxml) | 9.09 sec. | | html5_parser | 16.10 sec.| | selectolax (Modest) | 2.94 sec. | | selectolax (Lexbor) | 2.39 sec. | ## Links * [selectolax API reference and examples](https://selectolax.readthedocs.io/en/latest/index.html) * [Video introduction to web scraping using selectolax](https://youtu.be/HpRsfpPuUzE) * [How to Scrape 7k Products with Python using selectolax and httpx](https://www.youtube.com/watch?v=XpGvq755J2U) * [Modest introduction](https://lexborisov.github.io/Modest/) * [Modest benchmark](https://lexborisov.github.io/benchmark-html-parsers/) * [Python benchmark](https://rushter.com/blog/python-fast-html-parser/) * [Another Python benchmark](https://www.peterbe.com/plog/selectolax-or-pyquery) * [Universal interface to lxml and selectolax](https://github.com/lorien/domselect) ## License * Modest engine — [LGPL2.1](https://github.com/lexborisov/Modest/blob/master/LICENSE) * lexbor engine — [Apache-2.0 license](https://github.com/lexbor/lexbor?tab=Apache-2.0-1-ov-file#readme) * selectolax - [MIT](https://github.com/rushter/selectolax/blob/master/LICENSE) ## Contributors Thanks to all the contributors of selectolax! rushter-selectolax-240bf6c/docs/000077500000000000000000000000001525667221200167175ustar00rootroot00000000000000rushter-selectolax-240bf6c/docs/.gitignore000066400000000000000000000000571525667221200207110ustar00rootroot00000000000000/selectolax.rst /selectolax.*.rst /modules.rst rushter-selectolax-240bf6c/docs/Makefile000066400000000000000000000151721525667221200203650ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/selectolax.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/selectolax.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/selectolax" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/selectolax" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." rushter-selectolax-240bf6c/docs/conf.py000077500000000000000000000212311525667221200202200ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # selectolax documentation build configuration file, created by # sphinx-quickstart on Tue Jul 9 22:26:36 2013. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import platform import sys # If extensions (or modules to document with autodoc) are in another # directory, add these directories to sys.path here. If the directory is # relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # sys.path.insert(0, os.path.abspath('../../')) # Get the project root dir, which is the parent dir of this cwd = os.getcwd() project_root = os.path.dirname(cwd) # Insert the project root dir as the first element in the PYTHONPATH. # This lets us ensure that the source package is imported, and that its # version is used. if platform.system() == "Darwin": sys.path.insert(0, project_root) import selectolax # -- General configuration --------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.viewcode", "numpydoc", "sphinxext.opengraph", "sphinx_copybutton", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "selectolax" copyright = "2018-2026, Artem Golubin" # The version info for the project you're documenting, acts as replacement # for |version| and |release|, also used in various other places throughout # the built documents. # # The short X.Y version. version = selectolax.__version__ # The full version, including alpha/beta/rc tags. release = selectolax.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to # some non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built # documents. # keep_warnings = False # -- Options for HTML output ------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'default' html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a # theme further. For a list of options available for each theme, see the # documentation. html_theme_options = { "source_repository": "https://github.com/rushter/selectolax", "source_branch": "master", "source_directory": "docs/", } # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as # html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the # top of the sidebar. html_logo = "logo.png" # The name of an image file (within the static path) to use as favicon # of the docs. This file should be a Windows icon file (.ico) being # 16x16 or 32x32 pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) # here, relative to this directory. They are copied after the builtin # static files, so a file named "default.css" will overwrite the builtin # "default.css". # html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names # to template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. # Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. # Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages # will contain a tag referring to it. The value of this option # must be the base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "selectolaxdoc" # -- Options for LaTeX output ------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ("index", "selectolax.tex", "selectolax Documentation", "Artem Golubin", "manual"), ] # The name of an image file (relative to this directory) to place at # the top of the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings # are parts, not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output ------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("index", "selectolax", "selectolax Documentation", ["Artem Golubin"], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ---------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "selectolax", "selectolax Documentation", "Artem Golubin", "selectolax", "One line description of project.", "Miscellaneous", ), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False numpydoc_show_class_members = False rushter-selectolax-240bf6c/docs/examples.rst000066400000000000000000000536341525667221200213020ustar00rootroot00000000000000Examples ======== This page contains simple examples of how to use Selectolax for HTML parsing and manipulation. .. note:: All examples use the Lexbor backend (``from selectolax.lexbor import LexborHTMLParser``) which provides better performance and features compared to the older Modest backend. Basic HTML Parsing ------------------ There are 3 ways to create or parse objects in Selectolax: 1. Parse HTML as a full document using ``LexborHTMLParser()`` 2. Parse HTML as a fragment using ``LexborHTMLParser(..., is_fragment=True)`` 3. Create single node using ``LexborHTMLParser(...).create_node()`` - ``LexborHTMLParser()`` - Returns the HTML tree as parsed by Lexbor, unmodified. The HTML is assumed to be a full document. ````, ````, and ```` tags are added if missing. - ``LexborHTMLParser(..., is_fragment=True)`` - Intended for HTML fragments/partials. Behaves the same way as `DocumentFragment` in browsers. Drops ````, ````, and ```` tags if present in the input HTML. Use it to parse snippets of HTML that are not complete documents. .. code-block:: python from selectolax.lexbor import LexborHTMLParser html = """

Welcome to selectolax tutorial

Lorem ipsum

Lorem ipsum dolor sit amet, ea quo modus meliore platonem.

""" fragment = """

Hello there!

""" # Parse HTML as a full document parser = LexborHTMLParser(html) # Parse HTML as a fragment frag_parser = LexborHTMLParser(html, is_fragment=True) # Create a new node for `parser`. node = parser.create_node("div") CSS Selectors ------------- Select All Elements with CSS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Find all paragraph elements with class 'p3' and examine their properties. .. code-block:: python from selectolax.lexbor import LexborHTMLParser html = """

Lorem ipsum

Lorem ipsum dolor sit amet, ea quo modus meliore platonem.

""" parser = LexborHTMLParser(html) selector = "p.p3" for node in parser.css(selector): print('---------------------') print('Node: %s' % node.html) print('attributes: %s' % node.attributes) print('node text: %s' % node.text(deep=True, separator='', strip=False)) print('tag: %s' % node.tag) print('parent tag: %s' % node.parent.tag) if node.last_child: print('last child inside current node: %s' % node.last_child.html) print('---------------------\n') **Output:** .. code-block:: text --------------------- Node: attributes: {'class': 'p3', 'style': 'display:none;'} node text: Excepteur sint occaecat cupidatat non proident tag: p parent tag: div last child inside current node: Excepteur sint occaecat cupidatat non proident --------------------- --------------------- Node:

Lorem ipsum

attributes: {'class': 'p3', 'vid': ''} node text: Lorem ipsum tag: p parent tag: div last child inside current node: Lorem ipsum --------------------- Select First Match ~~~~~~~~~~~~~~~~~~ Get the first matching element using CSS selectors. .. code-block:: python parser = LexborHTMLParser(html) # Get first h1 element print("H1: %s" % parser.css_first('h1').text()) **Output:** .. code-block:: text H1: Welcome to selectolax tutorial Default Return Values ~~~~~~~~~~~~~~~~~~~~~ Handle cases where no elements match your selector by providing a default value. .. code-block:: python # Return default value if no matches found print("Title: %s" % parser.css_first('title', default='not-found')) **Output:** .. code-block:: text Title: not-found Strict Mode ~~~~~~~~~~~ Ensure exactly one match exists, otherwise raise an error. .. code-block:: python # This will raise an error if multiple matches are found try: result = parser.css_first("p.p3", default='not-found', strict=True) except Exception as e: print(f"Error: {e}") **Output:** .. code-block:: text ValueError: Expected 1 match, but found 2 matches CSS Chaining ~~~~~~~~~~~~ Chain multiple CSS selectors to progressively filter results. .. code-block:: python html = """
""" parser = LexborHTMLParser(html) # Chain selectors: start with div, then span, then .red red_spans = parser.select('div').css("span").css(".red").matches print([node.html for node in red_spans]) **Output:** .. code-block:: text ['', ''] HTML manipulation ----------------- Getting HTML data back ~~~~~~~~~~~~~~~~~~~~~~ You can get HTML data back using `.html` or `.inner_html` properties. They can be called on any node. .. code-block:: python from selectolax.lexbor import LexborHTMLParser html = """
Hi there
2021-08-15
""" parser = LexborHTMLParser(html) node = parser.css_first("#main") print("Inner html:\n") print(node.inner_html) print("\nOuter html:\n") print(node.html) **Output:** .. code-block:: text Inner html:
Hi there
2021-08-15
Outer html:
Hi there
2021-08-15
Changing HTML ~~~~~~~~~~~~~~ You can also change HTML by setting the `.inner_html` property. .. code-block:: python from selectolax.lexbor import LexborHTMLParser html = """
Hi there
""" parser = LexborHTMLParser(html) node = parser.css_first("#main") print("Old html:\n") print(node.html) node.inner_html = "Test" print("\nNew html:\n") print(node.inner_html) **Output:** Old html:
Hi there
New html:
Test
DOM Navigation -------------- Parent Elements ~~~~~~~~~~~~~~~ Get parent element in the DOM tree. .. code-block:: python # Print parent of p#stext print(parser.css_first('p#stext').parent.html) **Output:** .. code-block:: text

Lorem ipsum dolor sit amet, ea quo modus meliore platonem.

Nested Selectors ~~~~~~~~~~~~~~~~ Chain CSS selectors to find nested elements. .. code-block:: python # Chain CSS selectors result = parser.css_first('div#text').css_first('p:nth-child(2)').html print(result) **Output:** .. code-block:: text

Lorem ipsum

Iterating Over Child Nodes ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Walk all child nodes of an element. .. code-block:: python for node in parser.css("div#text"): for cnode in node.iter(): print(cnode.tag, cnode.html) **Output:** .. code-block:: text p p

Lorem ipsum

DOM Modification ---------------- Tag Removal ~~~~~~~~~~~ Completely remove elements from the DOM tree. .. code-block:: python parser = LexborHTMLParser(html) # Remove all p tags for node in parser.tags('p'): node.decompose() print(parser.body.html) **Output:** .. code-block:: text

Welcome to selectolax tutorial

Tag Unwrapping ~~~~~~~~~~~~~~ Remove tags but preserve their content. .. code-block:: python parser = LexborHTMLParser(html) # Remove p and i tags but keep their content parser.unwrap_tags(['p', 'i']) print(parser.body.html) **Output:** .. code-block:: text

Welcome to selectolax tutorial

Excepteur sint occaecat cupidatat non proident Lorem ipsum
Lorem ipsum dolor sit amet, ea quo modus meliore platonem.
Attribute Manipulation ~~~~~~~~~~~~~~~~~~~~~~ Add, modify, and remove element attributes. .. code-block:: python parser = LexborHTMLParser(html) node = parser.css_first('div#text') # Set attributes node.attrs['data'] = 'secret data' node.attrs['id'] = 'new_id' print(node.attributes) # Remove attributes del node.attrs['id'] print(node.attributes) print(node.html) **Output:** .. code-block:: text {'id': 'new_id', 'data': 'secret data'} {'data': 'secret data'}

Lorem ipsum

Inserting Nodes ~~~~~~~~~~~~~~~ Insert new content into the DOM at specific positions. .. code-block:: python html = """
""" parser = LexborHTMLParser(html) # Insert text before an element red_node = parser.css_first('.red') red_node.insert_before("Hello") # Insert HTML nodes subtree = LexborHTMLParser("
Hi
") green_node = parser.css_first('.green') green_node.insert_before(subtree) # Insert before, after, or as child car_div = parser.create_node("div") car_div.inner_html = "Car" green_node.insert_before(car_div) green_node.insert_after(car_div) green_node.insert_child(car_div) print(parser.body.html) Tree Traversal -------------- Walk every node in the DOM tree and extract text content. .. code-block:: python parser = LexborHTMLParser(html) # Traverse the entire tree for node in parser.root.traverse(include_text=True): if node.tag == '-text': text = node.text(deep=True).strip() if text: print(text) else: print(node.tag) **Output:** .. code-block:: text html head body div p Excepteur i sint occaecat cupidatat non proident p Lorem ipsum div p Lorem ipsum dolor sit amet, ea quo modus meliore platonem. Common Patterns --------------- Extract Text Content ~~~~~~~~~~~~~~~~~~~~ Extract text content from HTML elements with various formatting options. .. code-block:: python parser = LexborHTMLParser('

Hello world!

') # Get text content with different options node = parser.css_first('p') # Get all text content print(node.text()) # "Hello world!" # Get text with custom separator print(node.text(separator=' | ')) # "Hello | world | !" # Get text without stripping whitespace print(node.text(strip=False)) **Output:** .. code-block:: text Hello world! Hello | world | ! Hello world! Clean HTML ~~~~~~~~~~ Remove potentially dangerous or unwanted HTML elements. .. code-block:: python dirty_html = '''

Good content

More content

''' parser = LexborHTMLParser(dirty_html) # Remove unwanted tags for tag in parser.css('script, style'): tag.decompose() print(parser.body.html) **Output:** .. code-block:: text

Good content

More content

Extract Links and Images ~~~~~~~~~~~~~~~~~~~~~~~~ Extract all links and images from HTML content. .. code-block:: python html = ''' ''' parser = LexborHTMLParser(html) # Extract all links for link in parser.css('a[href]'): print(f"Link: {link.text()} -> {link.attrs['href']}") # Extract all images for img in parser.css('img[src]'): print(f"Image: {img.attrs.get('alt', 'No alt')} -> {img.attrs['src']}") **Output:** .. code-block:: text Link: Link 1 -> https://example.com Link: Link 2 -> /page2 Image: Image 1 -> image1.jpg Image: Image 2 -> image2.png Advanced selectors ------------------ Text Content Filtering ~~~~~~~~~~~~~~~~~~~~~~ Use advanced selectors to filter elements based on their text content. .. code-block:: python html = """ """ parser = LexborHTMLParser(html) # Filter script tags containing specific text scripts_with_super = parser.select('script').text_contains("super").matches print([node.text() for node in scripts_with_super]) **Output:** .. code-block:: text ['\n var super_variable = 100;\n'] CSS Attribute and Pseudo-class Selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python html = """

First Post

Content of first post

John 2023-01-01

Second Post

Content of second post

Jane 2023-01-02
""" parser = LexborHTMLParser(html) # Attribute selectors published_posts = parser.css('article.post.published') print(f"Published posts: {len(published_posts)}") # Descendant selectors authors = parser.css('article .meta .author') for author in authors: print(f"Author: {author.text()}") # Pseudo-class selectors first_article = parser.css('article:first-child') if first_article: print(f"First article title: {first_article[0].css_first('h2').text()}") # Attribute value selectors specific_post = parser.css_first('article[data-id="1"]') if specific_post: print(f"Post ID 1 title: {specific_post.css_first('h2').text()}") **Output:** .. code-block:: text Published posts: 1 Author: John Author: Jane First article title: First Post Post ID 1 title: First Post Text Content Pseudo-class Selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Use lexbor-specific pseudo-classes for case-sensitive and case-insensitive text matching. .. code-block:: python html = '

hello

lexbor is AwesOme

' parser = LexborHTMLParser(html) # Case-insensitive search results_ci = parser.css('p:lexbor-contains("awesome" i)') print(f"Case-insensitive results: {len(results_ci)}") # Case-sensitive search results_cs = parser.css('p:lexbor-contains("AwesOme")') print(f"Case-sensitive results: {len(results_cs)}") print(f"Matching text: {results_cs[0].text()}") **Output:** .. code-block:: text Case-insensitive results: 1 Case-sensitive results: 1 Matching text: lexbor is AwesOme Sibling Navigation ------------------ Navigate between sibling elements in the DOM. .. code-block:: python html = """ """ parser = LexborHTMLParser(html) active_link = parser.css_first("a.active") if active_link: print(f"Active link: {active_link.text()}") # We need to call it twice, because there are text nodes (spaces and new lines) between elements if active_link.prev: print(f"Previous link: {active_link.prev.prev.text()}") if active_link.next: print(f"Next link: {active_link.next.next.text()}") **Output:** .. code-block:: text Active link: Contact Previous link: About Next link: Blog Table Parsing ------------- Parse HTML tables and extract structured data. .. code-block:: python table_html = """
Name Age City Occupation
Alice Johnson 28 New York Software Engineer
Bob Smith 35 Los Angeles Designer
Carol Brown 42 Chicago Manager
""" parser = LexborHTMLParser(table_html) # Extract headers headers = [th.text() for th in parser.css('thead th')] print("Headers:", headers) # Extract data rows rows = [] for tr in parser.css('tbody tr'): row_data = [td.text() for td in tr.css('td')] rows.append(row_data) # Display as structured data for i, row in enumerate(rows): print(f"\nRow {i+1}:") for header, value in zip(headers, row): print(f" {header}: {value}") **Output:** .. code-block:: text Headers: ['Name', 'Age', 'City', 'Occupation'] Row 1: Name: Alice Johnson Age: 28 City: New York Occupation: Software Engineer Row 2: Name: Bob Smith Age: 35 City: Los Angeles Occupation: Designer Row 3: Name: Carol Brown Age: 42 City: Chicago Occupation: Manager Form Data Extraction -------------------- Parse HTML forms and extract input data. .. code-block:: python form_html = """
""" parser = LexborHTMLParser(form_html) # Extract form metadata form = parser.css_first('form') print(f"Form ID: {form.attrs.get('id')}") print(f"Form method: {form.attrs.get('method')}") print(f"Form action: {form.attrs.get('action')}") # Extract input fields print("\nInput fields:") for input_field in parser.css('input'): field_type = input_field.attrs.get('type', 'text') name = input_field.attrs.get('name') value = input_field.attrs.get('value', '') checked = 'checked' in input_field.attrs print(f" {name} ({field_type}): {value} {'[checked]' if checked else ''}") # Extract select options print("\nSelect fields:") for select in parser.css('select'): name = select.attrs.get('name') print(f" {name}:") for option in select.css('option'): value = option.attrs.get('value') text = option.text() selected = 'selected' in option.attrs print(f" {value}: {text} {'[selected]' if selected else ''}") # Extract textarea print("\nTextarea fields:") for textarea in parser.css('textarea'): name = textarea.attrs.get('name') content = textarea.text() print(f" {name}: {content}") **Output:** .. code-block:: text Form ID: contact-form Form method: post Form action: /submit Input fields: name (text): John Doe email (email): newsletter (checkbox): [checked] Select fields: country: us: United States ca: Canada [selected] uk: United Kingdom Textarea fields: message: Hello there! rushter-selectolax-240bf6c/docs/index.rst000066400000000000000000000010541525667221200205600ustar00rootroot00000000000000selectolax ========== Selectolax is a fast HTML5 parser with CSS selectors, written in Cython, using [Modest](https://github.com/lexborisov/Modest/) and [Lexbor](https://github.com/lexbor/lexbor) engines. It supports two backends: - `Lexbor `__ (preferred) - `Modest `__ (first generation of lexbor, **deprecated**) API === .. toctree:: :maxdepth: 2 parser lexbor examples Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` rushter-selectolax-240bf6c/docs/lexbor.rst000066400000000000000000000004501525667221200207430ustar00rootroot00000000000000selectolax.lexbor module ======================== .. automodule:: selectolax.lexbor LexborHTMLParser ---------------- .. autoclass:: LexborHTMLParser :members: LexborNode ---------- .. autoclass:: LexborNode :members: Selector -------- .. autoclass:: LexborSelector :members: rushter-selectolax-240bf6c/docs/logo.png000066400000000000000000000075321525667221200203740ustar00rootroot00000000000000‰PNG  IHDR¨¸ØUÜ�iCCPsRGB IEC61966-2.1(‘u‘Ï+DQÇ?3L#F£XXX¼„ÕŒ%6ÊH(i£ 63o~©ùñzo&M¶ÊVQbãׂ¿€­²VŠHÉš-±azΛ™ɜ۹çs¿÷žÓ½ç‚=”V3F£2Ù¼œò+‹á%ÅùŠíx°ETCf©kŸ÷جxëµjÕ?÷¯µÄ↠¶&á1UÓóÂÓ³kyÍâá5‰ Ÿ {t¹ ð�¥G+übq²Âßë¡àØÛ„•ä/Žþb5¥g„ååôdÒµzë%®xva^b·xA¦ð£0Ã$ 3À¨ÌÃx¤_VÔÉ÷•óçÈI®*³F�U’¤ÈKo R=.1!z\Fš¢Õÿ¿}5Cƒ•ê.?8žMó½œÛPÚ2ͯ#Ó,CÃ\fkù¹Cù}«¦õ€{ίjZt.6¡óQ‹è‘²Ô nO$àíZÃÐ~ÍË•žU÷9y€Ðº|Õ5ìíCŸœw¯ü%ÝgÈiÓ pHYs  šœ IDATxœíœy”WµÇ?ƒà ¸�àZź hlÙÔjµ h[‰Öc¥ EëÚÅ­K]jµ*Ç�R-mµ”Šˆ (d‰Z9Q”(Y…éIæå÷xï·Ìü”ž#ßsæ¼¼¼››ä½›Ü-¿©¡ÊPÆŽÎõ·ûj)Þ«v[ñåAM5™)ck�EÀnÀ4-Å׫É+¾|hVe~Gá„àUæ]6”±5ÊØ6[ªÿ­¨j«Ìï´¨¬«Ì»$”±­�á@`geì‹ÀýZЇšÈ÷Qà+�®ÔR<ÓÞM…2¶†d£Ù¤¥¨¯O Ü›ñh…–âè¦ò¯UPelsàTk´ ªÅ»Ìþw^Šª�ŽRƾ¯¥˜ÜöÇ;gÔ¯kÏjá ௾|80³ <÷ºdÔ¿UÞ¡š*þ(`w_Þêý* …3Æeì6�aªŒmE¶p‚³··4:Dåj�§CNý>ßjªø-¦Þ•±€óüí&à!`p!Ð8'¼sÁ¾ 0#ºï´÷åÿ4f¼UF¦�ÀGUâù…sîŠÛ̾ðù–% ÊØÓ€ ZŠOrž7úû[£¥x7õ|ðˆ–âóR‰Ça‡ü™–â~ßï<`„¯ßµ1ŒµuÀá^;¸øXÙÈñVA@ë´«ÁPKqp4|ÛðÝþ¿vPï Î$€,”Rïƒ�s•±JKñy¬Â#ýu ðpT?;*g ¨2v@õÀËZŠÏJôµ‡¿.*Ç!Qƶ¾æÛMÒR¬*A_àÛü xSK±)E³‰y¶—¿~”¹X™ZÏ_œ�9EK±¸ÄvšûrIUƶz“µ£±nÐR|êiš;ù&Ÿ¥ßI<�\T{,N%ž,)±:K©÷@`º2öEø4Â_çh)â]mngïL‹øPÔ%Àrày`P§Œýy‰¾‚€–\hÊØo‹ŒÃ ÑHÿ�²è;oóp }.`”±»¤Hçÿõ=}]—¨.üí7Rƶ¦øöc€•±O(cóll(´G‹ÎYÛx˜LSÆî X?–)ò7|ý\¿h�#£ñ�ÞìE)c·UÆÞé; «³®È bï}z†zo´õ·€ÉÊØ�Å&Ú„^ðA´k´cüß‚T›ë�¡Q[€]€;”±Å áƒÝM”±}�ñ$shüg§é;¯áìåÝ€±Ý6~œ¥P�[¡]àU Þ šý€kŠð‰ßiîœ=ÿ"únÀ)$šµA¸½Fão÷¥pÎWGåk TÛ˜ü2ª~øU‘ ôÚùòfê]K±ø.°ÂWm< Œî…·Xï¯{+c•"VÆî \UÍ#±³šãb©YíjˆT|þµÀí$&Ôb`IDrk†:¾�BaŽÑK{˜/·Ë¡Iã#ÿî.öÉ¡½@ûÕœgåF ~C"Œ}€–9mMÑ¡ŒíœìëÓRÌhæÔ*c‡àÔà!QÀô/X¬Þ3ÃKZŠq8;1Ž£]LTƦ'Õ˜¨|Ÿ2ö)e쉱êHá ÜBÙœƒ›s'àmÿüÀœv;“ØâÅÔÝé@øàÃ<¿ýIv�–$Ú eìÁÀ÷ýír`p<05âù݈Ûù‡£¢çã£ú¡ÀmÿÖ$›Îà&àD’¨F-n£ÉB¼ƒfÎÙ¿ç3ýíZß×@ÔO·}‰$êÐÇ_¯ò×zàZ€Zeìþ¸°L÷¨q0HKñTΠÃÀbï}FZ½ÇÐR¼íUç(“ 70Cû=-Åôb}•À$“7á>À8eì@-ÅÒý þú’–â>_^¨Œ�ˆ¬�”±Û£>BYê.âÿ)pYˆ^øŒT˜û8à›QÛ µ£=ýG¯5€–bp™Þø±>LK1>g<ØÞ—oÖRÜèیӘ�8-i„t¹–bMM'³ãN-Å<ÿHVÁûòÔc¸ ¢·×ÞA–þ®¥˜ ÎIº‘Báü J §G/Ѝ÷4¼£€+q«`Oà¢2ú*Æ÷5àÁŒG§�R×ÊØ¶¸Ý àÉ}¼À²>X¹êNúë„Th-æß:ƒ~}jLoâvÔudGQÊÏQylT^˜3ž%M åçé¨G²Ú5_ ܃;¼´ ¸ŽèA˜ä|ÜjCù(©ÞÓð¡™[”±¯:ç ¾Rœ ,Å©—XµŸ¡Œ½MKR€Ý¢gBÁÛÃßö·ËÈv ËQwÛ“ýÿŒêk€¾éܨÜÕ_'DZfòʳKÓã)öÿpÞsÀǸ�¢Ø6§m��b&Mx§«qŽXÀ²¨œÕ~Î[oK²Hÿ¬¥˜b�E²Ý—„WïÊßÎÐR̯ ív8ï1ØzMŽ�zõs±2v.«t1‰‡~Í»È�'Ô´4ÃÔ|Îö¦`ðWVh)–S¸8¤2¶n×>ÛÓ¶ºx�¶Þ³ÝI2[3�gp¡°Þ¸EÚxrN3ùó”Gã¶æS�{’^)ç¨=îƒ N¯¦pF?kÞ xÅ/®Ù$±¿(L±.ð×�·”±÷)c§�„ÌÆk)V��ж . ýžÿ›šA0V«•±Oàìzpªòňf).ýîØâ<\Ê5>ý3:c,õ$)p§†ÂxnJÑ~ேâÞÅ[¸Ý9ðÉúýQz.}ýØBçCC\;—;'p}Áןåi‚ã}9Ifë:Ï#ð7¨ùÜãvZŠ©8»ô¹Ô£J¼÷E@g-Å_ªñc®\‘ºïšºœêû^›¨%0ˆ$²ŽÂÓ4iŒÍ©�OêLcó—ݧ�À…˜6Fô«�›#úÖ$™.pi¿x„võ¸�§èxâ¨Dœcö$…¿�ªÓRlRÆvÄåéñÏCælvij´€‚Ëûj)'o·„Ư—Pïh)^ÕR¼_Œ¦©ÐRL$;͹%0)úU¸Tkë€s´od< ¸›ÂLI@:ët)Ùvá°œ±%ÛI™La&�ëHBiÅÆ3šl5>‰DX6ƒ_?¥ÐÎÌêãžÔ³¥8}$‰ „ïjûÿú°y¤v󔱇@…ÿ¸ÁNêiˆ–â·•´ÿ¼àíâ^¸ÈCgœ�ÖRÌ.ÒæPà‡8“e60\KQòW‹>v×�$– Ήy </> Selectolax rushter-selectolax-240bf6c/docs/make.bat000066400000000000000000000145031525667221200203270ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\selectolax.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\selectolax.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end rushter-selectolax-240bf6c/docs/modules.rst000066400000000000000000000001031525667221200211130ustar00rootroot00000000000000selectolax ========== .. toctree:: :maxdepth: 4 selectolax rushter-selectolax-240bf6c/docs/parser.rst000066400000000000000000000004141525667221200207440ustar00rootroot00000000000000selectolax.parser module ====================================== .. automodule:: selectolax.parser HtmlParser ---------- .. autoclass:: HTMLParser :members: Node ---- .. autoclass:: Node :members: Selector -------- .. autoclass:: Selector :members: rushter-selectolax-240bf6c/docs/selectolax.rst000066400000000000000000000007101525667221200216120ustar00rootroot00000000000000selectolax package ================== Submodules ---------- selectolax.lexbor module ------------------------ .. automodule:: selectolax.lexbor :members: :undoc-members: :show-inheritance: selectolax.parser module ------------------------ .. automodule:: selectolax.parser :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: selectolax :members: :undoc-members: :show-inheritance: rushter-selectolax-240bf6c/examples/000077500000000000000000000000001525667221200176055ustar00rootroot00000000000000rushter-selectolax-240bf6c/examples/benchmark.py000066400000000000000000000067031525667221200221170ustar00rootroot00000000000000# coding:utf-8 """A simple benchmark that measures speed of lxml and selectolax. How the benchmark works ----------------------- For each page, we extract: 1) Title 2) Number of script tag 3) The ``href`` attribute from all links 4) The content of the Meta description tag """ import functools import json import time from bs4 import BeautifulSoup from html5_parser import parse from lxml.html import fromstring from selectolax.parser import HTMLParser from selectolax.lexbor import LexborHTMLParser bad_urls = [] def bs4_parser(html_content, parser=HTMLParser): soup = BeautifulSoup(html_content, 'html.parser') title_text = soup.title.string assert title_text a_hrefs = [a.attrs.get('href', '') for a in soup.find_all('a')] assert len(a_hrefs) >= 5, 'href' num_script_tags = len(soup.find_all('script')) assert num_script_tags > 0, 'script' meta_description = soup.find('meta', attrs={"name": "description"}) if meta_description: meta_content = meta_description.get('content') def selectolax_parser(html_content, parser=HTMLParser): tree = parser(html_content) title_text = "" title_node = tree.css_first('title') if title_node: title_text = title_node.text() assert title_text a_hrefs = [a.attrs.get('href', '') for a in tree.css('a[href]')] assert len(a_hrefs) >= 5, 'href' num_script_tags = len(tree.css('script')) assert num_script_tags > 0, 'script' meta_description = tree.css_first('meta[name="description"]') if meta_description: meta_content = meta_description.attrs.sget('content', '') def lxml_parser(html_content): tree = fromstring(html_content) title_text = tree.xpath('//title/text()') assert title_text, 'title' a_hrefs = [a.attrib.get('href', '') for a in tree.xpath('//a[@href]')] assert len(a_hrefs) >= 5, 'href' num_script_tags = len(tree.xpath('//script')) assert num_script_tags > 0, 'script' meta_description = tree.xpath('meta[@name="description"]') if meta_description: meta_content = meta_description[0].attrib.get('content', '') def html5_parser(html_content): tree = parse(html_content) title_text = tree.xpath('//title/text()') assert title_text, 'title' a_hrefs = [a.attrib.get('href', '') for a in tree.xpath('//a[@href]')] assert len(a_hrefs) >= 5, 'href' num_script_tags = len(tree.xpath('//script')) assert num_script_tags > 0, 'script' meta_description = tree.xpath('meta[@name="description"]') if meta_description: meta_content = meta_description[0].attrib.get('content', '') def _perform_test(pages, parse_func): for page in pages: parse_func(page['html']) def main(): # # This file contains 754 main pages from the top internet domains (according to Alexa rank). # That translates to 324MB of HTML data. # Because of potential copyright infringements, I don't publish it. # html_pages = [json.loads(page) for page in open('pages/pages.json', 'rt')] available_parsers = [ ('bs4', bs4_parser,), ('lxml', lxml_parser,), ('html5_parser', html5_parser,), ('modest', selectolax_parser,), ('lexbor', functools.partial(selectolax_parser, parser=LexborHTMLParser)), ] for parser_name, parser in available_parsers: start = time.time() _perform_test(html_pages, parser) print('%r: %s' % (parser_name, time.time() - start)) if __name__ == '__main__': main() rushter-selectolax-240bf6c/examples/simple_example.py000066400000000000000000000005201525667221200231600ustar00rootroot00000000000000from selectolax.lexbor import LexborHTMLParser html = "

link

text

" selector = "div > :nth-child(2n+1):not(:has(a))" for node in LexborHTMLParser(html).css(selector): print(node.attributes, node.text(), node.tag) print(node.parent.tag) print(node.html) rushter-selectolax-240bf6c/lexbor/000077500000000000000000000000001525667221200172625ustar00rootroot00000000000000rushter-selectolax-240bf6c/modest/000077500000000000000000000000001525667221200172625ustar00rootroot00000000000000rushter-selectolax-240bf6c/pyproject.toml000066400000000000000000000046021525667221200207050ustar00rootroot00000000000000[build-system] requires = [ "wheel", "Cython", "setuptools>=62.4", ] [project] name = 'selectolax' version = '0.4.13' description = 'A fast HTML5 parser with CSS selectors, written in Cython, using Modest and Lexbor engines.' readme = 'README.md' requires-python = '>=3.9,<3.15' license = 'MIT' authors = [ { name = 'Artem Golubin', email = 'me@rushter.com' } ] dependencies = [] keywords = [ "selectolax", "html", "parser", "css", "fast", "lexbor", "modest", ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: Unix", "Programming Language :: Cython", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing", "Topic :: Text Processing :: Markup", "Topic :: Text Processing :: Markup :: HTML", "Typing :: Typed", ] [project.urls] Homepage = "https://github.com/rushter/selectolax" Repository = "https://github.com/rushter/selectolax" Documentation = "https://selectolax.readthedocs.io/en/latest/parser.html" Changelog = "https://github.com/rushter/selectolax/blob/master/CHANGES.md" [tool.cibuildwheel] build-frontend = "build" build-verbosity = 1 [tool.cibuildwheel.linux] environment = { LDFLAGS = "-Wl,--strip-debug" } skip = [ "*-manylinux_i686", "*-musllinux_i686", "*-win32*", "pp*" ] test-skip = "*-macosx_arm64" [project.optional-dependencies] cython = [ "Cython", ] [tool.cython-lint] max-line-length = 120 ignore = ['E221', 'E222', ] [tool.setuptools.packages.find] include = ["selectolax*"] rushter-selectolax-240bf6c/requirements_dev.txt000066400000000000000000000005461525667221200221160ustar00rootroot00000000000000pip>=18.0 bumpversion>=0.5.3 wheel>=0.29.0 watchdog>=0.8.3 tox>=2.3.1 coverage>=4.1 Sphinx==8.0.2 numpydoc==1.8.0 pytest>=3.7.2 pytest-runner>=4.2 Cython>=3.0.11 pluggy>=0.7.1 mypy==1.4.1 types-pyinstaller==6.10.0.20240812 furo==2024.8.6 sphinxext-opengraph==0.9.1 sphinx-copybutton==0.5.2 ruff setuptools>=75.7.0 pytest-mypy-plugins>=3.2,<4.0.0 cython-lint rushter-selectolax-240bf6c/ruff.toml000066400000000000000000000000601525667221200176220ustar00rootroot00000000000000[lint.per-file-ignores] "__init__.py" = ["F401"]rushter-selectolax-240bf6c/selectolax/000077500000000000000000000000001525667221200201325ustar00rootroot00000000000000rushter-selectolax-240bf6c/selectolax/__init__.py000066400000000000000000000001571525667221200222460ustar00rootroot00000000000000# -*- coding: utf-8 -*- __author__ = """Artem Golubin""" __email__ = "me@rushter.com" __version__ = "0.4.13" rushter-selectolax-240bf6c/selectolax/base.pxi000066400000000000000000000001311525667221200215610ustar00rootroot00000000000000 class SelectolaxError(Exception): """An exception that indicates error.""" pass rushter-selectolax-240bf6c/selectolax/lexbor.pxd000066400000000000000000000640161525667221200221510ustar00rootroot00000000000000# cython: freethreading_compatible = True from libc.stdint cimport uint8_t, uint32_t, uintptr_t cdef extern from "lexbor/core/core.h" nogil: ctypedef uint32_t lxb_codepoint_t ctypedef unsigned char lxb_char_t ctypedef unsigned int lxb_status_t ctypedef enum lexbor_status_t: LXB_STATUS_OK = 0x0000 LXB_STATUS_ERROR = 0x0001 LXB_STATUS_ERROR_MEMORY_ALLOCATION LXB_STATUS_ERROR_OBJECT_IS_NULL LXB_STATUS_ERROR_SMALL_BUFFER LXB_STATUS_ERROR_INCOMPLETE_OBJECT LXB_STATUS_ERROR_NO_FREE_SLOT LXB_STATUS_ERROR_TOO_SMALL_SIZE LXB_STATUS_ERROR_NOT_EXISTS LXB_STATUS_ERROR_WRONG_ARGS LXB_STATUS_ERROR_WRONG_STAGE LXB_STATUS_ERROR_UNEXPECTED_RESULT LXB_STATUS_ERROR_UNEXPECTED_DATA LXB_STATUS_ERROR_OVERFLOW LXB_STATUS_CONTINUE LXB_STATUS_SMALL_BUFFER LXB_STATUS_ABORTED LXB_STATUS_STOPPED LXB_STATUS_NEXT LXB_STATUS_STOP lexbor_str_t* lexbor_str_destroy(lexbor_str_t *str, lexbor_mraw_t *mraw, bint destroy_obj) lexbor_str_t* lexbor_str_create() lxb_char_t * lexbor_str_data_noi(lexbor_str_t *str) cdef extern from "lexbor/core/lexbor.h" nogil: ctypedef void *(*lexbor_memory_malloc_f)(size_t size) nogil ctypedef void *(*lexbor_memory_realloc_f)(void *dst, size_t size) nogil ctypedef void *(*lexbor_memory_calloc_f)(size_t num, size_t size) nogil ctypedef void (*lexbor_memory_free_f)(void *dst) nogil lxb_status_t lexbor_memory_setup( lexbor_memory_malloc_f new_malloc, lexbor_memory_realloc_f new_realloc, lexbor_memory_calloc_f new_calloc, lexbor_memory_free_f new_free ) cdef extern from "lexbor/html/html.h" nogil: ctypedef unsigned int lxb_html_document_opt_t ctypedef uint32_t lxb_dom_document_opt_t ctypedef enum lxb_dom_document_opt: LXB_DOM_DOCUMENT_OPT_UNDEF = 0x00 LXB_DOM_DOCUMENT_OPT_WO_EVENTS = 1 << 0 ctypedef struct lxb_html_tokenizer_t ctypedef struct lxb_html_form_element_t ctypedef struct lxb_html_head_element_t ctypedef struct lxb_html_body_element_t ctypedef struct lxb_dom_element_t ctypedef struct lexbor_mraw_t ctypedef struct lexbor_hash_t ctypedef struct lxb_dom_document_type_t ctypedef void lxb_dom_interface_t ctypedef uintptr_t lxb_tag_id_t ctypedef uintptr_t lxb_ns_id_t ctypedef lxb_dom_interface_t *(*lxb_dom_interface_destroy_f)(lxb_dom_interface_t *intrfc) ctypedef lxb_dom_interface_t *(*lxb_dom_interface_create_f)(lxb_dom_document_t *document, lxb_tag_id_t tag_id, lxb_ns_id_t ns) ctypedef struct lxb_dom_event_target_t: void *events ctypedef struct lexbor_str_t: lxb_char_t *data size_t length ctypedef struct lxb_dom_node_t: lxb_dom_event_target_t event_target uintptr_t local_name uintptr_t prefix uintptr_t ns lxb_dom_document_t *owner_document lxb_dom_node_t *next lxb_dom_node_t *prev lxb_dom_node_t *parent lxb_dom_node_t *first_child lxb_dom_node_t *last_child void *user lxb_dom_node_type_t type ctypedef struct lxb_dom_document_t: lxb_dom_node_t node lxb_dom_document_cmode_t compat_mode lxb_dom_document_dtype_t type lxb_dom_document_type_t *doctype lxb_dom_element_t *element lxb_dom_interface_create_f create_interface lxb_dom_interface_destroy_f destroy_interface lexbor_mraw_t *mraw lexbor_mraw_t *text lexbor_hash_t *tags lexbor_hash_t *attrs lexbor_hash_t *prefix lexbor_hash_t *ns void *parser void *user bint tags_inherited bint ns_inherited bint scripting ctypedef struct lxb_html_document_t: lxb_dom_document_t dom_document void *iframe_srcdoc lxb_html_head_element_t *head lxb_html_body_element_t *body lxb_html_document_ready_state_t ready_state lxb_html_document_opt_t opt ctypedef enum lxb_html_document_ready_state_t: LXB_HTML_DOCUMENT_READY_STATE_UNDEF = 0x00 LXB_HTML_DOCUMENT_READY_STATE_LOADING = 0x01 LXB_HTML_DOCUMENT_READY_STATE_INTERACTIVE = 0x02 LXB_HTML_DOCUMENT_READY_STATE_COMPLETE = 0x03 ctypedef enum lxb_html_parser_state_t: LXB_HTML_PARSER_STATE_BEGIN = 0x00 LXB_HTML_PARSER_STATE_PROCESS = 0x01 LXB_HTML_PARSER_STATE_END = 0x02 LXB_HTML_PARSER_STATE_FRAGMENT_PROCESS = 0x03 LXB_HTML_PARSER_STATE_ERROR = 0x04 ctypedef enum lxb_dom_node_type_t: LXB_DOM_NODE_TYPE_ELEMENT = 0x01 LXB_DOM_NODE_TYPE_ATTRIBUTE = 0x02 LXB_DOM_NODE_TYPE_TEXT = 0x03 LXB_DOM_NODE_TYPE_CDATA_SECTION = 0x04 LXB_DOM_NODE_TYPE_ENTITY_REFERENCE = 0x05 LXB_DOM_NODE_TYPE_ENTITY = 0x06 LXB_DOM_NODE_TYPE_PROCESSING_INSTRUCTION = 0x07 LXB_DOM_NODE_TYPE_COMMENT = 0x08 LXB_DOM_NODE_TYPE_DOCUMENT = 0x09 LXB_DOM_NODE_TYPE_DOCUMENT_TYPE = 0x0A LXB_DOM_NODE_TYPE_DOCUMENT_FRAGMENT = 0x0B LXB_DOM_NODE_TYPE_NOTATION = 0x0C LXB_DOM_NODE_TYPE_LAST_ENTRY = 0x0D ctypedef enum lxb_dom_document_cmode_t: LXB_DOM_DOCUMENT_CMODE_NO_QUIRKS = 0x00 LXB_DOM_DOCUMENT_CMODE_QUIRKS = 0x01 LXB_DOM_DOCUMENT_CMODE_LIMITED_QUIRKS = 0x02 ctypedef enum lxb_dom_document_dtype_t: LXB_DOM_DOCUMENT_DTYPE_UNDEF = 0x00, LXB_DOM_DOCUMENT_DTYPE_HTML = 0x01, LXB_DOM_DOCUMENT_DTYPE_XML = 0x02 ctypedef enum lxb_html_serialize_opt_t: LXB_HTML_SERIALIZE_OPT_UNDEF = 0x00 LXB_HTML_SERIALIZE_OPT_SKIP_WS_NODES = 0x01 LXB_HTML_SERIALIZE_OPT_SKIP_COMMENT = 0x02 LXB_HTML_SERIALIZE_OPT_RAW = 0x04 LXB_HTML_SERIALIZE_OPT_WITHOUT_CLOSING = 0x08 LXB_HTML_SERIALIZE_OPT_TAG_WITH_NS = 0x10 LXB_HTML_SERIALIZE_OPT_WITHOUT_TEXT_INDENT = 0x20 LXB_HTML_SERIALIZE_OPT_FULL_DOCTYPE = 0x40 ctypedef struct lexbor_array_t: void **list size_t size size_t length ctypedef struct lexbor_array_obj_t: uint8_t *list size_t size size_t length size_t struct_size ctypedef struct lxb_html_tree_pending_table_t ctypedef bint lxb_html_tree_insertion_mode_f ctypedef lxb_status_t lxb_html_tree_append_attr_f ctypedef struct lxb_html_tree_t: lxb_html_tokenizer_t *tkz_ref lxb_html_document_t *document lxb_dom_node_t *fragment lxb_html_form_element_t *form lexbor_array_t *open_elements lexbor_array_t *active_formatting lexbor_array_obj_t *template_insertion_modes lxb_html_tree_pending_table_t *pending_table lexbor_array_obj_t *parse_errors bint foster_parenting bint frameset_ok bint scripting lxb_html_tree_insertion_mode_f mode lxb_html_tree_insertion_mode_f original_mode lxb_html_tree_append_attr_f before_append_attr lxb_status_t status size_t ref_count ctypedef struct lxb_html_parser_t: lxb_html_tokenizer_t *tkz lxb_html_tree_t *tree lxb_html_tree_t *original_tree lxb_dom_node_t *root lxb_dom_node_t *form lxb_html_parser_state_t state lxb_status_t status size_t ref_count ctypedef struct lxb_html_element_t # Functions lxb_html_parser_t * lxb_html_parser_create() lxb_status_t lxb_html_parser_init(lxb_html_parser_t *parser) lxb_html_parser_t * lxb_html_parser_destroy(lxb_html_parser_t *parser) lxb_html_document_t * lxb_html_document_create() void lxb_html_document_dom_opt_set(lxb_html_document_t *document, lxb_dom_document_opt_t opt) lxb_dom_document_opt_t lxb_html_document_dom_opt(const lxb_html_document_t *document) lxb_html_element_t * lxb_html_document_create_element(lxb_html_document_t *document, const lxb_char_t *local_name, size_t lname_len, void *reserved_for_opt) lxb_status_t lxb_html_document_parse(lxb_html_document_t *document, const lxb_char_t *html, size_t size) lxb_dom_node_t * lxb_html_parse_fragment_by_tag_id(lxb_html_parser_t *parser, lxb_html_document_t *document, lxb_tag_id_t tag_id, lxb_ns_id_t ns, const lxb_char_t *html, size_t size) lxb_dom_node_t * lxb_html_document_parse_fragment(lxb_html_document_t *document, lxb_dom_element_t *element, const lxb_char_t *html, size_t size) lxb_html_body_element_t * lxb_html_document_body_element_noi(lxb_html_document_t *document) lxb_html_head_element_t * lxb_html_document_head_element_noi(lxb_html_document_t *document) lxb_dom_element_t * lxb_dom_document_element(lxb_dom_document_t *document) lxb_status_t lxb_html_serialize_tree_str(lxb_dom_node_t *node, lexbor_str_t *str) lxb_status_t lxb_html_serialize_deep_str(lxb_dom_node_t *node, lexbor_str_t *str) lxb_status_t lxb_html_serialize_pretty_tree_str(lxb_dom_node_t *node, lxb_html_serialize_opt_t opt, size_t indent, lexbor_str_t *str) lxb_status_t lxb_html_serialize_pretty_deep_str(lxb_dom_node_t *node, lxb_html_serialize_opt_t opt, size_t indent, lexbor_str_t *str) lxb_html_element_t* lxb_html_element_inner_html_set(lxb_html_element_t *element, const lxb_char_t *html, size_t size) cdef class LexborNode: cdef: lxb_dom_node_t *node public LexborHTMLParser parser cdef bint _is_fragment_root @staticmethod cdef LexborNode new(lxb_dom_node_t *node, LexborHTMLParser parser) cdef void set_as_fragment_root(self) cdef str _serialize_html(self, lxb_html_serialize_opt_t options, size_t indent, bint pretty) cdef str _serialize_inner_html(self, lxb_html_serialize_opt_t options, size_t indent, bint pretty) cdef inline LexborNode _get_node(self) cdef bint is_empty_text_node(lxb_dom_node_t *node) cdef inline bint _is_whitespace_only(const lxb_char_t *buffer, size_t buffer_length) nogil cdef class LexborCSSSelector: cdef lxb_css_parser_t* parser cdef lxb_selectors_t * selectors cdef lxb_css_selectors_t * css_selectors cdef public list results cdef public LexborNode current_node cdef int _create_css_parser(self) except -1 cpdef list find(self, str query, LexborNode node) cpdef list find_first(self, str query, LexborNode node) cpdef list _find(self, str query, LexborNode node, bint only_first) cpdef int any_matches(self, str query, LexborNode node) except -1 cdef class LexborHTMLParser: cdef lxb_html_document_t *document cdef lxb_dom_node_t *_fragment_wrapper cdef lxb_dom_node_t *_fragment_root cdef bint _is_fragment cdef lxb_tag_id_t _fragment_tag_id cdef lxb_ns_id_t _fragment_namespace_id cdef public bytes raw_html cdef LexborCSSSelector _selector cdef inline void _new_html_document(self) cdef inline lxb_status_t _parse_html_document(self, char *html, size_t html_len) nogil cdef inline lxb_status_t _parse_html_fragment(self, char *html, size_t html_len) nogil cdef int _parse_html(self, char *html, size_t html_len) except -1 cdef object cached_script_texts cdef object cached_script_srcs @staticmethod cdef LexborHTMLParser from_document(lxb_html_document_t * document, bytes raw_html) cdef extern from "lexbor/dom/dom.h" nogil: ctypedef enum lexbor_action_t: LEXBOR_ACTION_OK = 0x00 LEXBOR_ACTION_STOP = 0x01 LEXBOR_ACTION_NEXT = 0x02 ctypedef lexbor_action_t (*lxb_dom_node_simple_walker_f)(lxb_dom_node_t *node, void *ctx) ctypedef struct lxb_dom_character_data_t: lxb_dom_node_t node lexbor_str_t data ctypedef struct lxb_dom_text_t: lxb_dom_character_data_t char_data ctypedef uintptr_t lxb_dom_attr_id_t ctypedef struct lxb_dom_collection_t: lexbor_array_t array lxb_dom_document_t *document ctypedef struct lxb_dom_attr_t: lxb_dom_node_t node lxb_dom_attr_id_t upper_name lxb_dom_attr_id_t qualified_name lexbor_str_t *value lxb_dom_element_t *owner lxb_dom_attr_t *next lxb_dom_attr_t *prev lxb_dom_collection_t * lxb_dom_collection_make(lxb_dom_document_t *document, size_t start_list_size) lxb_char_t * lxb_dom_node_text_content(lxb_dom_node_t *node, size_t *len) lxb_status_t lxb_dom_node_text_content_set(lxb_dom_node_t *node, const lxb_char_t *content, size_t len) bint lxb_dom_node_is_empty(lxb_dom_node_t *node) void lxb_dom_node_remove(lxb_dom_node_t *node) void * lxb_dom_document_destroy_text_noi(lxb_dom_document_t *document, lxb_char_t *text) lxb_dom_node_t * lxb_dom_document_root(lxb_dom_document_t *document) lxb_dom_element_t * lxb_dom_interface_element(lxb_dom_node_t *node) lxb_char_t * lxb_dom_element_qualified_name(lxb_dom_element_t *element, size_t *len) lxb_dom_node_t * lxb_dom_node_destroy(lxb_dom_node_t *node) lxb_dom_node_t * lxb_dom_node_destroy_deep(lxb_dom_node_t *root) lxb_dom_attr_t * lxb_dom_element_first_attribute_noi(lxb_dom_element_t *element) const lxb_char_t * lxb_dom_attr_local_name_noi(lxb_dom_attr_t *attr, size_t *len) const lxb_char_t * lxb_dom_attr_value_noi(lxb_dom_attr_t *attr, size_t *len) lxb_dom_attr_t * lxb_dom_element_set_attribute(lxb_dom_element_t *element, const lxb_char_t *qualified_name, size_t qn_len, const lxb_char_t *value, size_t value_len) lxb_status_t lxb_dom_element_remove_attribute(lxb_dom_element_t *element, const lxb_char_t *qualified_name, size_t qn_len) lxb_dom_attr_t * lxb_dom_element_attr_by_name(lxb_dom_element_t *element, const lxb_char_t *qualified_name, size_t length) lxb_tag_id_t lxb_dom_node_tag_id_noi(lxb_dom_node_t *node) lxb_dom_node_t * lxb_dom_document_import_node(lxb_dom_document_t *doc, lxb_dom_node_t *node, bint deep) void lxb_dom_node_insert_after(lxb_dom_node_t *to, lxb_dom_node_t *node) lxb_status_t lxb_dom_node_replace_all(lxb_dom_node_t *parent, lxb_dom_node_t *node) void lxb_dom_node_insert_child(lxb_dom_node_t *to, lxb_dom_node_t *node) void lxb_dom_node_insert_before(lxb_dom_node_t *to, lxb_dom_node_t *node) void lxb_dom_node_insert_after(lxb_dom_node_t *to, lxb_dom_node_t *node) lxb_dom_text_t * lxb_dom_document_create_text_node(lxb_dom_document_t *document, const lxb_char_t *data, size_t len) void lxb_dom_node_simple_walk(lxb_dom_node_t *root, lxb_dom_node_simple_walker_f walker_cb, void *ctx) lxb_dom_node_t* lxb_dom_node_clone(lxb_dom_node_t *node, bint deep) cdef extern from "lexbor/dom/interfaces/element.h" nogil: lxb_status_t lxb_dom_elements_by_tag_name(lxb_dom_element_t *root, lxb_dom_collection_t *collection, const lxb_char_t *qualified_name, size_t len) cdef extern from "lexbor/dom/interfaces/document.h" nogil: lxb_html_document_t * lxb_html_document_destroy(lxb_html_document_t *document) cdef extern from "lexbor/dom/collection.h" nogil: size_t lxb_dom_collection_length_noi(lxb_dom_collection_t *col) lxb_dom_element_t * lxb_dom_collection_element_noi(lxb_dom_collection_t *col, size_t idx) lxb_dom_collection_t * lxb_dom_collection_destroy(lxb_dom_collection_t *col, bint self_destroy) cdef extern from "lexbor/css/css.h" nogil: ctypedef struct lxb_css_parser_t: lxb_css_memory_t* memory ctypedef struct lxb_css_syntax_tokenizer_t ctypedef struct lxb_css_memory_t lxb_css_parser_t * lxb_css_parser_create() lxb_status_t lxb_css_parser_init(lxb_css_parser_t *parser, lxb_css_syntax_tokenizer_t *tkz) lxb_css_parser_t * lxb_css_parser_destroy(lxb_css_parser_t *parser, bint self_destroy) void lxb_css_parser_clean(lxb_css_parser_t *parser) lxb_css_memory_t * lxb_css_memory_destroy(lxb_css_memory_t *memory, bint self_destroy) void lxb_css_selector_list_destroy_memory(lxb_css_selector_list_t *list) cdef extern from "lexbor/tag/tag.h" nogil: ctypedef struct lxb_tag_data_t: lxb_tag_id_t tag_id ctypedef enum lxb_tag_id_enum_t: LXB_TAG__UNDEF = 0x0000 LXB_TAG__END_OF_FILE = 0x0001 LXB_TAG__TEXT = 0x0002 LXB_TAG__DOCUMENT = 0x0003 LXB_TAG__EM_COMMENT = 0x0004 LXB_TAG__EM_DOCTYPE = 0x0005 LXB_TAG_A = 0x0006 LXB_TAG_ABBR = 0x0007 LXB_TAG_ACRONYM = 0x0008 LXB_TAG_ADDRESS = 0x0009 LXB_TAG_ALTGLYPH = 0x000a LXB_TAG_ALTGLYPHDEF = 0x000b LXB_TAG_ALTGLYPHITEM = 0x000c LXB_TAG_ANIMATECOLOR = 0x000d LXB_TAG_ANIMATEMOTION = 0x000e LXB_TAG_ANIMATETRANSFORM = 0x000f LXB_TAG_ANNOTATION_XML = 0x0010 LXB_TAG_APPLET = 0x0011 LXB_TAG_AREA = 0x0012 LXB_TAG_ARTICLE = 0x0013 LXB_TAG_ASIDE = 0x0014 LXB_TAG_AUDIO = 0x0015 LXB_TAG_B = 0x0016 LXB_TAG_BASE = 0x0017 LXB_TAG_BASEFONT = 0x0018 LXB_TAG_BDI = 0x0019 LXB_TAG_BDO = 0x001a LXB_TAG_BGSOUND = 0x001b LXB_TAG_BIG = 0x001c LXB_TAG_BLINK = 0x001d LXB_TAG_BLOCKQUOTE = 0x001e LXB_TAG_BODY = 0x001f LXB_TAG_BR = 0x0020 LXB_TAG_BUTTON = 0x0021 LXB_TAG_CANVAS = 0x0022 LXB_TAG_CAPTION = 0x0023 LXB_TAG_CENTER = 0x0024 LXB_TAG_CITE = 0x0025 LXB_TAG_CLIPPATH = 0x0026 LXB_TAG_CODE = 0x0027 LXB_TAG_COL = 0x0028 LXB_TAG_COLGROUP = 0x0029 LXB_TAG_DATA = 0x002a LXB_TAG_DATALIST = 0x002b LXB_TAG_DD = 0x002c LXB_TAG_DEL = 0x002d LXB_TAG_DESC = 0x002e LXB_TAG_DETAILS = 0x002f LXB_TAG_DFN = 0x0030 LXB_TAG_DIALOG = 0x0031 LXB_TAG_DIR = 0x0032 LXB_TAG_DIV = 0x0033 LXB_TAG_DL = 0x0034 LXB_TAG_DT = 0x0035 LXB_TAG_EM = 0x0036 LXB_TAG_EMBED = 0x0037 LXB_TAG_FEBLEND = 0x0038 LXB_TAG_FECOLORMATRIX = 0x0039 LXB_TAG_FECOMPONENTTRANSFER = 0x003a LXB_TAG_FECOMPOSITE = 0x003b LXB_TAG_FECONVOLVEMATRIX = 0x003c LXB_TAG_FEDIFFUSELIGHTING = 0x003d LXB_TAG_FEDISPLACEMENTMAP = 0x003e LXB_TAG_FEDISTANTLIGHT = 0x003f LXB_TAG_FEDROPSHADOW = 0x0040 LXB_TAG_FEFLOOD = 0x0041 LXB_TAG_FEFUNCA = 0x0042 LXB_TAG_FEFUNCB = 0x0043 LXB_TAG_FEFUNCG = 0x0044 LXB_TAG_FEFUNCR = 0x0045 LXB_TAG_FEGAUSSIANBLUR = 0x0046 LXB_TAG_FEIMAGE = 0x0047 LXB_TAG_FEMERGE = 0x0048 LXB_TAG_FEMERGENODE = 0x0049 LXB_TAG_FEMORPHOLOGY = 0x004a LXB_TAG_FEOFFSET = 0x004b LXB_TAG_FEPOINTLIGHT = 0x004c LXB_TAG_FESPECULARLIGHTING = 0x004d LXB_TAG_FESPOTLIGHT = 0x004e LXB_TAG_FETILE = 0x004f LXB_TAG_FETURBULENCE = 0x0050 LXB_TAG_FIELDSET = 0x0051 LXB_TAG_FIGCAPTION = 0x0052 LXB_TAG_FIGURE = 0x0053 LXB_TAG_FONT = 0x0054 LXB_TAG_FOOTER = 0x0055 LXB_TAG_FOREIGNOBJECT = 0x0056 LXB_TAG_FORM = 0x0057 LXB_TAG_FRAME = 0x0058 LXB_TAG_FRAMESET = 0x0059 LXB_TAG_GLYPHREF = 0x005a LXB_TAG_H1 = 0x005b LXB_TAG_H2 = 0x005c LXB_TAG_H3 = 0x005d LXB_TAG_H4 = 0x005e LXB_TAG_H5 = 0x005f LXB_TAG_H6 = 0x0060 LXB_TAG_HEAD = 0x0061 LXB_TAG_HEADER = 0x0062 LXB_TAG_HGROUP = 0x0063 LXB_TAG_HR = 0x0064 LXB_TAG_HTML = 0x0065 LXB_TAG_I = 0x0066 LXB_TAG_IFRAME = 0x0067 LXB_TAG_IMAGE = 0x0068 LXB_TAG_IMG = 0x0069 LXB_TAG_INPUT = 0x006a LXB_TAG_INS = 0x006b LXB_TAG_ISINDEX = 0x006c LXB_TAG_KBD = 0x006d LXB_TAG_KEYGEN = 0x006e LXB_TAG_LABEL = 0x006f LXB_TAG_LEGEND = 0x0070 LXB_TAG_LI = 0x0071 LXB_TAG_LINEARGRADIENT = 0x0072 LXB_TAG_LINK = 0x0073 LXB_TAG_LISTING = 0x0074 LXB_TAG_MAIN = 0x0075 LXB_TAG_MALIGNMARK = 0x0076 LXB_TAG_MAP = 0x0077 LXB_TAG_MARK = 0x0078 LXB_TAG_MARQUEE = 0x0079 LXB_TAG_MATH = 0x007a LXB_TAG_MENU = 0x007b LXB_TAG_META = 0x007c LXB_TAG_METER = 0x007d LXB_TAG_MFENCED = 0x007e LXB_TAG_MGLYPH = 0x007f LXB_TAG_MI = 0x0080 LXB_TAG_MN = 0x0081 LXB_TAG_MO = 0x0082 LXB_TAG_MS = 0x0083 LXB_TAG_MTEXT = 0x0084 LXB_TAG_MULTICOL = 0x0085 LXB_TAG_NAV = 0x0086 LXB_TAG_NEXTID = 0x0087 LXB_TAG_NOBR = 0x0088 LXB_TAG_NOEMBED = 0x0089 LXB_TAG_NOFRAMES = 0x008a LXB_TAG_NOSCRIPT = 0x008b LXB_TAG_OBJECT = 0x008c LXB_TAG_OL = 0x008d LXB_TAG_OPTGROUP = 0x008e LXB_TAG_OPTION = 0x008f LXB_TAG_OUTPUT = 0x0090 LXB_TAG_P = 0x0091 LXB_TAG_PARAM = 0x0092 LXB_TAG_PATH = 0x0093 LXB_TAG_PICTURE = 0x0094 LXB_TAG_PLAINTEXT = 0x0095 LXB_TAG_PRE = 0x0096 LXB_TAG_PROGRESS = 0x0097 LXB_TAG_Q = 0x0098 LXB_TAG_RADIALGRADIENT = 0x0099 LXB_TAG_RB = 0x009a LXB_TAG_RP = 0x009b LXB_TAG_RT = 0x009c LXB_TAG_RTC = 0x009d LXB_TAG_RUBY = 0x009e LXB_TAG_S = 0x009f LXB_TAG_SAMP = 0x00a0 LXB_TAG_SCRIPT = 0x00a1 LXB_TAG_SECTION = 0x00a2 LXB_TAG_SELECT = 0x00a3 LXB_TAG_SLOT = 0x00a4 LXB_TAG_SMALL = 0x00a5 LXB_TAG_SOURCE = 0x00a6 LXB_TAG_SPACER = 0x00a7 LXB_TAG_SPAN = 0x00a8 LXB_TAG_STRIKE = 0x00a9 LXB_TAG_STRONG = 0x00aa LXB_TAG_STYLE = 0x00ab LXB_TAG_SUB = 0x00ac LXB_TAG_SUMMARY = 0x00ad LXB_TAG_SUP = 0x00ae LXB_TAG_SVG = 0x00af LXB_TAG_TABLE = 0x00b0 LXB_TAG_TBODY = 0x00b1 LXB_TAG_TD = 0x00b2 LXB_TAG_TEMPLATE = 0x00b3 LXB_TAG_TEXTAREA = 0x00b4 LXB_TAG_TEXTPATH = 0x00b5 LXB_TAG_TFOOT = 0x00b6 LXB_TAG_TH = 0x00b7 LXB_TAG_THEAD = 0x00b8 LXB_TAG_TIME = 0x00b9 LXB_TAG_TITLE = 0x00ba LXB_TAG_TR = 0x00bb LXB_TAG_TRACK = 0x00bc LXB_TAG_TT = 0x00bd LXB_TAG_U = 0x00be LXB_TAG_UL = 0x00bf LXB_TAG_VAR = 0x00c0 LXB_TAG_VIDEO = 0x00c1 LXB_TAG_WBR = 0x00c2 LXB_TAG_XMP = 0x00c3 LXB_TAG__LAST_ENTRY = 0x00c4 lxb_tag_id_t lxb_tag_id_by_name_noi(lexbor_hash_t *hash, const lxb_char_t *name, size_t len) cdef extern from "lexbor/ns/ns.h" nogil: ctypedef struct lxb_ns_data_t: lxb_ns_id_t ns_id ctypedef struct lxb_ns_prefix_data_t: uintptr_t prefix_id ctypedef enum lxb_ns_id_enum_t: LXB_NS__UNDEF = 0x00 LXB_NS__ANY = 0x01 LXB_NS_HTML = 0x02 LXB_NS_MATH = 0x03 LXB_NS_SVG = 0x04 LXB_NS_XLINK = 0x05 LXB_NS_XML = 0x06 LXB_NS_XMLNS = 0x07 LXB_NS__LAST_ENTRY = 0x08 const lxb_ns_data_t * lxb_ns_data_by_link(lexbor_hash_t *hash, const lxb_char_t *name, size_t length) const lxb_ns_prefix_data_t * lxb_ns_prefix_data_by_name(lexbor_hash_t *hash, const lxb_char_t *name, size_t length) cdef extern from "lexbor/selectors/selectors.h" nogil: ctypedef struct lxb_css_selectors_t ctypedef struct lxb_selectors_t ctypedef struct lxb_css_selector_list_t ctypedef struct lxb_css_selector_specificity_t ctypedef lxb_status_t (*lxb_selectors_cb_f)(lxb_dom_node_t *node, lxb_css_selector_specificity_t *spec, void *ctx) ctypedef enum lxb_selectors_opt_t: LXB_SELECTORS_OPT_DEFAULT = 0x00 LXB_SELECTORS_OPT_MATCH_ROOT = 1 << 1 LXB_SELECTORS_OPT_MATCH_FIRST = 1 << 2 void lxb_selectors_opt_set(lxb_selectors_t *selectors, lxb_selectors_opt_t opt) lxb_css_selectors_t * lxb_css_selectors_create() lxb_status_t lxb_css_selectors_init(lxb_css_selectors_t *selectors) void lxb_css_parser_selectors_set(lxb_css_parser_t *parser, lxb_css_selectors_t *selectors) lxb_css_selector_list_t * lxb_css_selectors_parse(lxb_css_parser_t *parser, const lxb_char_t *data, size_t length) lxb_css_selectors_t * lxb_css_selectors_destroy(lxb_css_selectors_t *selectors, bint self_destroy) lxb_selectors_t * lxb_selectors_create() lxb_status_t lxb_selectors_init(lxb_selectors_t *selectors) lxb_selectors_t * lxb_selectors_destroy(lxb_selectors_t *selectors, bint self_destroy) lxb_status_t lxb_selectors_find(lxb_selectors_t *selectors, lxb_dom_node_t *root, lxb_css_selector_list_t *list, lxb_selectors_cb_f cb, void *ctx) rushter-selectolax-240bf6c/selectolax/lexbor.pyi000066400000000000000000001347151525667221200221630ustar00rootroot00000000000000from __future__ import annotations from enum import IntFlag from typing import Any, Iterator, Literal, NoReturn, Optional, TypeVar, overload DefaultT = TypeVar("DefaultT") class LexborDocumentOptions(IntFlag): """Parser options for the Lexbor document. These mirror the ``lxb_dom_document_opt`` flags from Lexbor. Multiple options can be combined with the bitwise OR operator, or by combining their integer values. Both of the following are equivalent: >>> LexborDocumentOptions.WO_EVENTS | LexborDocumentOptions.UNDEF >>> LexborDocumentOptions.WO_EVENTS.value | LexborDocumentOptions.UNDEF.value 1 The combined value can be passed directly to the parser:: LexborHTMLParser(html, options=LexborDocumentOptions.WO_EVENTS) """ """Original Lexbor name: ``LXB_DOM_DOCUMENT_OPT_UNDEF``. Default value. No options are set. """ UNDEF: int """Original Lexbor name: ``LXB_DOM_DOCUMENT_OPT_WO_EVENTS``. Disables mutation events ("without events"). When set, Lexbor skips the document mutation callbacks (``inserted``, ``removed``, ``moved``, ``children_changed``, ``connected``, and the attribute callbacks) while building or modifying the tree. This can speed up parsing, but it also disables behaviors implemented through those callbacks, such as ```` copying the selected ``