This is page 161 of 168. Use http://codebase.md/romanshablio/mcp_server?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .DS_Store
├── .venv
│ ├── __pycache__
│ │ └── hello.cpython-312.pyc
│ ├── bin
│ │ ├── activate
│ │ ├── activate.csh
│ │ ├── activate.fish
│ │ ├── Activate.ps1
│ │ ├── flask
│ │ ├── normalizer
│ │ ├── pip
│ │ ├── pip3
│ │ ├── pip3.12
│ │ ├── python
│ │ ├── python3
│ │ └── python3.12
│ ├── hello.py
│ ├── lib
│ │ └── python3.12
│ │ └── site-packages
│ │ ├── beautifulsoup4-4.12.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ ├── AUTHORS
│ │ │ │ └── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ └── WHEEL
│ │ ├── blinker
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _utilities.cpython-312.pyc
│ │ │ │ └── base.cpython-312.pyc
│ │ │ ├── _utilities.py
│ │ │ ├── base.py
│ │ │ └── py.typed
│ │ ├── blinker-1.8.2.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── bs4
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── css.cpython-312.pyc
│ │ │ │ ├── dammit.cpython-312.pyc
│ │ │ │ ├── diagnose.cpython-312.pyc
│ │ │ │ ├── element.cpython-312.pyc
│ │ │ │ └── formatter.cpython-312.pyc
│ │ │ ├── builder
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── _html5lib.cpython-312.pyc
│ │ │ │ │ ├── _htmlparser.cpython-312.pyc
│ │ │ │ │ └── _lxml.cpython-312.pyc
│ │ │ │ ├── _html5lib.py
│ │ │ │ ├── _htmlparser.py
│ │ │ │ └── _lxml.py
│ │ │ ├── css.py
│ │ │ ├── dammit.py
│ │ │ ├── diagnose.py
│ │ │ ├── element.py
│ │ │ ├── formatter.py
│ │ │ └── tests
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── test_builder_registry.cpython-312.pyc
│ │ │ │ ├── test_builder.cpython-312.pyc
│ │ │ │ ├── test_css.cpython-312.pyc
│ │ │ │ ├── test_dammit.cpython-312.pyc
│ │ │ │ ├── test_docs.cpython-312.pyc
│ │ │ │ ├── test_element.cpython-312.pyc
│ │ │ │ ├── test_formatter.cpython-312.pyc
│ │ │ │ ├── test_fuzz.cpython-312.pyc
│ │ │ │ ├── test_html5lib.cpython-312.pyc
│ │ │ │ ├── test_htmlparser.cpython-312.pyc
│ │ │ │ ├── test_lxml.cpython-312.pyc
│ │ │ │ ├── test_navigablestring.cpython-312.pyc
│ │ │ │ ├── test_pageelement.cpython-312.pyc
│ │ │ │ ├── test_soup.cpython-312.pyc
│ │ │ │ ├── test_tag.cpython-312.pyc
│ │ │ │ └── test_tree.cpython-312.pyc
│ │ │ ├── fuzz
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4670634698080256.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4818336571064320.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4999465949331456.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5000587759190016.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5167584867909632.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5270998950477824.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5375146639360000.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5492400320282624.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5703933063462912.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5843991618256896.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5984173902397440.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6124268085182464.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6241471367348224.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6306874195312640.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6450958476902400.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6600557255327744.testcase
│ │ │ │ ├── crash-0d306a50c8ed8bcd0785b67000fcd5dea1d33f08.testcase
│ │ │ │ └── crash-ffbdfa8a2b26f13537b68d3794b0478a4090ee4a.testcase
│ │ │ ├── test_builder_registry.py
│ │ │ ├── test_builder.py
│ │ │ ├── test_css.py
│ │ │ ├── test_dammit.py
│ │ │ ├── test_docs.py
│ │ │ ├── test_element.py
│ │ │ ├── test_formatter.py
│ │ │ ├── test_fuzz.py
│ │ │ ├── test_html5lib.py
│ │ │ ├── test_htmlparser.py
│ │ │ ├── test_lxml.py
│ │ │ ├── test_navigablestring.py
│ │ │ ├── test_pageelement.py
│ │ │ ├── test_soup.py
│ │ │ ├── test_tag.py
│ │ │ └── test_tree.py
│ │ ├── certifi
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ └── core.cpython-312.pyc
│ │ │ ├── cacert.pem
│ │ │ ├── core.py
│ │ │ └── py.typed
│ │ ├── certifi-2024.8.30.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── charset_normalizer
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ ├── cd.cpython-312.pyc
│ │ │ │ ├── constant.cpython-312.pyc
│ │ │ │ ├── legacy.cpython-312.pyc
│ │ │ │ ├── md.cpython-312.pyc
│ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── version.cpython-312.pyc
│ │ │ ├── api.py
│ │ │ ├── cd.py
│ │ │ ├── cli
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __main__.py
│ │ │ │ └── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── __main__.cpython-312.pyc
│ │ │ ├── constant.py
│ │ │ ├── legacy.py
│ │ │ ├── md__mypyc.cpython-312-darwin.so
│ │ │ ├── md.cpython-312-darwin.so
│ │ │ ├── md.py
│ │ │ ├── models.py
│ │ │ ├── py.typed
│ │ │ ├── utils.py
│ │ │ └── version.py
│ │ ├── charset_normalizer-3.4.0.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── click
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ ├── _termui_impl.cpython-312.pyc
│ │ │ │ ├── _textwrap.cpython-312.pyc
│ │ │ │ ├── _winconsole.cpython-312.pyc
│ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ ├── decorators.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── formatting.cpython-312.pyc
│ │ │ │ ├── globals.cpython-312.pyc
│ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ ├── shell_completion.cpython-312.pyc
│ │ │ │ ├── termui.cpython-312.pyc
│ │ │ │ ├── testing.cpython-312.pyc
│ │ │ │ ├── types.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── _compat.py
│ │ │ ├── _termui_impl.py
│ │ │ ├── _textwrap.py
│ │ │ ├── _winconsole.py
│ │ │ ├── core.py
│ │ │ ├── decorators.py
│ │ │ ├── exceptions.py
│ │ │ ├── formatting.py
│ │ │ ├── globals.py
│ │ │ ├── parser.py
│ │ │ ├── py.typed
│ │ │ ├── shell_completion.py
│ │ │ ├── termui.py
│ │ │ ├── testing.py
│ │ │ ├── types.py
│ │ │ └── utils.py
│ │ ├── click-8.1.7.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.rst
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── fake_useragent
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── errors.cpython-312.pyc
│ │ │ │ ├── fake.cpython-312.pyc
│ │ │ │ ├── log.cpython-312.pyc
│ │ │ │ ├── settings.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── data
│ │ │ │ └── browsers.json
│ │ │ ├── errors.py
│ │ │ ├── fake.py
│ │ │ ├── log.py
│ │ │ ├── settings.py
│ │ │ └── utils.py
│ │ ├── fake_useragent-1.5.1.dist-info
│ │ │ ├── AUTHORS
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── flask
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ ├── app.cpython-312.pyc
│ │ │ │ ├── blueprints.cpython-312.pyc
│ │ │ │ ├── cli.cpython-312.pyc
│ │ │ │ ├── config.cpython-312.pyc
│ │ │ │ ├── ctx.cpython-312.pyc
│ │ │ │ ├── debughelpers.cpython-312.pyc
│ │ │ │ ├── globals.cpython-312.pyc
│ │ │ │ ├── helpers.cpython-312.pyc
│ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ ├── signals.cpython-312.pyc
│ │ │ │ ├── templating.cpython-312.pyc
│ │ │ │ ├── testing.cpython-312.pyc
│ │ │ │ ├── typing.cpython-312.pyc
│ │ │ │ ├── views.cpython-312.pyc
│ │ │ │ └── wrappers.cpython-312.pyc
│ │ │ ├── app.py
│ │ │ ├── blueprints.py
│ │ │ ├── cli.py
│ │ │ ├── config.py
│ │ │ ├── ctx.py
│ │ │ ├── debughelpers.py
│ │ │ ├── globals.py
│ │ │ ├── helpers.py
│ │ │ ├── json
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── provider.cpython-312.pyc
│ │ │ │ │ └── tag.cpython-312.pyc
│ │ │ │ ├── provider.py
│ │ │ │ └── tag.py
│ │ │ ├── logging.py
│ │ │ ├── py.typed
│ │ │ ├── sansio
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── app.cpython-312.pyc
│ │ │ │ │ ├── blueprints.cpython-312.pyc
│ │ │ │ │ └── scaffold.cpython-312.pyc
│ │ │ │ ├── app.py
│ │ │ │ ├── blueprints.py
│ │ │ │ ├── README.md
│ │ │ │ └── scaffold.py
│ │ │ ├── sessions.py
│ │ │ ├── signals.py
│ │ │ ├── templating.py
│ │ │ ├── testing.py
│ │ │ ├── typing.py
│ │ │ ├── views.py
│ │ │ └── wrappers.py
│ │ ├── flask-3.0.3.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ └── WHEEL
│ │ ├── idna
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── codec.cpython-312.pyc
│ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ ├── idnadata.cpython-312.pyc
│ │ │ │ ├── intranges.cpython-312.pyc
│ │ │ │ ├── package_data.cpython-312.pyc
│ │ │ │ └── uts46data.cpython-312.pyc
│ │ │ ├── codec.py
│ │ │ ├── compat.py
│ │ │ ├── core.py
│ │ │ ├── idnadata.py
│ │ │ ├── intranges.py
│ │ │ ├── package_data.py
│ │ │ ├── py.typed
│ │ │ └── uts46data.py
│ │ ├── idna-3.10.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.md
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── itsdangerous
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _json.cpython-312.pyc
│ │ │ │ ├── encoding.cpython-312.pyc
│ │ │ │ ├── exc.cpython-312.pyc
│ │ │ │ ├── serializer.cpython-312.pyc
│ │ │ │ ├── signer.cpython-312.pyc
│ │ │ │ ├── timed.cpython-312.pyc
│ │ │ │ └── url_safe.cpython-312.pyc
│ │ │ ├── _json.py
│ │ │ ├── encoding.py
│ │ │ ├── exc.py
│ │ │ ├── py.typed
│ │ │ ├── serializer.py
│ │ │ ├── signer.py
│ │ │ ├── timed.py
│ │ │ └── url_safe.py
│ │ ├── itsdangerous-2.2.0.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── jinja2
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _identifier.cpython-312.pyc
│ │ │ │ ├── async_utils.cpython-312.pyc
│ │ │ │ ├── bccache.cpython-312.pyc
│ │ │ │ ├── compiler.cpython-312.pyc
│ │ │ │ ├── constants.cpython-312.pyc
│ │ │ │ ├── debug.cpython-312.pyc
│ │ │ │ ├── defaults.cpython-312.pyc
│ │ │ │ ├── environment.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── ext.cpython-312.pyc
│ │ │ │ ├── filters.cpython-312.pyc
│ │ │ │ ├── idtracking.cpython-312.pyc
│ │ │ │ ├── lexer.cpython-312.pyc
│ │ │ │ ├── loaders.cpython-312.pyc
│ │ │ │ ├── meta.cpython-312.pyc
│ │ │ │ ├── nativetypes.cpython-312.pyc
│ │ │ │ ├── nodes.cpython-312.pyc
│ │ │ │ ├── optimizer.cpython-312.pyc
│ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ ├── runtime.cpython-312.pyc
│ │ │ │ ├── sandbox.cpython-312.pyc
│ │ │ │ ├── tests.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── visitor.cpython-312.pyc
│ │ │ ├── _identifier.py
│ │ │ ├── async_utils.py
│ │ │ ├── bccache.py
│ │ │ ├── compiler.py
│ │ │ ├── constants.py
│ │ │ ├── debug.py
│ │ │ ├── defaults.py
│ │ │ ├── environment.py
│ │ │ ├── exceptions.py
│ │ │ ├── ext.py
│ │ │ ├── filters.py
│ │ │ ├── idtracking.py
│ │ │ ├── lexer.py
│ │ │ ├── loaders.py
│ │ │ ├── meta.py
│ │ │ ├── nativetypes.py
│ │ │ ├── nodes.py
│ │ │ ├── optimizer.py
│ │ │ ├── parser.py
│ │ │ ├── py.typed
│ │ │ ├── runtime.py
│ │ │ ├── sandbox.py
│ │ │ ├── tests.py
│ │ │ ├── utils.py
│ │ │ └── visitor.py
│ │ ├── jinja2-3.1.4.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── lxml
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _elementpath.cpython-312.pyc
│ │ │ │ ├── builder.cpython-312.pyc
│ │ │ │ ├── cssselect.cpython-312.pyc
│ │ │ │ ├── doctestcompare.cpython-312.pyc
│ │ │ │ ├── ElementInclude.cpython-312.pyc
│ │ │ │ ├── pyclasslookup.cpython-312.pyc
│ │ │ │ ├── sax.cpython-312.pyc
│ │ │ │ └── usedoctest.cpython-312.pyc
│ │ │ ├── _elementpath.cpython-312-darwin.so
│ │ │ ├── _elementpath.py
│ │ │ ├── apihelpers.pxi
│ │ │ ├── builder.cpython-312-darwin.so
│ │ │ ├── builder.py
│ │ │ ├── classlookup.pxi
│ │ │ ├── cleanup.pxi
│ │ │ ├── cssselect.py
│ │ │ ├── debug.pxi
│ │ │ ├── docloader.pxi
│ │ │ ├── doctestcompare.py
│ │ │ ├── dtd.pxi
│ │ │ ├── ElementInclude.py
│ │ │ ├── etree_api.h
│ │ │ ├── etree.cpython-312-darwin.so
│ │ │ ├── etree.h
│ │ │ ├── etree.pyx
│ │ │ ├── extensions.pxi
│ │ │ ├── html
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── _diffcommand.cpython-312.pyc
│ │ │ │ │ ├── _html5builder.cpython-312.pyc
│ │ │ │ │ ├── _setmixin.cpython-312.pyc
│ │ │ │ │ ├── builder.cpython-312.pyc
│ │ │ │ │ ├── clean.cpython-312.pyc
│ │ │ │ │ ├── defs.cpython-312.pyc
│ │ │ │ │ ├── diff.cpython-312.pyc
│ │ │ │ │ ├── ElementSoup.cpython-312.pyc
│ │ │ │ │ ├── formfill.cpython-312.pyc
│ │ │ │ │ ├── html5parser.cpython-312.pyc
│ │ │ │ │ ├── soupparser.cpython-312.pyc
│ │ │ │ │ └── usedoctest.cpython-312.pyc
│ │ │ │ ├── _diffcommand.py
│ │ │ │ ├── _html5builder.py
│ │ │ │ ├── _setmixin.py
│ │ │ │ ├── builder.py
│ │ │ │ ├── clean.py
│ │ │ │ ├── defs.py
│ │ │ │ ├── diff.cpython-312-darwin.so
│ │ │ │ ├── diff.py
│ │ │ │ ├── ElementSoup.py
│ │ │ │ ├── formfill.py
│ │ │ │ ├── html5parser.py
│ │ │ │ ├── soupparser.py
│ │ │ │ └── usedoctest.py
│ │ │ ├── includes
│ │ │ │ ├── __init__.pxd
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ ├── c14n.pxd
│ │ │ │ ├── config.pxd
│ │ │ │ ├── dtdvalid.pxd
│ │ │ │ ├── etree_defs.h
│ │ │ │ ├── etreepublic.pxd
│ │ │ │ ├── extlibs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── libcharset.h
│ │ │ │ │ ├── localcharset.h
│ │ │ │ │ ├── zconf.h
│ │ │ │ │ └── zlib.h
│ │ │ │ ├── htmlparser.pxd
│ │ │ │ ├── libexslt
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── exslt.h
│ │ │ │ │ ├── exsltconfig.h
│ │ │ │ │ └── exsltexports.h
│ │ │ │ ├── libxml
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── c14n.h
│ │ │ │ │ ├── catalog.h
│ │ │ │ │ ├── chvalid.h
│ │ │ │ │ ├── debugXML.h
│ │ │ │ │ ├── dict.h
│ │ │ │ │ ├── encoding.h
│ │ │ │ │ ├── entities.h
│ │ │ │ │ ├── globals.h
│ │ │ │ │ ├── hash.h
│ │ │ │ │ ├── HTMLparser.h
│ │ │ │ │ ├── HTMLtree.h
│ │ │ │ │ ├── list.h
│ │ │ │ │ ├── nanoftp.h
│ │ │ │ │ ├── nanohttp.h
│ │ │ │ │ ├── parser.h
│ │ │ │ │ ├── parserInternals.h
│ │ │ │ │ ├── relaxng.h
│ │ │ │ │ ├── SAX.h
│ │ │ │ │ ├── SAX2.h
│ │ │ │ │ ├── schemasInternals.h
│ │ │ │ │ ├── schematron.h
│ │ │ │ │ ├── threads.h
│ │ │ │ │ ├── tree.h
│ │ │ │ │ ├── uri.h
│ │ │ │ │ ├── valid.h
│ │ │ │ │ ├── xinclude.h
│ │ │ │ │ ├── xlink.h
│ │ │ │ │ ├── xmlautomata.h
│ │ │ │ │ ├── xmlerror.h
│ │ │ │ │ ├── xmlexports.h
│ │ │ │ │ ├── xmlIO.h
│ │ │ │ │ ├── xmlmemory.h
│ │ │ │ │ ├── xmlmodule.h
│ │ │ │ │ ├── xmlreader.h
│ │ │ │ │ ├── xmlregexp.h
│ │ │ │ │ ├── xmlsave.h
│ │ │ │ │ ├── xmlschemas.h
│ │ │ │ │ ├── xmlschemastypes.h
│ │ │ │ │ ├── xmlstring.h
│ │ │ │ │ ├── xmlunicode.h
│ │ │ │ │ ├── xmlversion.h
│ │ │ │ │ ├── xmlwriter.h
│ │ │ │ │ ├── xpath.h
│ │ │ │ │ ├── xpathInternals.h
│ │ │ │ │ └── xpointer.h
│ │ │ │ ├── libxslt
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── attributes.h
│ │ │ │ │ ├── documents.h
│ │ │ │ │ ├── extensions.h
│ │ │ │ │ ├── extra.h
│ │ │ │ │ ├── functions.h
│ │ │ │ │ ├── imports.h
│ │ │ │ │ ├── keys.h
│ │ │ │ │ ├── namespaces.h
│ │ │ │ │ ├── numbersInternals.h
│ │ │ │ │ ├── pattern.h
│ │ │ │ │ ├── preproc.h
│ │ │ │ │ ├── security.h
│ │ │ │ │ ├── templates.h
│ │ │ │ │ ├── transform.h
│ │ │ │ │ ├── variables.h
│ │ │ │ │ ├── xslt.h
│ │ │ │ │ ├── xsltconfig.h
│ │ │ │ │ ├── xsltexports.h
│ │ │ │ │ ├── xsltInternals.h
│ │ │ │ │ ├── xsltlocale.h
│ │ │ │ │ └── xsltutils.h
│ │ │ │ ├── lxml-version.h
│ │ │ │ ├── relaxng.pxd
│ │ │ │ ├── schematron.pxd
│ │ │ │ ├── tree.pxd
│ │ │ │ ├── uri.pxd
│ │ │ │ ├── xinclude.pxd
│ │ │ │ ├── xmlerror.pxd
│ │ │ │ ├── xmlparser.pxd
│ │ │ │ ├── xmlschema.pxd
│ │ │ │ ├── xpath.pxd
│ │ │ │ └── xslt.pxd
│ │ │ ├── isoschematron
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ └── resources
│ │ │ │ ├── rng
│ │ │ │ │ └── iso-schematron.rng
│ │ │ │ └── xsl
│ │ │ │ ├── iso-schematron-xslt1
│ │ │ │ │ ├── iso_abstract_expand.xsl
│ │ │ │ │ ├── iso_dsdl_include.xsl
│ │ │ │ │ ├── iso_schematron_message.xsl
│ │ │ │ │ ├── iso_schematron_skeleton_for_xslt1.xsl
│ │ │ │ │ ├── iso_svrl_for_xslt1.xsl
│ │ │ │ │ └── readme.txt
│ │ │ │ ├── RNG2Schtrn.xsl
│ │ │ │ └── XSD2Schtrn.xsl
│ │ │ ├── iterparse.pxi
│ │ │ ├── lxml.etree_api.h
│ │ │ ├── lxml.etree.h
│ │ │ ├── nsclasses.pxi
│ │ │ ├── objectify.cpython-312-darwin.so
│ │ │ ├── objectify.pyx
│ │ │ ├── objectpath.pxi
│ │ │ ├── parser.pxi
│ │ │ ├── parsertarget.pxi
│ │ │ ├── proxy.pxi
│ │ │ ├── public-api.pxi
│ │ │ ├── pyclasslookup.py
│ │ │ ├── readonlytree.pxi
│ │ │ ├── relaxng.pxi
│ │ │ ├── sax.cpython-312-darwin.so
│ │ │ ├── sax.py
│ │ │ ├── saxparser.pxi
│ │ │ ├── schematron.pxi
│ │ │ ├── serializer.pxi
│ │ │ ├── usedoctest.py
│ │ │ ├── xinclude.pxi
│ │ │ ├── xmlerror.pxi
│ │ │ ├── xmlid.pxi
│ │ │ ├── xmlschema.pxi
│ │ │ ├── xpath.pxi
│ │ │ ├── xslt.pxi
│ │ │ └── xsltext.pxi
│ │ ├── lxml-5.3.0.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── LICENSES.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── markupsafe
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── _native.cpython-312.pyc
│ │ │ ├── _native.py
│ │ │ ├── _speedups.c
│ │ │ ├── _speedups.cpython-312-darwin.so
│ │ │ ├── _speedups.pyi
│ │ │ └── py.typed
│ │ ├── MarkupSafe-3.0.1.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── pip
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pip-runner__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ └── __pip-runner__.cpython-312.pyc
│ │ │ ├── _internal
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── build_env.cpython-312.pyc
│ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ ├── configuration.cpython-312.pyc
│ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ ├── main.cpython-312.pyc
│ │ │ │ │ ├── pyproject.cpython-312.pyc
│ │ │ │ │ ├── self_outdated_check.cpython-312.pyc
│ │ │ │ │ └── wheel_builder.cpython-312.pyc
│ │ │ │ ├── build_env.py
│ │ │ │ ├── cache.py
│ │ │ │ ├── cli
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── autocompletion.cpython-312.pyc
│ │ │ │ │ │ ├── base_command.cpython-312.pyc
│ │ │ │ │ │ ├── cmdoptions.cpython-312.pyc
│ │ │ │ │ │ ├── command_context.cpython-312.pyc
│ │ │ │ │ │ ├── index_command.cpython-312.pyc
│ │ │ │ │ │ ├── main_parser.cpython-312.pyc
│ │ │ │ │ │ ├── main.cpython-312.pyc
│ │ │ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ │ │ ├── progress_bars.cpython-312.pyc
│ │ │ │ │ │ ├── req_command.cpython-312.pyc
│ │ │ │ │ │ ├── spinners.cpython-312.pyc
│ │ │ │ │ │ └── status_codes.cpython-312.pyc
│ │ │ │ │ ├── autocompletion.py
│ │ │ │ │ ├── base_command.py
│ │ │ │ │ ├── cmdoptions.py
│ │ │ │ │ ├── command_context.py
│ │ │ │ │ ├── index_command.py
│ │ │ │ │ ├── main_parser.py
│ │ │ │ │ ├── main.py
│ │ │ │ │ ├── parser.py
│ │ │ │ │ ├── progress_bars.py
│ │ │ │ │ ├── req_command.py
│ │ │ │ │ ├── spinners.py
│ │ │ │ │ └── status_codes.py
│ │ │ │ ├── commands
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── check.cpython-312.pyc
│ │ │ │ │ │ ├── completion.cpython-312.pyc
│ │ │ │ │ │ ├── configuration.cpython-312.pyc
│ │ │ │ │ │ ├── debug.cpython-312.pyc
│ │ │ │ │ │ ├── download.cpython-312.pyc
│ │ │ │ │ │ ├── freeze.cpython-312.pyc
│ │ │ │ │ │ ├── hash.cpython-312.pyc
│ │ │ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── inspect.cpython-312.pyc
│ │ │ │ │ │ ├── install.cpython-312.pyc
│ │ │ │ │ │ ├── list.cpython-312.pyc
│ │ │ │ │ │ ├── search.cpython-312.pyc
│ │ │ │ │ │ ├── show.cpython-312.pyc
│ │ │ │ │ │ ├── uninstall.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── check.py
│ │ │ │ │ ├── completion.py
│ │ │ │ │ ├── configuration.py
│ │ │ │ │ ├── debug.py
│ │ │ │ │ ├── download.py
│ │ │ │ │ ├── freeze.py
│ │ │ │ │ ├── hash.py
│ │ │ │ │ ├── help.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── inspect.py
│ │ │ │ │ ├── install.py
│ │ │ │ │ ├── list.py
│ │ │ │ │ ├── search.py
│ │ │ │ │ ├── show.py
│ │ │ │ │ ├── uninstall.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── configuration.py
│ │ │ │ ├── distributions
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ ├── installed.cpython-312.pyc
│ │ │ │ │ │ ├── sdist.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── installed.py
│ │ │ │ │ ├── sdist.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── index
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── collector.cpython-312.pyc
│ │ │ │ │ │ ├── package_finder.cpython-312.pyc
│ │ │ │ │ │ └── sources.cpython-312.pyc
│ │ │ │ │ ├── collector.py
│ │ │ │ │ ├── package_finder.py
│ │ │ │ │ └── sources.py
│ │ │ │ ├── locations
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _distutils.cpython-312.pyc
│ │ │ │ │ │ ├── _sysconfig.cpython-312.pyc
│ │ │ │ │ │ └── base.cpython-312.pyc
│ │ │ │ │ ├── _distutils.py
│ │ │ │ │ ├── _sysconfig.py
│ │ │ │ │ └── base.py
│ │ │ │ ├── main.py
│ │ │ │ ├── metadata
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _json.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ └── pkg_resources.cpython-312.pyc
│ │ │ │ │ ├── _json.py
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── importlib
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ │ │ │ ├── _dists.cpython-312.pyc
│ │ │ │ │ │ │ └── _envs.cpython-312.pyc
│ │ │ │ │ │ ├── _compat.py
│ │ │ │ │ │ ├── _dists.py
│ │ │ │ │ │ └── _envs.py
│ │ │ │ │ └── pkg_resources.py
│ │ │ │ ├── models
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── candidate.cpython-312.pyc
│ │ │ │ │ │ ├── direct_url.cpython-312.pyc
│ │ │ │ │ │ ├── format_control.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── installation_report.cpython-312.pyc
│ │ │ │ │ │ ├── link.cpython-312.pyc
│ │ │ │ │ │ ├── scheme.cpython-312.pyc
│ │ │ │ │ │ ├── search_scope.cpython-312.pyc
│ │ │ │ │ │ ├── selection_prefs.cpython-312.pyc
│ │ │ │ │ │ ├── target_python.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── candidate.py
│ │ │ │ │ ├── direct_url.py
│ │ │ │ │ ├── format_control.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── installation_report.py
│ │ │ │ │ ├── link.py
│ │ │ │ │ ├── scheme.py
│ │ │ │ │ ├── search_scope.py
│ │ │ │ │ ├── selection_prefs.py
│ │ │ │ │ ├── target_python.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── network
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── download.cpython-312.pyc
│ │ │ │ │ │ ├── lazy_wheel.cpython-312.pyc
│ │ │ │ │ │ ├── session.cpython-312.pyc
│ │ │ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ │ │ └── xmlrpc.cpython-312.pyc
│ │ │ │ │ ├── auth.py
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── download.py
│ │ │ │ │ ├── lazy_wheel.py
│ │ │ │ │ ├── session.py
│ │ │ │ │ ├── utils.py
│ │ │ │ │ └── xmlrpc.py
│ │ │ │ ├── operations
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── check.cpython-312.pyc
│ │ │ │ │ │ ├── freeze.cpython-312.pyc
│ │ │ │ │ │ └── prepare.cpython-312.pyc
│ │ │ │ │ ├── build
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── build_tracker.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata_editable.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata_legacy.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ │ ├── wheel_editable.cpython-312.pyc
│ │ │ │ │ │ │ ├── wheel_legacy.cpython-312.pyc
│ │ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ │ ├── build_tracker.py
│ │ │ │ │ │ ├── metadata_editable.py
│ │ │ │ │ │ ├── metadata_legacy.py
│ │ │ │ │ │ ├── metadata.py
│ │ │ │ │ │ ├── wheel_editable.py
│ │ │ │ │ │ ├── wheel_legacy.py
│ │ │ │ │ │ └── wheel.py
│ │ │ │ │ ├── check.py
│ │ │ │ │ ├── freeze.py
│ │ │ │ │ ├── install
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── editable_legacy.cpython-312.pyc
│ │ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ │ ├── editable_legacy.py
│ │ │ │ │ │ └── wheel.py
│ │ │ │ │ └── prepare.py
│ │ │ │ ├── pyproject.py
│ │ │ │ ├── req
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── constructors.cpython-312.pyc
│ │ │ │ │ │ ├── req_file.cpython-312.pyc
│ │ │ │ │ │ ├── req_install.cpython-312.pyc
│ │ │ │ │ │ ├── req_set.cpython-312.pyc
│ │ │ │ │ │ └── req_uninstall.cpython-312.pyc
│ │ │ │ │ ├── constructors.py
│ │ │ │ │ ├── req_file.py
│ │ │ │ │ ├── req_install.py
│ │ │ │ │ ├── req_set.py
│ │ │ │ │ └── req_uninstall.py
│ │ │ │ ├── resolution
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ └── base.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── legacy
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── resolver.cpython-312.pyc
│ │ │ │ │ │ └── resolver.py
│ │ │ │ │ └── resolvelib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ ├── candidates.cpython-312.pyc
│ │ │ │ │ │ ├── factory.cpython-312.pyc
│ │ │ │ │ │ ├── found_candidates.cpython-312.pyc
│ │ │ │ │ │ ├── provider.cpython-312.pyc
│ │ │ │ │ │ ├── reporter.cpython-312.pyc
│ │ │ │ │ │ ├── requirements.cpython-312.pyc
│ │ │ │ │ │ └── resolver.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── candidates.py
│ │ │ │ │ ├── factory.py
│ │ │ │ │ ├── found_candidates.py
│ │ │ │ │ ├── provider.py
│ │ │ │ │ ├── reporter.py
│ │ │ │ │ ├── requirements.py
│ │ │ │ │ └── resolver.py
│ │ │ │ ├── self_outdated_check.py
│ │ │ │ ├── utils
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _jaraco_text.cpython-312.pyc
│ │ │ │ │ │ ├── _log.cpython-312.pyc
│ │ │ │ │ │ ├── appdirs.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── compatibility_tags.cpython-312.pyc
│ │ │ │ │ │ ├── datetime.cpython-312.pyc
│ │ │ │ │ │ ├── deprecation.cpython-312.pyc
│ │ │ │ │ │ ├── direct_url_helpers.cpython-312.pyc
│ │ │ │ │ │ ├── egg_link.cpython-312.pyc
│ │ │ │ │ │ ├── encoding.cpython-312.pyc
│ │ │ │ │ │ ├── entrypoints.cpython-312.pyc
│ │ │ │ │ │ ├── filesystem.cpython-312.pyc
│ │ │ │ │ │ ├── filetypes.cpython-312.pyc
│ │ │ │ │ │ ├── glibc.cpython-312.pyc
│ │ │ │ │ │ ├── hashes.cpython-312.pyc
│ │ │ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ │ │ ├── misc.cpython-312.pyc
│ │ │ │ │ │ ├── packaging.cpython-312.pyc
│ │ │ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ │ │ ├── setuptools_build.cpython-312.pyc
│ │ │ │ │ │ ├── subprocess.cpython-312.pyc
│ │ │ │ │ │ ├── temp_dir.cpython-312.pyc
│ │ │ │ │ │ ├── unpacking.cpython-312.pyc
│ │ │ │ │ │ ├── urls.cpython-312.pyc
│ │ │ │ │ │ ├── virtualenv.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── _jaraco_text.py
│ │ │ │ │ ├── _log.py
│ │ │ │ │ ├── appdirs.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── compatibility_tags.py
│ │ │ │ │ ├── datetime.py
│ │ │ │ │ ├── deprecation.py
│ │ │ │ │ ├── direct_url_helpers.py
│ │ │ │ │ ├── egg_link.py
│ │ │ │ │ ├── encoding.py
│ │ │ │ │ ├── entrypoints.py
│ │ │ │ │ ├── filesystem.py
│ │ │ │ │ ├── filetypes.py
│ │ │ │ │ ├── glibc.py
│ │ │ │ │ ├── hashes.py
│ │ │ │ │ ├── logging.py
│ │ │ │ │ ├── misc.py
│ │ │ │ │ ├── packaging.py
│ │ │ │ │ ├── retry.py
│ │ │ │ │ ├── setuptools_build.py
│ │ │ │ │ ├── subprocess.py
│ │ │ │ │ ├── temp_dir.py
│ │ │ │ │ ├── unpacking.py
│ │ │ │ │ ├── urls.py
│ │ │ │ │ ├── virtualenv.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── vcs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── bazaar.cpython-312.pyc
│ │ │ │ │ │ ├── git.cpython-312.pyc
│ │ │ │ │ │ ├── mercurial.cpython-312.pyc
│ │ │ │ │ │ ├── subversion.cpython-312.pyc
│ │ │ │ │ │ └── versioncontrol.cpython-312.pyc
│ │ │ │ │ ├── bazaar.py
│ │ │ │ │ ├── git.py
│ │ │ │ │ ├── mercurial.py
│ │ │ │ │ ├── subversion.py
│ │ │ │ │ └── versioncontrol.py
│ │ │ │ └── wheel_builder.py
│ │ │ ├── _vendor
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ └── typing_extensions.cpython-312.pyc
│ │ │ │ ├── cachecontrol
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _cmd.cpython-312.pyc
│ │ │ │ │ │ ├── adapter.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── controller.cpython-312.pyc
│ │ │ │ │ │ ├── filewrapper.cpython-312.pyc
│ │ │ │ │ │ ├── heuristics.cpython-312.pyc
│ │ │ │ │ │ ├── serialize.cpython-312.pyc
│ │ │ │ │ │ └── wrapper.cpython-312.pyc
│ │ │ │ │ ├── _cmd.py
│ │ │ │ │ ├── adapter.py
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── caches
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── file_cache.cpython-312.pyc
│ │ │ │ │ │ │ └── redis_cache.cpython-312.pyc
│ │ │ │ │ │ ├── file_cache.py
│ │ │ │ │ │ └── redis_cache.py
│ │ │ │ │ ├── controller.py
│ │ │ │ │ ├── filewrapper.py
│ │ │ │ │ ├── heuristics.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── serialize.py
│ │ │ │ │ └── wrapper.py
│ │ │ │ ├── certifi
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ └── core.cpython-312.pyc
│ │ │ │ │ ├── cacert.pem
│ │ │ │ │ ├── core.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── distlib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── database.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── locators.cpython-312.pyc
│ │ │ │ │ │ ├── manifest.cpython-312.pyc
│ │ │ │ │ │ ├── markers.cpython-312.pyc
│ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ ├── resources.cpython-312.pyc
│ │ │ │ │ │ ├── scripts.cpython-312.pyc
│ │ │ │ │ │ ├── util.cpython-312.pyc
│ │ │ │ │ │ ├── version.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── database.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── locators.py
│ │ │ │ │ ├── manifest.py
│ │ │ │ │ ├── markers.py
│ │ │ │ │ ├── metadata.py
│ │ │ │ │ ├── resources.py
│ │ │ │ │ ├── scripts.py
│ │ │ │ │ ├── t32.exe
│ │ │ │ │ ├── t64-arm.exe
│ │ │ │ │ ├── t64.exe
│ │ │ │ │ ├── util.py
│ │ │ │ │ ├── version.py
│ │ │ │ │ ├── w32.exe
│ │ │ │ │ ├── w64-arm.exe
│ │ │ │ │ ├── w64.exe
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── distro
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ └── distro.cpython-312.pyc
│ │ │ │ │ ├── distro.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── idna
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── codec.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ │ │ ├── idnadata.cpython-312.pyc
│ │ │ │ │ │ ├── intranges.cpython-312.pyc
│ │ │ │ │ │ ├── package_data.cpython-312.pyc
│ │ │ │ │ │ └── uts46data.cpython-312.pyc
│ │ │ │ │ ├── codec.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── core.py
│ │ │ │ │ ├── idnadata.py
│ │ │ │ │ ├── intranges.py
│ │ │ │ │ ├── package_data.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ └── uts46data.py
│ │ │ │ ├── msgpack
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── ext.cpython-312.pyc
│ │ │ │ │ │ └── fallback.cpython-312.pyc
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── ext.py
│ │ │ │ │ └── fallback.py
│ │ │ │ ├── packaging
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _elffile.cpython-312.pyc
│ │ │ │ │ │ ├── _manylinux.cpython-312.pyc
│ │ │ │ │ │ ├── _musllinux.cpython-312.pyc
│ │ │ │ │ │ ├── _parser.cpython-312.pyc
│ │ │ │ │ │ ├── _structures.cpython-312.pyc
│ │ │ │ │ │ ├── _tokenizer.cpython-312.pyc
│ │ │ │ │ │ ├── markers.cpython-312.pyc
│ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ ├── requirements.cpython-312.pyc
│ │ │ │ │ │ ├── specifiers.cpython-312.pyc
│ │ │ │ │ │ ├── tags.cpython-312.pyc
│ │ │ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ │ │ └── version.cpython-312.pyc
│ │ │ │ │ ├── _elffile.py
│ │ │ │ │ ├── _manylinux.py
│ │ │ │ │ ├── _musllinux.py
│ │ │ │ │ ├── _parser.py
│ │ │ │ │ ├── _structures.py
│ │ │ │ │ ├── _tokenizer.py
│ │ │ │ │ ├── markers.py
│ │ │ │ │ ├── metadata.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── requirements.py
│ │ │ │ │ ├── specifiers.py
│ │ │ │ │ ├── tags.py
│ │ │ │ │ ├── utils.py
│ │ │ │ │ └── version.py
│ │ │ │ ├── pkg_resources
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ ├── platformdirs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── android.cpython-312.pyc
│ │ │ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ │ │ ├── macos.cpython-312.pyc
│ │ │ │ │ │ ├── unix.cpython-312.pyc
│ │ │ │ │ │ ├── version.cpython-312.pyc
│ │ │ │ │ │ └── windows.cpython-312.pyc
│ │ │ │ │ ├── android.py
│ │ │ │ │ ├── api.py
│ │ │ │ │ ├── macos.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── unix.py
│ │ │ │ │ ├── version.py
│ │ │ │ │ └── windows.py
│ │ │ │ ├── pygments
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── cmdline.cpython-312.pyc
│ │ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ │ ├── filter.cpython-312.pyc
│ │ │ │ │ │ ├── formatter.cpython-312.pyc
│ │ │ │ │ │ ├── lexer.cpython-312.pyc
│ │ │ │ │ │ ├── modeline.cpython-312.pyc
│ │ │ │ │ │ ├── plugin.cpython-312.pyc
│ │ │ │ │ │ ├── regexopt.cpython-312.pyc
│ │ │ │ │ │ ├── scanner.cpython-312.pyc
│ │ │ │ │ │ ├── sphinxext.cpython-312.pyc
│ │ │ │ │ │ ├── style.cpython-312.pyc
│ │ │ │ │ │ ├── token.cpython-312.pyc
│ │ │ │ │ │ ├── unistring.cpython-312.pyc
│ │ │ │ │ │ └── util.cpython-312.pyc
│ │ │ │ │ ├── cmdline.py
│ │ │ │ │ ├── console.py
│ │ │ │ │ ├── filter.py
│ │ │ │ │ ├── filters
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ └── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── formatter.py
│ │ │ │ │ ├── formatters
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _mapping.cpython-312.pyc
│ │ │ │ │ │ │ ├── bbcode.cpython-312.pyc
│ │ │ │ │ │ │ ├── groff.cpython-312.pyc
│ │ │ │ │ │ │ ├── html.cpython-312.pyc
│ │ │ │ │ │ │ ├── img.cpython-312.pyc
│ │ │ │ │ │ │ ├── irc.cpython-312.pyc
│ │ │ │ │ │ │ ├── latex.cpython-312.pyc
│ │ │ │ │ │ │ ├── other.cpython-312.pyc
│ │ │ │ │ │ │ ├── pangomarkup.cpython-312.pyc
│ │ │ │ │ │ │ ├── rtf.cpython-312.pyc
│ │ │ │ │ │ │ ├── svg.cpython-312.pyc
│ │ │ │ │ │ │ ├── terminal.cpython-312.pyc
│ │ │ │ │ │ │ └── terminal256.cpython-312.pyc
│ │ │ │ │ │ ├── _mapping.py
│ │ │ │ │ │ ├── bbcode.py
│ │ │ │ │ │ ├── groff.py
│ │ │ │ │ │ ├── html.py
│ │ │ │ │ │ ├── img.py
│ │ │ │ │ │ ├── irc.py
│ │ │ │ │ │ ├── latex.py
│ │ │ │ │ │ ├── other.py
│ │ │ │ │ │ ├── pangomarkup.py
│ │ │ │ │ │ ├── rtf.py
│ │ │ │ │ │ ├── svg.py
│ │ │ │ │ │ ├── terminal.py
│ │ │ │ │ │ └── terminal256.py
│ │ │ │ │ ├── lexer.py
│ │ │ │ │ ├── lexers
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _mapping.cpython-312.pyc
│ │ │ │ │ │ │ └── python.cpython-312.pyc
│ │ │ │ │ │ ├── _mapping.py
│ │ │ │ │ │ └── python.py
│ │ │ │ │ ├── modeline.py
│ │ │ │ │ ├── plugin.py
│ │ │ │ │ ├── regexopt.py
│ │ │ │ │ ├── scanner.py
│ │ │ │ │ ├── sphinxext.py
│ │ │ │ │ ├── style.py
│ │ │ │ │ ├── styles
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── _mapping.cpython-312.pyc
│ │ │ │ │ │ └── _mapping.py
│ │ │ │ │ ├── token.py
│ │ │ │ │ ├── unistring.py
│ │ │ │ │ └── util.py
│ │ │ │ ├── pyproject_hooks
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ │ │ └── _impl.cpython-312.pyc
│ │ │ │ │ ├── _compat.py
│ │ │ │ │ ├── _impl.py
│ │ │ │ │ └── _in_process
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ └── _in_process.cpython-312.pyc
│ │ │ │ │ └── _in_process.py
│ │ │ │ ├── requests
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __version__.cpython-312.pyc
│ │ │ │ │ │ ├── _internal_utils.cpython-312.pyc
│ │ │ │ │ │ ├── adapters.cpython-312.pyc
│ │ │ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ │ ├── certs.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── cookies.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ │ │ ├── hooks.cpython-312.pyc
│ │ │ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ │ │ ├── packages.cpython-312.pyc
│ │ │ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ │ │ ├── status_codes.cpython-312.pyc
│ │ │ │ │ │ ├── structures.cpython-312.pyc
│ │ │ │ │ │ └── utils.cpython-312.pyc
│ │ │ │ │ ├── __version__.py
│ │ │ │ │ ├── _internal_utils.py
│ │ │ │ │ ├── adapters.py
│ │ │ │ │ ├── api.py
│ │ │ │ │ ├── auth.py
│ │ │ │ │ ├── certs.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── cookies.py
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── help.py
│ │ │ │ │ ├── hooks.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ ├── packages.py
│ │ │ │ │ ├── sessions.py
│ │ │ │ │ ├── status_codes.py
│ │ │ │ │ ├── structures.py
│ │ │ │ │ └── utils.py
│ │ │ │ ├── resolvelib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── providers.cpython-312.pyc
│ │ │ │ │ │ ├── reporters.cpython-312.pyc
│ │ │ │ │ │ ├── resolvers.cpython-312.pyc
│ │ │ │ │ │ └── structs.cpython-312.pyc
│ │ │ │ │ ├── compat
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── collections_abc.cpython-312.pyc
│ │ │ │ │ │ └── collections_abc.py
│ │ │ │ │ ├── providers.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── reporters.py
│ │ │ │ │ ├── resolvers.py
│ │ │ │ │ └── structs.py
│ │ │ │ ├── rich
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── _cell_widths.cpython-312.pyc
│ │ │ │ │ │ ├── _emoji_codes.cpython-312.pyc
│ │ │ │ │ │ ├── _emoji_replace.cpython-312.pyc
│ │ │ │ │ │ ├── _export_format.cpython-312.pyc
│ │ │ │ │ │ ├── _extension.cpython-312.pyc
│ │ │ │ │ │ ├── _fileno.cpython-312.pyc
│ │ │ │ │ │ ├── _inspect.cpython-312.pyc
│ │ │ │ │ │ ├── _log_render.cpython-312.pyc
│ │ │ │ │ │ ├── _loop.cpython-312.pyc
│ │ │ │ │ │ ├── _null_file.cpython-312.pyc
│ │ │ │ │ │ ├── _palettes.cpython-312.pyc
│ │ │ │ │ │ ├── _pick.cpython-312.pyc
│ │ │ │ │ │ ├── _ratio.cpython-312.pyc
│ │ │ │ │ │ ├── _spinners.cpython-312.pyc
│ │ │ │ │ │ ├── _stack.cpython-312.pyc
│ │ │ │ │ │ ├── _timer.cpython-312.pyc
│ │ │ │ │ │ ├── _win32_console.cpython-312.pyc
│ │ │ │ │ │ ├── _windows_renderer.cpython-312.pyc
│ │ │ │ │ │ ├── _windows.cpython-312.pyc
│ │ │ │ │ │ ├── _wrap.cpython-312.pyc
│ │ │ │ │ │ ├── abc.cpython-312.pyc
│ │ │ │ │ │ ├── align.cpython-312.pyc
│ │ │ │ │ │ ├── ansi.cpython-312.pyc
│ │ │ │ │ │ ├── bar.cpython-312.pyc
│ │ │ │ │ │ ├── box.cpython-312.pyc
│ │ │ │ │ │ ├── cells.cpython-312.pyc
│ │ │ │ │ │ ├── color_triplet.cpython-312.pyc
│ │ │ │ │ │ ├── color.cpython-312.pyc
│ │ │ │ │ │ ├── columns.cpython-312.pyc
│ │ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ │ ├── constrain.cpython-312.pyc
│ │ │ │ │ │ ├── containers.cpython-312.pyc
│ │ │ │ │ │ ├── control.cpython-312.pyc
│ │ │ │ │ │ ├── default_styles.cpython-312.pyc
│ │ │ │ │ │ ├── diagnose.cpython-312.pyc
│ │ │ │ │ │ ├── emoji.cpython-312.pyc
│ │ │ │ │ │ ├── errors.cpython-312.pyc
│ │ │ │ │ │ ├── file_proxy.cpython-312.pyc
│ │ │ │ │ │ ├── filesize.cpython-312.pyc
│ │ │ │ │ │ ├── highlighter.cpython-312.pyc
│ │ │ │ │ │ ├── json.cpython-312.pyc
│ │ │ │ │ │ ├── jupyter.cpython-312.pyc
│ │ │ │ │ │ ├── layout.cpython-312.pyc
│ │ │ │ │ │ ├── live_render.cpython-312.pyc
│ │ │ │ │ │ ├── live.cpython-312.pyc
│ │ │ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ │ │ ├── markup.cpython-312.pyc
│ │ │ │ │ │ ├── measure.cpython-312.pyc
│ │ │ │ │ │ ├── padding.cpython-312.pyc
│ │ │ │ │ │ ├── pager.cpython-312.pyc
│ │ │ │ │ │ ├── palette.cpython-312.pyc
│ │ │ │ │ │ ├── panel.cpython-312.pyc
│ │ │ │ │ │ ├── pretty.cpython-312.pyc
│ │ │ │ │ │ ├── progress_bar.cpython-312.pyc
│ │ │ │ │ │ ├── progress.cpython-312.pyc
│ │ │ │ │ │ ├── prompt.cpython-312.pyc
│ │ │ │ │ │ ├── protocol.cpython-312.pyc
│ │ │ │ │ │ ├── region.cpython-312.pyc
│ │ │ │ │ │ ├── repr.cpython-312.pyc
│ │ │ │ │ │ ├── rule.cpython-312.pyc
│ │ │ │ │ │ ├── scope.cpython-312.pyc
│ │ │ │ │ │ ├── screen.cpython-312.pyc
│ │ │ │ │ │ ├── segment.cpython-312.pyc
│ │ │ │ │ │ ├── spinner.cpython-312.pyc
│ │ │ │ │ │ ├── status.cpython-312.pyc
│ │ │ │ │ │ ├── style.cpython-312.pyc
│ │ │ │ │ │ ├── styled.cpython-312.pyc
│ │ │ │ │ │ ├── syntax.cpython-312.pyc
│ │ │ │ │ │ ├── table.cpython-312.pyc
│ │ │ │ │ │ ├── terminal_theme.cpython-312.pyc
│ │ │ │ │ │ ├── text.cpython-312.pyc
│ │ │ │ │ │ ├── theme.cpython-312.pyc
│ │ │ │ │ │ ├── themes.cpython-312.pyc
│ │ │ │ │ │ ├── traceback.cpython-312.pyc
│ │ │ │ │ │ └── tree.cpython-312.pyc
│ │ │ │ │ ├── _cell_widths.py
│ │ │ │ │ ├── _emoji_codes.py
│ │ │ │ │ ├── _emoji_replace.py
│ │ │ │ │ ├── _export_format.py
│ │ │ │ │ ├── _extension.py
│ │ │ │ │ ├── _fileno.py
│ │ │ │ │ ├── _inspect.py
│ │ │ │ │ ├── _log_render.py
│ │ │ │ │ ├── _loop.py
│ │ │ │ │ ├── _null_file.py
│ │ │ │ │ ├── _palettes.py
│ │ │ │ │ ├── _pick.py
│ │ │ │ │ ├── _ratio.py
│ │ │ │ │ ├── _spinners.py
│ │ │ │ │ ├── _stack.py
│ │ │ │ │ ├── _timer.py
│ │ │ │ │ ├── _win32_console.py
│ │ │ │ │ ├── _windows_renderer.py
│ │ │ │ │ ├── _windows.py
│ │ │ │ │ ├── _wrap.py
│ │ │ │ │ ├── abc.py
│ │ │ │ │ ├── align.py
│ │ │ │ │ ├── ansi.py
│ │ │ │ │ ├── bar.py
│ │ │ │ │ ├── box.py
│ │ │ │ │ ├── cells.py
│ │ │ │ │ ├── color_triplet.py
│ │ │ │ │ ├── color.py
│ │ │ │ │ ├── columns.py
│ │ │ │ │ ├── console.py
│ │ │ │ │ ├── constrain.py
│ │ │ │ │ ├── containers.py
│ │ │ │ │ ├── control.py
│ │ │ │ │ ├── default_styles.py
│ │ │ │ │ ├── diagnose.py
│ │ │ │ │ ├── emoji.py
│ │ │ │ │ ├── errors.py
│ │ │ │ │ ├── file_proxy.py
│ │ │ │ │ ├── filesize.py
│ │ │ │ │ ├── highlighter.py
│ │ │ │ │ ├── json.py
│ │ │ │ │ ├── jupyter.py
│ │ │ │ │ ├── layout.py
│ │ │ │ │ ├── live_render.py
│ │ │ │ │ ├── live.py
│ │ │ │ │ ├── logging.py
│ │ │ │ │ ├── markup.py
│ │ │ │ │ ├── measure.py
│ │ │ │ │ ├── padding.py
│ │ │ │ │ ├── pager.py
│ │ │ │ │ ├── palette.py
│ │ │ │ │ ├── panel.py
│ │ │ │ │ ├── pretty.py
│ │ │ │ │ ├── progress_bar.py
│ │ │ │ │ ├── progress.py
│ │ │ │ │ ├── prompt.py
│ │ │ │ │ ├── protocol.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── region.py
│ │ │ │ │ ├── repr.py
│ │ │ │ │ ├── rule.py
│ │ │ │ │ ├── scope.py
│ │ │ │ │ ├── screen.py
│ │ │ │ │ ├── segment.py
│ │ │ │ │ ├── spinner.py
│ │ │ │ │ ├── status.py
│ │ │ │ │ ├── style.py
│ │ │ │ │ ├── styled.py
│ │ │ │ │ ├── syntax.py
│ │ │ │ │ ├── table.py
│ │ │ │ │ ├── terminal_theme.py
│ │ │ │ │ ├── text.py
│ │ │ │ │ ├── theme.py
│ │ │ │ │ ├── themes.py
│ │ │ │ │ ├── traceback.py
│ │ │ │ │ └── tree.py
│ │ │ │ ├── tomli
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _parser.cpython-312.pyc
│ │ │ │ │ │ ├── _re.cpython-312.pyc
│ │ │ │ │ │ └── _types.cpython-312.pyc
│ │ │ │ │ ├── _parser.py
│ │ │ │ │ ├── _re.py
│ │ │ │ │ ├── _types.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── truststore
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _api.cpython-312.pyc
│ │ │ │ │ │ ├── _macos.cpython-312.pyc
│ │ │ │ │ │ ├── _openssl.cpython-312.pyc
│ │ │ │ │ │ ├── _ssl_constants.cpython-312.pyc
│ │ │ │ │ │ └── _windows.cpython-312.pyc
│ │ │ │ │ ├── _api.py
│ │ │ │ │ ├── _macos.py
│ │ │ │ │ ├── _openssl.py
│ │ │ │ │ ├── _ssl_constants.py
│ │ │ │ │ ├── _windows.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── typing_extensions.py
│ │ │ │ ├── urllib3
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _collections.cpython-312.pyc
│ │ │ │ │ │ ├── _version.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── connectionpool.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── fields.cpython-312.pyc
│ │ │ │ │ │ ├── filepost.cpython-312.pyc
│ │ │ │ │ │ ├── poolmanager.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ │ ├── _collections.py
│ │ │ │ │ ├── _version.py
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── connectionpool.py
│ │ │ │ │ ├── contrib
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _appengine_environ.cpython-312.pyc
│ │ │ │ │ │ │ ├── appengine.cpython-312.pyc
│ │ │ │ │ │ │ ├── ntlmpool.cpython-312.pyc
│ │ │ │ │ │ │ ├── pyopenssl.cpython-312.pyc
│ │ │ │ │ │ │ ├── securetransport.cpython-312.pyc
│ │ │ │ │ │ │ └── socks.cpython-312.pyc
│ │ │ │ │ │ ├── _appengine_environ.py
│ │ │ │ │ │ ├── _securetransport
│ │ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ │ ├── bindings.cpython-312.pyc
│ │ │ │ │ │ │ │ └── low_level.cpython-312.pyc
│ │ │ │ │ │ │ ├── bindings.py
│ │ │ │ │ │ │ └── low_level.py
│ │ │ │ │ │ ├── appengine.py
│ │ │ │ │ │ ├── ntlmpool.py
│ │ │ │ │ │ ├── pyopenssl.py
│ │ │ │ │ │ ├── securetransport.py
│ │ │ │ │ │ └── socks.py
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── fields.py
│ │ │ │ │ ├── filepost.py
│ │ │ │ │ ├── packages
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── six.cpython-312.pyc
│ │ │ │ │ │ ├── backports
│ │ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ │ ├── makefile.cpython-312.pyc
│ │ │ │ │ │ │ │ └── weakref_finalize.cpython-312.pyc
│ │ │ │ │ │ │ ├── makefile.py
│ │ │ │ │ │ │ └── weakref_finalize.py
│ │ │ │ │ │ └── six.py
│ │ │ │ │ ├── poolmanager.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ ├── response.py
│ │ │ │ │ └── util
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── proxy.cpython-312.pyc
│ │ │ │ │ │ ├── queue.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ │ │ ├── ssl_.cpython-312.pyc
│ │ │ │ │ │ ├── ssl_match_hostname.cpython-312.pyc
│ │ │ │ │ │ ├── ssltransport.cpython-312.pyc
│ │ │ │ │ │ ├── timeout.cpython-312.pyc
│ │ │ │ │ │ ├── url.cpython-312.pyc
│ │ │ │ │ │ └── wait.cpython-312.pyc
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── proxy.py
│ │ │ │ │ ├── queue.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ ├── response.py
│ │ │ │ │ ├── retry.py
│ │ │ │ │ ├── ssl_.py
│ │ │ │ │ ├── ssl_match_hostname.py
│ │ │ │ │ ├── ssltransport.py
│ │ │ │ │ ├── timeout.py
│ │ │ │ │ ├── url.py
│ │ │ │ │ └── wait.py
│ │ │ │ └── vendor.txt
│ │ │ └── py.typed
│ │ ├── pip-24.2.dist-info
│ │ │ ├── AUTHORS.txt
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── requests
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __version__.cpython-312.pyc
│ │ │ │ ├── _internal_utils.cpython-312.pyc
│ │ │ │ ├── adapters.cpython-312.pyc
│ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ ├── certs.cpython-312.pyc
│ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ ├── cookies.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ ├── hooks.cpython-312.pyc
│ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ ├── packages.cpython-312.pyc
│ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ ├── status_codes.cpython-312.pyc
│ │ │ │ ├── structures.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── __version__.py
│ │ │ ├── _internal_utils.py
│ │ │ ├── adapters.py
│ │ │ ├── api.py
│ │ │ ├── auth.py
│ │ │ ├── certs.py
│ │ │ ├── compat.py
│ │ │ ├── cookies.py
│ │ │ ├── exceptions.py
│ │ │ ├── help.py
│ │ │ ├── hooks.py
│ │ │ ├── models.py
│ │ │ ├── packages.py
│ │ │ ├── sessions.py
│ │ │ ├── status_codes.py
│ │ │ ├── structures.py
│ │ │ └── utils.py
│ │ ├── requests-2.32.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── soupsieve
│ │ │ ├── __init__.py
│ │ │ ├── __meta__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __meta__.cpython-312.pyc
│ │ │ │ ├── css_match.cpython-312.pyc
│ │ │ │ ├── css_parser.cpython-312.pyc
│ │ │ │ ├── css_types.cpython-312.pyc
│ │ │ │ ├── pretty.cpython-312.pyc
│ │ │ │ └── util.cpython-312.pyc
│ │ │ ├── css_match.py
│ │ │ ├── css_parser.py
│ │ │ ├── css_types.py
│ │ │ ├── pretty.py
│ │ │ ├── py.typed
│ │ │ └── util.py
│ │ ├── soupsieve-2.6.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ └── LICENSE.md
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── urllib3
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _base_connection.cpython-312.pyc
│ │ │ │ ├── _collections.cpython-312.pyc
│ │ │ │ ├── _request_methods.cpython-312.pyc
│ │ │ │ ├── _version.cpython-312.pyc
│ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ ├── connectionpool.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── fields.cpython-312.pyc
│ │ │ │ ├── filepost.cpython-312.pyc
│ │ │ │ ├── poolmanager.cpython-312.pyc
│ │ │ │ └── response.cpython-312.pyc
│ │ │ ├── _base_connection.py
│ │ │ ├── _collections.py
│ │ │ ├── _request_methods.py
│ │ │ ├── _version.py
│ │ │ ├── connection.py
│ │ │ ├── connectionpool.py
│ │ │ ├── contrib
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── pyopenssl.cpython-312.pyc
│ │ │ │ │ └── socks.cpython-312.pyc
│ │ │ │ ├── emscripten
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── fetch.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── emscripten_fetch_worker.js
│ │ │ │ │ ├── fetch.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ └── response.py
│ │ │ │ ├── pyopenssl.py
│ │ │ │ └── socks.py
│ │ │ ├── exceptions.py
│ │ │ ├── fields.py
│ │ │ ├── filepost.py
│ │ │ ├── http2
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ └── probe.cpython-312.pyc
│ │ │ │ ├── connection.py
│ │ │ │ └── probe.py
│ │ │ ├── poolmanager.py
│ │ │ ├── py.typed
│ │ │ ├── response.py
│ │ │ └── util
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ ├── proxy.cpython-312.pyc
│ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ ├── ssl_.cpython-312.pyc
│ │ │ │ ├── ssl_match_hostname.cpython-312.pyc
│ │ │ │ ├── ssltransport.cpython-312.pyc
│ │ │ │ ├── timeout.cpython-312.pyc
│ │ │ │ ├── url.cpython-312.pyc
│ │ │ │ ├── util.cpython-312.pyc
│ │ │ │ └── wait.cpython-312.pyc
│ │ │ ├── connection.py
│ │ │ ├── proxy.py
│ │ │ ├── request.py
│ │ │ ├── response.py
│ │ │ ├── retry.py
│ │ │ ├── ssl_.py
│ │ │ ├── ssl_match_hostname.py
│ │ │ ├── ssltransport.py
│ │ │ ├── timeout.py
│ │ │ ├── url.py
│ │ │ ├── util.py
│ │ │ └── wait.py
│ │ ├── urllib3-2.2.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ └── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── useragent
│ │ │ ├── __init__.py
│ │ │ ├── __init__.pyc
│ │ │ ├── __pycache__
│ │ │ │ └── __init__.cpython-312.pyc
│ │ │ ├── resources
│ │ │ │ └── user_agent_data.json
│ │ │ └── test
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ └── __init__.cpython-312.pyc
│ │ │ ├── test_additional_os.json
│ │ │ ├── test_browser.json
│ │ │ ├── test_device.json
│ │ │ ├── test_firefox.json
│ │ │ ├── test_os.json
│ │ │ └── test_pgts_browser.json
│ │ ├── useragent-0.1.1.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── werkzeug
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _internal.cpython-312.pyc
│ │ │ │ ├── _reloader.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── formparser.cpython-312.pyc
│ │ │ │ ├── http.cpython-312.pyc
│ │ │ │ ├── local.cpython-312.pyc
│ │ │ │ ├── security.cpython-312.pyc
│ │ │ │ ├── serving.cpython-312.pyc
│ │ │ │ ├── test.cpython-312.pyc
│ │ │ │ ├── testapp.cpython-312.pyc
│ │ │ │ ├── urls.cpython-312.pyc
│ │ │ │ ├── user_agent.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── wsgi.cpython-312.pyc
│ │ │ ├── _internal.py
│ │ │ ├── _reloader.py
│ │ │ ├── datastructures
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── accept.cpython-312.pyc
│ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ ├── cache_control.cpython-312.pyc
│ │ │ │ │ ├── csp.cpython-312.pyc
│ │ │ │ │ ├── etag.cpython-312.pyc
│ │ │ │ │ ├── file_storage.cpython-312.pyc
│ │ │ │ │ ├── headers.cpython-312.pyc
│ │ │ │ │ ├── mixins.cpython-312.pyc
│ │ │ │ │ ├── range.cpython-312.pyc
│ │ │ │ │ └── structures.cpython-312.pyc
│ │ │ │ ├── accept.py
│ │ │ │ ├── accept.pyi
│ │ │ │ ├── auth.py
│ │ │ │ ├── cache_control.py
│ │ │ │ ├── cache_control.pyi
│ │ │ │ ├── csp.py
│ │ │ │ ├── csp.pyi
│ │ │ │ ├── etag.py
│ │ │ │ ├── etag.pyi
│ │ │ │ ├── file_storage.py
│ │ │ │ ├── file_storage.pyi
│ │ │ │ ├── headers.py
│ │ │ │ ├── headers.pyi
│ │ │ │ ├── mixins.py
│ │ │ │ ├── mixins.pyi
│ │ │ │ ├── range.py
│ │ │ │ ├── range.pyi
│ │ │ │ ├── structures.py
│ │ │ │ └── structures.pyi
│ │ │ ├── debug
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ ├── repr.cpython-312.pyc
│ │ │ │ │ └── tbtools.cpython-312.pyc
│ │ │ │ ├── console.py
│ │ │ │ ├── repr.py
│ │ │ │ ├── shared
│ │ │ │ │ ├── console.png
│ │ │ │ │ ├── debugger.js
│ │ │ │ │ ├── ICON_LICENSE.md
│ │ │ │ │ ├── less.png
│ │ │ │ │ ├── more.png
│ │ │ │ │ └── style.css
│ │ │ │ └── tbtools.py
│ │ │ ├── exceptions.py
│ │ │ ├── formparser.py
│ │ │ ├── http.py
│ │ │ ├── local.py
│ │ │ ├── middleware
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── dispatcher.cpython-312.pyc
│ │ │ │ │ ├── http_proxy.cpython-312.pyc
│ │ │ │ │ ├── lint.cpython-312.pyc
│ │ │ │ │ ├── profiler.cpython-312.pyc
│ │ │ │ │ ├── proxy_fix.cpython-312.pyc
│ │ │ │ │ └── shared_data.cpython-312.pyc
│ │ │ │ ├── dispatcher.py
│ │ │ │ ├── http_proxy.py
│ │ │ │ ├── lint.py
│ │ │ │ ├── profiler.py
│ │ │ │ ├── proxy_fix.py
│ │ │ │ └── shared_data.py
│ │ │ ├── py.typed
│ │ │ ├── routing
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── converters.cpython-312.pyc
│ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ ├── map.cpython-312.pyc
│ │ │ │ │ ├── matcher.cpython-312.pyc
│ │ │ │ │ └── rules.cpython-312.pyc
│ │ │ │ ├── converters.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── map.py
│ │ │ │ ├── matcher.py
│ │ │ │ └── rules.py
│ │ │ ├── sansio
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── http.cpython-312.pyc
│ │ │ │ │ ├── multipart.cpython-312.pyc
│ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ │ └── utils.cpython-312.pyc
│ │ │ │ ├── http.py
│ │ │ │ ├── multipart.py
│ │ │ │ ├── request.py
│ │ │ │ ├── response.py
│ │ │ │ └── utils.py
│ │ │ ├── security.py
│ │ │ ├── serving.py
│ │ │ ├── test.py
│ │ │ ├── testapp.py
│ │ │ ├── urls.py
│ │ │ ├── user_agent.py
│ │ │ ├── utils.py
│ │ │ ├── wrappers
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ ├── request.py
│ │ │ │ └── response.py
│ │ │ └── wsgi.py
│ │ └── werkzeug-3.0.4.dist-info
│ │ ├── INSTALLER
│ │ ├── LICENSE.txt
│ │ ├── METADATA
│ │ ├── RECORD
│ │ └── WHEEL
│ ├── pyvenv.cfg
│ ├── static
│ │ └── styles.css
│ ├── templates
│ │ └── index.html
│ └── test.py
├── cline_config.json
├── mcp_server.py
├── README.md
├── search_results.json
├── settings.json
└── test_files
├── text1.txt
└── text2.txt
```
# Files
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/bs4/element.py:
--------------------------------------------------------------------------------
```python
1 | # Use of this source code is governed by the MIT license.
2 | __license__ = "MIT"
3 |
4 | try:
5 | from collections.abc import Callable # Python 3.6
6 | except ImportError as e:
7 | from collections import Callable
8 | import re
9 | import sys
10 | import warnings
11 |
12 | from bs4.css import CSS
13 | from bs4.formatter import (
14 | Formatter,
15 | HTMLFormatter,
16 | XMLFormatter,
17 | )
18 |
19 | DEFAULT_OUTPUT_ENCODING = "utf-8"
20 |
21 | nonwhitespace_re = re.compile(r"\S+")
22 |
23 | # NOTE: This isn't used as of 4.7.0. I'm leaving it for a little bit on
24 | # the off chance someone imported it for their own use.
25 | whitespace_re = re.compile(r"\s+")
26 |
27 | def _alias(attr):
28 | """Alias one attribute name to another for backward compatibility"""
29 | @property
30 | def alias(self):
31 | return getattr(self, attr)
32 |
33 | @alias.setter
34 | def alias(self):
35 | return setattr(self, attr)
36 | return alias
37 |
38 |
39 | # These encodings are recognized by Python (so PageElement.encode
40 | # could theoretically support them) but XML and HTML don't recognize
41 | # them (so they should not show up in an XML or HTML document as that
42 | # document's encoding).
43 | #
44 | # If an XML document is encoded in one of these encodings, no encoding
45 | # will be mentioned in the XML declaration. If an HTML document is
46 | # encoded in one of these encodings, and the HTML document has a
47 | # <meta> tag that mentions an encoding, the encoding will be given as
48 | # the empty string.
49 | #
50 | # Source:
51 | # https://docs.python.org/3/library/codecs.html#python-specific-encodings
52 | PYTHON_SPECIFIC_ENCODINGS = set([
53 | "idna",
54 | "mbcs",
55 | "oem",
56 | "palmos",
57 | "punycode",
58 | "raw_unicode_escape",
59 | "undefined",
60 | "unicode_escape",
61 | "raw-unicode-escape",
62 | "unicode-escape",
63 | "string-escape",
64 | "string_escape",
65 | ])
66 |
67 |
68 | class NamespacedAttribute(str):
69 | """A namespaced string (e.g. 'xml:lang') that remembers the namespace
70 | ('xml') and the name ('lang') that were used to create it.
71 | """
72 |
73 | def __new__(cls, prefix, name=None, namespace=None):
74 | if not name:
75 | # This is the default namespace. Its name "has no value"
76 | # per https://www.w3.org/TR/xml-names/#defaulting
77 | name = None
78 |
79 | if not name:
80 | obj = str.__new__(cls, prefix)
81 | elif not prefix:
82 | # Not really namespaced.
83 | obj = str.__new__(cls, name)
84 | else:
85 | obj = str.__new__(cls, prefix + ":" + name)
86 | obj.prefix = prefix
87 | obj.name = name
88 | obj.namespace = namespace
89 | return obj
90 |
91 | class AttributeValueWithCharsetSubstitution(str):
92 | """A stand-in object for a character encoding specified in HTML."""
93 |
94 | class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution):
95 | """A generic stand-in for the value of a meta tag's 'charset' attribute.
96 |
97 | When Beautiful Soup parses the markup '<meta charset="utf8">', the
98 | value of the 'charset' attribute will be one of these objects.
99 | """
100 |
101 | def __new__(cls, original_value):
102 | obj = str.__new__(cls, original_value)
103 | obj.original_value = original_value
104 | return obj
105 |
106 | def encode(self, encoding):
107 | """When an HTML document is being encoded to a given encoding, the
108 | value of a meta tag's 'charset' is the name of the encoding.
109 | """
110 | if encoding in PYTHON_SPECIFIC_ENCODINGS:
111 | return ''
112 | return encoding
113 |
114 |
115 | class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution):
116 | """A generic stand-in for the value of a meta tag's 'content' attribute.
117 |
118 | When Beautiful Soup parses the markup:
119 | <meta http-equiv="content-type" content="text/html; charset=utf8">
120 |
121 | The value of the 'content' attribute will be one of these objects.
122 | """
123 |
124 | CHARSET_RE = re.compile(r"((^|;)\s*charset=)([^;]*)", re.M)
125 |
126 | def __new__(cls, original_value):
127 | match = cls.CHARSET_RE.search(original_value)
128 | if match is None:
129 | # No substitution necessary.
130 | return str.__new__(str, original_value)
131 |
132 | obj = str.__new__(cls, original_value)
133 | obj.original_value = original_value
134 | return obj
135 |
136 | def encode(self, encoding):
137 | if encoding in PYTHON_SPECIFIC_ENCODINGS:
138 | return ''
139 | def rewrite(match):
140 | return match.group(1) + encoding
141 | return self.CHARSET_RE.sub(rewrite, self.original_value)
142 |
143 |
144 | class PageElement(object):
145 | """Contains the navigational information for some part of the page:
146 | that is, its current location in the parse tree.
147 |
148 | NavigableString, Tag, etc. are all subclasses of PageElement.
149 | """
150 |
151 | # In general, we can't tell just by looking at an element whether
152 | # it's contained in an XML document or an HTML document. But for
153 | # Tags (q.v.) we can store this information at parse time.
154 | known_xml = None
155 |
156 | def setup(self, parent=None, previous_element=None, next_element=None,
157 | previous_sibling=None, next_sibling=None):
158 | """Sets up the initial relations between this element and
159 | other elements.
160 |
161 | :param parent: The parent of this element.
162 |
163 | :param previous_element: The element parsed immediately before
164 | this one.
165 |
166 | :param next_element: The element parsed immediately before
167 | this one.
168 |
169 | :param previous_sibling: The most recently encountered element
170 | on the same level of the parse tree as this one.
171 |
172 | :param previous_sibling: The next element to be encountered
173 | on the same level of the parse tree as this one.
174 | """
175 | self.parent = parent
176 |
177 | self.previous_element = previous_element
178 | if previous_element is not None:
179 | self.previous_element.next_element = self
180 |
181 | self.next_element = next_element
182 | if self.next_element is not None:
183 | self.next_element.previous_element = self
184 |
185 | self.next_sibling = next_sibling
186 | if self.next_sibling is not None:
187 | self.next_sibling.previous_sibling = self
188 |
189 | if (previous_sibling is None
190 | and self.parent is not None and self.parent.contents):
191 | previous_sibling = self.parent.contents[-1]
192 |
193 | self.previous_sibling = previous_sibling
194 | if previous_sibling is not None:
195 | self.previous_sibling.next_sibling = self
196 |
197 | def format_string(self, s, formatter):
198 | """Format the given string using the given formatter.
199 |
200 | :param s: A string.
201 | :param formatter: A Formatter object, or a string naming one of the standard formatters.
202 | """
203 | if formatter is None:
204 | return s
205 | if not isinstance(formatter, Formatter):
206 | formatter = self.formatter_for_name(formatter)
207 | output = formatter.substitute(s)
208 | return output
209 |
210 | def formatter_for_name(self, formatter):
211 | """Look up or create a Formatter for the given identifier,
212 | if necessary.
213 |
214 | :param formatter: Can be a Formatter object (used as-is), a
215 | function (used as the entity substitution hook for an
216 | XMLFormatter or HTMLFormatter), or a string (used to look
217 | up an XMLFormatter or HTMLFormatter in the appropriate
218 | registry.
219 | """
220 | if isinstance(formatter, Formatter):
221 | return formatter
222 | if self._is_xml:
223 | c = XMLFormatter
224 | else:
225 | c = HTMLFormatter
226 | if isinstance(formatter, Callable):
227 | return c(entity_substitution=formatter)
228 | return c.REGISTRY[formatter]
229 |
230 | @property
231 | def _is_xml(self):
232 | """Is this element part of an XML tree or an HTML tree?
233 |
234 | This is used in formatter_for_name, when deciding whether an
235 | XMLFormatter or HTMLFormatter is more appropriate. It can be
236 | inefficient, but it should be called very rarely.
237 | """
238 | if self.known_xml is not None:
239 | # Most of the time we will have determined this when the
240 | # document is parsed.
241 | return self.known_xml
242 |
243 | # Otherwise, it's likely that this element was created by
244 | # direct invocation of the constructor from within the user's
245 | # Python code.
246 | if self.parent is None:
247 | # This is the top-level object. It should have .known_xml set
248 | # from tree creation. If not, take a guess--BS is usually
249 | # used on HTML markup.
250 | return getattr(self, 'is_xml', False)
251 | return self.parent._is_xml
252 |
253 | nextSibling = _alias("next_sibling") # BS3
254 | previousSibling = _alias("previous_sibling") # BS3
255 |
256 | default = object()
257 | def _all_strings(self, strip=False, types=default):
258 | """Yield all strings of certain classes, possibly stripping them.
259 |
260 | This is implemented differently in Tag and NavigableString.
261 | """
262 | raise NotImplementedError()
263 |
264 | @property
265 | def stripped_strings(self):
266 | """Yield all strings in this PageElement, stripping them first.
267 |
268 | :yield: A sequence of stripped strings.
269 | """
270 | for string in self._all_strings(True):
271 | yield string
272 |
273 | def get_text(self, separator="", strip=False,
274 | types=default):
275 | """Get all child strings of this PageElement, concatenated using the
276 | given separator.
277 |
278 | :param separator: Strings will be concatenated using this separator.
279 |
280 | :param strip: If True, strings will be stripped before being
281 | concatenated.
282 |
283 | :param types: A tuple of NavigableString subclasses. Any
284 | strings of a subclass not found in this list will be
285 | ignored. Although there are exceptions, the default
286 | behavior in most cases is to consider only NavigableString
287 | and CData objects. That means no comments, processing
288 | instructions, etc.
289 |
290 | :return: A string.
291 | """
292 | return separator.join([s for s in self._all_strings(
293 | strip, types=types)])
294 | getText = get_text
295 | text = property(get_text)
296 |
297 | def replace_with(self, *args):
298 | """Replace this PageElement with one or more PageElements, keeping the
299 | rest of the tree the same.
300 |
301 | :param args: One or more PageElements.
302 | :return: `self`, no longer part of the tree.
303 | """
304 | if self.parent is None:
305 | raise ValueError(
306 | "Cannot replace one element with another when the "
307 | "element to be replaced is not part of a tree.")
308 | if len(args) == 1 and args[0] is self:
309 | return
310 | if any(x is self.parent for x in args):
311 | raise ValueError("Cannot replace a Tag with its parent.")
312 | old_parent = self.parent
313 | my_index = self.parent.index(self)
314 | self.extract(_self_index=my_index)
315 | for idx, replace_with in enumerate(args, start=my_index):
316 | old_parent.insert(idx, replace_with)
317 | return self
318 | replaceWith = replace_with # BS3
319 |
320 | def unwrap(self):
321 | """Replace this PageElement with its contents.
322 |
323 | :return: `self`, no longer part of the tree.
324 | """
325 | my_parent = self.parent
326 | if self.parent is None:
327 | raise ValueError(
328 | "Cannot replace an element with its contents when that"
329 | "element is not part of a tree.")
330 | my_index = self.parent.index(self)
331 | self.extract(_self_index=my_index)
332 | for child in reversed(self.contents[:]):
333 | my_parent.insert(my_index, child)
334 | return self
335 | replace_with_children = unwrap
336 | replaceWithChildren = unwrap # BS3
337 |
338 | def wrap(self, wrap_inside):
339 | """Wrap this PageElement inside another one.
340 |
341 | :param wrap_inside: A PageElement.
342 | :return: `wrap_inside`, occupying the position in the tree that used
343 | to be occupied by `self`, and with `self` inside it.
344 | """
345 | me = self.replace_with(wrap_inside)
346 | wrap_inside.append(me)
347 | return wrap_inside
348 |
349 | def extract(self, _self_index=None):
350 | """Destructively rips this element out of the tree.
351 |
352 | :param _self_index: The location of this element in its parent's
353 | .contents, if known. Passing this in allows for a performance
354 | optimization.
355 |
356 | :return: `self`, no longer part of the tree.
357 | """
358 | if self.parent is not None:
359 | if _self_index is None:
360 | _self_index = self.parent.index(self)
361 | del self.parent.contents[_self_index]
362 |
363 | #Find the two elements that would be next to each other if
364 | #this element (and any children) hadn't been parsed. Connect
365 | #the two.
366 | last_child = self._last_descendant()
367 | next_element = last_child.next_element
368 |
369 | if (self.previous_element is not None and
370 | self.previous_element is not next_element):
371 | self.previous_element.next_element = next_element
372 | if next_element is not None and next_element is not self.previous_element:
373 | next_element.previous_element = self.previous_element
374 | self.previous_element = None
375 | last_child.next_element = None
376 |
377 | self.parent = None
378 | if (self.previous_sibling is not None
379 | and self.previous_sibling is not self.next_sibling):
380 | self.previous_sibling.next_sibling = self.next_sibling
381 | if (self.next_sibling is not None
382 | and self.next_sibling is not self.previous_sibling):
383 | self.next_sibling.previous_sibling = self.previous_sibling
384 | self.previous_sibling = self.next_sibling = None
385 | return self
386 |
387 | def _last_descendant(self, is_initialized=True, accept_self=True):
388 | """Finds the last element beneath this object to be parsed.
389 |
390 | :param is_initialized: Has `setup` been called on this PageElement
391 | yet?
392 | :param accept_self: Is `self` an acceptable answer to the question?
393 | """
394 | if is_initialized and self.next_sibling is not None:
395 | last_child = self.next_sibling.previous_element
396 | else:
397 | last_child = self
398 | while isinstance(last_child, Tag) and last_child.contents:
399 | last_child = last_child.contents[-1]
400 | if not accept_self and last_child is self:
401 | last_child = None
402 | return last_child
403 | # BS3: Not part of the API!
404 | _lastRecursiveChild = _last_descendant
405 |
406 | def insert(self, position, new_child):
407 | """Insert a new PageElement in the list of this PageElement's children.
408 |
409 | This works the same way as `list.insert`.
410 |
411 | :param position: The numeric position that should be occupied
412 | in `self.children` by the new PageElement.
413 | :param new_child: A PageElement.
414 | """
415 | if new_child is None:
416 | raise ValueError("Cannot insert None into a tag.")
417 | if new_child is self:
418 | raise ValueError("Cannot insert a tag into itself.")
419 | if (isinstance(new_child, str)
420 | and not isinstance(new_child, NavigableString)):
421 | new_child = NavigableString(new_child)
422 |
423 | from bs4 import BeautifulSoup
424 | if isinstance(new_child, BeautifulSoup):
425 | # We don't want to end up with a situation where one BeautifulSoup
426 | # object contains another. Insert the children one at a time.
427 | for subchild in list(new_child.contents):
428 | self.insert(position, subchild)
429 | position += 1
430 | return
431 | position = min(position, len(self.contents))
432 | if hasattr(new_child, 'parent') and new_child.parent is not None:
433 | # We're 'inserting' an element that's already one
434 | # of this object's children.
435 | if new_child.parent is self:
436 | current_index = self.index(new_child)
437 | if current_index < position:
438 | # We're moving this element further down the list
439 | # of this object's children. That means that when
440 | # we extract this element, our target index will
441 | # jump down one.
442 | position -= 1
443 | new_child.extract()
444 |
445 | new_child.parent = self
446 | previous_child = None
447 | if position == 0:
448 | new_child.previous_sibling = None
449 | new_child.previous_element = self
450 | else:
451 | previous_child = self.contents[position - 1]
452 | new_child.previous_sibling = previous_child
453 | new_child.previous_sibling.next_sibling = new_child
454 | new_child.previous_element = previous_child._last_descendant(False)
455 | if new_child.previous_element is not None:
456 | new_child.previous_element.next_element = new_child
457 |
458 | new_childs_last_element = new_child._last_descendant(False)
459 |
460 | if position >= len(self.contents):
461 | new_child.next_sibling = None
462 |
463 | parent = self
464 | parents_next_sibling = None
465 | while parents_next_sibling is None and parent is not None:
466 | parents_next_sibling = parent.next_sibling
467 | parent = parent.parent
468 | if parents_next_sibling is not None:
469 | # We found the element that comes next in the document.
470 | break
471 | if parents_next_sibling is not None:
472 | new_childs_last_element.next_element = parents_next_sibling
473 | else:
474 | # The last element of this tag is the last element in
475 | # the document.
476 | new_childs_last_element.next_element = None
477 | else:
478 | next_child = self.contents[position]
479 | new_child.next_sibling = next_child
480 | if new_child.next_sibling is not None:
481 | new_child.next_sibling.previous_sibling = new_child
482 | new_childs_last_element.next_element = next_child
483 |
484 | if new_childs_last_element.next_element is not None:
485 | new_childs_last_element.next_element.previous_element = new_childs_last_element
486 | self.contents.insert(position, new_child)
487 |
488 | def append(self, tag):
489 | """Appends the given PageElement to the contents of this one.
490 |
491 | :param tag: A PageElement.
492 | """
493 | self.insert(len(self.contents), tag)
494 |
495 | def extend(self, tags):
496 | """Appends the given PageElements to this one's contents.
497 |
498 | :param tags: A list of PageElements. If a single Tag is
499 | provided instead, this PageElement's contents will be extended
500 | with that Tag's contents.
501 | """
502 | if isinstance(tags, Tag):
503 | tags = tags.contents
504 | if isinstance(tags, list):
505 | # Moving items around the tree may change their position in
506 | # the original list. Make a list that won't change.
507 | tags = list(tags)
508 | for tag in tags:
509 | self.append(tag)
510 |
511 | def insert_before(self, *args):
512 | """Makes the given element(s) the immediate predecessor of this one.
513 |
514 | All the elements will have the same parent, and the given elements
515 | will be immediately before this one.
516 |
517 | :param args: One or more PageElements.
518 | """
519 | parent = self.parent
520 | if parent is None:
521 | raise ValueError(
522 | "Element has no parent, so 'before' has no meaning.")
523 | if any(x is self for x in args):
524 | raise ValueError("Can't insert an element before itself.")
525 | for predecessor in args:
526 | # Extract first so that the index won't be screwed up if they
527 | # are siblings.
528 | if isinstance(predecessor, PageElement):
529 | predecessor.extract()
530 | index = parent.index(self)
531 | parent.insert(index, predecessor)
532 |
533 | def insert_after(self, *args):
534 | """Makes the given element(s) the immediate successor of this one.
535 |
536 | The elements will have the same parent, and the given elements
537 | will be immediately after this one.
538 |
539 | :param args: One or more PageElements.
540 | """
541 | # Do all error checking before modifying the tree.
542 | parent = self.parent
543 | if parent is None:
544 | raise ValueError(
545 | "Element has no parent, so 'after' has no meaning.")
546 | if any(x is self for x in args):
547 | raise ValueError("Can't insert an element after itself.")
548 |
549 | offset = 0
550 | for successor in args:
551 | # Extract first so that the index won't be screwed up if they
552 | # are siblings.
553 | if isinstance(successor, PageElement):
554 | successor.extract()
555 | index = parent.index(self)
556 | parent.insert(index+1+offset, successor)
557 | offset += 1
558 |
559 | def find_next(self, name=None, attrs={}, string=None, **kwargs):
560 | """Find the first PageElement that matches the given criteria and
561 | appears later in the document than this PageElement.
562 |
563 | All find_* methods take a common set of arguments. See the online
564 | documentation for detailed explanations.
565 |
566 | :param name: A filter on tag name.
567 | :param attrs: A dictionary of filters on attribute values.
568 | :param string: A filter for a NavigableString with specific text.
569 | :kwargs: A dictionary of filters on attribute values.
570 | :return: A PageElement.
571 | :rtype: bs4.element.Tag | bs4.element.NavigableString
572 | """
573 | return self._find_one(self.find_all_next, name, attrs, string, **kwargs)
574 | findNext = find_next # BS3
575 |
576 | def find_all_next(self, name=None, attrs={}, string=None, limit=None,
577 | **kwargs):
578 | """Find all PageElements that match the given criteria and appear
579 | later in the document than this PageElement.
580 |
581 | All find_* methods take a common set of arguments. See the online
582 | documentation for detailed explanations.
583 |
584 | :param name: A filter on tag name.
585 | :param attrs: A dictionary of filters on attribute values.
586 | :param string: A filter for a NavigableString with specific text.
587 | :param limit: Stop looking after finding this many results.
588 | :kwargs: A dictionary of filters on attribute values.
589 | :return: A ResultSet containing PageElements.
590 | """
591 | _stacklevel = kwargs.pop('_stacklevel', 2)
592 | return self._find_all(name, attrs, string, limit, self.next_elements,
593 | _stacklevel=_stacklevel+1, **kwargs)
594 | findAllNext = find_all_next # BS3
595 |
596 | def find_next_sibling(self, name=None, attrs={}, string=None, **kwargs):
597 | """Find the closest sibling to this PageElement that matches the
598 | given criteria and appears later in the document.
599 |
600 | All find_* methods take a common set of arguments. See the
601 | online documentation for detailed explanations.
602 |
603 | :param name: A filter on tag name.
604 | :param attrs: A dictionary of filters on attribute values.
605 | :param string: A filter for a NavigableString with specific text.
606 | :kwargs: A dictionary of filters on attribute values.
607 | :return: A PageElement.
608 | :rtype: bs4.element.Tag | bs4.element.NavigableString
609 | """
610 | return self._find_one(self.find_next_siblings, name, attrs, string,
611 | **kwargs)
612 | findNextSibling = find_next_sibling # BS3
613 |
614 | def find_next_siblings(self, name=None, attrs={}, string=None, limit=None,
615 | **kwargs):
616 | """Find all siblings of this PageElement that match the given criteria
617 | and appear later in the document.
618 |
619 | All find_* methods take a common set of arguments. See the online
620 | documentation for detailed explanations.
621 |
622 | :param name: A filter on tag name.
623 | :param attrs: A dictionary of filters on attribute values.
624 | :param string: A filter for a NavigableString with specific text.
625 | :param limit: Stop looking after finding this many results.
626 | :kwargs: A dictionary of filters on attribute values.
627 | :return: A ResultSet of PageElements.
628 | :rtype: bs4.element.ResultSet
629 | """
630 | _stacklevel = kwargs.pop('_stacklevel', 2)
631 | return self._find_all(
632 | name, attrs, string, limit,
633 | self.next_siblings, _stacklevel=_stacklevel+1, **kwargs
634 | )
635 | findNextSiblings = find_next_siblings # BS3
636 | fetchNextSiblings = find_next_siblings # BS2
637 |
638 | def find_previous(self, name=None, attrs={}, string=None, **kwargs):
639 | """Look backwards in the document from this PageElement and find the
640 | first PageElement that matches the given criteria.
641 |
642 | All find_* methods take a common set of arguments. See the online
643 | documentation for detailed explanations.
644 |
645 | :param name: A filter on tag name.
646 | :param attrs: A dictionary of filters on attribute values.
647 | :param string: A filter for a NavigableString with specific text.
648 | :kwargs: A dictionary of filters on attribute values.
649 | :return: A PageElement.
650 | :rtype: bs4.element.Tag | bs4.element.NavigableString
651 | """
652 | return self._find_one(
653 | self.find_all_previous, name, attrs, string, **kwargs)
654 | findPrevious = find_previous # BS3
655 |
656 | def find_all_previous(self, name=None, attrs={}, string=None, limit=None,
657 | **kwargs):
658 | """Look backwards in the document from this PageElement and find all
659 | PageElements that match the given criteria.
660 |
661 | All find_* methods take a common set of arguments. See the online
662 | documentation for detailed explanations.
663 |
664 | :param name: A filter on tag name.
665 | :param attrs: A dictionary of filters on attribute values.
666 | :param string: A filter for a NavigableString with specific text.
667 | :param limit: Stop looking after finding this many results.
668 | :kwargs: A dictionary of filters on attribute values.
669 | :return: A ResultSet of PageElements.
670 | :rtype: bs4.element.ResultSet
671 | """
672 | _stacklevel = kwargs.pop('_stacklevel', 2)
673 | return self._find_all(
674 | name, attrs, string, limit, self.previous_elements,
675 | _stacklevel=_stacklevel+1, **kwargs
676 | )
677 | findAllPrevious = find_all_previous # BS3
678 | fetchPrevious = find_all_previous # BS2
679 |
680 | def find_previous_sibling(self, name=None, attrs={}, string=None, **kwargs):
681 | """Returns the closest sibling to this PageElement that matches the
682 | given criteria and appears earlier in the document.
683 |
684 | All find_* methods take a common set of arguments. See the online
685 | documentation for detailed explanations.
686 |
687 | :param name: A filter on tag name.
688 | :param attrs: A dictionary of filters on attribute values.
689 | :param string: A filter for a NavigableString with specific text.
690 | :kwargs: A dictionary of filters on attribute values.
691 | :return: A PageElement.
692 | :rtype: bs4.element.Tag | bs4.element.NavigableString
693 | """
694 | return self._find_one(self.find_previous_siblings, name, attrs, string,
695 | **kwargs)
696 | findPreviousSibling = find_previous_sibling # BS3
697 |
698 | def find_previous_siblings(self, name=None, attrs={}, string=None,
699 | limit=None, **kwargs):
700 | """Returns all siblings to this PageElement that match the
701 | given criteria and appear earlier in the document.
702 |
703 | All find_* methods take a common set of arguments. See the online
704 | documentation for detailed explanations.
705 |
706 | :param name: A filter on tag name.
707 | :param attrs: A dictionary of filters on attribute values.
708 | :param string: A filter for a NavigableString with specific text.
709 | :param limit: Stop looking after finding this many results.
710 | :kwargs: A dictionary of filters on attribute values.
711 | :return: A ResultSet of PageElements.
712 | :rtype: bs4.element.ResultSet
713 | """
714 | _stacklevel = kwargs.pop('_stacklevel', 2)
715 | return self._find_all(
716 | name, attrs, string, limit,
717 | self.previous_siblings, _stacklevel=_stacklevel+1, **kwargs
718 | )
719 | findPreviousSiblings = find_previous_siblings # BS3
720 | fetchPreviousSiblings = find_previous_siblings # BS2
721 |
722 | def find_parent(self, name=None, attrs={}, **kwargs):
723 | """Find the closest parent of this PageElement that matches the given
724 | criteria.
725 |
726 | All find_* methods take a common set of arguments. See the online
727 | documentation for detailed explanations.
728 |
729 | :param name: A filter on tag name.
730 | :param attrs: A dictionary of filters on attribute values.
731 | :kwargs: A dictionary of filters on attribute values.
732 |
733 | :return: A PageElement.
734 | :rtype: bs4.element.Tag | bs4.element.NavigableString
735 | """
736 | # NOTE: We can't use _find_one because findParents takes a different
737 | # set of arguments.
738 | r = None
739 | l = self.find_parents(name, attrs, 1, _stacklevel=3, **kwargs)
740 | if l:
741 | r = l[0]
742 | return r
743 | findParent = find_parent # BS3
744 |
745 | def find_parents(self, name=None, attrs={}, limit=None, **kwargs):
746 | """Find all parents of this PageElement that match the given criteria.
747 |
748 | All find_* methods take a common set of arguments. See the online
749 | documentation for detailed explanations.
750 |
751 | :param name: A filter on tag name.
752 | :param attrs: A dictionary of filters on attribute values.
753 | :param limit: Stop looking after finding this many results.
754 | :kwargs: A dictionary of filters on attribute values.
755 |
756 | :return: A PageElement.
757 | :rtype: bs4.element.Tag | bs4.element.NavigableString
758 | """
759 | _stacklevel = kwargs.pop('_stacklevel', 2)
760 | return self._find_all(name, attrs, None, limit, self.parents,
761 | _stacklevel=_stacklevel+1, **kwargs)
762 | findParents = find_parents # BS3
763 | fetchParents = find_parents # BS2
764 |
765 | @property
766 | def next(self):
767 | """The PageElement, if any, that was parsed just after this one.
768 |
769 | :return: A PageElement.
770 | :rtype: bs4.element.Tag | bs4.element.NavigableString
771 | """
772 | return self.next_element
773 |
774 | @property
775 | def previous(self):
776 | """The PageElement, if any, that was parsed just before this one.
777 |
778 | :return: A PageElement.
779 | :rtype: bs4.element.Tag | bs4.element.NavigableString
780 | """
781 | return self.previous_element
782 |
783 | #These methods do the real heavy lifting.
784 |
785 | def _find_one(self, method, name, attrs, string, **kwargs):
786 | r = None
787 | l = method(name, attrs, string, 1, _stacklevel=4, **kwargs)
788 | if l:
789 | r = l[0]
790 | return r
791 |
792 | def _find_all(self, name, attrs, string, limit, generator, **kwargs):
793 | "Iterates over a generator looking for things that match."
794 | _stacklevel = kwargs.pop('_stacklevel', 3)
795 |
796 | if string is None and 'text' in kwargs:
797 | string = kwargs.pop('text')
798 | warnings.warn(
799 | "The 'text' argument to find()-type methods is deprecated. Use 'string' instead.",
800 | DeprecationWarning, stacklevel=_stacklevel
801 | )
802 |
803 | if isinstance(name, SoupStrainer):
804 | strainer = name
805 | else:
806 | strainer = SoupStrainer(name, attrs, string, **kwargs)
807 |
808 | if string is None and not limit and not attrs and not kwargs:
809 | if name is True or name is None:
810 | # Optimization to find all tags.
811 | result = (element for element in generator
812 | if isinstance(element, Tag))
813 | return ResultSet(strainer, result)
814 | elif isinstance(name, str):
815 | # Optimization to find all tags with a given name.
816 | if name.count(':') == 1:
817 | # This is a name with a prefix. If this is a namespace-aware document,
818 | # we need to match the local name against tag.name. If not,
819 | # we need to match the fully-qualified name against tag.name.
820 | prefix, local_name = name.split(':', 1)
821 | else:
822 | prefix = None
823 | local_name = name
824 | result = (element for element in generator
825 | if isinstance(element, Tag)
826 | and (
827 | element.name == name
828 | ) or (
829 | element.name == local_name
830 | and (prefix is None or element.prefix == prefix)
831 | )
832 | )
833 | return ResultSet(strainer, result)
834 | results = ResultSet(strainer)
835 | while True:
836 | try:
837 | i = next(generator)
838 | except StopIteration:
839 | break
840 | if i:
841 | found = strainer.search(i)
842 | if found:
843 | results.append(found)
844 | if limit and len(results) >= limit:
845 | break
846 | return results
847 |
848 | #These generators can be used to navigate starting from both
849 | #NavigableStrings and Tags.
850 | @property
851 | def next_elements(self):
852 | """All PageElements that were parsed after this one.
853 |
854 | :yield: A sequence of PageElements.
855 | """
856 | i = self.next_element
857 | while i is not None:
858 | yield i
859 | i = i.next_element
860 |
861 | @property
862 | def next_siblings(self):
863 | """All PageElements that are siblings of this one but were parsed
864 | later.
865 |
866 | :yield: A sequence of PageElements.
867 | """
868 | i = self.next_sibling
869 | while i is not None:
870 | yield i
871 | i = i.next_sibling
872 |
873 | @property
874 | def previous_elements(self):
875 | """All PageElements that were parsed before this one.
876 |
877 | :yield: A sequence of PageElements.
878 | """
879 | i = self.previous_element
880 | while i is not None:
881 | yield i
882 | i = i.previous_element
883 |
884 | @property
885 | def previous_siblings(self):
886 | """All PageElements that are siblings of this one but were parsed
887 | earlier.
888 |
889 | :yield: A sequence of PageElements.
890 | """
891 | i = self.previous_sibling
892 | while i is not None:
893 | yield i
894 | i = i.previous_sibling
895 |
896 | @property
897 | def parents(self):
898 | """All PageElements that are parents of this PageElement.
899 |
900 | :yield: A sequence of PageElements.
901 | """
902 | i = self.parent
903 | while i is not None:
904 | yield i
905 | i = i.parent
906 |
907 | @property
908 | def decomposed(self):
909 | """Check whether a PageElement has been decomposed.
910 |
911 | :rtype: bool
912 | """
913 | return getattr(self, '_decomposed', False) or False
914 |
915 | # Old non-property versions of the generators, for backwards
916 | # compatibility with BS3.
917 | def nextGenerator(self):
918 | return self.next_elements
919 |
920 | def nextSiblingGenerator(self):
921 | return self.next_siblings
922 |
923 | def previousGenerator(self):
924 | return self.previous_elements
925 |
926 | def previousSiblingGenerator(self):
927 | return self.previous_siblings
928 |
929 | def parentGenerator(self):
930 | return self.parents
931 |
932 |
933 | class NavigableString(str, PageElement):
934 | """A Python Unicode string that is part of a parse tree.
935 |
936 | When Beautiful Soup parses the markup <b>penguin</b>, it will
937 | create a NavigableString for the string "penguin".
938 | """
939 |
940 | PREFIX = ''
941 | SUFFIX = ''
942 |
943 | def __new__(cls, value):
944 | """Create a new NavigableString.
945 |
946 | When unpickling a NavigableString, this method is called with
947 | the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be
948 | passed in to the superclass's __new__ or the superclass won't know
949 | how to handle non-ASCII characters.
950 | """
951 | if isinstance(value, str):
952 | u = str.__new__(cls, value)
953 | else:
954 | u = str.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
955 | u.setup()
956 | return u
957 |
958 | def __deepcopy__(self, memo, recursive=False):
959 | """A copy of a NavigableString has the same contents and class
960 | as the original, but it is not connected to the parse tree.
961 |
962 | :param recursive: This parameter is ignored; it's only defined
963 | so that NavigableString.__deepcopy__ implements the same
964 | signature as Tag.__deepcopy__.
965 | """
966 | return type(self)(self)
967 |
968 | def __copy__(self):
969 | """A copy of a NavigableString can only be a deep copy, because
970 | only one PageElement can occupy a given place in a parse tree.
971 | """
972 | return self.__deepcopy__({})
973 |
974 | def __getnewargs__(self):
975 | return (str(self),)
976 |
977 | def __getattr__(self, attr):
978 | """text.string gives you text. This is for backwards
979 | compatibility for Navigable*String, but for CData* it lets you
980 | get the string without the CData wrapper."""
981 | if attr == 'string':
982 | return self
983 | else:
984 | raise AttributeError(
985 | "'%s' object has no attribute '%s'" % (
986 | self.__class__.__name__, attr))
987 |
988 | def output_ready(self, formatter="minimal"):
989 | """Run the string through the provided formatter.
990 |
991 | :param formatter: A Formatter object, or a string naming one of the standard formatters.
992 | """
993 | output = self.format_string(self, formatter)
994 | return self.PREFIX + output + self.SUFFIX
995 |
996 | @property
997 | def name(self):
998 | """Since a NavigableString is not a Tag, it has no .name.
999 |
1000 | This property is implemented so that code like this doesn't crash
1001 | when run on a mixture of Tag and NavigableString objects:
1002 | [x.name for x in tag.children]
1003 | """
1004 | return None
1005 |
1006 | @name.setter
1007 | def name(self, name):
1008 | """Prevent NavigableString.name from ever being set."""
1009 | raise AttributeError("A NavigableString cannot be given a name.")
1010 |
1011 | def _all_strings(self, strip=False, types=PageElement.default):
1012 | """Yield all strings of certain classes, possibly stripping them.
1013 |
1014 | This makes it easy for NavigableString to implement methods
1015 | like get_text() as conveniences, creating a consistent
1016 | text-extraction API across all PageElements.
1017 |
1018 | :param strip: If True, all strings will be stripped before being
1019 | yielded.
1020 |
1021 | :param types: A tuple of NavigableString subclasses. If this
1022 | NavigableString isn't one of those subclasses, the
1023 | sequence will be empty. By default, the subclasses
1024 | considered are NavigableString and CData objects. That
1025 | means no comments, processing instructions, etc.
1026 |
1027 | :yield: A sequence that either contains this string, or is empty.
1028 |
1029 | """
1030 | if types is self.default:
1031 | # This is kept in Tag because it's full of subclasses of
1032 | # this class, which aren't defined until later in the file.
1033 | types = Tag.DEFAULT_INTERESTING_STRING_TYPES
1034 |
1035 | # Do nothing if the caller is looking for specific types of
1036 | # string, and we're of a different type.
1037 | #
1038 | # We check specific types instead of using isinstance(self,
1039 | # types) because all of these classes subclass
1040 | # NavigableString. Anyone who's using this feature probably
1041 | # wants generic NavigableStrings but not other stuff.
1042 | my_type = type(self)
1043 | if types is not None:
1044 | if isinstance(types, type):
1045 | # Looking for a single type.
1046 | if my_type is not types:
1047 | return
1048 | elif my_type not in types:
1049 | # Looking for one of a list of types.
1050 | return
1051 |
1052 | value = self
1053 | if strip:
1054 | value = value.strip()
1055 | if len(value) > 0:
1056 | yield value
1057 | strings = property(_all_strings)
1058 |
1059 | class PreformattedString(NavigableString):
1060 | """A NavigableString not subject to the normal formatting rules.
1061 |
1062 | This is an abstract class used for special kinds of strings such
1063 | as comments (the Comment class) and CDATA blocks (the CData
1064 | class).
1065 | """
1066 |
1067 | PREFIX = ''
1068 | SUFFIX = ''
1069 |
1070 | def output_ready(self, formatter=None):
1071 | """Make this string ready for output by adding any subclass-specific
1072 | prefix or suffix.
1073 |
1074 | :param formatter: A Formatter object, or a string naming one
1075 | of the standard formatters. The string will be passed into the
1076 | Formatter, but only to trigger any side effects: the return
1077 | value is ignored.
1078 |
1079 | :return: The string, with any subclass-specific prefix and
1080 | suffix added on.
1081 | """
1082 | if formatter is not None:
1083 | ignore = self.format_string(self, formatter)
1084 | return self.PREFIX + self + self.SUFFIX
1085 |
1086 | class CData(PreformattedString):
1087 | """A CDATA block."""
1088 | PREFIX = '<![CDATA['
1089 | SUFFIX = ']]>'
1090 |
1091 | class ProcessingInstruction(PreformattedString):
1092 | """A SGML processing instruction."""
1093 |
1094 | PREFIX = '<?'
1095 | SUFFIX = '>'
1096 |
1097 | class XMLProcessingInstruction(ProcessingInstruction):
1098 | """An XML processing instruction."""
1099 | PREFIX = '<?'
1100 | SUFFIX = '?>'
1101 |
1102 | class Comment(PreformattedString):
1103 | """An HTML or XML comment."""
1104 | PREFIX = '<!--'
1105 | SUFFIX = '-->'
1106 |
1107 |
1108 | class Declaration(PreformattedString):
1109 | """An XML declaration."""
1110 | PREFIX = '<?'
1111 | SUFFIX = '?>'
1112 |
1113 |
1114 | class Doctype(PreformattedString):
1115 | """A document type declaration."""
1116 | @classmethod
1117 | def for_name_and_ids(cls, name, pub_id, system_id):
1118 | """Generate an appropriate document type declaration for a given
1119 | public ID and system ID.
1120 |
1121 | :param name: The name of the document's root element, e.g. 'html'.
1122 | :param pub_id: The Formal Public Identifier for this document type,
1123 | e.g. '-//W3C//DTD XHTML 1.1//EN'
1124 | :param system_id: The system identifier for this document type,
1125 | e.g. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
1126 |
1127 | :return: A Doctype.
1128 | """
1129 | value = name or ''
1130 | if pub_id is not None:
1131 | value += ' PUBLIC "%s"' % pub_id
1132 | if system_id is not None:
1133 | value += ' "%s"' % system_id
1134 | elif system_id is not None:
1135 | value += ' SYSTEM "%s"' % system_id
1136 |
1137 | return Doctype(value)
1138 |
1139 | PREFIX = '<!DOCTYPE '
1140 | SUFFIX = '>\n'
1141 |
1142 |
1143 | class Stylesheet(NavigableString):
1144 | """A NavigableString representing an stylesheet (probably
1145 | CSS).
1146 |
1147 | Used to distinguish embedded stylesheets from textual content.
1148 | """
1149 | pass
1150 |
1151 |
1152 | class Script(NavigableString):
1153 | """A NavigableString representing an executable script (probably
1154 | Javascript).
1155 |
1156 | Used to distinguish executable code from textual content.
1157 | """
1158 | pass
1159 |
1160 |
1161 | class TemplateString(NavigableString):
1162 | """A NavigableString representing a string found inside an HTML
1163 | template embedded in a larger document.
1164 |
1165 | Used to distinguish such strings from the main body of the document.
1166 | """
1167 | pass
1168 |
1169 |
1170 | class RubyTextString(NavigableString):
1171 | """A NavigableString representing the contents of the <rt> HTML
1172 | element.
1173 |
1174 | https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rt-element
1175 |
1176 | Can be used to distinguish such strings from the strings they're
1177 | annotating.
1178 | """
1179 | pass
1180 |
1181 |
1182 | class RubyParenthesisString(NavigableString):
1183 | """A NavigableString representing the contents of the <rp> HTML
1184 | element.
1185 |
1186 | https://dev.w3.org/html5/spec-LC/text-level-semantics.html#the-rp-element
1187 | """
1188 | pass
1189 |
1190 |
1191 | class Tag(PageElement):
1192 | """Represents an HTML or XML tag that is part of a parse tree, along
1193 | with its attributes and contents.
1194 |
1195 | When Beautiful Soup parses the markup <b>penguin</b>, it will
1196 | create a Tag object representing the <b> tag.
1197 | """
1198 |
1199 | def __init__(self, parser=None, builder=None, name=None, namespace=None,
1200 | prefix=None, attrs=None, parent=None, previous=None,
1201 | is_xml=None, sourceline=None, sourcepos=None,
1202 | can_be_empty_element=None, cdata_list_attributes=None,
1203 | preserve_whitespace_tags=None,
1204 | interesting_string_types=None,
1205 | namespaces=None
1206 | ):
1207 | """Basic constructor.
1208 |
1209 | :param parser: A BeautifulSoup object.
1210 | :param builder: A TreeBuilder.
1211 | :param name: The name of the tag.
1212 | :param namespace: The URI of this Tag's XML namespace, if any.
1213 | :param prefix: The prefix for this Tag's XML namespace, if any.
1214 | :param attrs: A dictionary of this Tag's attribute values.
1215 | :param parent: The PageElement to use as this Tag's parent.
1216 | :param previous: The PageElement that was parsed immediately before
1217 | this tag.
1218 | :param is_xml: If True, this is an XML tag. Otherwise, this is an
1219 | HTML tag.
1220 | :param sourceline: The line number where this tag was found in its
1221 | source document.
1222 | :param sourcepos: The character position within `sourceline` where this
1223 | tag was found.
1224 | :param can_be_empty_element: If True, this tag should be
1225 | represented as <tag/>. If False, this tag should be represented
1226 | as <tag></tag>.
1227 | :param cdata_list_attributes: A list of attributes whose values should
1228 | be treated as CDATA if they ever show up on this tag.
1229 | :param preserve_whitespace_tags: A list of tag names whose contents
1230 | should have their whitespace preserved.
1231 | :param interesting_string_types: This is a NavigableString
1232 | subclass or a tuple of them. When iterating over this
1233 | Tag's strings in methods like Tag.strings or Tag.get_text,
1234 | these are the types of strings that are interesting enough
1235 | to be considered. The default is to consider
1236 | NavigableString and CData the only interesting string
1237 | subtypes.
1238 | :param namespaces: A dictionary mapping currently active
1239 | namespace prefixes to URIs. This can be used later to
1240 | construct CSS selectors.
1241 | """
1242 | if parser is None:
1243 | self.parser_class = None
1244 | else:
1245 | # We don't actually store the parser object: that lets extracted
1246 | # chunks be garbage-collected.
1247 | self.parser_class = parser.__class__
1248 | if name is None:
1249 | raise ValueError("No value provided for new tag's name.")
1250 | self.name = name
1251 | self.namespace = namespace
1252 | self._namespaces = namespaces or {}
1253 | self.prefix = prefix
1254 | if ((not builder or builder.store_line_numbers)
1255 | and (sourceline is not None or sourcepos is not None)):
1256 | self.sourceline = sourceline
1257 | self.sourcepos = sourcepos
1258 | if attrs is None:
1259 | attrs = {}
1260 | elif attrs:
1261 | if builder is not None and builder.cdata_list_attributes:
1262 | attrs = builder._replace_cdata_list_attribute_values(
1263 | self.name, attrs)
1264 | else:
1265 | attrs = dict(attrs)
1266 | else:
1267 | attrs = dict(attrs)
1268 |
1269 | # If possible, determine ahead of time whether this tag is an
1270 | # XML tag.
1271 | if builder:
1272 | self.known_xml = builder.is_xml
1273 | else:
1274 | self.known_xml = is_xml
1275 | self.attrs = attrs
1276 | self.contents = []
1277 | self.setup(parent, previous)
1278 | self.hidden = False
1279 |
1280 | if builder is None:
1281 | # In the absence of a TreeBuilder, use whatever values were
1282 | # passed in here. They're probably None, unless this is a copy of some
1283 | # other tag.
1284 | self.can_be_empty_element = can_be_empty_element
1285 | self.cdata_list_attributes = cdata_list_attributes
1286 | self.preserve_whitespace_tags = preserve_whitespace_tags
1287 | self.interesting_string_types = interesting_string_types
1288 | else:
1289 | # Set up any substitutions for this tag, such as the charset in a META tag.
1290 | builder.set_up_substitutions(self)
1291 |
1292 | # Ask the TreeBuilder whether this tag might be an empty-element tag.
1293 | self.can_be_empty_element = builder.can_be_empty_element(name)
1294 |
1295 | # Keep track of the list of attributes of this tag that
1296 | # might need to be treated as a list.
1297 | #
1298 | # For performance reasons, we store the whole data structure
1299 | # rather than asking the question of every tag. Asking would
1300 | # require building a new data structure every time, and
1301 | # (unlike can_be_empty_element), we almost never need
1302 | # to check this.
1303 | self.cdata_list_attributes = builder.cdata_list_attributes
1304 |
1305 | # Keep track of the names that might cause this tag to be treated as a
1306 | # whitespace-preserved tag.
1307 | self.preserve_whitespace_tags = builder.preserve_whitespace_tags
1308 |
1309 | if self.name in builder.string_containers:
1310 | # This sort of tag uses a special string container
1311 | # subclass for most of its strings. When we ask the
1312 | self.interesting_string_types = builder.string_containers[self.name]
1313 | else:
1314 | self.interesting_string_types = self.DEFAULT_INTERESTING_STRING_TYPES
1315 |
1316 | parserClass = _alias("parser_class") # BS3
1317 |
1318 | def __deepcopy__(self, memo, recursive=True):
1319 | """A deepcopy of a Tag is a new Tag, unconnected to the parse tree.
1320 | Its contents are a copy of the old Tag's contents.
1321 | """
1322 | clone = self._clone()
1323 |
1324 | if recursive:
1325 | # Clone this tag's descendants recursively, but without
1326 | # making any recursive function calls.
1327 | tag_stack = [clone]
1328 | for event, element in self._event_stream(self.descendants):
1329 | if event is Tag.END_ELEMENT_EVENT:
1330 | # Stop appending incoming Tags to the Tag that was
1331 | # just closed.
1332 | tag_stack.pop()
1333 | else:
1334 | descendant_clone = element.__deepcopy__(
1335 | memo, recursive=False
1336 | )
1337 | # Add to its parent's .contents
1338 | tag_stack[-1].append(descendant_clone)
1339 |
1340 | if event is Tag.START_ELEMENT_EVENT:
1341 | # Add the Tag itself to the stack so that its
1342 | # children will be .appended to it.
1343 | tag_stack.append(descendant_clone)
1344 | return clone
1345 |
1346 | def __copy__(self):
1347 | """A copy of a Tag must always be a deep copy, because a Tag's
1348 | children can only have one parent at a time.
1349 | """
1350 | return self.__deepcopy__({})
1351 |
1352 | def _clone(self):
1353 | """Create a new Tag just like this one, but with no
1354 | contents and unattached to any parse tree.
1355 |
1356 | This is the first step in the deepcopy process.
1357 | """
1358 | clone = type(self)(
1359 | None, None, self.name, self.namespace,
1360 | self.prefix, self.attrs, is_xml=self._is_xml,
1361 | sourceline=self.sourceline, sourcepos=self.sourcepos,
1362 | can_be_empty_element=self.can_be_empty_element,
1363 | cdata_list_attributes=self.cdata_list_attributes,
1364 | preserve_whitespace_tags=self.preserve_whitespace_tags,
1365 | interesting_string_types=self.interesting_string_types
1366 | )
1367 | for attr in ('can_be_empty_element', 'hidden'):
1368 | setattr(clone, attr, getattr(self, attr))
1369 | return clone
1370 |
1371 | @property
1372 | def is_empty_element(self):
1373 | """Is this tag an empty-element tag? (aka a self-closing tag)
1374 |
1375 | A tag that has contents is never an empty-element tag.
1376 |
1377 | A tag that has no contents may or may not be an empty-element
1378 | tag. It depends on the builder used to create the tag. If the
1379 | builder has a designated list of empty-element tags, then only
1380 | a tag whose name shows up in that list is considered an
1381 | empty-element tag.
1382 |
1383 | If the builder has no designated list of empty-element tags,
1384 | then any tag with no contents is an empty-element tag.
1385 | """
1386 | return len(self.contents) == 0 and self.can_be_empty_element
1387 | isSelfClosing = is_empty_element # BS3
1388 |
1389 | @property
1390 | def string(self):
1391 | """Convenience property to get the single string within this
1392 | PageElement.
1393 |
1394 | TODO It might make sense to have NavigableString.string return
1395 | itself.
1396 |
1397 | :return: If this element has a single string child, return
1398 | value is that string. If this element has one child tag,
1399 | return value is the 'string' attribute of the child tag,
1400 | recursively. If this element is itself a string, has no
1401 | children, or has more than one child, return value is None.
1402 | """
1403 | if len(self.contents) != 1:
1404 | return None
1405 | child = self.contents[0]
1406 | if isinstance(child, NavigableString):
1407 | return child
1408 | return child.string
1409 |
1410 | @string.setter
1411 | def string(self, string):
1412 | """Replace this PageElement's contents with `string`."""
1413 | self.clear()
1414 | self.append(string.__class__(string))
1415 |
1416 | DEFAULT_INTERESTING_STRING_TYPES = (NavigableString, CData)
1417 | def _all_strings(self, strip=False, types=PageElement.default):
1418 | """Yield all strings of certain classes, possibly stripping them.
1419 |
1420 | :param strip: If True, all strings will be stripped before being
1421 | yielded.
1422 |
1423 | :param types: A tuple of NavigableString subclasses. Any strings of
1424 | a subclass not found in this list will be ignored. By
1425 | default, the subclasses considered are the ones found in
1426 | self.interesting_string_types. If that's not specified,
1427 | only NavigableString and CData objects will be
1428 | considered. That means no comments, processing
1429 | instructions, etc.
1430 |
1431 | :yield: A sequence of strings.
1432 |
1433 | """
1434 | if types is self.default:
1435 | types = self.interesting_string_types
1436 |
1437 | for descendant in self.descendants:
1438 | if (types is None and not isinstance(descendant, NavigableString)):
1439 | continue
1440 | descendant_type = type(descendant)
1441 | if isinstance(types, type):
1442 | if descendant_type is not types:
1443 | # We're not interested in strings of this type.
1444 | continue
1445 | elif types is not None and descendant_type not in types:
1446 | # We're not interested in strings of this type.
1447 | continue
1448 | if strip:
1449 | descendant = descendant.strip()
1450 | if len(descendant) == 0:
1451 | continue
1452 | yield descendant
1453 | strings = property(_all_strings)
1454 |
1455 | def decompose(self):
1456 | """Recursively destroys this PageElement and its children.
1457 |
1458 | This element will be removed from the tree and wiped out; so
1459 | will everything beneath it.
1460 |
1461 | The behavior of a decomposed PageElement is undefined and you
1462 | should never use one for anything, but if you need to _check_
1463 | whether an element has been decomposed, you can use the
1464 | `decomposed` property.
1465 | """
1466 | self.extract()
1467 | i = self
1468 | while i is not None:
1469 | n = i.next_element
1470 | i.__dict__.clear()
1471 | i.contents = []
1472 | i._decomposed = True
1473 | i = n
1474 |
1475 | def clear(self, decompose=False):
1476 | """Wipe out all children of this PageElement by calling extract()
1477 | on them.
1478 |
1479 | :param decompose: If this is True, decompose() (a more
1480 | destructive method) will be called instead of extract().
1481 | """
1482 | if decompose:
1483 | for element in self.contents[:]:
1484 | if isinstance(element, Tag):
1485 | element.decompose()
1486 | else:
1487 | element.extract()
1488 | else:
1489 | for element in self.contents[:]:
1490 | element.extract()
1491 |
1492 | def smooth(self):
1493 | """Smooth out this element's children by consolidating consecutive
1494 | strings.
1495 |
1496 | This makes pretty-printed output look more natural following a
1497 | lot of operations that modified the tree.
1498 | """
1499 | # Mark the first position of every pair of children that need
1500 | # to be consolidated. Do this rather than making a copy of
1501 | # self.contents, since in most cases very few strings will be
1502 | # affected.
1503 | marked = []
1504 | for i, a in enumerate(self.contents):
1505 | if isinstance(a, Tag):
1506 | # Recursively smooth children.
1507 | a.smooth()
1508 | if i == len(self.contents)-1:
1509 | # This is the last item in .contents, and it's not a
1510 | # tag. There's no chance it needs any work.
1511 | continue
1512 | b = self.contents[i+1]
1513 | if (isinstance(a, NavigableString)
1514 | and isinstance(b, NavigableString)
1515 | and not isinstance(a, PreformattedString)
1516 | and not isinstance(b, PreformattedString)
1517 | ):
1518 | marked.append(i)
1519 |
1520 | # Go over the marked positions in reverse order, so that
1521 | # removing items from .contents won't affect the remaining
1522 | # positions.
1523 | for i in reversed(marked):
1524 | a = self.contents[i]
1525 | b = self.contents[i+1]
1526 | b.extract()
1527 | n = NavigableString(a+b)
1528 | a.replace_with(n)
1529 |
1530 | def index(self, element):
1531 | """Find the index of a child by identity, not value.
1532 |
1533 | Avoids issues with tag.contents.index(element) getting the
1534 | index of equal elements.
1535 |
1536 | :param element: Look for this PageElement in `self.contents`.
1537 | """
1538 | for i, child in enumerate(self.contents):
1539 | if child is element:
1540 | return i
1541 | raise ValueError("Tag.index: element not in tag")
1542 |
1543 | def get(self, key, default=None):
1544 | """Returns the value of the 'key' attribute for the tag, or
1545 | the value given for 'default' if it doesn't have that
1546 | attribute."""
1547 | return self.attrs.get(key, default)
1548 |
1549 | def get_attribute_list(self, key, default=None):
1550 | """The same as get(), but always returns a list.
1551 |
1552 | :param key: The attribute to look for.
1553 | :param default: Use this value if the attribute is not present
1554 | on this PageElement.
1555 | :return: A list of values, probably containing only a single
1556 | value.
1557 | """
1558 | value = self.get(key, default)
1559 | if not isinstance(value, list):
1560 | value = [value]
1561 | return value
1562 |
1563 | def has_attr(self, key):
1564 | """Does this PageElement have an attribute with the given name?"""
1565 | return key in self.attrs
1566 |
1567 | def __hash__(self):
1568 | return str(self).__hash__()
1569 |
1570 | def __getitem__(self, key):
1571 | """tag[key] returns the value of the 'key' attribute for the Tag,
1572 | and throws an exception if it's not there."""
1573 | return self.attrs[key]
1574 |
1575 | def __iter__(self):
1576 | "Iterating over a Tag iterates over its contents."
1577 | return iter(self.contents)
1578 |
1579 | def __len__(self):
1580 | "The length of a Tag is the length of its list of contents."
1581 | return len(self.contents)
1582 |
1583 | def __contains__(self, x):
1584 | return x in self.contents
1585 |
1586 | def __bool__(self):
1587 | "A tag is non-None even if it has no contents."
1588 | return True
1589 |
1590 | def __setitem__(self, key, value):
1591 | """Setting tag[key] sets the value of the 'key' attribute for the
1592 | tag."""
1593 | self.attrs[key] = value
1594 |
1595 | def __delitem__(self, key):
1596 | "Deleting tag[key] deletes all 'key' attributes for the tag."
1597 | self.attrs.pop(key, None)
1598 |
1599 | def __call__(self, *args, **kwargs):
1600 | """Calling a Tag like a function is the same as calling its
1601 | find_all() method. Eg. tag('a') returns a list of all the A tags
1602 | found within this tag."""
1603 | return self.find_all(*args, **kwargs)
1604 |
1605 | def __getattr__(self, tag):
1606 | """Calling tag.subtag is the same as calling tag.find(name="subtag")"""
1607 | #print("Getattr %s.%s" % (self.__class__, tag))
1608 | if len(tag) > 3 and tag.endswith('Tag'):
1609 | # BS3: soup.aTag -> "soup.find("a")
1610 | tag_name = tag[:-3]
1611 | warnings.warn(
1612 | '.%(name)sTag is deprecated, use .find("%(name)s") instead. If you really were looking for a tag called %(name)sTag, use .find("%(name)sTag")' % dict(
1613 | name=tag_name
1614 | ),
1615 | DeprecationWarning, stacklevel=2
1616 | )
1617 | return self.find(tag_name)
1618 | # We special case contents to avoid recursion.
1619 | elif not tag.startswith("__") and not tag == "contents":
1620 | return self.find(tag)
1621 | raise AttributeError(
1622 | "'%s' object has no attribute '%s'" % (self.__class__, tag))
1623 |
1624 | def __eq__(self, other):
1625 | """Returns true iff this Tag has the same name, the same attributes,
1626 | and the same contents (recursively) as `other`."""
1627 | if self is other:
1628 | return True
1629 | if (not hasattr(other, 'name') or
1630 | not hasattr(other, 'attrs') or
1631 | not hasattr(other, 'contents') or
1632 | self.name != other.name or
1633 | self.attrs != other.attrs or
1634 | len(self) != len(other)):
1635 | return False
1636 | for i, my_child in enumerate(self.contents):
1637 | if my_child != other.contents[i]:
1638 | return False
1639 | return True
1640 |
1641 | def __ne__(self, other):
1642 | """Returns true iff this Tag is not identical to `other`,
1643 | as defined in __eq__."""
1644 | return not self == other
1645 |
1646 | def __repr__(self, encoding="unicode-escape"):
1647 | """Renders this PageElement as a string.
1648 |
1649 | :param encoding: The encoding to use (Python 2 only).
1650 | TODO: This is now ignored and a warning should be issued
1651 | if a value is provided.
1652 | :return: A (Unicode) string.
1653 | """
1654 | # "The return value must be a string object", i.e. Unicode
1655 | return self.decode()
1656 |
1657 | def __unicode__(self):
1658 | """Renders this PageElement as a Unicode string."""
1659 | return self.decode()
1660 |
1661 | __str__ = __repr__ = __unicode__
1662 |
1663 | def encode(self, encoding=DEFAULT_OUTPUT_ENCODING,
1664 | indent_level=None, formatter="minimal",
1665 | errors="xmlcharrefreplace"):
1666 | """Render a bytestring representation of this PageElement and its
1667 | contents.
1668 |
1669 | :param encoding: The destination encoding.
1670 | :param indent_level: Each line of the rendering will be
1671 | indented this many levels. (The formatter decides what a
1672 | 'level' means in terms of spaces or other characters
1673 | output.) Used internally in recursive calls while
1674 | pretty-printing.
1675 | :param formatter: A Formatter object, or a string naming one of
1676 | the standard formatters.
1677 | :param errors: An error handling strategy such as
1678 | 'xmlcharrefreplace'. This value is passed along into
1679 | encode() and its value should be one of the constants
1680 | defined by Python.
1681 | :return: A bytestring.
1682 |
1683 | """
1684 | # Turn the data structure into Unicode, then encode the
1685 | # Unicode.
1686 | u = self.decode(indent_level, encoding, formatter)
1687 | return u.encode(encoding, errors)
1688 |
1689 | def decode(self, indent_level=None,
1690 | eventual_encoding=DEFAULT_OUTPUT_ENCODING,
1691 | formatter="minimal",
1692 | iterator=None):
1693 | pieces = []
1694 | # First off, turn a non-Formatter `formatter` into a Formatter
1695 | # object. This will stop the lookup from happening over and
1696 | # over again.
1697 | if not isinstance(formatter, Formatter):
1698 | formatter = self.formatter_for_name(formatter)
1699 |
1700 | if indent_level is True:
1701 | indent_level = 0
1702 |
1703 | # The currently active tag that put us into string literal
1704 | # mode. Until this element is closed, children will be treated
1705 | # as string literals and not pretty-printed. String literal
1706 | # mode is turned on immediately after this tag begins, and
1707 | # turned off immediately before it's closed. This means there
1708 | # will be whitespace before and after the tag itself.
1709 | string_literal_tag = None
1710 |
1711 | for event, element in self._event_stream(iterator):
1712 | if event in (Tag.START_ELEMENT_EVENT, Tag.EMPTY_ELEMENT_EVENT):
1713 | piece = element._format_tag(
1714 | eventual_encoding, formatter, opening=True
1715 | )
1716 | elif event is Tag.END_ELEMENT_EVENT:
1717 | piece = element._format_tag(
1718 | eventual_encoding, formatter, opening=False
1719 | )
1720 | if indent_level is not None:
1721 | indent_level -= 1
1722 | else:
1723 | piece = element.output_ready(formatter)
1724 |
1725 | # Now we need to apply the 'prettiness' -- extra
1726 | # whitespace before and/or after this tag. This can get
1727 | # complicated because certain tags, like <pre> and
1728 | # <script>, can't be prettified, since adding whitespace would
1729 | # change the meaning of the content.
1730 |
1731 | # The default behavior is to add whitespace before and
1732 | # after an element when string literal mode is off, and to
1733 | # leave things as they are when string literal mode is on.
1734 | if string_literal_tag:
1735 | indent_before = indent_after = False
1736 | else:
1737 | indent_before = indent_after = True
1738 |
1739 | # The only time the behavior is more complex than that is
1740 | # when we encounter an opening or closing tag that might
1741 | # put us into or out of string literal mode.
1742 | if (event is Tag.START_ELEMENT_EVENT
1743 | and not string_literal_tag
1744 | and not element._should_pretty_print()):
1745 | # We are about to enter string literal mode. Add
1746 | # whitespace before this tag, but not after. We
1747 | # will stay in string literal mode until this tag
1748 | # is closed.
1749 | indent_before = True
1750 | indent_after = False
1751 | string_literal_tag = element
1752 | elif (event is Tag.END_ELEMENT_EVENT
1753 | and element is string_literal_tag):
1754 | # We are about to exit string literal mode by closing
1755 | # the tag that sent us into that mode. Add whitespace
1756 | # after this tag, but not before.
1757 | indent_before = False
1758 | indent_after = True
1759 | string_literal_tag = None
1760 |
1761 | # Now we know whether to add whitespace before and/or
1762 | # after this element.
1763 | if indent_level is not None:
1764 | if (indent_before or indent_after):
1765 | if isinstance(element, NavigableString):
1766 | piece = piece.strip()
1767 | if piece:
1768 | piece = self._indent_string(
1769 | piece, indent_level, formatter,
1770 | indent_before, indent_after
1771 | )
1772 | if event == Tag.START_ELEMENT_EVENT:
1773 | indent_level += 1
1774 | pieces.append(piece)
1775 | return "".join(pieces)
1776 |
1777 | # Names for the different events yielded by _event_stream
1778 | START_ELEMENT_EVENT = object()
1779 | END_ELEMENT_EVENT = object()
1780 | EMPTY_ELEMENT_EVENT = object()
1781 | STRING_ELEMENT_EVENT = object()
1782 |
1783 | def _event_stream(self, iterator=None):
1784 | """Yield a sequence of events that can be used to reconstruct the DOM
1785 | for this element.
1786 |
1787 | This lets us recreate the nested structure of this element
1788 | (e.g. when formatting it as a string) without using recursive
1789 | method calls.
1790 |
1791 | This is similar in concept to the SAX API, but it's a simpler
1792 | interface designed for internal use. The events are different
1793 | from SAX and the arguments associated with the events are Tags
1794 | and other Beautiful Soup objects.
1795 |
1796 | :param iterator: An alternate iterator to use when traversing
1797 | the tree.
1798 | """
1799 | tag_stack = []
1800 |
1801 | iterator = iterator or self.self_and_descendants
1802 |
1803 | for c in iterator:
1804 | # If the parent of the element we're about to yield is not
1805 | # the tag currently on the stack, it means that the tag on
1806 | # the stack closed before this element appeared.
1807 | while tag_stack and c.parent != tag_stack[-1]:
1808 | now_closed_tag = tag_stack.pop()
1809 | yield Tag.END_ELEMENT_EVENT, now_closed_tag
1810 |
1811 | if isinstance(c, Tag):
1812 | if c.is_empty_element:
1813 | yield Tag.EMPTY_ELEMENT_EVENT, c
1814 | else:
1815 | yield Tag.START_ELEMENT_EVENT, c
1816 | tag_stack.append(c)
1817 | continue
1818 | else:
1819 | yield Tag.STRING_ELEMENT_EVENT, c
1820 |
1821 | while tag_stack:
1822 | now_closed_tag = tag_stack.pop()
1823 | yield Tag.END_ELEMENT_EVENT, now_closed_tag
1824 |
1825 | def _indent_string(self, s, indent_level, formatter,
1826 | indent_before, indent_after):
1827 | """Add indentation whitespace before and/or after a string.
1828 |
1829 | :param s: The string to amend with whitespace.
1830 | :param indent_level: The indentation level; affects how much
1831 | whitespace goes before the string.
1832 | :param indent_before: Whether or not to add whitespace
1833 | before the string.
1834 | :param indent_after: Whether or not to add whitespace
1835 | (a newline) after the string.
1836 | """
1837 | space_before = ''
1838 | if indent_before and indent_level:
1839 | space_before = (formatter.indent * indent_level)
1840 |
1841 | space_after = ''
1842 | if indent_after:
1843 | space_after = "\n"
1844 |
1845 | return space_before + s + space_after
1846 |
1847 | def _format_tag(self, eventual_encoding, formatter, opening):
1848 | if self.hidden:
1849 | # A hidden tag is invisible, although its contents
1850 | # are visible.
1851 | return ''
1852 |
1853 | # A tag starts with the < character (see below).
1854 |
1855 | # Then the / character, if this is a closing tag.
1856 | closing_slash = ''
1857 | if not opening:
1858 | closing_slash = '/'
1859 |
1860 | # Then an optional namespace prefix.
1861 | prefix = ''
1862 | if self.prefix:
1863 | prefix = self.prefix + ":"
1864 |
1865 | # Then a list of attribute values, if this is an opening tag.
1866 | attribute_string = ''
1867 | if opening:
1868 | attributes = formatter.attributes(self)
1869 | attrs = []
1870 | for key, val in attributes:
1871 | if val is None:
1872 | decoded = key
1873 | else:
1874 | if isinstance(val, list) or isinstance(val, tuple):
1875 | val = ' '.join(val)
1876 | elif not isinstance(val, str):
1877 | val = str(val)
1878 | elif (
1879 | isinstance(val, AttributeValueWithCharsetSubstitution)
1880 | and eventual_encoding is not None
1881 | ):
1882 | val = val.encode(eventual_encoding)
1883 |
1884 | text = formatter.attribute_value(val)
1885 | decoded = (
1886 | str(key) + '='
1887 | + formatter.quoted_attribute_value(text))
1888 | attrs.append(decoded)
1889 | if attrs:
1890 | attribute_string = ' ' + ' '.join(attrs)
1891 |
1892 | # Then an optional closing slash (for a void element in an
1893 | # XML document).
1894 | void_element_closing_slash = ''
1895 | if self.is_empty_element:
1896 | void_element_closing_slash = formatter.void_element_close_prefix or ''
1897 |
1898 | # Put it all together.
1899 | return '<' + closing_slash + prefix + self.name + attribute_string + void_element_closing_slash + '>'
1900 |
1901 | def _should_pretty_print(self, indent_level=1):
1902 | """Should this tag be pretty-printed?
1903 |
1904 | Most of them should, but some (such as <pre> in HTML
1905 | documents) should not.
1906 | """
1907 | return (
1908 | indent_level is not None
1909 | and (
1910 | not self.preserve_whitespace_tags
1911 | or self.name not in self.preserve_whitespace_tags
1912 | )
1913 | )
1914 |
1915 | def prettify(self, encoding=None, formatter="minimal"):
1916 | """Pretty-print this PageElement as a string.
1917 |
1918 | :param encoding: The eventual encoding of the string. If this is None,
1919 | a Unicode string will be returned.
1920 | :param formatter: A Formatter object, or a string naming one of
1921 | the standard formatters.
1922 | :return: A Unicode string (if encoding==None) or a bytestring
1923 | (otherwise).
1924 | """
1925 | if encoding is None:
1926 | return self.decode(True, formatter=formatter)
1927 | else:
1928 | return self.encode(encoding, True, formatter=formatter)
1929 |
1930 | def decode_contents(self, indent_level=None,
1931 | eventual_encoding=DEFAULT_OUTPUT_ENCODING,
1932 | formatter="minimal"):
1933 | """Renders the contents of this tag as a Unicode string.
1934 |
1935 | :param indent_level: Each line of the rendering will be
1936 | indented this many levels. (The formatter decides what a
1937 | 'level' means in terms of spaces or other characters
1938 | output.) Used internally in recursive calls while
1939 | pretty-printing.
1940 |
1941 | :param eventual_encoding: The tag is destined to be
1942 | encoded into this encoding. decode_contents() is _not_
1943 | responsible for performing that encoding. This information
1944 | is passed in so that it can be substituted in if the
1945 | document contains a <META> tag that mentions the document's
1946 | encoding.
1947 |
1948 | :param formatter: A Formatter object, or a string naming one of
1949 | the standard Formatters.
1950 |
1951 | """
1952 | return self.decode(indent_level, eventual_encoding, formatter,
1953 | iterator=self.descendants)
1954 |
1955 | def encode_contents(
1956 | self, indent_level=None, encoding=DEFAULT_OUTPUT_ENCODING,
1957 | formatter="minimal"):
1958 | """Renders the contents of this PageElement as a bytestring.
1959 |
1960 | :param indent_level: Each line of the rendering will be
1961 | indented this many levels. (The formatter decides what a
1962 | 'level' means in terms of spaces or other characters
1963 | output.) Used internally in recursive calls while
1964 | pretty-printing.
1965 |
1966 | :param eventual_encoding: The bytestring will be in this encoding.
1967 |
1968 | :param formatter: A Formatter object, or a string naming one of
1969 | the standard Formatters.
1970 |
1971 | :return: A bytestring.
1972 | """
1973 | contents = self.decode_contents(indent_level, encoding, formatter)
1974 | return contents.encode(encoding)
1975 |
1976 | # Old method for BS3 compatibility
1977 | def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING,
1978 | prettyPrint=False, indentLevel=0):
1979 | """Deprecated method for BS3 compatibility."""
1980 | if not prettyPrint:
1981 | indentLevel = None
1982 | return self.encode_contents(
1983 | indent_level=indentLevel, encoding=encoding)
1984 |
1985 | #Soup methods
1986 |
1987 | def find(self, name=None, attrs={}, recursive=True, string=None,
1988 | **kwargs):
1989 | """Look in the children of this PageElement and find the first
1990 | PageElement that matches the given criteria.
1991 |
1992 | All find_* methods take a common set of arguments. See the online
1993 | documentation for detailed explanations.
1994 |
1995 | :param name: A filter on tag name.
1996 | :param attrs: A dictionary of filters on attribute values.
1997 | :param recursive: If this is True, find() will perform a
1998 | recursive search of this PageElement's children. Otherwise,
1999 | only the direct children will be considered.
2000 | :param limit: Stop looking after finding this many results.
2001 | :kwargs: A dictionary of filters on attribute values.
2002 | :return: A PageElement.
2003 | :rtype: bs4.element.Tag | bs4.element.NavigableString
2004 | """
2005 | r = None
2006 | l = self.find_all(name, attrs, recursive, string, 1, _stacklevel=3,
2007 | **kwargs)
2008 | if l:
2009 | r = l[0]
2010 | return r
2011 | findChild = find #BS2
2012 |
2013 | def find_all(self, name=None, attrs={}, recursive=True, string=None,
2014 | limit=None, **kwargs):
2015 | """Look in the children of this PageElement and find all
2016 | PageElements that match the given criteria.
2017 |
2018 | All find_* methods take a common set of arguments. See the online
2019 | documentation for detailed explanations.
2020 |
2021 | :param name: A filter on tag name.
2022 | :param attrs: A dictionary of filters on attribute values.
2023 | :param recursive: If this is True, find_all() will perform a
2024 | recursive search of this PageElement's children. Otherwise,
2025 | only the direct children will be considered.
2026 | :param limit: Stop looking after finding this many results.
2027 | :kwargs: A dictionary of filters on attribute values.
2028 | :return: A ResultSet of PageElements.
2029 | :rtype: bs4.element.ResultSet
2030 | """
2031 | generator = self.descendants
2032 | if not recursive:
2033 | generator = self.children
2034 | _stacklevel = kwargs.pop('_stacklevel', 2)
2035 | return self._find_all(name, attrs, string, limit, generator,
2036 | _stacklevel=_stacklevel+1, **kwargs)
2037 | findAll = find_all # BS3
2038 | findChildren = find_all # BS2
2039 |
2040 | #Generator methods
2041 | @property
2042 | def children(self):
2043 | """Iterate over all direct children of this PageElement.
2044 |
2045 | :yield: A sequence of PageElements.
2046 | """
2047 | # return iter() to make the purpose of the method clear
2048 | return iter(self.contents) # XXX This seems to be untested.
2049 |
2050 | @property
2051 | def self_and_descendants(self):
2052 | """Iterate over this PageElement and its children in a
2053 | breadth-first sequence.
2054 |
2055 | :yield: A sequence of PageElements.
2056 | """
2057 | if not self.hidden:
2058 | yield self
2059 | for i in self.descendants:
2060 | yield i
2061 |
2062 | @property
2063 | def descendants(self):
2064 | """Iterate over all children of this PageElement in a
2065 | breadth-first sequence.
2066 |
2067 | :yield: A sequence of PageElements.
2068 | """
2069 | if not len(self.contents):
2070 | return
2071 | stopNode = self._last_descendant().next_element
2072 | current = self.contents[0]
2073 | while current is not stopNode:
2074 | yield current
2075 | current = current.next_element
2076 |
2077 | # CSS selector code
2078 | def select_one(self, selector, namespaces=None, **kwargs):
2079 | """Perform a CSS selection operation on the current element.
2080 |
2081 | :param selector: A CSS selector.
2082 |
2083 | :param namespaces: A dictionary mapping namespace prefixes
2084 | used in the CSS selector to namespace URIs. By default,
2085 | Beautiful Soup will use the prefixes it encountered while
2086 | parsing the document.
2087 |
2088 | :param kwargs: Keyword arguments to be passed into Soup Sieve's
2089 | soupsieve.select() method.
2090 |
2091 | :return: A Tag.
2092 | :rtype: bs4.element.Tag
2093 | """
2094 | return self.css.select_one(selector, namespaces, **kwargs)
2095 |
2096 | def select(self, selector, namespaces=None, limit=None, **kwargs):
2097 | """Perform a CSS selection operation on the current element.
2098 |
2099 | This uses the SoupSieve library.
2100 |
2101 | :param selector: A string containing a CSS selector.
2102 |
2103 | :param namespaces: A dictionary mapping namespace prefixes
2104 | used in the CSS selector to namespace URIs. By default,
2105 | Beautiful Soup will use the prefixes it encountered while
2106 | parsing the document.
2107 |
2108 | :param limit: After finding this number of results, stop looking.
2109 |
2110 | :param kwargs: Keyword arguments to be passed into SoupSieve's
2111 | soupsieve.select() method.
2112 |
2113 | :return: A ResultSet of Tags.
2114 | :rtype: bs4.element.ResultSet
2115 | """
2116 | return self.css.select(selector, namespaces, limit, **kwargs)
2117 |
2118 | @property
2119 | def css(self):
2120 | """Return an interface to the CSS selector API."""
2121 | return CSS(self)
2122 |
2123 | # Old names for backwards compatibility
2124 | def childGenerator(self):
2125 | """Deprecated generator."""
2126 | return self.children
2127 |
2128 | def recursiveChildGenerator(self):
2129 | """Deprecated generator."""
2130 | return self.descendants
2131 |
2132 | def has_key(self, key):
2133 | """Deprecated method. This was kind of misleading because has_key()
2134 | (attributes) was different from __in__ (contents).
2135 |
2136 | has_key() is gone in Python 3, anyway.
2137 | """
2138 | warnings.warn(
2139 | 'has_key is deprecated. Use has_attr(key) instead.',
2140 | DeprecationWarning, stacklevel=2
2141 | )
2142 | return self.has_attr(key)
2143 |
2144 | # Next, a couple classes to represent queries and their results.
2145 | class SoupStrainer(object):
2146 | """Encapsulates a number of ways of matching a markup element (tag or
2147 | string).
2148 |
2149 | This is primarily used to underpin the find_* methods, but you can
2150 | create one yourself and pass it in as `parse_only` to the
2151 | `BeautifulSoup` constructor, to parse a subset of a large
2152 | document.
2153 | """
2154 |
2155 | def __init__(self, name=None, attrs={}, string=None, **kwargs):
2156 | """Constructor.
2157 |
2158 | The SoupStrainer constructor takes the same arguments passed
2159 | into the find_* methods. See the online documentation for
2160 | detailed explanations.
2161 |
2162 | :param name: A filter on tag name.
2163 | :param attrs: A dictionary of filters on attribute values.
2164 | :param string: A filter for a NavigableString with specific text.
2165 | :kwargs: A dictionary of filters on attribute values.
2166 | """
2167 | if string is None and 'text' in kwargs:
2168 | string = kwargs.pop('text')
2169 | warnings.warn(
2170 | "The 'text' argument to the SoupStrainer constructor is deprecated. Use 'string' instead.",
2171 | DeprecationWarning, stacklevel=2
2172 | )
2173 |
2174 | self.name = self._normalize_search_value(name)
2175 | if not isinstance(attrs, dict):
2176 | # Treat a non-dict value for attrs as a search for the 'class'
2177 | # attribute.
2178 | kwargs['class'] = attrs
2179 | attrs = None
2180 |
2181 | if 'class_' in kwargs:
2182 | # Treat class_="foo" as a search for the 'class'
2183 | # attribute, overriding any non-dict value for attrs.
2184 | kwargs['class'] = kwargs['class_']
2185 | del kwargs['class_']
2186 |
2187 | if kwargs:
2188 | if attrs:
2189 | attrs = attrs.copy()
2190 | attrs.update(kwargs)
2191 | else:
2192 | attrs = kwargs
2193 | normalized_attrs = {}
2194 | for key, value in list(attrs.items()):
2195 | normalized_attrs[key] = self._normalize_search_value(value)
2196 |
2197 | self.attrs = normalized_attrs
2198 | self.string = self._normalize_search_value(string)
2199 |
2200 | # DEPRECATED but just in case someone is checking this.
2201 | self.text = self.string
2202 |
2203 | def _normalize_search_value(self, value):
2204 | # Leave it alone if it's a Unicode string, a callable, a
2205 | # regular expression, a boolean, or None.
2206 | if (isinstance(value, str) or isinstance(value, Callable) or hasattr(value, 'match')
2207 | or isinstance(value, bool) or value is None):
2208 | return value
2209 |
2210 | # If it's a bytestring, convert it to Unicode, treating it as UTF-8.
2211 | if isinstance(value, bytes):
2212 | return value.decode("utf8")
2213 |
2214 | # If it's listlike, convert it into a list of strings.
2215 | if hasattr(value, '__iter__'):
2216 | new_value = []
2217 | for v in value:
2218 | if (hasattr(v, '__iter__') and not isinstance(v, bytes)
2219 | and not isinstance(v, str)):
2220 | # This is almost certainly the user's mistake. In the
2221 | # interests of avoiding infinite loops, we'll let
2222 | # it through as-is rather than doing a recursive call.
2223 | new_value.append(v)
2224 | else:
2225 | new_value.append(self._normalize_search_value(v))
2226 | return new_value
2227 |
2228 | # Otherwise, convert it into a Unicode string.
2229 | # The unicode(str()) thing is so this will do the same thing on Python 2
2230 | # and Python 3.
2231 | return str(str(value))
2232 |
2233 | def __str__(self):
2234 | """A human-readable representation of this SoupStrainer."""
2235 | if self.string:
2236 | return self.string
2237 | else:
2238 | return "%s|%s" % (self.name, self.attrs)
2239 |
2240 | def search_tag(self, markup_name=None, markup_attrs={}):
2241 | """Check whether a Tag with the given name and attributes would
2242 | match this SoupStrainer.
2243 |
2244 | Used prospectively to decide whether to even bother creating a Tag
2245 | object.
2246 |
2247 | :param markup_name: A tag name as found in some markup.
2248 | :param markup_attrs: A dictionary of attributes as found in some markup.
2249 |
2250 | :return: True if the prospective tag would match this SoupStrainer;
2251 | False otherwise.
2252 | """
2253 | found = None
2254 | markup = None
2255 | if isinstance(markup_name, Tag):
2256 | markup = markup_name
2257 | markup_attrs = markup
2258 |
2259 | if isinstance(self.name, str):
2260 | # Optimization for a very common case where the user is
2261 | # searching for a tag with one specific name, and we're
2262 | # looking at a tag with a different name.
2263 | if markup and not markup.prefix and self.name != markup.name:
2264 | return False
2265 |
2266 | call_function_with_tag_data = (
2267 | isinstance(self.name, Callable)
2268 | and not isinstance(markup_name, Tag))
2269 |
2270 | if ((not self.name)
2271 | or call_function_with_tag_data
2272 | or (markup and self._matches(markup, self.name))
2273 | or (not markup and self._matches(markup_name, self.name))):
2274 | if call_function_with_tag_data:
2275 | match = self.name(markup_name, markup_attrs)
2276 | else:
2277 | match = True
2278 | markup_attr_map = None
2279 | for attr, match_against in list(self.attrs.items()):
2280 | if not markup_attr_map:
2281 | if hasattr(markup_attrs, 'get'):
2282 | markup_attr_map = markup_attrs
2283 | else:
2284 | markup_attr_map = {}
2285 | for k, v in markup_attrs:
2286 | markup_attr_map[k] = v
2287 | attr_value = markup_attr_map.get(attr)
2288 | if not self._matches(attr_value, match_against):
2289 | match = False
2290 | break
2291 | if match:
2292 | if markup:
2293 | found = markup
2294 | else:
2295 | found = markup_name
2296 | if found and self.string and not self._matches(found.string, self.string):
2297 | found = None
2298 | return found
2299 |
2300 | # For BS3 compatibility.
2301 | searchTag = search_tag
2302 |
2303 | def search(self, markup):
2304 | """Find all items in `markup` that match this SoupStrainer.
2305 |
2306 | Used by the core _find_all() method, which is ultimately
2307 | called by all find_* methods.
2308 |
2309 | :param markup: A PageElement or a list of them.
2310 | """
2311 | # print('looking for %s in %s' % (self, markup))
2312 | found = None
2313 | # If given a list of items, scan it for a text element that
2314 | # matches.
2315 | if hasattr(markup, '__iter__') and not isinstance(markup, (Tag, str)):
2316 | for element in markup:
2317 | if isinstance(element, NavigableString) \
2318 | and self.search(element):
2319 | found = element
2320 | break
2321 | # If it's a Tag, make sure its name or attributes match.
2322 | # Don't bother with Tags if we're searching for text.
2323 | elif isinstance(markup, Tag):
2324 | if not self.string or self.name or self.attrs:
2325 | found = self.search_tag(markup)
2326 | # If it's text, make sure the text matches.
2327 | elif isinstance(markup, NavigableString) or \
2328 | isinstance(markup, str):
2329 | if not self.name and not self.attrs and self._matches(markup, self.string):
2330 | found = markup
2331 | else:
2332 | raise Exception(
2333 | "I don't know how to match against a %s" % markup.__class__)
2334 | return found
2335 |
2336 | def _matches(self, markup, match_against, already_tried=None):
2337 | # print(u"Matching %s against %s" % (markup, match_against))
2338 | result = False
2339 | if isinstance(markup, list) or isinstance(markup, tuple):
2340 | # This should only happen when searching a multi-valued attribute
2341 | # like 'class'.
2342 | for item in markup:
2343 | if self._matches(item, match_against):
2344 | return True
2345 | # We didn't match any particular value of the multivalue
2346 | # attribute, but maybe we match the attribute value when
2347 | # considered as a string.
2348 | if self._matches(' '.join(markup), match_against):
2349 | return True
2350 | return False
2351 |
2352 | if match_against is True:
2353 | # True matches any non-None value.
2354 | return markup is not None
2355 |
2356 | if isinstance(match_against, Callable):
2357 | return match_against(markup)
2358 |
2359 | # Custom callables take the tag as an argument, but all
2360 | # other ways of matching match the tag name as a string.
2361 | original_markup = markup
2362 | if isinstance(markup, Tag):
2363 | markup = markup.name
2364 |
2365 | # Ensure that `markup` is either a Unicode string, or None.
2366 | markup = self._normalize_search_value(markup)
2367 |
2368 | if markup is None:
2369 | # None matches None, False, an empty string, an empty list, and so on.
2370 | return not match_against
2371 |
2372 | if (hasattr(match_against, '__iter__')
2373 | and not isinstance(match_against, str)):
2374 | # We're asked to match against an iterable of items.
2375 | # The markup must be match at least one item in the
2376 | # iterable. We'll try each one in turn.
2377 | #
2378 | # To avoid infinite recursion we need to keep track of
2379 | # items we've already seen.
2380 | if not already_tried:
2381 | already_tried = set()
2382 | for item in match_against:
2383 | if item.__hash__:
2384 | key = item
2385 | else:
2386 | key = id(item)
2387 | if key in already_tried:
2388 | continue
2389 | else:
2390 | already_tried.add(key)
2391 | if self._matches(original_markup, item, already_tried):
2392 | return True
2393 | else:
2394 | return False
2395 |
2396 | # Beyond this point we might need to run the test twice: once against
2397 | # the tag's name and once against its prefixed name.
2398 | match = False
2399 |
2400 | if not match and isinstance(match_against, str):
2401 | # Exact string match
2402 | match = markup == match_against
2403 |
2404 | if not match and hasattr(match_against, 'search'):
2405 | # Regexp match
2406 | return match_against.search(markup)
2407 |
2408 | if (not match
2409 | and isinstance(original_markup, Tag)
2410 | and original_markup.prefix):
2411 | # Try the whole thing again with the prefixed tag name.
2412 | return self._matches(
2413 | original_markup.prefix + ':' + original_markup.name, match_against
2414 | )
2415 |
2416 | return match
2417 |
2418 |
2419 | class ResultSet(list):
2420 | """A ResultSet is just a list that keeps track of the SoupStrainer
2421 | that created it."""
2422 | def __init__(self, source, result=()):
2423 | """Constructor.
2424 |
2425 | :param source: A SoupStrainer.
2426 | :param result: A list of PageElements.
2427 | """
2428 | super(ResultSet, self).__init__(result)
2429 | self.source = source
2430 |
2431 | def __getattr__(self, key):
2432 | """Raise a helpful exception to explain a common code fix."""
2433 | raise AttributeError(
2434 | "ResultSet object has no attribute '%s'. You're probably treating a list of elements like a single element. Did you call find_all() when you meant to call find()?" % key
2435 | )
2436 |
```