linkml-reference-validator 0.3.0rc2__tar.gz → 0.3.1rc1__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.
Files changed (236) hide show
  1. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/PKG-INFO +2 -2
  2. linkml_reference_validator-0.3.1rc1/benchmarks/repeated_quotes.py +49 -0
  3. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/concepts/content-types.md +1 -0
  4. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/use-local-files-and-urls.md +49 -2
  5. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/validate-urls.md +57 -16
  6. linkml_reference_validator-0.3.1rc1/docs/reference/cache-format.md +150 -0
  7. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/troubleshooting.md +45 -0
  8. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/mkdocs.yml +1 -0
  9. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/pyproject.toml +1 -1
  10. linkml_reference_validator-0.3.1rc1/src/linkml_reference_validator/__init__.py +23 -0
  11. linkml_reference_validator-0.3.1rc1/src/linkml_reference_validator/cache.py +251 -0
  12. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/acquire.py +71 -0
  13. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/extract/__init__.py +5 -2
  14. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/extract/html.py +225 -7
  15. linkml_reference_validator-0.3.1rc1/src/linkml_reference_validator/etl/extract/pdf.py +136 -0
  16. linkml_reference_validator-0.3.1rc1/src/linkml_reference_validator/etl/extract/svg.py +897 -0
  17. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/extract/xml.py +3 -14
  18. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/pmc.py +5 -9
  19. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/reference_fetcher.py +275 -142
  20. linkml_reference_validator-0.3.1rc1/src/linkml_reference_validator/etl/rules.py +195 -0
  21. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/file.py +35 -3
  22. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/pmid.py +2 -6
  23. linkml_reference_validator-0.3.1rc1/src/linkml_reference_validator/etl/sources/url.py +273 -0
  24. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/matching.py +6 -0
  25. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/models.py +12 -0
  26. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/validation/fuzzy_text_utils.py +103 -2
  27. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/validation/repairer.py +12 -5
  28. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/validation/supporting_text_validator.py +59 -9
  29. linkml_reference_validator-0.3.1rc1/tests/test_cache_validation.py +512 -0
  30. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_extractor_fidelity.py +11 -20
  31. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_jats_tables.py +3 -1
  32. linkml_reference_validator-0.3.1rc1/tests/test_normalized_content_cache.py +71 -0
  33. linkml_reference_validator-0.3.1rc1/tests/test_pdf_url_title.py +228 -0
  34. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_reference_fetcher.py +9 -1
  35. linkml_reference_validator-0.3.1rc1/tests/test_rules.py +129 -0
  36. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_serve_stale_html_optin.py +59 -0
  37. linkml_reference_validator-0.3.1rc1/tests/test_svg_extractor.py +1047 -0
  38. linkml_reference_validator-0.3.1rc1/tests/test_trust_cached_entries.py +81 -0
  39. linkml_reference_validator-0.3.1rc1/tests/test_url_html_sanitize.py +160 -0
  40. linkml_reference_validator-0.3.1rc1/tests/test_url_html_to_text.py +226 -0
  41. linkml_reference_validator-0.3.1rc1/tests/test_url_source_cache_version.py +147 -0
  42. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/uv.lock +1 -1
  43. linkml_reference_validator-0.3.0rc2/src/linkml_reference_validator/__init__.py +0 -5
  44. linkml_reference_validator-0.3.0rc2/src/linkml_reference_validator/etl/extract/pdf.py +0 -74
  45. linkml_reference_validator-0.3.0rc2/src/linkml_reference_validator/etl/sources/url.py +0 -148
  46. linkml_reference_validator-0.3.0rc2/tests/test_pmc_modern_container.py +0 -59
  47. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.claude/.mcp.json +0 -0
  48. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.claude/settings.json +0 -0
  49. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.copier-answers.yml +0 -0
  50. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.devcontainer/devcontainer.json +0 -0
  51. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.devcontainer/post_create.sh +0 -0
  52. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.editorconfig +0 -0
  53. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/actions/claude-code-action/action.yml +0 -0
  54. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/actions/claude-issue-summarize-action/action.yml +0 -0
  55. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/actions/claude-issue-triage-action/action.yml +0 -0
  56. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/ai-controllers.json +0 -0
  57. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/copilot-instructions.md +0 -0
  58. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/copilot-setup-steps.yml +0 -0
  59. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/dependabot.yml +0 -0
  60. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/claude-code-review.yml +0 -0
  61. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/claude-issue-summarize.yml +0 -0
  62. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/claude-issue-triage.yml +0 -0
  63. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/claude-ontology-review.yml +0 -0
  64. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/claude.yml +0 -0
  65. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/deploy-docs.yaml +0 -0
  66. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/dragon-ai.yml +0 -0
  67. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/main.yaml +0 -0
  68. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.github/workflows/pypi-publish.yaml +0 -0
  69. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.gitignore +0 -0
  70. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.goosehints +0 -0
  71. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.pre-commit-config.yaml +0 -0
  72. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/.yamllint.yaml +0 -0
  73. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/AGENTS.md +0 -0
  74. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/BUG_GEO_ACCESSION_TO_UID.md +0 -0
  75. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/BUG_ZENODO_DOI_AND_URL_HANDLING.md +0 -0
  76. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/CLAUDE.md +0 -0
  77. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/CLI-ISSUES.md +0 -0
  78. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/CODE_OF_CONDUCT.md +0 -0
  79. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/CONTRIBUTING.md +0 -0
  80. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/FEATURE_REQUEST_REPAIR.md +0 -0
  81. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/LICENSE +0 -0
  82. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/README.md +0 -0
  83. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/REVIEW2.md +0 -0
  84. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/REVIEW3.md +0 -0
  85. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/ai.just +0 -0
  86. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/copilot-instructions.md +0 -0
  87. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/about.md +0 -0
  88. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/assets/zotero-find-full-text.png +0 -0
  89. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/concepts/editorial-conventions.md +0 -0
  90. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/concepts/how-it-works.md +0 -0
  91. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/elements/.gitkeep +0 -0
  92. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/add-reference-source.md +0 -0
  93. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/export-cache-to-zotero.md +0 -0
  94. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/fetch-full-text-and-pdfs.md +0 -0
  95. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/repair-validation-errors.md +0 -0
  96. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/skip-unsupported-references.md +0 -0
  97. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/validate-clinical-trials.md +0 -0
  98. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/validate-dois.md +0 -0
  99. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/validate-entrez.md +0 -0
  100. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/validate-obo-files.md +0 -0
  101. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/how-to/validate-titles.md +0 -0
  102. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/index.md +0 -0
  103. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/notebooks/01_getting_started.ipynb +0 -0
  104. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/notebooks/02_advanced_usage.ipynb +0 -0
  105. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/notebooks/03_python_api.ipynb +0 -0
  106. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/notebooks/04_obo_validation.ipynb +0 -0
  107. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/notebooks/05_geo_validation.ipynb +0 -0
  108. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/quickstart.md +0 -0
  109. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/reference/cli.md +0 -0
  110. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/reference/splitting.md +0 -0
  111. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/setup-guide.md +0 -0
  112. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/superpowers/plans/2026-06-12-modular-pdf-fulltext-fetching.md +0 -0
  113. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/superpowers/plans/2026-08-05-private-library-full-text.md +0 -0
  114. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/superpowers/specs/2026-06-12-modular-pdf-fulltext-fetching-design.md +0 -0
  115. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/superpowers/specs/2026-08-05-private-library-full-text-design.md +0 -0
  116. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/templates-linkml/README.md +0 -0
  117. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/todo.md +0 -0
  118. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/docs/tutorials/complete-workflow.md +0 -0
  119. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/examples/README.md +0 -0
  120. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/justfile +0 -0
  121. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/mypy.ini +0 -0
  122. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/notebooks/01_getting_started.ipynb +0 -0
  123. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/notebooks/02_advanced_usage.ipynb +0 -0
  124. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/notebooks/03_python_api.ipynb +0 -0
  125. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/notebooks/README.md +0 -0
  126. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/project/README.md +0 -0
  127. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/project.justfile +0 -0
  128. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/pytest.ini +0 -0
  129. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/_version.py +0 -0
  130. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/cli/__init__.py +0 -0
  131. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/cli/cache.py +0 -0
  132. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/cli/lookup.py +0 -0
  133. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/cli/repair.py +0 -0
  134. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/cli/shared.py +0 -0
  135. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/cli/validate.py +0 -0
  136. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/__init__.py +0 -0
  137. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/csl_export.py +0 -0
  138. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/extract/base.py +0 -0
  139. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/__init__.py +0 -0
  140. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/base.py +0 -0
  141. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/epmc_preprint.py +0 -0
  142. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/json_api.py +0 -0
  143. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/loader.py +0 -0
  144. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/openalex.py +0 -0
  145. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/unpaywall.py +0 -0
  146. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/fulltext/zotero.py +0 -0
  147. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/identifiers.py +0 -0
  148. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/__init__.py +0 -0
  149. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/base.py +0 -0
  150. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/clinicaltrials.py +0 -0
  151. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/doi.py +0 -0
  152. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/entrez.py +0 -0
  153. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/json_api.py +0 -0
  154. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/loader.py +0 -0
  155. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/ppr.py +0 -0
  156. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/sources/utils.py +0 -0
  157. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/etl/text_extractor.py +0 -0
  158. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/field_detection.py +0 -0
  159. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/plugins/REVIEW.md +0 -0
  160. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/plugins/__init__.py +0 -0
  161. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/plugins/reference_validation_plugin.py +0 -0
  162. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/src/linkml_reference_validator/validation/__init__.py +0 -0
  163. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/__init__.py +0 -0
  164. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/conftest.py +0 -0
  165. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/data/test_data_deep_nesting_invalid.yaml +0 -0
  166. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/data/test_data_deep_nesting_valid.yaml +0 -0
  167. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/data/test_data_invalid.yaml +0 -0
  168. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/data/test_data_valid.yaml +0 -0
  169. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/data/test_schema.yaml +0 -0
  170. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/data/test_schema_deep_nesting.yaml +0 -0
  171. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/GEO_GSE12345.md +0 -0
  172. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMC_TEST001.md +0 -0
  173. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMC_TEST001.txt +0 -0
  174. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMC_TEST002.md +0 -0
  175. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMC_TEST002.txt +0 -0
  176. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMID_TEST001.md +0 -0
  177. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMID_TEST001.txt +0 -0
  178. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMID_TEST002.md +0 -0
  179. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMID_TEST002.txt +0 -0
  180. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMID_TEST003.md +0 -0
  181. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/PMID_TEST003.txt +0 -0
  182. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/clinicaltrials_NCT00000001.md +0 -0
  183. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/fulltext_html/README.md +0 -0
  184. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/fulltext_html/dspace-32894695.html +0 -0
  185. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/fulltext_html/iris-19049553.html +0 -0
  186. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/fulltext_html/plos-0000308.html +0 -0
  187. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/jats/PMC5593426.xml +0 -0
  188. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/jats/README.md +0 -0
  189. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/fixtures/sample.obo +0 -0
  190. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_absent_abstract_recovery.py +0 -0
  191. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_acquire.py +0 -0
  192. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_cache_author_recovery.py +0 -0
  193. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_cli.py +0 -0
  194. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_cli_cache_enrich.py +0 -0
  195. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_cli_cache_export.py +0 -0
  196. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_cli_cache_reference.py +0 -0
  197. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_cli_lookup.py +0 -0
  198. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_cli_repair.py +0 -0
  199. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_clinicaltrials_cache.py +0 -0
  200. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_clinicaltrials_integration.py +0 -0
  201. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_deep_nesting.py +0 -0
  202. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_doi_preprint.py +0 -0
  203. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_e2e_integration.py +0 -0
  204. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_empty_snippets.py +0 -0
  205. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_epmc_preprint_provider.py +0 -0
  206. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_extractors.py +0 -0
  207. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_field_detection.py +0 -0
  208. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_frontmatter_and_doi_case.py +0 -0
  209. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_fulltext_loader.py +0 -0
  210. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_fulltext_providers.py +0 -0
  211. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_fuzzy_suggestions.py +0 -0
  212. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_geo_integration.py +0 -0
  213. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_html_fulltext_acceptance.py +0 -0
  214. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_identifiers.py +0 -0
  215. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_json_api_source.py +0 -0
  216. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_ligature_normalization.py +0 -0
  217. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_models.py +0 -0
  218. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_no_cache_downgrade.py +0 -0
  219. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_open_access_policy.py +0 -0
  220. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_optional_linkml_dependency.py +0 -0
  221. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_plugin_integration.py +0 -0
  222. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_pmc_fulltext.py +0 -0
  223. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_pmid_network.py +0 -0
  224. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_ppr_source.py +0 -0
  225. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_real_validation.py +0 -0
  226. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_repair.py +0 -0
  227. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_source_utils.py +0 -0
  228. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_sources.py +0 -0
  229. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_split_supporting_text.py +0 -0
  230. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_supporting_text_validator.py +0 -0
  231. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_text_extractor.py +0 -0
  232. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_title_validation.py +0 -0
  233. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_unknown_slot_traversal.py +0 -0
  234. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_validation_config.py +0 -0
  235. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_validation_counts.py +0 -0
  236. {linkml_reference_validator-0.3.0rc2 → linkml_reference_validator-0.3.1rc1}/tests/test_zotero_provider.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: linkml_reference_validator
3
- Version: 0.3.0rc2
3
+ Version: 0.3.1rc1
4
4
  Summary: Validation of supporting text from references and publications
5
5
  Author-email: Chris Mungall <cjmungall@lbl.gov>
6
6
  License-Expression: Apache-2.0
@@ -10,7 +10,7 @@ Requires-Dist: beautifulsoup4>=4.9.0
10
10
  Requires-Dist: biopython>=1.80
11
11
  Requires-Dist: jsonpath-ng>=1.6.0
12
12
  Requires-Dist: linkml-runtime>=1.9.4
13
- Requires-Dist: lxml>=4.9.0
13
+ Requires-Dist: lxml>=5.0.0
14
14
  Requires-Dist: pydantic>=2.0.0
15
15
  Requires-Dist: pypdf>=4.0.0
16
16
  Requires-Dist: rapidfuzz>=3.14.3
@@ -0,0 +1,49 @@
1
+ """Time repeated matching against a local UTF-8 document, without network access.
2
+
3
+ Run the same script and input with each checkout's package on PYTHONPATH:
4
+ uv run python benchmarks/repeated_quotes.py /path/to/cached/publication.md
5
+
6
+ Only successful matching is timed; this is a CPU microbenchmark, not end-to-end
7
+ CLI validation. It includes each validator's first normalization of the body.
8
+ """
9
+
10
+ import argparse
11
+ import json
12
+ import re
13
+ from pathlib import Path
14
+ from statistics import median
15
+ from time import perf_counter
16
+
17
+ from linkml_reference_validator.models import ReferenceContent, ReferenceValidationConfig
18
+ from linkml_reference_validator.validation.supporting_text_validator import SupportingTextValidator
19
+
20
+
21
+ def main() -> None:
22
+ """Report median time to match evenly spaced literal excerpts from a document."""
23
+ parser = argparse.ArgumentParser(description=__doc__)
24
+ parser.add_argument("document", type=Path)
25
+ parser.add_argument("--quotes", type=int, default=100)
26
+ parser.add_argument("--rounds", type=int, default=3)
27
+ args = parser.parse_args()
28
+ content = args.document.read_text(encoding="utf-8")
29
+ # Each query is taken verbatim from the source; avoid editorial-bracket and
30
+ # ellipsis syntax, which would change what the matching API searches for.
31
+ candidates = [m.group() for m in re.finditer(r"[\w ,;:-]{30,100}", content)]
32
+ if args.quotes < 1 or args.rounds < 1 or not candidates:
33
+ parser.error("positive --quotes/--rounds and a document with prose are required")
34
+ quotes = [candidates[i * len(candidates) // args.quotes] for i in range(args.quotes)]
35
+ reference = ReferenceContent(reference_id="TEST:benchmark", content=content)
36
+ elapsed = []
37
+ for _ in range(args.rounds):
38
+ validator = SupportingTextValidator(ReferenceValidationConfig())
39
+ start = perf_counter()
40
+ for quote in quotes:
41
+ if not validator.find_text_in_reference(quote, reference).found:
42
+ raise SystemExit(f"Excerpt unexpectedly failed validation: {quote!r}")
43
+ elapsed.append(perf_counter() - start)
44
+ print(json.dumps({"characters": len(content), "quotes": len(quotes),
45
+ "rounds_seconds": elapsed, "median_seconds": median(elapsed)}))
46
+
47
+
48
+ if __name__ == "__main__":
49
+ main()
@@ -11,6 +11,7 @@ When fetching references, the validator tracks what type of content was retrieve
11
11
  | `abstract_only` | PMID, DOI | Only the abstract was available |
12
12
  | `summary` | GEO, BioProject, BioSample, ClinicalTrials | Database summary/description field |
13
13
  | `local_file` | file: | Content read from local filesystem |
14
+ | `local_svg` | file: (`.svg`) | Text an SVG figure renders, a blank line between labels; a quote must fall within one label |
14
15
  | `url` | url: | Content fetched from web URL |
15
16
  | `unavailable` | Any | No content could be retrieved |
16
17
 
@@ -89,7 +89,7 @@ URLs are cached the same way as PMID and DOI references:
89
89
 
90
90
  ### Title Extraction
91
91
 
92
- For HTML pages, the title is extracted from the `<title>` tag. For other content types, the URL itself is used as the title.
92
+ For HTML pages, the title is the `citation_title` meta tag when present, then the `<title>` tag. For PDFs, the validator tries a publisher landing page's `citation_title` and then the PDF's embedded title. When neither is found, and for other content types, the URL itself is used as the title. See [Validating URLs](validate-urls.md#titles).
93
93
 
94
94
  ### Example: Validating Against a Web Page
95
95
 
@@ -105,6 +105,51 @@ linkml-reference-validator validate text \
105
105
  url:https://example.com/pangram-examples.html
106
106
  ```
107
107
 
108
+ ## Diagrams as References (SVG)
109
+
110
+ A `file:` reference can point at an `.svg` figure, such as a pathway diagram.
111
+ The quote is then matched against the text the figure **renders**, not its
112
+ markup:
113
+
114
+ ```bash
115
+ linkml-reference-validator validate text \
116
+ "loss of PTEN activates AKT" \
117
+ file:./figures/pathway.svg
118
+ ```
119
+
120
+ - Each `<text>` element is one label. A label split over several `<tspan>`
121
+ lines can be quoted across the break.
122
+ - A quote must fall within one label. A figure with a `PTEN` label and a
123
+ separate `activates AKT` label does not support `"PTEN activates AKT"`,
124
+ however close together they sit: neither label says it. To cite both, quote
125
+ them as separate parts, `"PTEN ... activates AKT"`.
126
+ - HTML labels inside `<foreignObject>`, which is how drawio's **Export as SVG**
127
+ writes them, are read too.
128
+ - Text the reader cannot see is never quotable. That covers `<desc>`, `<title>`,
129
+ `<metadata>`, comments, attribute values (`id`, `fill`, `class`, ...),
130
+ `<defs>`/`<symbol>` content that no `<use>` draws, and text hidden by
131
+ `display:none`, `visibility:hidden`, zero `opacity`, `fill="none"` or a zero
132
+ `font-size`, whether set on the element, inherited from a group, or set by a
133
+ `<style>` rule.
134
+ - If the extractor cannot tell what the figure renders, the reference is
135
+ `unavailable` rather than read as raw markup. That happens when a `<style>`
136
+ rule hides or shows text through a selector other than a type, class or id
137
+ selector (`g > text`, `:first-child`, `@media`), when visibility depends on
138
+ `var()` or `calc()`, when a stylesheet is imported or linked, when the SVG
139
+ contains a `<script>` or animates visibility, or when something changes the
140
+ characters drawn: CSS `content`, a string `list-style-type` or `quotes`,
141
+ `-webkit-text-security`, a `unicode-bidi` override, `<bdo>`, or a bidi
142
+ control character in the text.
143
+
144
+ Known limits: a label drawn as several `<text>` elements, one per line, is
145
+ read as several labels, so quote its lines with `...` between them. Text
146
+ clipped by `clip-path` or `mask`, placed outside the
147
+ `viewBox`, scaled to nothing, covered by another shape, drawn in the
148
+ background colour, or cut short by CSS `text-overflow` or `-webkit-line-clamp`
149
+ is still read. Text converted to outlines is not text and
150
+ cannot be quoted. Native `.drawio` files and connector (edge) labels between
151
+ nodes are not yet supported.
152
+
108
153
  ## Using in Data Files
109
154
 
110
155
  Both file and URL references work in LinkML data files:
@@ -145,6 +190,8 @@ Both file and URL references work in LinkML data files:
145
190
 
146
191
  ## Limitations
147
192
 
148
- - **PDF files**: Not yet supported (planned for future)
193
+ - **Local files are read as text**, apart from `.svg` figures (see above).
194
+ An `.html` file is matched against its raw markup.
195
+ - **Local PDF files**: Not yet supported as `file:` references. PDFs fetched by `url:` are supported.
149
196
  - **Authentication**: URLs requiring login are not supported
150
197
  - **Dynamic content**: JavaScript-rendered pages may not work
@@ -15,8 +15,8 @@ The linkml-reference-validator supports validating references that point to web
15
15
  When a reference field contains a URL, the validator:
16
16
 
17
17
  1. Fetches the web page content
18
- 2. Extracts the page title from `<title>` tag (for HTML)
19
- 3. Caches the content for future validations
18
+ 2. Extracts the page title (see [Titles](#titles))
19
+ 3. Converts HTML to readable text and caches it for future validations
20
20
  4. Validates your supporting text against the page content
21
21
 
22
22
  ## URL Format
@@ -79,14 +79,54 @@ When the validator encounters a URL reference, it:
79
79
 
80
80
  The fetcher stores:
81
81
 
82
- - **Title**: Extracted from the `<title>` tag (for HTML pages)
83
- - **Content**: The raw page content as received
84
- - **Content type**: Marked as `url` to distinguish from other reference types
85
-
86
- Note: The validator stores raw page content without HTML-to-text conversion.
87
- HTML tags remain in the cached file, and tag names can surface during
88
- normalization. If validation fails because tags interrupt the text, consider
89
- extracting plain text and validating against a local `file:` reference instead.
82
+ - **Title**: See [Titles](#titles)
83
+ - **Content**: Readable text for HTML pages; plain text and XML as received;
84
+ extracted text for PDFs
85
+ - **Content type**: `url` for pages, `full_text_pdf` for PDFs with text
86
+
87
+ HTML is converted to readable text before it is cached. A quote copied from
88
+ the page then matches even when it runs through a link, bold or italic text,
89
+ and a cached page can be read, and diffed, line by line:
90
+
91
+ - Inline tags (`<a>`, `<b>`, `<em>`, `<span>`, ...) are removed and their
92
+ text is kept in place.
93
+ - Block-level tags (`<p>`, `<li>`, `<h1>`-`<h6>`, `<div>`, `<br>`, ...) start
94
+ a new line.
95
+ - A table row is one line, its cells separated by ` | `. Normalization drops
96
+ the `|`, so a quote copied from a rendered row with its cells separated by
97
+ spaces still matches.
98
+ - `<pre>` keeps its whitespace. Elsewhere whitespace collapses to one space.
99
+ - Entities such as `&amp;` are unescaped.
100
+
101
+ The whole page is kept, including navigation, sidebars and footers; it is not
102
+ narrowed to the article body. Because caches are often committed to public
103
+ repositories and page scripts routinely carry signed asset URLs and API keys,
104
+ `<script>`, `<style>`, `<noscript>` and `<template>` elements and HTML comments
105
+ are removed with their text, and no tag attribute reaches the cache.
106
+
107
+ #### Titles
108
+
109
+ For HTML pages the title is the `citation_title` meta tag when present (the
110
+ Highwire / Google Scholar convention, which names the article rather than the
111
+ site), then the `<title>` tag.
112
+
113
+ For PDFs the validator tries, in order:
114
+
115
+ 1. The `citation_title` of the publisher's landing page, where a known rule
116
+ maps the PDF URL to it. Currently J-STAGE: `.../_pdf` becomes
117
+ `.../_article`.
118
+ 2. The PDF's embedded `/Title` metadata, ignoring placeholders such as
119
+ `Microsoft Word - draft.doc`, bare filenames and `Untitled`.
120
+ 3. The URL itself.
121
+
122
+ So a PDF entry whose `title` equals its URL is one for which no title was
123
+ found.
124
+
125
+ The landing-page rules and the placeholder titles are kept in
126
+ `src/linkml_reference_validator/etl/rules.py`, with the other publisher and
127
+ site rules. Each rule sits beside examples of what it must and must not match.
128
+ To support another publisher, add a rule there with at least one example;
129
+ `tests/test_rules.py` fails for a rule without one.
90
130
 
91
131
  ### 3. Caching
92
132
 
@@ -104,9 +144,9 @@ content_type: url
104
144
  ## Content
105
145
 
106
146
  <html>
107
- <head>
108
- <title>Chapter 3: Cell Structure and Function</title>
109
- </head>
147
+ <head>
148
+ <title>Chapter 3: Cell Structure and Function</title>
149
+ </head>
110
150
  ...
111
151
  ```
112
152
 
@@ -145,10 +185,11 @@ URL validation is designed for static web pages. It may not work well with:
145
185
 
146
186
  ### Raw Content
147
187
 
148
- The validator stores raw page content. For HTML pages:
188
+ The validator stores the page's text, not its markup. For HTML pages:
149
189
 
150
- - HTML tags are preserved in the cache
151
- - The text normalization during validation handles most cases
190
+ - Text that only appears in an attribute, such as an image's `alt` text, is
191
+ not kept
192
+ - Text inside `<noscript>` or `<template>` is not kept
152
193
  - Complex HTML layouts may require careful text extraction
153
194
 
154
195
  ### No Rendering
@@ -0,0 +1,150 @@
1
+ # Reference cache format and validation
2
+
3
+ The library owns the contract for Markdown cache files. Use its public model and
4
+ checkers instead of maintaining a downstream copy of the frontmatter fields:
5
+
6
+ ```python
7
+ from linkml_reference_validator import (
8
+ CacheFrontmatter,
9
+ scan_cache_dir,
10
+ validate_cache_file,
11
+ )
12
+
13
+ result = validate_cache_file("references_cache/PMID_12345.md")
14
+ for finding in result.findings:
15
+ print(result.path, finding.code, finding.field, finding.message)
16
+
17
+ results = scan_cache_dir("references_cache")
18
+ invalid = [result for result in results if not result.is_valid]
19
+ if invalid:
20
+ raise SystemExit(f"{len(invalid)} invalid cache files")
21
+ ```
22
+
23
+ These functions read files without fetching references, refreshing extraction
24
+ stamps, or rewriting the cache. They return `CacheValidationResult` objects with
25
+ `path`, `frontmatter`, `findings`, and an `is_valid` property. `frontmatter` is a
26
+ `CacheFrontmatter` instance when the header passes validation, including when the
27
+ filename fails its separate check; otherwise it is `None`.
28
+
29
+ `scan_cache_dir(directory, recursive=False)` returns a list in path order. It
30
+ checks immediate `*.md` files by default; pass `recursive=True` to include nested
31
+ directories. It skips symlinked directories during recursion but reads symlinked
32
+ Markdown files. Non-Markdown files, including legacy `.txt` caches and downloaded
33
+ XML/PDF files, are outside this contract and are ignored. An existing empty directory returns `[]`. Missing,
34
+ unreadable, or non-directory scan roots raise `OSError` (including its subclasses)
35
+ so a mistaken path cannot silently pass a gate. Individual file errors are
36
+ reported as findings and do not stop the scan.
37
+
38
+ ## Frontmatter contract
39
+
40
+ Files contain UTF-8 text without a byte-order mark (BOM), with a YAML mapping
41
+ between opening and closing `---` lines, beginning on the first line. The remaining
42
+ text is an unrestricted Markdown body. Duplicate YAML keys are invalid.
43
+
44
+ Only `reference_id` and `content_type` are required; both must be nonblank strings.
45
+ All other known fields are optional and may be null:
46
+
47
+ - Bibliography: `title`, `authors`, `journal`, `year`, `doi`, `keywords`, and
48
+ `publication_types`.
49
+ - Extraction stamps: `extractor_version`, `html_full_text_version`,
50
+ `xml_extraction_version`, `url_source_version`, `svg_extraction_version`, and
51
+ `absent_content_version`.
52
+ - Publication status: `is_preprint` and `peer_review_status`.
53
+ - Full-text metadata: `full_text_attempted`, `full_text_declined`,
54
+ `full_text_provider`, `full_text_url`, `oa_status`, `license`, `local_pdf_path`,
55
+ `full_text_access_type`, and `full_text_source_item_id`.
56
+ - Captured fields and attachments: `extra_fields_captured` and
57
+ `supplementary_files`.
58
+
59
+ Known fields are checked without coercion. Extraction stamps and supplementary
60
+ file sizes are integers (booleans do not count); `is_preprint` and
61
+ `full_text_attempted` are booleans. Authors, keywords, publication types, and
62
+ captured field names are lists of strings. `year` accepts a string or an integer
63
+ for compatibility with older unquoted YAML years. Other scalar metadata is text.
64
+ Each supplementary file requires a nonempty string `filename`; its optional fields
65
+ are `download_url`, `content_type`, `size_bytes`, `checksum`, `description`, and
66
+ `local_path`. Older cache files may contain unquoted numeric values in fields
67
+ such as `checksum` that require strings. These are reported as `invalid_field`;
68
+ quote the intended text to repair them. The lenient fetcher also recovers numeric
69
+ and boolean scalars in string-valued metadata fields (including attachment
70
+ metadata) by converting their parsed values to strings. The field types come
71
+ from the public model; reference IDs, version stamps, booleans, lists, and
72
+ unknown extension fields are not coerced.
73
+ A cache rewrite then quotes these strings and retains consumer extensions.
74
+ This compatibility recovery applies only to disk reads, not source plugin output
75
+ or the public validator. It cannot reconstruct leading zeros or original
76
+ scientific notation already lost during YAML parsing; check the source when the
77
+ exact spelling matters.
78
+
79
+ **Unknown keys are allowed and preserved**, both in the header and in
80
+ supplementary-file metadata. No extension namespace is required: a consumer's
81
+ `database: Orphanet` is valid. String vocabularies such as `content_type`,
82
+ `peer_review_status`, and `full_text_access_type` are open, so future values do not
83
+ break older consumers. Extraction stamps may be absent, old, or newer than this
84
+ library; format validity does not imply freshness.
85
+
86
+ When rewriting an existing cache with a valid header for the same reference,
87
+ the fetcher carries over unknown fields, including explicit null values. Unknown
88
+ fields are retained as-is, not recalculated or certified current on refresh;
89
+ this also applies to fields written by a newer version of the library. Known
90
+ fields always come from the new reference. Attachment extensions follow filenames
91
+ that are unique in both the old and new attachment lists; removed or ambiguous
92
+ attachments do not transfer their extensions. Preservation reads only the file
93
+ being overwritten, so metadata is not copied between public and private caches
94
+ or between different references whose sanitized filenames collide. A damaged
95
+ header that remains invalid after legacy scalar recovery cannot supply validated
96
+ extensions: the fetcher logs a warning and permits
97
+ a fresh fetch to repair the file.
98
+
99
+ The writer omits empty optional attachment strings, retaining the historical
100
+ serialization policy. Invalid known metadata supplied by a source or carried
101
+ through a lenient read raises `pydantic.ValidationError` before a cache write;
102
+ `fetch()` propagates that error. An existing cache file is left intact. This
103
+ makes a source's type-contract error visible instead of silently coercing values
104
+ or reporting a successful cache write that never happened.
105
+
106
+ `CacheFrontmatter` is a public Pydantic model released with the library. The writer
107
+ serializes this same model. To inspect the complete, machine-readable contract:
108
+
109
+ ```python
110
+ schema = CacheFrontmatter.model_json_schema()
111
+ header = CacheFrontmatter.model_validate({
112
+ "reference_id": "PMID:12345",
113
+ "content_type": "abstract_only",
114
+ "database": "Orphanet",
115
+ })
116
+ assert header.model_dump()["database"] == "Orphanet"
117
+ ```
118
+
119
+ The checker derives the filename from the stored `reference_id` by replacing
120
+ `:`, `/`, `?`, and `=` with `_` and appending `.md`. DOI filenames are compared
121
+ case-insensitively, matching the fetcher's existing behavior. Other filenames must
122
+ match exactly. This uses the ID already stored in the file, without applying
123
+ configured prefix aliases or requiring a currently registered reference source.
124
+
125
+ ## Findings and local policy
126
+
127
+ Each `CacheFinding` has a stable `code`, a human-readable `message`, and a `field`
128
+ when applicable. Field locations use dotted paths, for example `authors.0` or
129
+ `supplementary_files.0.size_bytes`.
130
+
131
+ | Code | Meaning |
132
+ | --- | --- |
133
+ | `read_error` | The file cannot be read or is not valid UTF-8. |
134
+ | `invalid_frontmatter` | Delimiters are missing/misplaced or the YAML is not a mapping. |
135
+ | `invalid_yaml` | YAML parsing failed, including duplicate keys. |
136
+ | `invalid_field` | A required field is absent or a known field has an invalid type/value. |
137
+ | `filename_mismatch` | The filename does not match the stored reference ID. |
138
+
139
+ Apply local requirements after `result.is_valid`, using `result.frontmatter`.
140
+ Requiring authors or a journal for particular reference sources, for example,
141
+ is a consumer policy rather than a format rule. The ordinary fetcher's reader
142
+ remains lenient for recovering old cache data; the checker reports malformed
143
+ known fields instead of silently recovering them. In particular, the reader
144
+ accepts text before the opening frontmatter delimiter; the checker requires the
145
+ opening delimiter on the first line.
146
+
147
+ Validation cannot prove that a file was never hand-edited, that its text agrees
148
+ with the publication, or that its bibliography is complete. A well-formed edit
149
+ still passes; use version-control review or an external integrity record to
150
+ track changes when needed.
@@ -902,6 +902,51 @@ rather than as `unavailable`, so it falls outside this stamp and is not
902
902
  re-tested. Clear such entries by hand if you were running that setting before
903
903
  this version.
904
904
 
905
+ `url:` entries carry their own stamp, `url_source_version`. Version 1 is
906
+ two URLSource changes: HTML is sanitized before caching, so page scripts,
907
+ comments and attributes stop reaching the cache (issue #92), and a PDF's title
908
+ is recovered from a publisher landing page or its embedded metadata rather than
909
+ set to its URL (issue #93). Version 2 caches an HTML page as readable text
910
+ rather than markup, so a quote that runs through a link or bold text matches
911
+ (issue #102). Every quote that matched the version 1 markup still matches the
912
+ text, provided the page itself has not changed. A `url:` entry with a missing or older stamp is
913
+ re-fetched once on the next validation fetch. If the page cannot be reached,
914
+ the old entry is still served and is not rewritten, so a later run retries.
915
+ Other sources are unaffected. With `trust_cached_entries` set, old entries are
916
+ served as they are and are not refreshed.
917
+
918
+ `file:` references to `.svg` figures carry their own stamp,
919
+ `svg_extraction_version`. Version 1 reads a figure as the text it renders
920
+ (issue #104); before it, the file was cached as raw markup, so a quote could
921
+ match a `<desc>`, an attribute value or hidden text. Such an entry is re-read
922
+ once on the next validation fetch. **A quote that only matched the markup will
923
+ then fail**, which is the point: cite text the figure actually shows. Unlike
924
+ other stale entries, an old SVG entry is never served in place of a fresh
925
+ read: not when the file has moved, not when the new read finds no rendered
926
+ text, and not with `trust_cached_entries`. The file is the source, and its raw
927
+ markup is not evidence. Version 2 separates a figure's labels, so a quote is
928
+ matched within one label; version 1 entries are re-read once for it.
929
+
930
+ **A quote that verified against a `url:` page can fail after that refresh.**
931
+ Sanitizing removes `<noscript>` and `<template>` elements with their text.
932
+ Some publisher pages put a fallback copy of the abstract inside `<noscript>`,
933
+ for readers without JavaScript. A quote that only ever matched that copy will
934
+ stop matching once the entry is re-fetched. If the same text appears elsewhere
935
+ on the page, nothing changes. If it does not, cite a source that carries it,
936
+ such as the `PMID:` or `DOI:` of the article.
937
+
938
+ **The version 2 refresh rewrites every cached HTML page.** Each `url:` HTML
939
+ entry changes from one line of markup to many lines of text, so expect a large
940
+ diff in a committed cache. The refresh fetches each page again, so a page that
941
+ has changed or gone since it was cached can stop supporting a quote; an
942
+ unreachable page keeps its old entry, as above. To keep that diff reviewable,
943
+ refresh in a commit of its own: right after upgrading, run your usual
944
+ validation on an otherwise unchanged checkout and commit only the rewritten
945
+ cache files, before any curation changes. Entries are refreshed only as a
946
+ validation uses them (see
947
+ [Cached references re-fetched after upgrading](#cached-references-re-fetched-after-upgrading)),
948
+ so a page no validation cites keeps its old entry until one does.
949
+
905
950
  **A cache-wide refresh adds one line to every enriched entry.** Both index
906
951
  providers now set `access_type` where they previously left it unset, so a
907
952
  refreshed public entry gains a `full_text_access_type: open` line. It means the
@@ -52,6 +52,7 @@ nav:
52
52
  - Editorial Conventions: concepts/editorial-conventions.md
53
53
  - Reference:
54
54
  - CLI Reference: reference/cli.md
55
+ - Cache Format and Validation: reference/cache-format.md
55
56
  - Supporting Text Splitting: reference/splitting.md
56
57
  - Troubleshooting: troubleshooting.md
57
58
  - Roadmap: todo.md
@@ -19,7 +19,7 @@ dependencies = [
19
19
  "linkml-runtime >=1.9.4",
20
20
  "biopython >= 1.80",
21
21
  "beautifulsoup4 >= 4.9.0",
22
- "lxml >= 4.9.0",
22
+ "lxml >= 5.0.0",
23
23
  "requests >= 2.28.0",
24
24
  "pydantic >= 2.0.0",
25
25
  "ruamel-yaml >= 0.18.0",
@@ -0,0 +1,23 @@
1
+ try:
2
+ from linkml_reference_validator._version import __version__, __version_tuple__
3
+ except ImportError: # pragma: no cover
4
+ __version__ = "0.0.0"
5
+ __version_tuple__ = (0, 0, 0)
6
+
7
+ from linkml_reference_validator.cache import (
8
+ CacheFinding,
9
+ CacheFrontmatter,
10
+ CacheSupplementaryFile,
11
+ CacheValidationResult,
12
+ scan_cache_dir,
13
+ validate_cache_file,
14
+ )
15
+
16
+ __all__ = [
17
+ "CacheFinding",
18
+ "CacheFrontmatter",
19
+ "CacheSupplementaryFile",
20
+ "CacheValidationResult",
21
+ "scan_cache_dir",
22
+ "validate_cache_file",
23
+ ]