linkml-reference-validator 0.5.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/PKG-INFO +1 -1
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/xml.py +66 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/doi.py +31 -1
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/pmid.py +94 -1
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/validation/supporting_text_validator.py +10 -3
- linkml_reference_validator-0.5.1/tests/test_inline_abstract_headings.py +127 -0
- linkml_reference_validator-0.5.1/tests/test_welded_markers.py +184 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.claude/.mcp.json +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.claude/settings.json +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.copier-answers.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.devcontainer/devcontainer.json +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.devcontainer/post_create.sh +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.editorconfig +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/actions/claude-code-action/action.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/actions/claude-issue-summarize-action/action.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/actions/claude-issue-triage-action/action.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/ai-controllers.json +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/copilot-instructions.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/copilot-setup-steps.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/dependabot.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude-code-review.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude-issue-summarize.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude-issue-triage.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude-ontology-review.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/deploy-docs.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/dragon-ai.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/main.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/pypi-publish.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.gitignore +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.goosehints +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.pre-commit-config.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.yamllint.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/AGENTS.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/BUG_GEO_ACCESSION_TO_UID.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/BUG_ZENODO_DOI_AND_URL_HANDLING.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/CLAUDE.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/CLI-ISSUES.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/CODE_OF_CONDUCT.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/CONTRIBUTING.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/FEATURE_REQUEST_REPAIR.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/LICENSE +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/README.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/REVIEW2.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/REVIEW3.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/ai.just +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/benchmarks/repeated_quotes.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/copilot-instructions.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/about.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/assets/zotero-find-full-text.png +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/concepts/content-types.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/concepts/editorial-conventions.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/concepts/how-it-works.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/elements/.gitkeep +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/add-reference-source.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/export-cache-to-zotero.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/fetch-full-text-and-pdfs.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/repair-validation-errors.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/skip-unsupported-references.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/use-local-files-and-urls.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-clinical-trials.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-dois.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-entrez.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-obo-files.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-titles.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-urls.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/index.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/notebooks/01_getting_started.ipynb +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/notebooks/02_advanced_usage.ipynb +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/notebooks/03_python_api.ipynb +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/notebooks/04_obo_validation.ipynb +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/notebooks/05_geo_validation.ipynb +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/quickstart.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/reference/cache-format.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/reference/cli.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/reference/splitting.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/setup-guide.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/superpowers/plans/2026-06-12-modular-pdf-fulltext-fetching.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/superpowers/plans/2026-08-05-private-library-full-text.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/superpowers/specs/2026-06-12-modular-pdf-fulltext-fetching-design.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/superpowers/specs/2026-08-05-private-library-full-text-design.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/templates-linkml/README.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/todo.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/troubleshooting.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/docs/tutorials/complete-workflow.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/examples/README.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/justfile +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/mkdocs.yml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/mypy.ini +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/notebooks/01_getting_started.ipynb +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/notebooks/02_advanced_usage.ipynb +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/notebooks/03_python_api.ipynb +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/notebooks/README.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/project/README.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/project.justfile +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/pyproject.toml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/pytest.ini +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/skills/linkml-reference-validator/SKILL.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/skills/linkml-reference-validator/references/configuration.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/skills/linkml-reference-validator/references/guardrails.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/_version.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cache.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/cache.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/lookup.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/repair.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/shared.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/validate.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/acquire.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/csl_export.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/base.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/drawio.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/html.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/pdf.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/svg.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/svg_geometry.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/untrusted_xml.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/base.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/bioc.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/bookshelf.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/epmc.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/epmc_preprint.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/json_api.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/loader.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/openalex.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/pmc.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/unpaywall.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/zotero.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/identifiers.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/reference_fetcher.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/rules.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/base.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/clinicaltrials.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/entrez.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/file.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/json_api.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/loader.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/ppr.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/url.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/utils.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/text_extractor.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/field_detection.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/matching.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/models.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/plugins/REVIEW.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/plugins/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/plugins/reference_validation_plugin.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/validation/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/validation/fuzzy_text_utils.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/validation/repairer.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/__init__.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/conftest.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/data/test_data_deep_nesting_invalid.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/data/test_data_deep_nesting_valid.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/data/test_data_invalid.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/data/test_data_valid.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/data/test_schema.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/data/test_schema_deep_nesting.yaml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/GEO_GSE12345.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMC_TEST001.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMC_TEST001.txt +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMC_TEST002.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMC_TEST002.txt +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST001.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST001.txt +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST002.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST002.txt +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST003.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST003.txt +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/bookshelf/esummary_20301710.xml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/bookshelf/gene_NBK1116/men1.nxml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/bookshelf/pubmed_20301710.xml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/clinicaltrials_NCT00000001.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/fulltext_html/README.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/fulltext_html/dspace-32894695.html +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/fulltext_html/iris-19049553.html +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/fulltext_html/plos-0000308.html +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/jats/PMC5593426.xml +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/jats/README.md +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/fixtures/sample.obo +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_absent_abstract_recovery.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_acquire.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_bookshelf_provider.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_bot_challenge.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cache_author_recovery.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cache_filename_collisions.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cache_read_robustness.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cache_validation.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cli.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cli_cache_enrich.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cli_cache_export.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cli_cache_reference.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cli_lookup.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_cli_repair.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_clinicaltrials_cache.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_clinicaltrials_integration.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_deep_nesting.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_doi_preprint.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_doi_url_encoding.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_drawio.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_e2e_integration.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_empty_snippets.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_epmc_preprint_provider.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_extractor_fidelity.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_extractors.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_field_detection.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_field_detection_qualifiers.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_file_reference_cache.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_frontmatter_and_doi_case.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_fulltext_loader.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_fulltext_providers.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_fuzzy_suggestions.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_geo_integration.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_greek_variants.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_html_fulltext_acceptance.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_html_scope_text.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_identifiers.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_jats_tables.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_json_api_source.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_ligature_normalization.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_long_cache_filenames.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_meaning_changes.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_meaning_symbols.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_models.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_multivalued_reference.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_no_cache_downgrade.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_normalized_content_cache.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_open_access_policy.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_optional_linkml_dependency.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_pdf_url_title.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_plugin_integration.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_plugin_value_shapes.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_pmc_fulltext.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_pmid_network.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_ppr_source.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_publisher_typography.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_real_validation.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_reference_fetcher.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_repair.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_repair_data_output.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_repair_thresholds.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_rules.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_serve_stale_html_optin.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_source_network_errors.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_source_utils.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_sources.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_split_supporting_text.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_supporting_text_validator.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_svg_edges.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_svg_extractor.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_text_extractor.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_title_only_shapes.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_title_validation.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_trust_cached_entries.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_unknown_slot_traversal.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_url_fragment_cache.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_url_html_sanitize.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_url_html_to_text.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_url_source_cache_version.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_validation_config.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_validation_counts.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_word_boundaries.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_xml_extractor_text.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/tests/test_zotero_provider.py +0 -0
- {linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/uv.lock +0 -0
|
@@ -119,6 +119,53 @@ BLOCK_ELEMENTS = frozenset(
|
|
|
119
119
|
)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
#: Dashes that can join the numbers of a citation range ("1-3", "1–3").
|
|
123
|
+
_DASHES = "-\u2010\u2011\u2012\u2013\u2014\u2015\u2212"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _is_citation_marker(tag: Tag) -> bool:
|
|
127
|
+
"""Whether ``tag`` is a bibliographic citation marker.
|
|
128
|
+
|
|
129
|
+
A JATS ``<xref ref-type="bibr">``, or a ``<sup>`` holding only such xrefs
|
|
130
|
+
and the commas and dashes between them. Inline markup is otherwise kept
|
|
131
|
+
contiguous (``H<sub>2</sub>O``), but a citation glued to the word before it
|
|
132
|
+
(``FOXL2<sup>8</sup>``) reads as another word, and one after a full stop
|
|
133
|
+
(``PGE2.<sup>41</sup>``) as a decimal number.
|
|
134
|
+
|
|
135
|
+
Examples:
|
|
136
|
+
>>> soup = BeautifulSoup('<p>A<sup><xref ref-type="bibr">8</xref></sup>'
|
|
137
|
+
... '10<sup>6</sup></p>', "xml")
|
|
138
|
+
>>> [_is_citation_marker(sup) for sup in soup.find_all("sup")]
|
|
139
|
+
[True, False]
|
|
140
|
+
"""
|
|
141
|
+
if tag.name == "xref":
|
|
142
|
+
return tag.get("ref-type") == "bibr"
|
|
143
|
+
if tag.name != "sup":
|
|
144
|
+
return False
|
|
145
|
+
xrefs = tag.find_all("xref")
|
|
146
|
+
if not xrefs or any(xref.get("ref-type") != "bibr" for xref in xrefs):
|
|
147
|
+
return False
|
|
148
|
+
loose = "".join(
|
|
149
|
+
str(child) for child in tag.children if type(child) in (NavigableString, CData)
|
|
150
|
+
)
|
|
151
|
+
return re.fullmatch(rf"[\s,;{re.escape(_DASHES)}]*", loose) is not None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _welded_to_next_word(tag: Tag) -> bool:
|
|
155
|
+
"""Whether the text right after ``tag`` starts with a letter, with no space.
|
|
156
|
+
|
|
157
|
+
That is what makes a bold lead-in a welded label (``R 2.6</bold>We``)
|
|
158
|
+
rather than part of a word (``IL</bold>-6``) or already spaced.
|
|
159
|
+
|
|
160
|
+
Examples:
|
|
161
|
+
>>> soup = BeautifulSoup("<x><p><bold>R 2.6</bold>We</p><p><bold>IL</bold>-6</p></x>", "xml")
|
|
162
|
+
>>> [_welded_to_next_word(b) for b in soup.find_all("bold")]
|
|
163
|
+
[True, False]
|
|
164
|
+
"""
|
|
165
|
+
following = tag.next_sibling
|
|
166
|
+
return isinstance(following, NavigableString) and str(following)[:1].isalpha()
|
|
167
|
+
|
|
168
|
+
|
|
122
169
|
def _block_text(node: Tag) -> str:
|
|
123
170
|
"""Keep inline text contiguous, separating blocks and excluding nested tables.
|
|
124
171
|
|
|
@@ -137,8 +184,27 @@ def _block_text(node: Tag) -> str:
|
|
|
137
184
|
parts.append(str(child))
|
|
138
185
|
elif isinstance(child, Tag) and child.name not in {"table", "table-wrap"}:
|
|
139
186
|
value = walk(child)
|
|
187
|
+
lead_in = (
|
|
188
|
+
tag.name == "p"
|
|
189
|
+
and child.name == "bold"
|
|
190
|
+
and not "".join(parts).strip()
|
|
191
|
+
and _welded_to_next_word(child)
|
|
192
|
+
)
|
|
140
193
|
if child.name in BLOCK_ELEMENTS:
|
|
141
194
|
value = " " + value + " "
|
|
195
|
+
elif _is_citation_marker(child):
|
|
196
|
+
# No space before a dash that follows: "[1</xref>-3]" is
|
|
197
|
+
# a range, and " -3" would read as a minus sign.
|
|
198
|
+
following = child.next_sibling
|
|
199
|
+
dash_next = (
|
|
200
|
+
isinstance(following, NavigableString)
|
|
201
|
+
and str(following)[:1] in _DASHES
|
|
202
|
+
)
|
|
203
|
+
value = " " + value + ("" if dash_next else " ")
|
|
204
|
+
elif lead_in:
|
|
205
|
+
# A paragraph's bold lead-in label ("<bold>R 2.6</bold>We
|
|
206
|
+
# recommend") is not part of the sentence after it.
|
|
207
|
+
value = value + " "
|
|
142
208
|
parts.append(value)
|
|
143
209
|
return "".join(parts)
|
|
144
210
|
|
|
@@ -12,6 +12,7 @@ Examples:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import logging
|
|
15
|
+
import re
|
|
15
16
|
import time
|
|
16
17
|
from typing import Optional
|
|
17
18
|
|
|
@@ -33,6 +34,16 @@ from linkml_reference_validator.etl.sources.utils import (
|
|
|
33
34
|
logger = logging.getLogger(__name__)
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
#: JATS elements that end a run of text in a Crossref abstract. Their local
|
|
38
|
+
#: names, so ``jats:p`` and a bare ``p`` both count.
|
|
39
|
+
_JATS_ABSTRACT_BLOCKS = frozenset(
|
|
40
|
+
{
|
|
41
|
+
"title", "p", "sec", "list", "list-item", "label", "caption", "def-item",
|
|
42
|
+
"disp-quote", "div", "br",
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
36
47
|
@ReferenceSourceRegistry.register
|
|
37
48
|
class DOISource(ReferenceSource):
|
|
38
49
|
"""Fetch references from Crossref using DOI, with DataCite fallback.
|
|
@@ -515,6 +526,15 @@ class DOISource(ReferenceSource):
|
|
|
515
526
|
def _clean_abstract(self, abstract: str) -> str:
|
|
516
527
|
"""Clean JATS/XML markup from abstract text.
|
|
517
528
|
|
|
529
|
+
Block elements (titles, paragraphs, sections, list items, and HTML
|
|
530
|
+
``div`` and ``br``) become paragraphs separated by a blank line.
|
|
531
|
+
Crossref nests a structured abstract's sections as ``<jats:title>`` and
|
|
532
|
+
``<jats:p>`` with no text between them, so stripping the tags alone
|
|
533
|
+
welded each heading onto the next sentence ("AbstractDisease
|
|
534
|
+
overviewAcute myeloid..."), and a quote beginning at that sentence
|
|
535
|
+
failed whole-word matching. Whitespace inside a paragraph is
|
|
536
|
+
collapsed; inline markup stays contiguous.
|
|
537
|
+
|
|
518
538
|
Args:
|
|
519
539
|
abstract: Abstract text potentially containing JATS markup
|
|
520
540
|
|
|
@@ -525,8 +545,18 @@ class DOISource(ReferenceSource):
|
|
|
525
545
|
>>> source = DOISource()
|
|
526
546
|
>>> source._clean_abstract("<jats:p>Test abstract.</jats:p>")
|
|
527
547
|
'Test abstract.'
|
|
548
|
+
>>> source._clean_abstract(
|
|
549
|
+
... "<jats:title>Abstract</jats:title><jats:sec><jats:title>Aim"
|
|
550
|
+
... "</jats:title><jats:p>To test <jats:italic>it</jats:italic>.</jats:p>"
|
|
551
|
+
... "</jats:sec>")
|
|
552
|
+
'Abstract\\n\\nAim\\n\\nTo test it.'
|
|
528
553
|
"""
|
|
529
554
|
if not abstract:
|
|
530
555
|
return ""
|
|
531
556
|
soup = BeautifulSoup(abstract, "html.parser")
|
|
532
|
-
|
|
557
|
+
for tag in soup.find_all(True):
|
|
558
|
+
if tag.name.rpartition(":")[2] in _JATS_ABSTRACT_BLOCKS:
|
|
559
|
+
tag.insert_before("\n\n")
|
|
560
|
+
tag.insert_after("\n\n")
|
|
561
|
+
paragraphs = (" ".join(chunk.split()) for chunk in re.split(r"\n\s*\n", soup.get_text()))
|
|
562
|
+
return "\n\n".join(paragraph for paragraph in paragraphs if paragraph)
|
|
@@ -37,6 +37,54 @@ from linkml_reference_validator.etl.sources.utils import (
|
|
|
37
37
|
logger = logging.getLogger(__name__)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
#: Section headings of structured abstracts. PubMed sometimes stores them
|
|
41
|
+
#: welded onto the next sentence with no markup at all ("IntroductionThe black
|
|
42
|
+
#: widow spider..."), which no tag can mark.
|
|
43
|
+
_ABSTRACT_HEADINGS = (
|
|
44
|
+
"Abstract|Background|Introduction|Objectives?|Aims?|Purpose|Importance|"
|
|
45
|
+
"Context|Design|Setting|Participants|Patients|Methods|Materials and Methods|"
|
|
46
|
+
"Interventions?|Measurements|Main Outcome Measures|Results|Findings|"
|
|
47
|
+
"Observations|Case Report|Case Presentation|Discussion|Interpretation|"
|
|
48
|
+
"Conclusions?|Conclusions and Relevance|Significance|Limitations"
|
|
49
|
+
)
|
|
50
|
+
_WELDED_HEADING = re.compile(
|
|
51
|
+
rf"(?:^|(?<=[.!?])|(?<=: ))[ \t]*({_ABSTRACT_HEADINGS})(?=[A-Z])"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _set_off_welded_headings(text: str) -> str:
|
|
56
|
+
"""Split a heading welded onto its sentence into a ``"Heading: "`` label.
|
|
57
|
+
|
|
58
|
+
Only a listed heading, at the start of the text or right after a sentence
|
|
59
|
+
ends, and directly followed by a capital letter, so "The Results were"
|
|
60
|
+
and "Methodsical" are left alone. A heading right after ": " also counts,
|
|
61
|
+
which splits chained headings ("AbstractBackgroundIt" becomes "Abstract:
|
|
62
|
+
Background: It") and a welded heading after any earlier label ("Aim:
|
|
63
|
+
MethodsWe"). Title-case headings only, on purpose: all-caps welds
|
|
64
|
+
("RESULTSThe") and welds after a bracket ("exposures)Conclusions") are
|
|
65
|
+
left as they are.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
>>> _set_off_welded_headings("IntroductionThe spider. ResultsA total.")
|
|
69
|
+
'Introduction: The spider.\\n\\nResults: A total.'
|
|
70
|
+
>>> _set_off_welded_headings("The Results were striking.")
|
|
71
|
+
'The Results were striking.'
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def label(match: re.Match[str]) -> str:
|
|
75
|
+
after_label = text[: match.start()].endswith(": ")
|
|
76
|
+
lead = "" if match.start() == 0 or after_label else "\n\n"
|
|
77
|
+
return lead + match.group(1) + ": "
|
|
78
|
+
|
|
79
|
+
# Repeat for chained headings: "AbstractBackgroundIt began." only exposes
|
|
80
|
+
# "Background" once "Abstract" has been split off.
|
|
81
|
+
while True:
|
|
82
|
+
updated = _WELDED_HEADING.sub(label, text)
|
|
83
|
+
if updated == text:
|
|
84
|
+
return text
|
|
85
|
+
text = updated
|
|
86
|
+
|
|
87
|
+
|
|
40
88
|
@ReferenceSourceRegistry.register
|
|
41
89
|
class PMIDSource(ReferenceSource):
|
|
42
90
|
"""Fetch references from PubMed using PMID.
|
|
@@ -236,7 +284,9 @@ class PMIDSource(ReferenceSource):
|
|
|
236
284
|
"""
|
|
237
285
|
sections = []
|
|
238
286
|
for node in element.find_all("AbstractText"):
|
|
239
|
-
|
|
287
|
+
# Edits the parsed node in place; nothing reads it again.
|
|
288
|
+
PMIDSource._set_off_inline_headings(node)
|
|
289
|
+
text = _set_off_welded_headings(node.get_text().strip())
|
|
240
290
|
if not text:
|
|
241
291
|
continue
|
|
242
292
|
label = node.get("Label")
|
|
@@ -245,6 +295,49 @@ class PMIDSource(ReferenceSource):
|
|
|
245
295
|
joined = "\n\n".join(sections)
|
|
246
296
|
return joined if joined else None
|
|
247
297
|
|
|
298
|
+
@staticmethod
|
|
299
|
+
def _set_off_inline_headings(node: Any) -> None:
|
|
300
|
+
"""Turn inline bold section headings into ``"Heading: "`` labels, in place.
|
|
301
|
+
|
|
302
|
+
Some publishers supply a structured abstract as one ``AbstractText``
|
|
303
|
+
whose headings are inline ``<b>``, with no space after them
|
|
304
|
+
(``<b>Background</b>Neuroblastoma can arise...``), so ``get_text()``
|
|
305
|
+
welds each heading onto the next word and a quote beginning at that
|
|
306
|
+
word fails whole-word matching. A ``<b>`` or ``<strong>`` counts as a
|
|
307
|
+
heading only at the start of the text or right after a sentence ends,
|
|
308
|
+
and only when the text after it starts with a letter; bold inside a
|
|
309
|
+
sentence, or not welded to the next word, is left as it is.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
node: An ``AbstractText`` element, modified in place
|
|
313
|
+
|
|
314
|
+
Examples:
|
|
315
|
+
>>> from bs4 import BeautifulSoup
|
|
316
|
+
>>> xml = ("<AbstractText><b>Background</b>It began."
|
|
317
|
+
... "<b>Results</b>It worked.</AbstractText>")
|
|
318
|
+
>>> node = BeautifulSoup(xml, "xml").find("AbstractText")
|
|
319
|
+
>>> PMIDSource._set_off_inline_headings(node)
|
|
320
|
+
>>> node.get_text()
|
|
321
|
+
'Background: It began.\\n\\nResults: It worked.'
|
|
322
|
+
"""
|
|
323
|
+
for bold in node.find_all(["b", "strong"]):
|
|
324
|
+
before = "".join(
|
|
325
|
+
str(sibling.get_text() if hasattr(sibling, "get_text") else sibling)
|
|
326
|
+
for sibling in reversed(list(bold.previous_siblings))
|
|
327
|
+
).rstrip()
|
|
328
|
+
if before and not before.endswith((".", "!", "?")):
|
|
329
|
+
continue
|
|
330
|
+
# Only a real weld: the next text starts with a letter, no space.
|
|
331
|
+
# "<b>IL</b>-6" and "<b>Importantly</b>," are not headings.
|
|
332
|
+
following = bold.next_sibling
|
|
333
|
+
if not (isinstance(following, str) and following[:1].isalpha()):
|
|
334
|
+
continue
|
|
335
|
+
heading = bold.get_text().strip()
|
|
336
|
+
if not heading:
|
|
337
|
+
continue
|
|
338
|
+
label = heading if heading.endswith(":") else f"{heading}:"
|
|
339
|
+
bold.replace_with(f"{chr(10) * 2 if before else ''}{label} ")
|
|
340
|
+
|
|
248
341
|
def _parse_abstract(self, soup: BeautifulSoup) -> Optional[str]:
|
|
249
342
|
"""Parse the abstract from a PubMed article XML document.
|
|
250
343
|
|
|
@@ -62,6 +62,10 @@ def is_blank_text(value: object) -> bool:
|
|
|
62
62
|
return isinstance(value, str) and not value.strip()
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
#: Digits a citation marker welded onto a word can carry: decimal digits and
|
|
66
|
+
#: the superscript digits that text printing the marker itself uses (``ears¹``).
|
|
67
|
+
_CITATION_DIGITS = r"\d¹²³⁴⁵⁶⁷⁸⁹⁰"
|
|
68
|
+
|
|
65
69
|
#: Characters typesetters print for a minus sign: "-", the Unicode hyphens
|
|
66
70
|
#: U+2010 and U+2011, the figure dash U+2012, the en dash, "−" (U+2212) and the
|
|
67
71
|
#: small and fullwidth hyphen-minus. The em dash is left out: it is a
|
|
@@ -627,8 +631,9 @@ class SupportingTextValidator:
|
|
|
627
631
|
One allowance: when the part ends in a letter, the reference word it
|
|
628
632
|
ends on may carry trailing digits. Extraction welds superscript
|
|
629
633
|
citations onto the word before them (JATS ``suppressor<sup>12</sup>``,
|
|
630
|
-
PMC HTML and PDF text all give ``suppressor12
|
|
631
|
-
|
|
634
|
+
PMC HTML and PDF text all give ``suppressor12``, and text that prints
|
|
635
|
+
the citation itself as superscript gives ``ears¹``), and a quote of
|
|
636
|
+
that sentence must not fail on its last word. A part ending in a digit gets
|
|
632
637
|
no allowance, so ``10`` never matches ``105``.
|
|
633
638
|
|
|
634
639
|
An empty part (one that was all punctuation) is found, as with a
|
|
@@ -657,6 +662,8 @@ class SupportingTextValidator:
|
|
|
657
662
|
True
|
|
658
663
|
>>> SupportingTextValidator.contains_words("a dose of 10", "a dose of 105 mg")
|
|
659
664
|
False
|
|
665
|
+
>>> SupportingTextValidator.contains_words("small ears", "small ears¹ ³ both")
|
|
666
|
+
True
|
|
660
667
|
>>> SupportingTextValidator.contains_words("", "anything")
|
|
661
668
|
True
|
|
662
669
|
"""
|
|
@@ -666,7 +673,7 @@ class SupportingTextValidator:
|
|
|
666
673
|
return True
|
|
667
674
|
if not part[-1].isalpha():
|
|
668
675
|
return False
|
|
669
|
-
citation = rf"(?:^| ){re.escape(part)}
|
|
676
|
+
citation = rf"(?:^| ){re.escape(part)}[{_CITATION_DIGITS}][{_CITATION_DIGITS},]*(?: |$)"
|
|
670
677
|
return re.search(citation, text) is not None
|
|
671
678
|
|
|
672
679
|
def generate_suggested_fix(
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Inline section headings in a PubMed abstract stay separate words.
|
|
2
|
+
|
|
3
|
+
Some publishers supply a structured abstract as one ``AbstractText`` whose
|
|
4
|
+
section headings are inline bold, with no space after them:
|
|
5
|
+
``<b>Background</b>Neuroblastoma can arise...``. ``get_text()`` then welds the
|
|
6
|
+
heading onto the first word ("BackgroundNeuroblastoma"), and since matching
|
|
7
|
+
became whole-word (#150) a correct quote beginning at that word fails.
|
|
8
|
+
PMID:33307929 is the real record this comes from.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from bs4 import BeautifulSoup
|
|
12
|
+
|
|
13
|
+
from linkml_reference_validator.etl.sources.pmid import PMIDSource
|
|
14
|
+
from linkml_reference_validator.models import ReferenceContent, ReferenceValidationConfig
|
|
15
|
+
from linkml_reference_validator.validation.supporting_text_validator import SupportingTextValidator
|
|
16
|
+
|
|
17
|
+
PMID_33307929 = (
|
|
18
|
+
"<Abstract><AbstractText><b>Background</b>Neuroblastoma can arise from "
|
|
19
|
+
"extra-adrenal sites in the paraspinal sympathetic chain, including the "
|
|
20
|
+
"presacral region, where they must be differentiated from an immature or "
|
|
21
|
+
"malignant neural lesion arising from a teratoma.<b>Case Report</b>We "
|
|
22
|
+
"describe two congenital presacral neuroblastomas.</AbstractText></Abstract>"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def render(xml: str) -> str:
|
|
27
|
+
"""The abstract text the PMID source would cache for ``xml``."""
|
|
28
|
+
rendered = PMIDSource()._parse_abstract(BeautifulSoup(xml, "xml"))
|
|
29
|
+
assert rendered is not None
|
|
30
|
+
return rendered
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def found(quote: str, content: str) -> bool:
|
|
34
|
+
"""Whether ``quote`` validates against ``content``."""
|
|
35
|
+
validator = SupportingTextValidator(ReferenceValidationConfig())
|
|
36
|
+
reference = ReferenceContent(reference_id="PMID:1", content=content)
|
|
37
|
+
return validator.find_text_in_reference(quote, reference).found
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_inline_headings_are_set_off_as_labels():
|
|
41
|
+
text = render(PMID_33307929)
|
|
42
|
+
assert text.startswith("Background: Neuroblastoma can arise")
|
|
43
|
+
assert "a teratoma.\n\nCase Report: We describe two" in text
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_quotes_beginning_after_an_inline_heading_match():
|
|
47
|
+
text = render(PMID_33307929)
|
|
48
|
+
assert found("Neuroblastoma can arise from extra-adrenal sites", text)
|
|
49
|
+
assert found("We describe two congenital presacral neuroblastomas.", text)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_bold_inside_a_sentence_is_left_alone():
|
|
53
|
+
xml = (
|
|
54
|
+
"<Abstract><AbstractText>Variants in <b>BRCA1</b> and "
|
|
55
|
+
"<b>BRCA2</b>were found.</AbstractText></Abstract>"
|
|
56
|
+
)
|
|
57
|
+
assert render(xml) == "Variants in BRCA1 and BRCA2were found."
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_a_heading_already_followed_by_a_space_or_colon_is_unchanged_in_words():
|
|
61
|
+
xml = (
|
|
62
|
+
"<Abstract><AbstractText><b>Background:</b> It began. "
|
|
63
|
+
"<b>Results</b> It worked.</AbstractText></Abstract>"
|
|
64
|
+
)
|
|
65
|
+
text = render(xml)
|
|
66
|
+
assert found("It began", text) and found("It worked", text)
|
|
67
|
+
assert "Background:" in text and "::" not in text
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_labelled_abstract_text_is_unchanged():
|
|
71
|
+
xml = (
|
|
72
|
+
'<Abstract><AbstractText Label="METHODS">We ran a trial.</AbstractText>'
|
|
73
|
+
'<AbstractText Label="RESULTS">It worked.</AbstractText></Abstract>'
|
|
74
|
+
)
|
|
75
|
+
assert render(xml) == "METHODS: We ran a trial.\n\nRESULTS: It worked."
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# PubMed itself sometimes stores the headings welded with no markup at all
|
|
79
|
+
# (PMID:39670790): "IntroductionThe black widow spider ... exposures.Conclusions
|
|
80
|
+
# Poison center data ...".
|
|
81
|
+
WELDED_PLAIN = (
|
|
82
|
+
"<Abstract><AbstractText>IntroductionThe black widow spider is common. "
|
|
83
|
+
"ResultsA total of 15,000 exposures were reported in 3.4% (n=521) of exposures."
|
|
84
|
+
"ConclusionsPoison center data indicate that most exposures are minor."
|
|
85
|
+
"</AbstractText></Abstract>"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_plain_welded_headings_are_set_off():
|
|
90
|
+
text = render(WELDED_PLAIN)
|
|
91
|
+
assert text.startswith("Introduction: The black widow spider")
|
|
92
|
+
assert "of exposures.\n\nConclusions: Poison center data" in text
|
|
93
|
+
assert found("The black widow spider is common", text)
|
|
94
|
+
assert found("Poison center data indicate that most exposures are minor", text)
|
|
95
|
+
assert found("A total of 15,000 exposures", text)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_heading_words_inside_sentences_are_left_alone():
|
|
99
|
+
xml = (
|
|
100
|
+
"<Abstract><AbstractText>The Results were striking. Methodsical work "
|
|
101
|
+
"followed.</AbstractText></Abstract>"
|
|
102
|
+
)
|
|
103
|
+
text = render(xml)
|
|
104
|
+
assert "The Results were striking." in text
|
|
105
|
+
assert "Methodsical work" in text
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_leading_bold_followed_by_a_hyphen_is_not_a_label():
|
|
109
|
+
text = render("<Abstract><AbstractText><b>IL</b>-6 levels were raised.</AbstractText></Abstract>")
|
|
110
|
+
assert found("IL-6 levels were raised", text), text
|
|
111
|
+
assert "IL:" not in text
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_bold_after_a_full_stop_but_not_welded_is_not_a_label():
|
|
115
|
+
xml = (
|
|
116
|
+
"<Abstract><AbstractText>It was observed. <b>Importantly</b>, the effect held, "
|
|
117
|
+
"vs. <b>BRCA2</b> carriers.</AbstractText></Abstract>"
|
|
118
|
+
)
|
|
119
|
+
text = render(xml)
|
|
120
|
+
assert "Importantly:" not in text and "BRCA2:" not in text
|
|
121
|
+
assert found("observed. Importantly, the effect held", text)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_chained_welded_headings_are_all_split():
|
|
125
|
+
text = render("<Abstract><AbstractText>AbstractBackgroundIt began.</AbstractText></Abstract>")
|
|
126
|
+
assert found("It began", text), text
|
|
127
|
+
assert text == "Abstract: Background: It began."
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Citation markers, lead-in labels and section titles stay separate words.
|
|
2
|
+
|
|
3
|
+
Matching became whole-word in #150, so text an extractor welds together now
|
|
4
|
+
fails a correct quote where a substring test used to pass it. Each case here is
|
|
5
|
+
from a real cached reference that a downstream knowledge base quoted:
|
|
6
|
+
|
|
7
|
+
- a JATS citation ``FOXL2<sup><xref ref-type="bibr">8</xref></sup>.`` read as
|
|
8
|
+
``FOXL28.`` (PMC6851130), and ``PGE2.<sup>41</sup>`` as the number ``pge2.41``;
|
|
9
|
+
- a paragraph's bold lead-in label ``<bold>R 2.6</bold>We recommend`` read as
|
|
10
|
+
``2.6We`` (PMC9066594);
|
|
11
|
+
- a Crossref JATS abstract ``<jats:title>Abstract</jats:title><jats:sec>
|
|
12
|
+
<jats:title>Disease overview</jats:title><jats:p>Acute myeloid...`` read as
|
|
13
|
+
``AbstractDisease overviewAcute myeloid`` (DOI:10.1002/ajh.26822);
|
|
14
|
+
- a superscript citation range ``ears¹⁻³`` (PMID:21358632), which the
|
|
15
|
+
trailing-citation allowance did not cover because ``¹`` is not a decimal
|
|
16
|
+
digit.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
from linkml_reference_validator.etl.extract.xml import XMLExtractor
|
|
22
|
+
from linkml_reference_validator.etl.sources.doi import DOISource
|
|
23
|
+
from linkml_reference_validator.models import ReferenceContent, ReferenceValidationConfig
|
|
24
|
+
from linkml_reference_validator.validation.supporting_text_validator import (
|
|
25
|
+
SupportingTextValidator,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def extract(body: str) -> str:
|
|
30
|
+
text = XMLExtractor().extract(f"<article><body>{body}</body></article>".encode())
|
|
31
|
+
assert text is not None
|
|
32
|
+
return text
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def found(quote: str, content: str) -> bool:
|
|
36
|
+
validator = SupportingTextValidator(ReferenceValidationConfig())
|
|
37
|
+
reference = ReferenceContent(reference_id="PMID:1", content=content)
|
|
38
|
+
return validator.find_text_in_reference(quote, reference).found
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# --- JATS citation markers -------------------------------------------------
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.mark.parametrize(
|
|
45
|
+
"body,quote",
|
|
46
|
+
[
|
|
47
|
+
(
|
|
48
|
+
'<p>It inhibits the expression of FOXL2<sup><xref ref-type="bibr" '
|
|
49
|
+
'rid="CR8">8</xref></sup>. Here we add PITX2.</p>',
|
|
50
|
+
"inhibits the expression of FOXL2",
|
|
51
|
+
),
|
|
52
|
+
(
|
|
53
|
+
'<p>caused by mutations in KCNA1<xref ref-type="bibr" rid="B18">18</xref>). '
|
|
54
|
+
"At last follow up.</p>",
|
|
55
|
+
"caused by mutations in KCNA1",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
'<p>and a deficiency of PGE2.<sup><xref ref-type="bibr" rid="B41">41</xref></sup>'
|
|
59
|
+
" Next sentence.</p>",
|
|
60
|
+
"a deficiency of PGE2",
|
|
61
|
+
),
|
|
62
|
+
(
|
|
63
|
+
'<p>if left untreated.<sup><xref ref-type="bibr" rid="B2">2</xref>,'
|
|
64
|
+
'<xref ref-type="bibr" rid="B8">8</xref></sup>Damage to major organs.</p>',
|
|
65
|
+
"Damage to major organs",
|
|
66
|
+
),
|
|
67
|
+
],
|
|
68
|
+
)
|
|
69
|
+
def test_citation_markers_do_not_weld(body, quote):
|
|
70
|
+
text = extract(body)
|
|
71
|
+
assert found(quote, text), text
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_citation_marker_still_reads_as_its_number():
|
|
75
|
+
text = extract('<p>shown before<sup><xref ref-type="bibr" rid="B8">8</xref></sup>.</p>')
|
|
76
|
+
assert "shown before 8" in text
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_non_citation_superscripts_stay_attached():
|
|
80
|
+
"""A unit exponent or a figure link is not a citation; only bibr is padded."""
|
|
81
|
+
text = extract(
|
|
82
|
+
'<p>at 10<sup>6</sup> cells, see <xref ref-type="fig" rid="F1">Figure 1</xref>'
|
|
83
|
+
"a, and H<sub>2</sub>O.</p>"
|
|
84
|
+
)
|
|
85
|
+
assert "10⁶" in text or "106" in text
|
|
86
|
+
assert "H2O" in text
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# --- Bold lead-in labels ---------------------------------------------------
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_leading_bold_label_is_set_off():
|
|
93
|
+
text = extract(
|
|
94
|
+
"<p><bold>R 2.6</bold>We recommend to use 17β-oestradiol for puberty induction.</p>"
|
|
95
|
+
)
|
|
96
|
+
assert found("We recommend to use 17β-oestradiol", text), text
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_bold_inside_a_sentence_is_left_alone():
|
|
100
|
+
text = extract("<p>Variants in <bold>BRCA1</bold>were found.</p>")
|
|
101
|
+
assert "BRCA1were" in text
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# --- Crossref JATS abstracts -----------------------------------------------
|
|
105
|
+
|
|
106
|
+
CROSSREF_ABSTRACT = (
|
|
107
|
+
"<jats:title>Abstract</jats:title>\n"
|
|
108
|
+
" <jats:sec>\n"
|
|
109
|
+
" <jats:title>Disease overview</jats:title>\n"
|
|
110
|
+
" <jats:p>Acute myeloid leukemia (AML) is a frequently fatal "
|
|
111
|
+
"bone marrow stem cell cancer.</jats:p>\n"
|
|
112
|
+
" </jats:sec>\n"
|
|
113
|
+
" <jats:sec><jats:title>Diagnosis</jats:title>"
|
|
114
|
+
"<jats:p>One updated and one <jats:italic>new</jats:italic> classification.</jats:p>"
|
|
115
|
+
"</jats:sec>"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_crossref_abstract_sections_are_separate_paragraphs():
|
|
120
|
+
text = DOISource()._clean_abstract(CROSSREF_ABSTRACT)
|
|
121
|
+
assert text.split("\n\n") == [
|
|
122
|
+
"Abstract",
|
|
123
|
+
"Disease overview",
|
|
124
|
+
"Acute myeloid leukemia (AML) is a frequently fatal bone marrow stem cell cancer.",
|
|
125
|
+
"Diagnosis",
|
|
126
|
+
"One updated and one new classification.",
|
|
127
|
+
]
|
|
128
|
+
assert found("Acute myeloid leukemia (AML) is a frequently fatal", text)
|
|
129
|
+
assert found("One updated and one new classification", text)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_crossref_plain_paragraph_is_unchanged():
|
|
133
|
+
assert DOISource()._clean_abstract("<jats:p>Test abstract.</jats:p>") == "Test abstract."
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# --- Superscript citations in plain text -----------------------------------
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.mark.parametrize(
|
|
140
|
+
"quote,content",
|
|
141
|
+
[
|
|
142
|
+
("markedly small ears", "patellae and markedly small ears¹⁻³. Both pre-"),
|
|
143
|
+
("markedly small ears", "patellae and markedly small ears²³ and"),
|
|
144
|
+
],
|
|
145
|
+
)
|
|
146
|
+
def test_superscript_citation_after_a_word_is_allowed(quote, content):
|
|
147
|
+
assert found(quote, content)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_superscript_does_not_extend_a_number():
|
|
151
|
+
"""The allowance stays letter-only: a quote ending in a digit gets none."""
|
|
152
|
+
assert not found("a dose of 10", "a dose of 10⁶ cells")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@pytest.mark.parametrize(
|
|
156
|
+
"body,quote",
|
|
157
|
+
[
|
|
158
|
+
("<p><bold>IL</bold>-6 levels were raised.</p>", "IL-6 levels were raised"),
|
|
159
|
+
("<p><bold>BRCA1</bold> mutations were found.</p>", "BRCA1 mutations were found"),
|
|
160
|
+
],
|
|
161
|
+
)
|
|
162
|
+
def test_leading_bold_that_is_not_welded_is_left_alone(body, quote):
|
|
163
|
+
"""Only a bold followed directly by a letter is a welded label (IL-6 is not)."""
|
|
164
|
+
text = extract(body)
|
|
165
|
+
assert found(quote, text), text
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_crossref_html_breaks_and_divs_separate_paragraphs():
|
|
169
|
+
text = DOISource()._clean_abstract("<div>First part.</div><div>Second part.</div>a<br/>b")
|
|
170
|
+
assert text.split("\n\n") == ["First part.", "Second part.", "a", "b"]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@pytest.mark.parametrize(
|
|
174
|
+
"body,quote",
|
|
175
|
+
[
|
|
176
|
+
("<p>shown (<xref ref-type=\"bibr\" rid=\"B1\">1</xref>-3) before.</p>", "shown (1-3) before"),
|
|
177
|
+
('<p>small ears<sup><xref ref-type="bibr" rid="B1">1</xref>–3</sup> in all.</p>', "small ears"),
|
|
178
|
+
],
|
|
179
|
+
)
|
|
180
|
+
def test_citation_range_with_its_first_number_linked_keeps_its_dash(body, quote):
|
|
181
|
+
"""A dash after a padded marker must not become a minus sign."""
|
|
182
|
+
text = extract(body)
|
|
183
|
+
assert found(quote, text), text
|
|
184
|
+
assert "neg" not in SupportingTextValidator.normalize_text(text), text
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.devcontainer/post_create.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/ai-controllers.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/dependabot.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linkml_reference_validator-0.5.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude.yml
RENAMED
|
File without changes
|