linkml-reference-validator 0.4.0__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/PKG-INFO +1 -1
  2. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/concepts/how-it-works.md +64 -1
  3. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/fetch-full-text-and-pdfs.md +9 -5
  4. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/repair-validation-errors.md +37 -3
  5. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-titles.md +17 -0
  6. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/reference/cache-format.md +16 -1
  7. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/reference/cli.md +3 -3
  8. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/troubleshooting.md +65 -0
  9. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cache.py +63 -5
  10. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/lookup.py +6 -17
  11. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/repair.py +144 -38
  12. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/html.py +37 -40
  13. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/pdf.py +76 -13
  14. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/svg.py +9 -18
  15. linkml_reference_validator-0.5.1/src/linkml_reference_validator/etl/extract/untrusted_xml.py +57 -0
  16. linkml_reference_validator-0.5.1/src/linkml_reference_validator/etl/extract/xml.py +536 -0
  17. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/json_api.py +7 -1
  18. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/openalex.py +2 -1
  19. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/unpaywall.py +2 -1
  20. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/identifiers.py +23 -0
  21. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/reference_fetcher.py +334 -91
  22. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/doi.py +56 -20
  23. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/file.py +22 -17
  24. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/json_api.py +3 -8
  25. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/pmid.py +94 -1
  26. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/url.py +10 -4
  27. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/utils.py +43 -0
  28. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/field_detection.py +80 -23
  29. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/models.py +84 -8
  30. linkml_reference_validator-0.5.1/src/linkml_reference_validator/plugins/reference_validation_plugin.py +1019 -0
  31. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/validation/fuzzy_text_utils.py +4 -2
  32. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/validation/repairer.py +153 -26
  33. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/validation/supporting_text_validator.py +166 -20
  34. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMC_TEST002.md +1 -1
  35. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST003.md +1 -1
  36. linkml_reference_validator-0.5.1/tests/test_cache_filename_collisions.py +168 -0
  37. linkml_reference_validator-0.5.1/tests/test_cache_read_robustness.py +235 -0
  38. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_cache_validation.py +12 -4
  39. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_cli_cache_reference.py +17 -0
  40. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_doi_preprint.py +4 -4
  41. linkml_reference_validator-0.5.1/tests/test_doi_url_encoding.py +82 -0
  42. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_e2e_integration.py +1 -1
  43. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_empty_snippets.py +2 -2
  44. linkml_reference_validator-0.5.1/tests/test_field_detection_qualifiers.py +207 -0
  45. linkml_reference_validator-0.5.1/tests/test_file_reference_cache.py +131 -0
  46. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_frontmatter_and_doi_case.py +3 -3
  47. linkml_reference_validator-0.5.1/tests/test_greek_variants.py +54 -0
  48. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_html_fulltext_acceptance.py +5 -2
  49. linkml_reference_validator-0.5.1/tests/test_html_scope_text.py +206 -0
  50. linkml_reference_validator-0.5.1/tests/test_inline_abstract_headings.py +127 -0
  51. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_jats_tables.py +15 -8
  52. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_json_api_source.py +6 -6
  53. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_ligature_normalization.py +6 -2
  54. linkml_reference_validator-0.5.1/tests/test_long_cache_filenames.py +115 -0
  55. linkml_reference_validator-0.5.1/tests/test_meaning_symbols.py +121 -0
  56. linkml_reference_validator-0.5.1/tests/test_multivalued_reference.py +248 -0
  57. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_no_cache_downgrade.py +7 -3
  58. linkml_reference_validator-0.5.1/tests/test_plugin_value_shapes.py +348 -0
  59. linkml_reference_validator-0.5.1/tests/test_publisher_typography.py +138 -0
  60. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_reference_fetcher.py +9 -9
  61. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_repair.py +221 -0
  62. linkml_reference_validator-0.5.1/tests/test_repair_data_output.py +315 -0
  63. linkml_reference_validator-0.5.1/tests/test_repair_thresholds.py +243 -0
  64. linkml_reference_validator-0.5.1/tests/test_source_network_errors.py +323 -0
  65. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_sources.py +1 -1
  66. linkml_reference_validator-0.5.1/tests/test_title_only_shapes.py +335 -0
  67. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_title_validation.py +1 -0
  68. linkml_reference_validator-0.5.1/tests/test_welded_markers.py +184 -0
  69. linkml_reference_validator-0.5.1/tests/test_word_boundaries.py +151 -0
  70. linkml_reference_validator-0.5.1/tests/test_xml_extractor_text.py +333 -0
  71. linkml_reference_validator-0.4.0/src/linkml_reference_validator/etl/extract/xml.py +0 -253
  72. linkml_reference_validator-0.4.0/src/linkml_reference_validator/plugins/reference_validation_plugin.py +0 -642
  73. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.claude/.mcp.json +0 -0
  74. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.claude/settings.json +0 -0
  75. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.copier-answers.yml +0 -0
  76. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.devcontainer/devcontainer.json +0 -0
  77. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.devcontainer/post_create.sh +0 -0
  78. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.editorconfig +0 -0
  79. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/actions/claude-code-action/action.yml +0 -0
  80. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/actions/claude-issue-summarize-action/action.yml +0 -0
  81. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/actions/claude-issue-triage-action/action.yml +0 -0
  82. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/ai-controllers.json +0 -0
  83. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/copilot-instructions.md +0 -0
  84. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/copilot-setup-steps.yml +0 -0
  85. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/dependabot.yml +0 -0
  86. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude-code-review.yml +0 -0
  87. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude-issue-summarize.yml +0 -0
  88. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude-issue-triage.yml +0 -0
  89. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude-ontology-review.yml +0 -0
  90. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/claude.yml +0 -0
  91. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/deploy-docs.yaml +0 -0
  92. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/dragon-ai.yml +0 -0
  93. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/main.yaml +0 -0
  94. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.github/workflows/pypi-publish.yaml +0 -0
  95. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.gitignore +0 -0
  96. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.goosehints +0 -0
  97. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.pre-commit-config.yaml +0 -0
  98. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/.yamllint.yaml +0 -0
  99. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/AGENTS.md +0 -0
  100. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/BUG_GEO_ACCESSION_TO_UID.md +0 -0
  101. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/BUG_ZENODO_DOI_AND_URL_HANDLING.md +0 -0
  102. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/CLAUDE.md +0 -0
  103. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/CLI-ISSUES.md +0 -0
  104. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/CODE_OF_CONDUCT.md +0 -0
  105. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/CONTRIBUTING.md +0 -0
  106. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/FEATURE_REQUEST_REPAIR.md +0 -0
  107. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/LICENSE +0 -0
  108. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/README.md +0 -0
  109. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/REVIEW2.md +0 -0
  110. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/REVIEW3.md +0 -0
  111. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/ai.just +0 -0
  112. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/benchmarks/repeated_quotes.py +0 -0
  113. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/copilot-instructions.md +0 -0
  114. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/about.md +0 -0
  115. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/assets/zotero-find-full-text.png +0 -0
  116. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/concepts/content-types.md +0 -0
  117. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/concepts/editorial-conventions.md +0 -0
  118. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/elements/.gitkeep +0 -0
  119. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/add-reference-source.md +0 -0
  120. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/export-cache-to-zotero.md +0 -0
  121. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/skip-unsupported-references.md +0 -0
  122. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/use-local-files-and-urls.md +0 -0
  123. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-clinical-trials.md +0 -0
  124. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-dois.md +0 -0
  125. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-entrez.md +0 -0
  126. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-obo-files.md +0 -0
  127. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/how-to/validate-urls.md +0 -0
  128. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/index.md +0 -0
  129. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/notebooks/01_getting_started.ipynb +0 -0
  130. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/notebooks/02_advanced_usage.ipynb +0 -0
  131. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/notebooks/03_python_api.ipynb +0 -0
  132. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/notebooks/04_obo_validation.ipynb +0 -0
  133. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/notebooks/05_geo_validation.ipynb +0 -0
  134. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/quickstart.md +0 -0
  135. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/reference/splitting.md +0 -0
  136. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/setup-guide.md +0 -0
  137. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/superpowers/plans/2026-06-12-modular-pdf-fulltext-fetching.md +0 -0
  138. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/superpowers/plans/2026-08-05-private-library-full-text.md +0 -0
  139. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/superpowers/specs/2026-06-12-modular-pdf-fulltext-fetching-design.md +0 -0
  140. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/superpowers/specs/2026-08-05-private-library-full-text-design.md +0 -0
  141. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/templates-linkml/README.md +0 -0
  142. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/todo.md +0 -0
  143. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/docs/tutorials/complete-workflow.md +0 -0
  144. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/examples/README.md +0 -0
  145. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/justfile +0 -0
  146. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/mkdocs.yml +0 -0
  147. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/mypy.ini +0 -0
  148. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/notebooks/01_getting_started.ipynb +0 -0
  149. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/notebooks/02_advanced_usage.ipynb +0 -0
  150. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/notebooks/03_python_api.ipynb +0 -0
  151. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/notebooks/README.md +0 -0
  152. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/project/README.md +0 -0
  153. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/project.justfile +0 -0
  154. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/pyproject.toml +0 -0
  155. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/pytest.ini +0 -0
  156. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/skills/linkml-reference-validator/SKILL.md +0 -0
  157. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/skills/linkml-reference-validator/references/configuration.md +0 -0
  158. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/skills/linkml-reference-validator/references/guardrails.md +0 -0
  159. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/__init__.py +0 -0
  160. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/_version.py +0 -0
  161. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/__init__.py +0 -0
  162. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/cache.py +0 -0
  163. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/shared.py +0 -0
  164. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/cli/validate.py +0 -0
  165. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/__init__.py +0 -0
  166. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/acquire.py +0 -0
  167. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/csl_export.py +0 -0
  168. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/__init__.py +0 -0
  169. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/base.py +0 -0
  170. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/drawio.py +0 -0
  171. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/extract/svg_geometry.py +0 -0
  172. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/__init__.py +0 -0
  173. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/base.py +0 -0
  174. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/bioc.py +0 -0
  175. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/bookshelf.py +0 -0
  176. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/epmc.py +0 -0
  177. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/epmc_preprint.py +0 -0
  178. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/loader.py +0 -0
  179. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/pmc.py +0 -0
  180. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/fulltext/zotero.py +0 -0
  181. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/rules.py +0 -0
  182. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/__init__.py +0 -0
  183. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/base.py +0 -0
  184. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/clinicaltrials.py +0 -0
  185. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/entrez.py +0 -0
  186. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/loader.py +0 -0
  187. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/sources/ppr.py +0 -0
  188. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/etl/text_extractor.py +0 -0
  189. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/matching.py +0 -0
  190. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/plugins/REVIEW.md +0 -0
  191. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/plugins/__init__.py +0 -0
  192. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/src/linkml_reference_validator/validation/__init__.py +0 -0
  193. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/__init__.py +0 -0
  194. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/conftest.py +0 -0
  195. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/data/test_data_deep_nesting_invalid.yaml +0 -0
  196. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/data/test_data_deep_nesting_valid.yaml +0 -0
  197. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/data/test_data_invalid.yaml +0 -0
  198. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/data/test_data_valid.yaml +0 -0
  199. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/data/test_schema.yaml +0 -0
  200. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/data/test_schema_deep_nesting.yaml +0 -0
  201. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/GEO_GSE12345.md +0 -0
  202. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMC_TEST001.md +0 -0
  203. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMC_TEST001.txt +0 -0
  204. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMC_TEST002.txt +0 -0
  205. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST001.md +0 -0
  206. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST001.txt +0 -0
  207. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST002.md +0 -0
  208. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST002.txt +0 -0
  209. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/PMID_TEST003.txt +0 -0
  210. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/bookshelf/esummary_20301710.xml +0 -0
  211. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/bookshelf/gene_NBK1116/men1.nxml +0 -0
  212. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/bookshelf/pubmed_20301710.xml +0 -0
  213. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/clinicaltrials_NCT00000001.md +0 -0
  214. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/fulltext_html/README.md +0 -0
  215. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/fulltext_html/dspace-32894695.html +0 -0
  216. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/fulltext_html/iris-19049553.html +0 -0
  217. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/fulltext_html/plos-0000308.html +0 -0
  218. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/jats/PMC5593426.xml +0 -0
  219. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/jats/README.md +0 -0
  220. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/fixtures/sample.obo +0 -0
  221. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_absent_abstract_recovery.py +0 -0
  222. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_acquire.py +0 -0
  223. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_bookshelf_provider.py +0 -0
  224. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_bot_challenge.py +0 -0
  225. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_cache_author_recovery.py +0 -0
  226. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_cli.py +0 -0
  227. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_cli_cache_enrich.py +0 -0
  228. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_cli_cache_export.py +0 -0
  229. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_cli_lookup.py +0 -0
  230. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_cli_repair.py +0 -0
  231. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_clinicaltrials_cache.py +0 -0
  232. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_clinicaltrials_integration.py +0 -0
  233. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_deep_nesting.py +0 -0
  234. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_drawio.py +0 -0
  235. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_epmc_preprint_provider.py +0 -0
  236. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_extractor_fidelity.py +0 -0
  237. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_extractors.py +0 -0
  238. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_field_detection.py +0 -0
  239. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_fulltext_loader.py +0 -0
  240. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_fulltext_providers.py +0 -0
  241. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_fuzzy_suggestions.py +0 -0
  242. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_geo_integration.py +0 -0
  243. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_identifiers.py +0 -0
  244. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_meaning_changes.py +0 -0
  245. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_models.py +0 -0
  246. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_normalized_content_cache.py +0 -0
  247. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_open_access_policy.py +0 -0
  248. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_optional_linkml_dependency.py +0 -0
  249. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_pdf_url_title.py +0 -0
  250. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_plugin_integration.py +0 -0
  251. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_pmc_fulltext.py +0 -0
  252. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_pmid_network.py +0 -0
  253. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_ppr_source.py +0 -0
  254. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_real_validation.py +0 -0
  255. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_rules.py +0 -0
  256. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_serve_stale_html_optin.py +0 -0
  257. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_source_utils.py +0 -0
  258. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_split_supporting_text.py +0 -0
  259. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_supporting_text_validator.py +0 -0
  260. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_svg_edges.py +0 -0
  261. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_svg_extractor.py +0 -0
  262. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_text_extractor.py +0 -0
  263. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_trust_cached_entries.py +0 -0
  264. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_unknown_slot_traversal.py +0 -0
  265. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_url_fragment_cache.py +0 -0
  266. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_url_html_sanitize.py +0 -0
  267. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_url_html_to_text.py +0 -0
  268. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_url_source_cache_version.py +0 -0
  269. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_validation_config.py +0 -0
  270. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_validation_counts.py +0 -0
  271. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/tests/test_zotero_provider.py +0 -0
  272. {linkml_reference_validator-0.4.0 → linkml_reference_validator-0.5.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: linkml_reference_validator
3
- Version: 0.4.0
3
+ Version: 0.5.1
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
@@ -13,7 +13,23 @@ linkml-reference-validator validates that quoted text (supporting text) actually
13
13
  Before matching, both the supporting text and reference content are normalized:
14
14
 
15
15
  - **Lowercased**: `"MUC1"` → `"muc1"`
16
- - **Punctuation removed**: `"c-Abl"` → `"c abl"`
16
+ - **Punctuation removed**: `"c-Abl"` → `"c abl"` (a point or comma between
17
+ digits is kept, so `"10.5"` stays one number)
18
+ - **Meaning-bearing symbols kept as words**: `<` `>` `≤` `≥` become `lt` `gt`
19
+ `le` `ge`, `±` (or `+/-`) becomes `pm`, and a minus sign before a number stays
20
+ on it, so `"p < 0.05"` does not match `"p > 0.05"` and `"2.5 fold"` does not
21
+ match `"-2.5 fold"`. ASCII and Unicode spellings are equivalent (`<=` and `≤`,
22
+ `-` and `−`). Publisher spellings count too: MEDLINE's `>/=` and `</=` for
23
+ `≥` and `≤`, a middle-dot decimal (`57·4` is `57.4`), and an en dash or
24
+ Unicode hyphen printed as a minus sign (`z score, –1.98`) or inside `+/–`.
25
+ A `−` set off from its number by a space (`from − 2.92`) is still a sign,
26
+ unless a number comes before it (`10 − 2` is a subtraction). A hyphen or en
27
+ dash after a word or digit (`IL-6`, `3-5`, `1–12`) is still punctuation. A
28
+ dash after a space and before a digit is a sign, so a range spaced on one
29
+ side only (`18 –65`) reads as `18` and `-65`; quote it as the source
30
+ prints it. A middle dot after a letter-joined number is not a decimal
31
+ point, so `MgCl2·6H2O` keeps its hydrate dot (and `pH7·4` does not read as
32
+ `pH7.4`; `pH 7·4` does).
17
33
  - **Whitespace collapsed**: Multiple spaces become single space
18
34
  - **Editorial notes removed**: `"[mucin 1]"` → `""`
19
35
 
@@ -36,6 +52,34 @@ reference_content = "...The MUC1 oncoprotein blocks nuclear..."
36
52
  # Match: "muc1 oncoprotein" found in normalized reference
37
53
  ```
38
54
 
55
+ **Matches start and end on whole words:**
56
+
57
+ A quote may not begin or end inside a word of the reference. Otherwise a quote
58
+ stating the opposite of the paper would pass:
59
+
60
+ ```python
61
+ supporting_text = "activates AKT signaling"
62
+ reference_content = "...Loss of PTEN inactivates AKT signaling..."
63
+ # NOT FOUND: "activates" only occurs inside "inactivates"
64
+
65
+ supporting_text = "5 mM H2O2"
66
+ reference_content = "...treated with 10.5 mM H2O2..."
67
+ # NOT FOUND: "5" only occurs inside "10.5"
68
+ ```
69
+
70
+ Each part of a quote split with `...` is held to the same rule.
71
+
72
+ One allowance: when a quote ends in a letter, the reference word it ends on may
73
+ carry trailing digits. Full-text extraction welds superscript citations onto the
74
+ word before them, so `tumor suppressor<sup>12</sup>` reads as `suppressor12`,
75
+ and a quote of `PTEN is a tumor suppressor` must still match. A quote ending in
76
+ a digit gets no allowance (`10` never matches `105`). The same allowance means a
77
+ quote ending in `AKT` matches `AKT1`, as plain substring matching always did.
78
+
79
+ Because a point or comma between digits stays part of the number, numbers must
80
+ be quoted with the reference's own separators: `days 3, 5 and 7` does not match
81
+ `days 3,5 and 7`, and `10 000` does not match `10,000`.
82
+
39
83
  **Empty excerpts are rejected:**
40
84
 
41
85
  An empty or whitespace-only excerpt is always an error, never a pass. The
@@ -224,6 +268,16 @@ content_type: abstract_only
224
268
  The MUC1 oncoprotein blocks nuclear targeting...
225
269
  ```
226
270
 
271
+ An entry with no title has no `# title`, authors or journal lines, but it still
272
+ gets the `## Content` marker. The content is everything after the first exact
273
+ `## Content` line of that header, so a heading in the text such as
274
+ `## Contents of the kit` stays part of the content. A file written before untitled
275
+ entries got the marker is read whole.
276
+
277
+ A cache file whose frontmatter is empty, isn't valid YAML, or isn't a mapping is
278
+ treated as missing. Validation re-fetches the reference and rewrites the file,
279
+ and tools that walk the cache skip the file with a warning.
280
+
227
281
  ### Cache Benefits
228
282
 
229
283
  - **Offline usage**: Work without network after initial fetch
@@ -254,6 +308,15 @@ The plugin discovers fields via:
254
308
  - `slot_uri` attribute (e.g., `slot_uri: dcterms:title`)
255
309
  - Fallback slot names (`reference`, `supporting_text`, `title`)
256
310
 
311
+ Besides the URIs above, a URI of your own counts as a reference or excerpt
312
+ field when its local name *ends* in the word. One `Field`, `Slot` or `Value`
313
+ suffix may follow it, and for references also an identifier suffix such as
314
+ `Id` or `Uri`. So `ex:reference`, `ex:cross_reference`, `ex:myReferenceField`
315
+ and `ex:reference_id` are reference fields, and `ex:myExcerptField` is an
316
+ excerpt field. `ex:referenceType`, `ex:excerpt_location` and `ex:excerpt_id`
317
+ describe a reference or excerpt, so they are not. If a field of yours is missed
318
+ (say `ex:excerpt_text`), declare it with `implements` or one of the URIs above.
319
+
257
320
  ## Editorial Conventions
258
321
 
259
322
  ### Square Brackets `[...]`
@@ -405,14 +405,18 @@ fetcher does not crawl repository download links. If every provider completes
405
405
  without usable text or an error, the reference retains its original abstract
406
406
  and records `full_text_attempted: true`.
407
407
 
408
- New `full_text_html` cache entries carry `html_full_text_version: 1`. Older
409
- Markdown HTML full-text entries without this stamp are refreshed on their next
410
- validation read. This migration covers all providers because cached plain text
408
+ New `full_text_html` cache entries carry `html_full_text_version: 2`. Entries
409
+ with an older stamp, or none, are refreshed on their next validation read.
410
+ Version 2 keeps all of an article region's text rather than only its `<p>`
411
+ text, so lists, tables, headings and bare `<div>` text can be quoted. This migration covers all providers because cached plain text
411
412
  cannot establish which HTML body structure was present. It does not invalidate
412
413
  current PDF, XML, or abstract caches.
413
414
 
414
- If refreshing an old HTML entry fails, validation does **not** fall back to that
415
- entry: its text may be repository metadata rather than article evidence. The
415
+ If refreshing an entry stamped `html_full_text_version: 1` or later fails,
416
+ validation falls back to it with a warning, as for other content types: it is a
417
+ checked article body, though it may lack list and table text. If refreshing an
418
+ entry with no stamp fails, validation does **not** fall back to that entry: its
419
+ text may be repository metadata rather than article evidence. The
416
420
  file remains untouched for inventory/export, and the next validation run retries
417
421
  recovery. Once the source is available, normal fetching replaces it with a
418
422
  verified article or the available metadata/abstract. Other content types retain
@@ -26,7 +26,10 @@ The distinct letters `Æ/æ` and `Œ/œ` remain distinct from `AE/ae` and `OE/oe
26
26
  Equating these letters would accept spelling changes beyond typographic repair.
27
27
  Validation uses an explicit ligature table instead of broad Unicode NFKC
28
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 `μ`.
29
+ with `H2O` or `10⁶` with `106`. The micro sign `µ` and Greek `μ` *are* treated
30
+ as the same letter, since sources use them interchangeably (`5 µM` matches
31
+ `5 μM`), as are the variant Greek forms `ϕ ϵ ϑ ϐ ϖ ϰ ϱ` and the ordinary
32
+ letters they stand for.
30
33
 
31
34
  ### Unmapped PDF glyphs do not match
32
35
 
@@ -87,6 +90,18 @@ The repair command uses confidence thresholds to determine how to handle each er
87
90
  | **LOW** | 0.50-0.80 | Flag for manual review |
88
91
  | **VERY_LOW** | 0.00-0.50 | Recommend removal |
89
92
 
93
+ The score ranges are the defaults. A fuzzy match is graded by the repair
94
+ config's `auto_fix_threshold`, `suggest_threshold` and `removal_threshold`, so
95
+ raising `auto_fix_threshold` (or passing `--auto-fix-threshold`) turns
96
+ borderline auto-fixes into suggestions, and lowering it does the reverse. The
97
+ three must be in order, `removal <= suggest <= auto_fix`. A lower threshold
98
+ you leave unset follows one you set below it (`auto_fix_threshold: 0.75` alone
99
+ also lowers `suggest_threshold` to 0.75), and `--auto-fix-threshold` lowers the
100
+ other two with it. Thresholds you set out of order are an error.
101
+ The flag overrides the config file only when it is given. Character
102
+ normalization and ellipsis repairs are not scored, so the thresholds do not
103
+ apply to them.
104
+
90
105
  ## Repair Strategies
91
106
 
92
107
  ### 1. Character Normalization (High Confidence)
@@ -105,11 +120,30 @@ supporting_text: "CO₂ levels were measured"
105
120
 
106
121
  Common mappings:
107
122
  - `CO2` → `CO₂`
123
+ - `H2O2` → `H₂O₂`
108
124
  - `H2O` → `H₂O`
109
125
  - `O2` → `O₂`
110
126
  - `+/-` → `±`
111
127
  - `+-` → `±`
112
128
 
129
+ Where a mapping starts or ends with a letter or digit, it applies only if no
130
+ letter or digit sits directly next to it on that side. So `O2` is left alone in
131
+ `CDKN2A` and `N2` in `HEK293N2`. The same rule also skips formulas joined to a
132
+ coefficient or prefix, such as `6CO2`, `2H2O2` and `pCO2`. Fix those by hand.
133
+ A mapping that starts and ends with punctuation, such as `+/-`, applies
134
+ anywhere, so `5+/-2` becomes `5±2`. All mappings are applied in one pass,
135
+ longest first.
136
+
137
+ Validation reads `+-` (except before a letter), `+/-` and `±` all as the same
138
+ word, ignores other punctuation, spells out Greek letters and folds ligatures,
139
+ so it cannot confirm a mapping that changes only those, such as `+-` → `±` or a
140
+ custom `alpha` → `α`. Such a mapping is kept only when the reference contains
141
+ the repaired text exactly as written, apart from case and whitespace.
142
+ That is what keeps `Ca2+-dependent` from becoming `Ca2±dependent`.
143
+
144
+ Quotes with `...` or `[editorial brackets]` are checked part by part, the same
145
+ way validation checks them.
146
+
113
147
  ### 2. Ellipsis Insertion (Medium Confidence)
114
148
 
115
149
  When text parts exist in the reference but aren't contiguous:
@@ -245,8 +279,8 @@ linkml-reference-validator repair data <DATA_FILE> --schema <SCHEMA> [OPTIONS]
245
279
  - `--schema PATH` - Path to LinkML schema (required)
246
280
  - `--target-class CLASS` - Target class to validate
247
281
  - `--dry-run / --no-dry-run` - Show changes without applying (default: dry-run)
248
- - `--auto-fix-threshold FLOAT` - Minimum similarity for auto-fixes (default: 0.95)
249
- - `--output PATH` - Output file path (default: overwrite with backup)
282
+ - `--auto-fix-threshold FLOAT` - Minimum similarity for auto-fixes (default: the config file's `auto_fix_threshold`, else 0.95)
283
+ - `--output PATH` - Output file path (default: overwrite the input, keeping a `.bak` backup). When given with `--no-dry-run`, it is always written, as an unchanged copy of the input when there is nothing to fix, and the input itself is not touched, so no backup is made
250
284
  - `--config PATH` - Path to repair configuration file
251
285
  - `--cache-dir PATH` - Directory for caching references
252
286
  - `--verbose` - Show detailed output
@@ -106,6 +106,23 @@ classes:
106
106
  title: "MUC1 oncoprotein blocks nuclear targeting of c-Abl"
107
107
  ```
108
108
 
109
+ The reference may take the same shapes as for excerpts:
110
+
111
+ - **A list of one** is checked like a single reference.
112
+ - **Several references with one title field** pass if the title is any one
113
+ reference's title, since it names one paper. If it is none of them, a single
114
+ error names each reference with its own reason. A reference whose prefix is
115
+ skipped vouches for nothing.
116
+ - **Reference objects with their own `title`**, listed or not, and with no title
117
+ field beside them, each have that title checked against their own reference.
118
+ - **A reference with no usable identifier** (an object without `id`, an empty
119
+ string) is reported as an error, not skipped.
120
+
121
+ With an excerpt as well, the title field is checked the same way: against the
122
+ one reference, or, with several, passing if it is any one reference's title.
123
+ It replaces any `title` a reference object carries. Without a title field,
124
+ each reference object's own `title` is checked with the excerpt.
125
+
109
126
  ## Combined Validation
110
127
 
111
128
  When both title and excerpt fields are present, both are validated together:
@@ -117,7 +117,22 @@ assert header.model_dump()["database"] == "Orphanet"
117
117
  ```
118
118
 
119
119
  The checker derives the filename from the stored `reference_id` by replacing
120
- `:`, `/`, `?`, and `=` with `_` and appending `.md`. DOI filenames are compared
120
+ `:`, `/`, `?`, and `=` with `_` and appending `.md`. A name that would exceed 200
121
+ bytes of UTF-8 is cut on a character boundary and ends in `_` plus the first 16
122
+ hex digits of the SHA-256 of the full `reference_id` (lowercased for a DOI), so
123
+ a long `url:` reference fits common filesystems' 255-byte limit and two long IDs
124
+ sharing a prefix still get different files. Shorter names are unchanged.
125
+
126
+ Because the replacement is lossy, distinct references can sanitize to the same
127
+ name, such as `url:https://ex.org/a?b=c` and `url:https://ex.org/a/b/c`. The
128
+ first one written keeps that name. When the name is already held by a file whose
129
+ `reference_id` is a different reference, the other one is written under the same
130
+ name ending in `_` and the 16-hex-digit hash of its own ID instead, and the
131
+ checker accepts that name for it. An entry is never served for a reference other
132
+ than the one it records: a hand-written entry whose `reference_id` is a
133
+ placeholder, or an alias spelling such as `pubmed:123` in `PMID_123.md`, is now
134
+ treated as a miss and fetched afresh under the disambiguated name. DOI filenames
135
+ are compared
121
136
  case-insensitively, matching the fetcher's existing behavior. Other filenames must
122
137
  match exactly. This uses the ID already stored in the file, without applying
123
138
  configured prefix aliases or requiring a currently registered reference source.
@@ -467,7 +467,7 @@ linkml-reference-validator repair text [OPTIONS] TEXT REFERENCE_ID
467
467
  - `--cache-dir PATH, -c PATH` - Directory for caching references
468
468
  - `--config PATH` - Path to configuration file (.yaml)
469
469
  - `--verbose, -v` - Verbose output with detailed logging
470
- - `--auto-fix-threshold FLOAT, -a FLOAT` - Minimum similarity for auto-fixes (default: 0.95)
470
+ - `--auto-fix-threshold FLOAT, -a FLOAT` - Minimum similarity for auto-fixes (default: the config file's `auto_fix_threshold`, else 0.95)
471
471
  - `--help` - Show help message
472
472
 
473
473
  ### Examples
@@ -542,8 +542,8 @@ linkml-reference-validator repair data [OPTIONS] DATA_FILE
542
542
  - `--schema PATH, -s PATH` (required) - Path to LinkML schema file
543
543
  - `--target-class TEXT, -t TEXT` - Target class to validate
544
544
  - `--dry-run / --no-dry-run, -n / -N` - Show changes without applying (default: dry-run)
545
- - `--auto-fix-threshold FLOAT, -a FLOAT` - Minimum similarity for auto-fixes (default: 0.95)
546
- - `--output PATH, -o PATH` - Output file path (default: overwrite with backup)
545
+ - `--auto-fix-threshold FLOAT, -a FLOAT` - Minimum similarity for auto-fixes (default: the config file's `auto_fix_threshold`, else 0.95)
546
+ - `--output PATH, -o PATH` - Output file path (default: overwrite the input, keeping a `.bak` backup). When given with `--no-dry-run`, it is always written, as an unchanged copy of the input when there is nothing to fix, and the input itself is not touched, so no backup is made
547
547
  - `--config PATH` - Path to configuration file (.yaml)
548
548
  - `--cache-dir PATH, -c PATH` - Directory for caching references
549
549
  - `--verbose, -v` - Verbose output with detailed logging
@@ -724,6 +724,57 @@ attributes:
724
724
  slot_uri: linkml:authoritative_reference
725
725
  ```
726
726
 
727
+ ### Citing several references for one excerpt
728
+
729
+ A reference slot may be `multivalued: true`, holding a list of identifiers
730
+ (or inlined objects with an `id`). The excerpt then passes if **any** listed
731
+ reference contains it: a statement can rest on several papers, and its quote
732
+ comes from one of them. References are tried in order, and the remaining ones
733
+ are not fetched once one passes. A reference whose prefix is in
734
+ `skip_prefixes` is not evidence either way: the other references still decide,
735
+ and the excerpt is counted as skipped only if every listed reference is skipped.
736
+ If none holds the excerpt, a single error names each reference with its own
737
+ reason. The excerpt counts once in "Snippets
738
+ checked", however many references were tried.
739
+
740
+ A title slot names one paper, so it is compared only when a single reference
741
+ is listed. With several, give each listed object its own `title`.
742
+
743
+ Before this, a list was silently ignored: its excerpts were never compared and a
744
+ fabricated quote passed with exit code 0.
745
+
746
+ ### Several excerpts, or evidence in a keyed mapping
747
+
748
+ An excerpt slot may also be `multivalued: true`. Each listed excerpt is checked
749
+ on its own against the reference, and reported at its own path, such as
750
+ `supporting_text[1]`: a true excerpt beside a fabricated one does not carry it.
751
+ A blank item, or one that is not text, is an error at its index. Before this,
752
+ a list of excerpts crashed `validate data` with `TypeError: expected string or
753
+ bytes-like object, got 'list'`.
754
+
755
+ Evidence objects in a multivalued slot written as a mapping from identifier to
756
+ object (`inlined_as_list: false`) are each validated, at a path such as
757
+ `has_evidence[E1].supporting_text`; the key is used as the object's identifier
758
+ (or `key` slot) when the object omits it. A dict is read this way only when the
759
+ class has an identifier or key slot and none of the dict's keys is a slot of
760
+ the class; a dict with such a key is a single object written without the list,
761
+ and is validated as one. A compact (non-object) entry in a mapping is left to
762
+ schema validation, and does not stop the objects beside it being checked. Before this, the objects in such a mapping were never
763
+ visited, and a fabricated quote passed with exit code 0. The list form of the
764
+ same slot was, and is, validated the same way.
765
+
766
+ ### "Reference at '...' has no usable identifier"
767
+
768
+ **Cause:**
769
+ The reference slot holds a value, but no identifier can be read from it: an
770
+ inlined object with no `id` (or `reference_id`), an empty string, a number, or
771
+ a `null` list item. The excerpt beside it cannot be checked, so this is an
772
+ error rather than a silent pass.
773
+
774
+ **Solution:**
775
+ Give the reference an identifier, e.g. `reference: {id: PMID:12345678}`. An
776
+ excerpt with no reference value at all is not affected by this check.
777
+
727
778
  ### "Reference base directory not found"
728
779
 
729
780
  **Cause:**
@@ -905,6 +956,20 @@ XML remains available with the existing stale-cache warning and is not rewritten
905
956
  it may still lack table rows. A later process retries the refresh. Existing
906
957
  stale HTML rejection remains unchanged.
907
958
 
959
+ `xml_extraction_version: 2` fixes body text that version 1 could get wrong. A
960
+ paragraph nested in another (a `<list>` or `<fig>` inside `<p>`, which JATS
961
+ does routinely) was emitted twice; adjacent list items and lines split by
962
+ `<break/>` were welded together ("alpha itembeta item"); HTML named entities
963
+ such as `&nbsp;` and `&mdash;`, which PMC's external DTD defines but which is
964
+ never loaded, were dropped and glued the words either side; and a document
965
+ declaring its own entity in an internal DTD subset lost its whole body. A quote
966
+ spanning any of these could fail against correct text. Version 1 XML entries
967
+ are refreshed on the next fetch, as above. Whitespace inside a paragraph is now
968
+ collapsed to single spaces (a no-break space included), as it already was in
969
+ table cells.
970
+ The HTML-entity repair applies to UTF-8 and other ASCII-compatible encodings
971
+ only; in a UTF-16 or UTF-32 document an undeclared `&nbsp;` is still dropped.
972
+
908
973
  Entries that record *no* content carry their own stamp. A cache entry written
909
974
  as `content_type: unavailable` is a claim about what an extractor could not
910
975
  find, so an extractor fix can make it wrong -- and because such an entry carries
@@ -5,6 +5,7 @@ Unknown fields are preserved, so consumers can add metadata without a namespace.
5
5
  """
6
6
 
7
7
  from dataclasses import dataclass, field
8
+ import hashlib
8
9
  from pathlib import Path
9
10
  import re
10
11
 
@@ -154,9 +155,64 @@ def legacy_cache_filename(reference_id: str) -> str:
154
155
  return _sanitized_filename(reference_id)
155
156
 
156
157
 
158
+ #: The longest cache filename written, in UTF-8 bytes. Common filesystems allow
159
+ #: 255; this leaves room for a ``.txt`` legacy probe and other tools' suffixes.
160
+ MAX_CACHE_FILENAME_BYTES = 200
161
+
162
+ #: Hex digits of the reference ID's SHA-256 kept in a capped filename.
163
+ _FILENAME_HASH_CHARS = 16
164
+
165
+
157
166
  def _sanitized_filename(reference_id: str) -> str:
158
- """Sanitize a reference ID into a filename, keeping everything else verbatim."""
159
- return re.sub(r"[:/?=]", "_", reference_id) + ".md"
167
+ """Sanitize a reference ID into a filename, keeping everything else verbatim.
168
+
169
+ A name over ``MAX_CACHE_FILENAME_BYTES`` is cut on a character boundary and
170
+ ends in ``_`` plus a hash of the full ID, so it fits the filesystem and two
171
+ long IDs that share a prefix still get different files (#132). A DOI is
172
+ hashed case-insensitively, so case variants of one DOI still differ only in
173
+ case, as the fetcher's case-variant lookup and the checker expect.
174
+
175
+ >>> _sanitized_filename("PMID:1")
176
+ 'PMID_1.md'
177
+ >>> name = _sanitized_filename("url:https://example.org/?q=" + "x" * 300)
178
+ >>> len(name.encode("utf-8")) <= MAX_CACHE_FILENAME_BYTES, name[-20:]
179
+ (True, '_8d32b36eb989401f.md')
180
+ """
181
+ name = re.sub(r"[:/?=]", "_", reference_id)
182
+ if len(name.encode("utf-8")) + len(".md") <= MAX_CACHE_FILENAME_BYTES:
183
+ return name + ".md"
184
+ return _hashed_filename(name, reference_id)
185
+
186
+
187
+ def disambiguated_cache_filename(reference_id: str) -> str:
188
+ """Return the filename for a reference whose usual name another one holds.
189
+
190
+ Sanitizing maps ``:`` ``/`` ``?`` and ``=`` to ``_``, so distinct
191
+ references can share :func:`cache_filename`: ``url:https://ex.org/a?b=c``
192
+ and ``url:https://ex.org/a/b/c``. The first writer keeps the shared name;
193
+ the other is written here, under the same name ending in a hash of its own
194
+ ID, so no existing entry is renamed (#131).
195
+
196
+ >>> disambiguated_cache_filename("url:https://ex.org/a/b/c")
197
+ 'url_https___ex.org_a_b_c_5acbea067cb82733.md'
198
+ """
199
+ key = cache_key(reference_id)
200
+ return _hashed_filename(re.sub(r"[:/?=]", "_", key), key)
201
+
202
+
203
+ def _hashed_filename(name: str, reference_id: str) -> str:
204
+ """``name``, cut to fit, ending in ``_`` and a hash of ``reference_id``.
205
+
206
+ A DOI is hashed case-insensitively, so case variants of one DOI still
207
+ differ only in case.
208
+ """
209
+ digest_source = (
210
+ reference_id.casefold() if reference_id.upper().startswith("DOI:") else reference_id
211
+ )
212
+ digest = hashlib.sha256(digest_source.encode("utf-8")).hexdigest()[:_FILENAME_HASH_CHARS]
213
+ room = MAX_CACHE_FILENAME_BYTES - len(".md") - len("_") - len(digest)
214
+ prefix = name.encode("utf-8")[:room].decode("utf-8", errors="ignore")
215
+ return f"{prefix}_{digest}.md"
160
216
 
161
217
 
162
218
  def _same_reference_id(first: str, second: str) -> bool:
@@ -257,11 +313,13 @@ def _validate_cache_file(
257
313
 
258
314
  reference_id = result.frontmatter.reference_id
259
315
  expected = cache_filename(reference_id)
260
- # A fragment-keyed name from before #114 is still read, so still valid.
261
- accepted = {expected, legacy_cache_filename(reference_id)}
316
+ # A fragment-keyed name from before #114 is still read, so still valid, as
317
+ # is the name a reference gets when another holds its usual one (#131).
318
+ disambiguated = disambiguated_cache_filename(reference_id)
319
+ accepted = {expected, legacy_cache_filename(reference_id), disambiguated}
262
320
  matches = path.name in accepted
263
321
  if reference_id.upper().startswith("DOI:"):
264
- matches = path.name.casefold() == expected.casefold()
322
+ matches = path.name.casefold() in {expected.casefold(), disambiguated.casefold()}
265
323
  if not matches:
266
324
  result.findings.append(
267
325
  CacheFinding(
@@ -9,7 +9,10 @@ import typer
9
9
  from ruamel.yaml import YAML
10
10
  from typing_extensions import Annotated
11
11
 
12
- from linkml_reference_validator.etl.reference_fetcher import ReferenceFetcher
12
+ from linkml_reference_validator.etl.reference_fetcher import (
13
+ ReferenceFetcher,
14
+ markdown_body_lines,
15
+ )
13
16
  from linkml_reference_validator.models import ReferenceContent
14
17
  from .shared import (
15
18
  CacheDirOption,
@@ -128,22 +131,8 @@ def _format_as_markdown(reference: ReferenceContent, fetcher: ReferenceFetcher)
128
131
  yaml_obj.dump(frontmatter, stream)
129
132
 
130
133
  lines = ["---", stream.getvalue().rstrip(), "---", ""]
131
- if reference.title:
132
- lines.append(f"# {reference.title}")
133
- if reference.authors:
134
- lines.append(f"**Authors:** {', '.join(reference.authors)}")
135
- if reference.journal:
136
- journal_info = reference.journal
137
- if reference.year:
138
- journal_info += f" ({reference.year})"
139
- lines.append(f"**Journal:** {journal_info}")
140
- if reference.doi:
141
- lines.append(f"**DOI:** [{reference.doi}](https://doi.org/{reference.doi})")
142
- lines.append("")
143
- lines.append("## Content")
144
- lines.append("")
145
- if reference.content:
146
- lines.append(reference.content)
134
+ # The same body the cache writer emits, so the output reads back as saved.
135
+ lines.extend(markdown_body_lines(reference))
147
136
  return "\n".join(lines)
148
137
 
149
138