linkml-reference-validator 0.2.1rc2__tar.gz → 0.3.0__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 (239) hide show
  1. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/PKG-INFO +2 -2
  2. linkml_reference_validator-0.3.0/docs/assets/zotero-find-full-text.png +0 -0
  3. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/concepts/editorial-conventions.md +9 -4
  4. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/concepts/how-it-works.md +45 -0
  5. linkml_reference_validator-0.3.0/docs/how-to/export-cache-to-zotero.md +114 -0
  6. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/fetch-full-text-and-pdfs.md +145 -0
  7. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/repair-validation-errors.md +43 -1
  8. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/use-local-files-and-urls.md +2 -2
  9. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/validate-clinical-trials.md +4 -0
  10. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/validate-urls.md +47 -15
  11. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/reference/cli.md +144 -24
  12. linkml_reference_validator-0.3.0/docs/reference/splitting.md +69 -0
  13. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/setup-guide.md +4 -0
  14. linkml_reference_validator-0.3.0/docs/superpowers/plans/2026-08-05-private-library-full-text.md +190 -0
  15. linkml_reference_validator-0.3.0/docs/superpowers/specs/2026-08-05-private-library-full-text-design.md +309 -0
  16. linkml_reference_validator-0.3.0/docs/troubleshooting.md +986 -0
  17. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/tutorials/complete-workflow.md +1 -1
  18. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/mkdocs.yml +2 -0
  19. linkml_reference_validator-0.3.0/src/linkml_reference_validator/cli/cache.py +410 -0
  20. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/cli/lookup.py +3 -0
  21. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/cli/repair.py +66 -12
  22. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/cli/validate.py +53 -21
  23. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/__init__.py +12 -0
  24. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/acquire.py +80 -1
  25. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/csl_export.py +73 -0
  26. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/extract/__init__.py +31 -0
  27. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/extract/base.py +11 -3
  28. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/extract/html.py +287 -0
  29. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/extract/pdf.py +136 -0
  30. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/extract/xml.py +253 -0
  31. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/fulltext/__init__.py +2 -0
  32. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/fulltext/base.py +200 -0
  33. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/fulltext/openalex.py +106 -0
  34. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/fulltext/pmc.py +198 -0
  35. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/fulltext/unpaywall.py +25 -9
  36. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/fulltext/zotero.py +252 -0
  37. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/identifiers.py +19 -0
  38. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/reference_fetcher.py +1941 -0
  39. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/rules.py +195 -0
  40. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/sources/pmid.py +594 -0
  41. linkml_reference_validator-0.3.0/src/linkml_reference_validator/etl/sources/url.py +273 -0
  42. linkml_reference_validator-0.3.0/src/linkml_reference_validator/matching.py +75 -0
  43. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/models.py +149 -18
  44. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/plugins/reference_validation_plugin.py +50 -1
  45. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/validation/repairer.py +83 -1
  46. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/validation/supporting_text_validator.py +191 -21
  47. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/GEO_GSE12345.md +1 -0
  48. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMC_TEST001.md +1 -0
  49. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMC_TEST002.md +2 -0
  50. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMID_TEST001.md +1 -0
  51. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMID_TEST002.md +1 -0
  52. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMID_TEST003.md +2 -0
  53. linkml_reference_validator-0.2.1rc2/tests/fixtures/CLINICALTRIALS_NCT00000001.md → linkml_reference_validator-0.3.0/tests/fixtures/clinicaltrials_NCT00000001.md +1 -0
  54. linkml_reference_validator-0.3.0/tests/fixtures/fulltext_html/README.md +22 -0
  55. linkml_reference_validator-0.3.0/tests/fixtures/fulltext_html/dspace-32894695.html +1 -0
  56. linkml_reference_validator-0.3.0/tests/fixtures/fulltext_html/iris-19049553.html +359 -0
  57. linkml_reference_validator-0.3.0/tests/fixtures/fulltext_html/plos-0000308.html +78 -0
  58. linkml_reference_validator-0.3.0/tests/fixtures/jats/PMC5593426.xml +5 -0
  59. linkml_reference_validator-0.3.0/tests/fixtures/jats/README.md +21 -0
  60. linkml_reference_validator-0.3.0/tests/test_absent_abstract_recovery.py +140 -0
  61. linkml_reference_validator-0.3.0/tests/test_acquire.py +224 -0
  62. linkml_reference_validator-0.3.0/tests/test_cache_author_recovery.py +102 -0
  63. linkml_reference_validator-0.3.0/tests/test_cli_cache_enrich.py +227 -0
  64. linkml_reference_validator-0.3.0/tests/test_cli_cache_export.py +246 -0
  65. linkml_reference_validator-0.3.0/tests/test_cli_cache_reference.py +271 -0
  66. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_cli_repair.py +134 -0
  67. linkml_reference_validator-0.3.0/tests/test_clinicaltrials_cache.py +234 -0
  68. linkml_reference_validator-0.3.0/tests/test_empty_snippets.py +794 -0
  69. linkml_reference_validator-0.3.0/tests/test_extractor_fidelity.py +656 -0
  70. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_extractors.py +13 -0
  71. linkml_reference_validator-0.3.0/tests/test_frontmatter_and_doi_case.py +334 -0
  72. linkml_reference_validator-0.3.0/tests/test_fulltext_providers.py +356 -0
  73. linkml_reference_validator-0.3.0/tests/test_html_fulltext_acceptance.py +432 -0
  74. linkml_reference_validator-0.3.0/tests/test_jats_tables.py +346 -0
  75. linkml_reference_validator-0.3.0/tests/test_ligature_normalization.py +91 -0
  76. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_models.py +13 -0
  77. linkml_reference_validator-0.3.0/tests/test_no_cache_downgrade.py +540 -0
  78. linkml_reference_validator-0.3.0/tests/test_open_access_policy.py +686 -0
  79. linkml_reference_validator-0.3.0/tests/test_pdf_url_title.py +228 -0
  80. linkml_reference_validator-0.3.0/tests/test_pmid_network.py +327 -0
  81. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_reference_fetcher.py +700 -3
  82. linkml_reference_validator-0.3.0/tests/test_rules.py +129 -0
  83. linkml_reference_validator-0.3.0/tests/test_serve_stale_html_optin.py +134 -0
  84. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_sources.py +300 -0
  85. linkml_reference_validator-0.3.0/tests/test_split_supporting_text.py +75 -0
  86. linkml_reference_validator-0.3.0/tests/test_trust_cached_entries.py +81 -0
  87. linkml_reference_validator-0.3.0/tests/test_url_html_sanitize.py +161 -0
  88. linkml_reference_validator-0.3.0/tests/test_url_source_cache_version.py +141 -0
  89. linkml_reference_validator-0.3.0/tests/test_validation_counts.py +407 -0
  90. linkml_reference_validator-0.3.0/tests/test_zotero_provider.py +277 -0
  91. linkml_reference_validator-0.2.1rc2/docs/troubleshooting.md +0 -706
  92. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/cli/cache.py +0 -143
  93. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/__init__.py +0 -9
  94. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/__init__.py +0 -16
  95. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/html.py +0 -46
  96. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/pdf.py +0 -67
  97. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/extract/xml.py +0 -46
  98. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/base.py +0 -70
  99. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/openalex.py +0 -70
  100. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/fulltext/pmc.py +0 -124
  101. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/reference_fetcher.py +0 -786
  102. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/sources/pmid.py +0 -390
  103. linkml_reference_validator-0.2.1rc2/src/linkml_reference_validator/etl/sources/url.py +0 -148
  104. linkml_reference_validator-0.2.1rc2/tests/test_acquire.py +0 -116
  105. linkml_reference_validator-0.2.1rc2/tests/test_fulltext_providers.py +0 -175
  106. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.claude/.mcp.json +0 -0
  107. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.claude/settings.json +0 -0
  108. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.copier-answers.yml +0 -0
  109. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.devcontainer/devcontainer.json +0 -0
  110. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.devcontainer/post_create.sh +0 -0
  111. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.editorconfig +0 -0
  112. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/actions/claude-code-action/action.yml +0 -0
  113. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/actions/claude-issue-summarize-action/action.yml +0 -0
  114. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/actions/claude-issue-triage-action/action.yml +0 -0
  115. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/ai-controllers.json +0 -0
  116. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/copilot-instructions.md +0 -0
  117. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/copilot-setup-steps.yml +0 -0
  118. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/dependabot.yml +0 -0
  119. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/claude-code-review.yml +0 -0
  120. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/claude-issue-summarize.yml +0 -0
  121. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/claude-issue-triage.yml +0 -0
  122. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/claude-ontology-review.yml +0 -0
  123. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/claude.yml +0 -0
  124. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/deploy-docs.yaml +0 -0
  125. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/dragon-ai.yml +0 -0
  126. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/main.yaml +0 -0
  127. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.github/workflows/pypi-publish.yaml +0 -0
  128. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.gitignore +0 -0
  129. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.goosehints +0 -0
  130. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.pre-commit-config.yaml +0 -0
  131. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/.yamllint.yaml +0 -0
  132. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/AGENTS.md +0 -0
  133. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/BUG_GEO_ACCESSION_TO_UID.md +0 -0
  134. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/BUG_ZENODO_DOI_AND_URL_HANDLING.md +0 -0
  135. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/CLAUDE.md +0 -0
  136. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/CLI-ISSUES.md +0 -0
  137. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  138. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/CONTRIBUTING.md +0 -0
  139. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/FEATURE_REQUEST_REPAIR.md +0 -0
  140. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/LICENSE +0 -0
  141. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/README.md +0 -0
  142. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/REVIEW2.md +0 -0
  143. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/REVIEW3.md +0 -0
  144. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/ai.just +0 -0
  145. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/copilot-instructions.md +0 -0
  146. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/about.md +0 -0
  147. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/concepts/content-types.md +0 -0
  148. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/elements/.gitkeep +0 -0
  149. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/add-reference-source.md +0 -0
  150. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/skip-unsupported-references.md +0 -0
  151. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/validate-dois.md +0 -0
  152. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/validate-entrez.md +0 -0
  153. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/validate-obo-files.md +0 -0
  154. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/how-to/validate-titles.md +0 -0
  155. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/index.md +0 -0
  156. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/notebooks/01_getting_started.ipynb +0 -0
  157. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/notebooks/02_advanced_usage.ipynb +0 -0
  158. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/notebooks/03_python_api.ipynb +0 -0
  159. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/notebooks/04_obo_validation.ipynb +0 -0
  160. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/notebooks/05_geo_validation.ipynb +0 -0
  161. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/quickstart.md +0 -0
  162. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/superpowers/plans/2026-06-12-modular-pdf-fulltext-fetching.md +0 -0
  163. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/superpowers/specs/2026-06-12-modular-pdf-fulltext-fetching-design.md +0 -0
  164. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/templates-linkml/README.md +0 -0
  165. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/docs/todo.md +0 -0
  166. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/examples/README.md +0 -0
  167. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/justfile +0 -0
  168. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/mypy.ini +0 -0
  169. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/notebooks/01_getting_started.ipynb +0 -0
  170. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/notebooks/02_advanced_usage.ipynb +0 -0
  171. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/notebooks/03_python_api.ipynb +0 -0
  172. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/notebooks/README.md +0 -0
  173. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/project/README.md +0 -0
  174. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/project.justfile +0 -0
  175. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/pyproject.toml +0 -0
  176. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/pytest.ini +0 -0
  177. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/__init__.py +0 -0
  178. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/_version.py +0 -0
  179. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/cli/__init__.py +0 -0
  180. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/cli/shared.py +0 -0
  181. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/fulltext/epmc_preprint.py +0 -0
  182. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/fulltext/json_api.py +0 -0
  183. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/fulltext/loader.py +0 -0
  184. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/__init__.py +0 -0
  185. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/base.py +0 -0
  186. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/clinicaltrials.py +0 -0
  187. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/doi.py +0 -0
  188. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/entrez.py +0 -0
  189. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/file.py +0 -0
  190. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/json_api.py +0 -0
  191. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/loader.py +0 -0
  192. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/ppr.py +0 -0
  193. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/sources/utils.py +0 -0
  194. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/etl/text_extractor.py +0 -0
  195. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/field_detection.py +0 -0
  196. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/plugins/REVIEW.md +0 -0
  197. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/plugins/__init__.py +0 -0
  198. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/validation/__init__.py +0 -0
  199. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/src/linkml_reference_validator/validation/fuzzy_text_utils.py +0 -0
  200. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/__init__.py +0 -0
  201. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/conftest.py +0 -0
  202. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/data/test_data_deep_nesting_invalid.yaml +0 -0
  203. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/data/test_data_deep_nesting_valid.yaml +0 -0
  204. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/data/test_data_invalid.yaml +0 -0
  205. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/data/test_data_valid.yaml +0 -0
  206. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/data/test_schema.yaml +0 -0
  207. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/data/test_schema_deep_nesting.yaml +0 -0
  208. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMC_TEST001.txt +0 -0
  209. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMC_TEST002.txt +0 -0
  210. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMID_TEST001.txt +0 -0
  211. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMID_TEST002.txt +0 -0
  212. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/PMID_TEST003.txt +0 -0
  213. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/fixtures/sample.obo +0 -0
  214. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_cli.py +0 -0
  215. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_cli_lookup.py +0 -0
  216. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_clinicaltrials_integration.py +0 -0
  217. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_deep_nesting.py +0 -0
  218. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_doi_preprint.py +0 -0
  219. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_e2e_integration.py +0 -0
  220. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_epmc_preprint_provider.py +0 -0
  221. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_field_detection.py +0 -0
  222. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_fulltext_loader.py +0 -0
  223. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_fuzzy_suggestions.py +0 -0
  224. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_geo_integration.py +0 -0
  225. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_identifiers.py +0 -0
  226. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_json_api_source.py +0 -0
  227. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_optional_linkml_dependency.py +0 -0
  228. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_plugin_integration.py +0 -0
  229. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_pmc_fulltext.py +0 -0
  230. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_ppr_source.py +0 -0
  231. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_real_validation.py +0 -0
  232. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_repair.py +0 -0
  233. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_source_utils.py +0 -0
  234. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_supporting_text_validator.py +0 -0
  235. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_text_extractor.py +0 -0
  236. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_title_validation.py +0 -0
  237. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_unknown_slot_traversal.py +0 -0
  238. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/tests/test_validation_config.py +0 -0
  239. {linkml_reference_validator-0.2.1rc2 → linkml_reference_validator-0.3.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: linkml_reference_validator
3
- Version: 0.2.1rc2
3
+ Version: 0.3.0
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
@@ -248,7 +248,7 @@ Editorial conventions work the same in data files:
248
248
 
249
249
  ### Normalization Order
250
250
 
251
- 1. Remove everything inside `[...]`
251
+ 1. Remove editorial `[...]` content unless a configured literal pattern matches
252
252
  2. Split on `...` if present
253
253
  3. For each part:
254
254
  - Lowercase
@@ -258,10 +258,15 @@ Editorial conventions work the same in data files:
258
258
 
259
259
  ### Regular Expressions
260
260
 
261
- Brackets: `\[([^\]]*)\]` (removes content and brackets)
262
- Ellipsis: `\.\.\.` (splits on three dots exactly)
261
+ Brackets: `\[.*?\]` (single-line, non-nested matching; literal patterns can preserve matches)
263
262
 
264
- Note: `..` (two dots) and `....` (four dots) are NOT treated as ellipsis.
263
+ Ellipsis: `\s*\.{2,}\s*` (splits on two or more ASCII periods)
264
+
265
+ Both `..` (two dots) and `....` (four dots) are treated as separators. The Unicode
266
+ ellipsis `…` is not a separator.
267
+
268
+ Python callers can use the public [supporting text splitting helper](../reference/splitting.md)
269
+ without constructing a validator.
265
270
 
266
271
  ## See Also
267
272
 
@@ -36,6 +36,51 @@ reference_content = "...The MUC1 oncoprotein blocks nuclear..."
36
36
  # Match: "muc1 oncoprotein" found in normalized reference
37
37
  ```
38
38
 
39
+ **Empty excerpts are rejected:**
40
+
41
+ An empty or whitespace-only excerpt is always an error, never a pass. The
42
+ empty string is a substring of every document, so a blank excerpt would
43
+ otherwise be reported as verified evidence while quoting nothing at all:
44
+
45
+ ```python
46
+ supporting_text = "" # or " ", "\n"
47
+ # ERROR: Supporting text is empty
48
+ ```
49
+
50
+ The check runs before the reference is fetched, and applies even when the
51
+ reference prefix is on the [skip list](../how-to/skip-unsupported-references.md):
52
+ a blank excerpt is a defect in the data regardless of what it cites. An
53
+ excerpt slot that is *absent* is a different matter — whether it is required
54
+ is up to your schema.
55
+
56
+ **Minimum excerpt length (optional):**
57
+
58
+ A blank excerpt is the extreme case. A two-character one is barely better: a
59
+ short string matches almost any paper by chance, so the match succeeds without
60
+ the excerpt being real evidence. Set `min_excerpt_length` to require a minimum:
61
+
62
+ ```yaml
63
+ # .linkml-reference-validator.yaml
64
+ validation:
65
+ min_excerpt_length: 20
66
+ ```
67
+
68
+ Keep it under `validation:`. A config file that has a `validation:` section
69
+ ignores validation keys placed at the top level — silently, which for an
70
+ opt-in check means it looks enabled while never running.
71
+
72
+ The check is off by default (`0`), so existing data keeps validating as before.
73
+ Length is measured in **non-whitespace characters of quoted text**:
74
+
75
+ - Whitespace is ignored, so PDF-to-text extraction damage does not change the
76
+ count — `"t he pro tein"` and `"the protein"` both measure 10.
77
+ - No tokenizing, so chemical nomenclature and other punctuation-dense strings
78
+ are counted in full — `"2,3-dihydroxybenzoate"` measures 21, not 2 words.
79
+ - Editorial `[brackets]` and `...` separators do not count, since they are
80
+ never matched against the reference.
81
+
82
+ Like the blank check, this runs before the reference is fetched.
83
+
39
84
  ### 3. Ellipsis Handling
40
85
 
41
86
  When supporting text contains `...`, each part is matched separately:
@@ -0,0 +1,114 @@
1
+ # Export a Reference Cache to Zotero
2
+
3
+ This workflow turns a checked-in public reference cache into a Zotero collection,
4
+ asks Zotero to find available PDFs, and then inventories the resulting attachments
5
+ without putting closed manuscripts into the project repository.
6
+
7
+ The three stores remain separate:
8
+
9
+ | Store | Purpose | Safe to commit? |
10
+ |---|---|---:|
11
+ | Project `references_cache` | Reproducible public validation evidence | Yes |
12
+ | Zotero library | User-managed references and PDF attachments | No |
13
+ | Private research cache | Extracted user-library text for agent context | No |
14
+
15
+ Normal validation reads only the first store.
16
+
17
+ ## 1. Export metadata from the public cache
18
+
19
+ Run the exporter from the `linkml-reference-validator` checkout. For dismech:
20
+
21
+ ```bash
22
+ uv run linkml-reference-validator cache export \
23
+ --cache-dir /Users/cjm/repos/dismech/references_cache \
24
+ --needs-full-text \
25
+ --output /Users/cjm/Downloads/dismech-zotero.json
26
+ ```
27
+
28
+ `--needs-full-text` is the default. It excludes cache entries that already have
29
+ full text, excludes non-publication identifiers, and deduplicates by normalized
30
+ DOI and then PMID. The CSL JSON output uses an explicit metadata allowlist:
31
+
32
+ - title and authors;
33
+ - journal and year;
34
+ - DOI; or
35
+ - PMID and its public PubMed URL.
36
+
37
+ It never contains cached article text, excerpts, provenance, PDFs, local paths,
38
+ or private-cache data. The command refuses to replace an existing output file;
39
+ use `--force` only after checking the destination. Use `--all` if you explicitly
40
+ want eligible references that already contain full text.
41
+
42
+ ## 2. Import the file into Zotero
43
+
44
+ In Zotero:
45
+
46
+ 1. Choose **File → Import**.
47
+ 2. Choose **A file**.
48
+ 3. Select `dismech-zotero.json`.
49
+ 4. Keep the imported records in a project-specific collection such as
50
+ **dismech**.
51
+
52
+ CSL JSON is a standard format supported by Zotero. Importing the file adds only
53
+ bibliographic records; it does not copy anything from the validator's caches.
54
+
55
+ ## 3. Ask Zotero to find PDFs
56
+
57
+ Select one or more top-level journal articles in the center item list,
58
+ right-click the selection, and choose **Find Full Text**. It is a built-in Zotero
59
+ command, not a plugin.
60
+
61
+ ![Zotero Find Full Text processing a batch of imported references](../assets/zotero-find-full-text.png)
62
+
63
+ The command appears for an eligible parent item that has a DOI and does not
64
+ already have a file attachment. It may also be unavailable in a group library
65
+ where files cannot be added. If it is absent, first test a single article in
66
+ your personal library and confirm that its DOI field is populated.
67
+
68
+ For a large import, start with a modest batch rather than all records at once.
69
+ Publishers can temporarily rate-limit repeated requests, and a later retry may
70
+ find additional files. Zotero reports **Full Text PDF** or **No file found** for
71
+ each attempted item.
72
+
73
+ ## 4. Inventory the enriched Zotero library
74
+
75
+ Once Zotero finishes, scan the project cache again:
76
+
77
+ ```bash
78
+ uv run linkml-reference-validator cache enrich \
79
+ --provider zotero \
80
+ --cache-dir /Users/cjm/repos/dismech/references_cache \
81
+ --dry-run
82
+ ```
83
+
84
+ This read-only scan matches exact DOI, PMID, or PMCID identifiers and reports
85
+ which cached references now have usable Zotero text or PDF attachments. It does
86
+ not invoke **Find Full Text** and does not modify Zotero.
87
+
88
+ After reviewing the matches, materialize them into the separate private research
89
+ cache:
90
+
91
+ ```bash
92
+ uv run linkml-reference-validator cache enrich \
93
+ --provider zotero \
94
+ --cache-dir /Users/cjm/repos/dismech/references_cache \
95
+ --apply
96
+ ```
97
+
98
+ Apply mode leaves `references_cache` unchanged. Private content goes to
99
+ `~/.cache/linkml-reference-validator/private` by default with owner-only
100
+ permissions. Agents may inspect it for context, but validation cannot use it as
101
+ evidence.
102
+
103
+ ## 5. Validate reproducibly
104
+
105
+ Run validation normally. Results remain reproducible because the validator
106
+ reads the public project cache and accepts only public full-text provider results.
107
+ Neither the contents of Zotero nor the machine-specific private cache affect
108
+ validation.
109
+
110
+ ## See also
111
+
112
+ - [Fetching Full Text and PDFs](fetch-full-text-and-pdfs.md)
113
+ - [Zotero: importing standardized formats](https://www.zotero.org/support/kb/importing_standardized_formats)
114
+ - [Zotero: adding files to a library](https://www.zotero.org/support/attaching_files)
@@ -46,9 +46,11 @@ your config YAML or on the `ReferenceValidationConfig` object):
46
46
  |-----|---------|-------------|
47
47
  | `fetch_full_text` | `true` | Attempt to obtain full text via the provider chain when a metadata source does not already return full text. |
48
48
  | `full_text_providers` | `[pmc, epmc_preprint, unpaywall, openalex]` | Ordered list of provider names to try until one yields usable full text. |
49
+ | `private_cache_dir` | `~/.cache/linkml-reference-validator/private` | Separate research cache for closed/user-library full text; never read by validation. |
49
50
  | `pdf_backend` | `pypdf` | Name of the PDF text-extraction backend. |
50
51
  | `download_pdfs` | `true` | If true, persist downloaded PDFs to the files cache directory. |
51
52
  | `full_text_providers_file` | `null` | Optional path to a YAML file defining custom full-text providers. |
53
+ | `zotero_base_url` | `http://localhost:23119/api/users/0` | Read-only Zotero local API library used by the opt-in `zotero` provider. |
52
54
 
53
55
  Two existing keys are also reused by the full-text machinery:
54
56
 
@@ -71,6 +73,106 @@ pdf_backend: pypdf
71
73
  download_pdfs: true
72
74
  ```
73
75
 
76
+ ## Private manuscripts in Zotero
77
+
78
+ The opt-in **`zotero`** provider can find non-open manuscripts that are already
79
+ in your personal Zotero library. It matches exact DOI, PMID, or PMCID identifiers;
80
+ it does not automatically accept fuzzy title matches.
81
+
82
+ Zotero must be running, and its local API must be enabled in Zotero's advanced
83
+ settings. Inventory the reference cache before changing it:
84
+
85
+ ```bash
86
+ linkml-reference-validator cache enrich \
87
+ --provider zotero \
88
+ --cache-dir references_cache \
89
+ --dry-run
90
+ ```
91
+
92
+ The report distinguishes `found`, `not_found`, `already_full_text`, and `error`.
93
+ Dry-run is the default and never changes cache files. After reviewing exact
94
+ matches, apply usable text with:
95
+
96
+ ```bash
97
+ linkml-reference-validator cache enrich \
98
+ --provider zotero \
99
+ --cache-dir references_cache \
100
+ --apply
101
+ ```
102
+
103
+ Apply mode does **not** modify `references_cache`. It writes the enriched entry
104
+ to a separate research cache at `~/.cache/linkml-reference-validator/private`
105
+ by default. Agents may inspect that cache for background context, but ordinary
106
+ validation never reads it and never cites it as evidence. Validation therefore
107
+ has the same inputs locally, in CI, and on another contributor's machine.
108
+
109
+ To keep the research cache in a private repository or another secured location:
110
+
111
+ ```bash
112
+ linkml-reference-validator cache enrich \
113
+ --provider zotero \
114
+ --cache-dir references_cache \
115
+ --private-cache-dir /path/to/private-reference-cache \
116
+ --apply
117
+ ```
118
+
119
+ The same location can be configured for all commands:
120
+
121
+ ```yaml
122
+ cache_dir: references_cache
123
+ private_cache_dir: /path/to/private-reference-cache
124
+ ```
125
+
126
+ Zotero-indexed text is preferred. If Zotero has not indexed an attachment, the
127
+ provider downloads it from Zotero's local PDF endpoint and sends it through the
128
+ normal PDF extractor. The source attachment key and `user_library` access type
129
+ are persisted, but the ephemeral localhost URL is not.
130
+
131
+ !!! warning "Keep research caches private"
132
+
133
+ Private cache entries contain extracted manuscript text and may contain a
134
+ copied PDF. Do not place this cache in a public repository or share it
135
+ unless you have permission to redistribute that content.
136
+
137
+ Do not add `zotero` to `full_text_providers`. Even if it is configured there,
138
+ ordinary validation rejects its `user_library` result and continues to public
139
+ providers. Use `cache enrich` when you want to search Zotero.
140
+
141
+ Support for reproducible validation backed by private manuscripts is a future,
142
+ separate feature. The proposed design is a trusted offline job that emits a
143
+ minimal signed excerpt attestation into the public cache. The validator would
144
+ verify the signature using a checked-in public key; neither agents nor CI would
145
+ have the signing key or permission to generate those files directly.
146
+
147
+ ## Export a cache to a Zotero collection
148
+
149
+ Export references that still need full text as metadata-only CSL JSON:
150
+
151
+ ```bash
152
+ linkml-reference-validator cache export \
153
+ --cache-dir references_cache \
154
+ --format csl-json \
155
+ --needs-full-text \
156
+ --output project-zotero.json
157
+ ```
158
+
159
+ The default `--needs-full-text` mode excludes cache entries that already contain
160
+ full text. It also excludes non-publication identifiers, deduplicates by
161
+ normalized DOI and then PMID, and exports only an explicit metadata allowlist:
162
+ title, authors, journal, year, DOI, PMID, and PubMed URL. Cached article text,
163
+ excerpts, provenance, PDFs, and local paths are never exported. Use `--all` only
164
+ when you deliberately want eligible records that already have full text.
165
+
166
+ The exporter refuses to replace an existing output file unless `--force` is
167
+ given. Review the reported counts, then in Zotero choose **File → Import → A
168
+ file**, select the JSON file, and place the imported items in a project-specific
169
+ collection. Zotero supports CSL JSON as a standard import format. After import,
170
+ select the collection and run **Find Full Text**, then rerun `cache enrich`.
171
+
172
+ See [Zotero's standardized-format import instructions](https://www.zotero.org/support/kb/importing_standardized_formats).
173
+ For the complete workflow with screenshots, see
174
+ [Export a Reference Cache to Zotero](export-cache-to-zotero.md).
175
+
74
176
  ## Preprints
75
177
 
76
178
  Preprints are first-class references. They are increasingly cited for early
@@ -183,3 +285,46 @@ downloaded PDF lives under the cache directory and can be located at
183
285
  - [Validating DOIs](validate-dois.md) — DOI metadata sources and supplementary files
184
286
  - [Validating Entrez Accessions](validate-entrez.md) — PubMed/PMC references
185
287
  - [CLI Reference](../reference/cli.md) — complete command documentation
288
+
289
+ ## HTML article checks and cache recovery
290
+
291
+ An OpenAlex or Unpaywall URL may lead to a repository item record containing
292
+ only an abstract and metadata. HTML full-text acquisition now requires an
293
+ identifiable research body: publisher body markup or narrative research sections
294
+ with paragraphs, after removing abstracts and navigation. Length and additional
295
+ metadata text alone do not establish full text. This also applies when a URL
296
+ advertised as a PDF actually returns HTML. Genuine HTML research bodies remain
297
+ eligible, including short bodies above the existing 500-character minimum.
298
+
299
+ Ambiguous layouts are rejected and the next configured provider is tried. The
300
+ fetcher does not crawl repository download links. If every provider completes
301
+ without usable text or an error, the reference retains its original abstract
302
+ and records `full_text_attempted: true`.
303
+
304
+ New `full_text_html` cache entries carry `html_full_text_version: 1`. Older
305
+ Markdown HTML full-text entries without this stamp are refreshed on their next
306
+ validation read. This migration covers all providers because cached plain text
307
+ cannot establish which HTML body structure was present. It does not invalidate
308
+ current PDF, XML, or abstract caches.
309
+
310
+ If refreshing an old HTML entry fails, validation does **not** fall back to that
311
+ entry: its text may be repository metadata rather than article evidence. The
312
+ file remains untouched for inventory/export, and the next validation run retries
313
+ recovery. Once the source is available, normal fetching replaces it with a
314
+ verified article or the available metadata/abstract. Other content types retain
315
+ the existing stale-cache fallback. Legacy `.txt` caches remain a separate,
316
+ read-only compatibility path and are not migrated by this HTML stamp.
317
+
318
+ Research headings establish body presence; they do not filter the accepted
319
+ article's other sections. For example, `Main`, `Case presentation`, and
320
+ `Limitations` remain in an accepted article. A separately marked body is also
321
+ preserved when an abstract heading uses a higher heading level. Without that
322
+ structural boundary, an unmarked structured abstract remains ambiguous and is
323
+ rejected conservatively.
324
+
325
+ A provider's `FullTextLocation.text` (including a custom API `text_field`) is a
326
+ trusted **already-extracted article body** contract. Configure it only for APIs
327
+ that return article text, not arbitrary page text or metadata. Plain extracted
328
+ text cannot be structurally rechecked; raw HTML supplied with an HTML hint still
329
+ passes the body classifier. The acceptance stamp records that either the HTML
330
+ check or this trusted provider contract was used.
@@ -13,6 +13,21 @@ After validating your data files, you may find validation errors due to:
13
13
 
14
14
  The `repair` command attempts to fix these issues automatically or flags them for manual review.
15
15
 
16
+ ### Typographic ligatures already match
17
+
18
+ Validation folds Latin compatibility ligatures on both the reference content and
19
+ the quote, including each part separated by `...`. For example, PDF-extracted
20
+ `amyloid fibrils` matches `amyloid fibrils` without a repair. The supported folds
21
+ are `ff → ff`, `fi → fi`, `fl → fl`, `ffi → ffi`, `ffl → ffl`, `ſt/st → st`, and
22
+ `IJ/ij → IJ/ij`. IJ is included because Unicode gives it a compatibility
23
+ decomposition into I and J; ordinary case normalization still applies.
24
+
25
+ The distinct letters `Æ/æ` and `Œ/œ` remain distinct from `AE/ae` and `OE/oe`.
26
+ Equating these letters would accept spelling changes beyond typographic repair.
27
+ Validation uses an explicit ligature table instead of broad Unicode NFKC
28
+ normalization so this change does not equate scientific notation such as `H₂O`
29
+ with `H2O`, `10⁶` with `106`, or the micro sign `µ` with Greek `μ`.
30
+
16
31
  ## Quick Start
17
32
 
18
33
  ### Repair a Single Quote
@@ -112,7 +127,7 @@ Flags text that appears fabricated or hallucinated:
112
127
 
113
128
  ```
114
129
  RECOMMENDED REMOVALS:
115
- PMID:34567890 at evidence[2]:
130
+ PMID:34567890 at evidence[2].supporting_text:
116
131
  Similarity: 8%
117
132
  Snippet: 'This completely made up text...'
118
133
  ```
@@ -290,6 +305,33 @@ The supporting text wasn't found in the reference. Possible causes:
290
305
  - Wrong PMID
291
306
  - AI-generated/hallucinated quote
292
307
 
308
+ ### "Supporting text is empty"
309
+
310
+ The excerpt is empty or whitespace-only, so there is nothing to match against
311
+ the reference. This is usually a bug in whatever generated the data (for
312
+ example, a string slice with a backwards range yielding `""`).
313
+
314
+ Empty excerpts cannot be repaired automatically — there is no text to correct,
315
+ and fuzzy-matching nothing against the reference would invent a quote. They
316
+ appear under **INSUFFICIENT EXCERPTS (nothing to verify)** in the report, so
317
+ you can supply the real quote or drop the evidence item.
318
+
319
+ They are deliberately *not* counted as removals. A removal recommendation means
320
+ "this quote does not match the reference", a verdict reached by comparing the
321
+ two; here no comparison happened. So `removal_count` excludes them and the
322
+ action type is `INSUFFICIENT_EXCERPT`, not `REMOVAL` — worth knowing if you
323
+ script against the report. They do still make `repair data` exit non-zero.
324
+
325
+ ### "Supporting text is too short"
326
+
327
+ The excerpt is below the `min_excerpt_length` you configured (off by default,
328
+ and set under `validation:`; see
329
+ [How It Works](../concepts/how-it-works.md)). A very short excerpt matches
330
+ almost any paper by chance, so it is weak evidence even when the match succeeds.
331
+
332
+ Like empty excerpts, these report under **INSUFFICIENT EXCERPTS** rather than
333
+ being repaired: there is no way to guess which longer passage the curator meant.
334
+
293
335
  ### "Reference content not available"
294
336
 
295
337
  The reference exists but has no retrievable content:
@@ -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
 
@@ -145,6 +145,6 @@ Both file and URL references work in LinkML data files:
145
145
 
146
146
  ## Limitations
147
147
 
148
- - **PDF files**: Not yet supported (planned for future)
148
+ - **Local PDF files**: Not yet supported as `file:` references. PDFs fetched by `url:` are supported.
149
149
  - **Authentication**: URLs requiring login are not supported
150
150
  - **Dynamic content**: JavaScript-rendered pages may not work
@@ -40,6 +40,10 @@ clinicaltrials:NCT00000001
40
40
  CLINICALTRIALS:NCT00000001
41
41
  ```
42
42
 
43
+ These forms normalize to `clinicaltrials:NCT00000001` and share the cache file
44
+ `clinicaltrials_NCT00000001.md`. Existing files with that canonical name are
45
+ reused without renaming or fetching the trial again.
46
+
43
47
  ## Prefix Aliases and Normalization
44
48
 
45
49
  If your data uses alternate prefix styles (e.g., the legacy `NCT:` prefix), configure normalization in `.linkml-reference-validator.yaml`:
@@ -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. Sanitizes HTML and caches the content for future validations
20
20
  4. Validates your supporting text against the page content
21
21
 
22
22
  ## URL Format
@@ -79,14 +79,45 @@ 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**: Sanitized HTML 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 sanitized before it is cached, because caches are often committed to
88
+ public repositories and page scripts routinely carry signed asset URLs and API
89
+ keys. `<script>`, `<style>`, `<noscript>` and `<template>` elements and HTML
90
+ comments are removed. So are `<meta>`, `<link>` and `<base>`, which hold nothing
91
+ but attributes. Every other tag attribute is removed except `rowspan`,
92
+ `colspan` and `scope`, which carry table structure. Body markup and text are
93
+ kept. There is no HTML-to-text conversion, so tag names can still surface
94
+ during normalization. If validation fails because tags interrupt the text,
95
+ consider extracting plain text and validating against a local `file:`
96
+ reference instead.
97
+
98
+ #### Titles
99
+
100
+ For HTML pages the title is the `citation_title` meta tag when present (the
101
+ Highwire / Google Scholar convention, which names the article rather than the
102
+ site), then the `<title>` tag.
103
+
104
+ For PDFs the validator tries, in order:
105
+
106
+ 1. The `citation_title` of the publisher's landing page, where a known rule
107
+ maps the PDF URL to it. Currently J-STAGE: `.../_pdf` becomes
108
+ `.../_article`.
109
+ 2. The PDF's embedded `/Title` metadata, ignoring placeholders such as
110
+ `Microsoft Word - draft.doc`, bare filenames and `Untitled`.
111
+ 3. The URL itself.
112
+
113
+ So a PDF entry whose `title` equals its URL is one for which no title was
114
+ found.
115
+
116
+ The landing-page rules and the placeholder titles are kept in
117
+ `src/linkml_reference_validator/etl/rules.py`, with the other publisher and
118
+ site rules. Each rule sits beside examples of what it must and must not match.
119
+ To support another publisher, add a rule there with at least one example;
120
+ `tests/test_rules.py` fails for a rule without one.
90
121
 
91
122
  ### 3. Caching
92
123
 
@@ -104,9 +135,9 @@ content_type: url
104
135
  ## Content
105
136
 
106
137
  <html>
107
- <head>
108
- <title>Chapter 3: Cell Structure and Function</title>
109
- </head>
138
+ <head>
139
+ <title>Chapter 3: Cell Structure and Function</title>
140
+ </head>
110
141
  ...
111
142
  ```
112
143
 
@@ -145,9 +176,10 @@ URL validation is designed for static web pages. It may not work well with:
145
176
 
146
177
  ### Raw Content
147
178
 
148
- The validator stores raw page content. For HTML pages:
179
+ The validator stores sanitized page markup, not extracted text. For HTML pages:
149
180
 
150
- - HTML tags are preserved in the cache
181
+ - HTML tags are preserved in the cache, without attributes other than
182
+ `rowspan`, `colspan` and `scope`
151
183
  - The text normalization during validation handles most cases
152
184
  - Complex HTML layouts may require careful text extraction
153
185