From 9c04b718e65869f44441fe1077a534c492c15b57 Mon Sep 17 00:00:00 2001 From: OpenClaw Assistant Date: Mon, 13 Jul 2026 14:09:28 +0000 Subject: [PATCH 1/4] AI-assisted: deduplicate publication venue metadata --- _layouts/bib.liquid | 54 +++++++++++-------- _plugins/publication_metadata.rb | 87 +++++++++++++++++++++++++++++++ assets/css/main.scss | 9 ++++ test/publication_metadata_test.rb | 66 +++++++++++++++++++++++ 4 files changed, 193 insertions(+), 23 deletions(-) create mode 100644 _plugins/publication_metadata.rb create mode 100644 test/publication_metadata_test.rb diff --git a/_layouts/bib.liquid b/_layouts/bib.liquid index fda7c6a6..3f0ab007 100644 --- a/_layouts/bib.liquid +++ b/_layouts/bib.liquid @@ -160,44 +160,52 @@ {% endif %} - + {% assign proceedings = 'inproceedings,incollection' | split: ',' %} {% assign thesis = 'thesis,mastersthesis,phdthesis' | split: ',' %} + {% assign full_venue = '' %} {% if entry.type == 'article' %} - {% capture entrytype %}{{ entry.journal }}{% endcapture %} + {% assign full_venue = entry.journal %} {% elsif proceedings contains entry.type %} - {% capture entrytype %}In {{ entry.booktitle }}{% endcapture %} + {% assign full_venue = entry.booktitle %} {% elsif thesis contains entry.type %} - {% capture entrytype %}{{ entry.school }}{% endcapture %} - {% else %} - {% capture entrytype %}{% endcapture %} - {% endif %} - {% if entry.month %} - {% capture entrymonth %}{{ " " }}{{ entry.month | capitalize }}{% endcapture %} - {% endif %} - {% if entry.year %} - {% capture entryyear %}{{ " " }}{{ entry.year }}{% endcapture %} - {% endif %} - {% if entry.location %} - {% capture entrytype %}{{ entrytype }}{{ ", " }}{{ entry.location }}{% endcapture %} - {% endif -%} - {% if entry.additional_info %} - {% capture entrytype %}{{ entrytype }}{{ entry.additional_info | markdownify | remove: '

' | remove: '

' }}{% endcapture %} + {% assign full_venue = entry.school %} {% endif %} + {% assign full_venue = full_venue | visible_derived_status: entry.award_name %} + {% assign show_full_venue = full_venue | venue_distinct: entry.abbr %} + {% assign derived_status = entry.status | visible_derived_status: entry.award_name %} + {% assign display_location = entry.location | visible_derived_status: entry.award_name %} + {% assign display_note = entry.note | visible_derived_status: entry.award_name %} + + {% capture venue_accessible_name %}{% if full_venue != blank %}{{ full_venue | strip }}{% else %}{{ entry.abbr }}{% endif %}{% endcapture %} + {% capture publication_date %}{% if entry.month %}{{ entry.month | capitalize }} {% endif %}{{ entry.year }}{% endcapture %} + {% capture primary_details %} + {{ publication_date | strip }} + {% if display_location != blank %}{% if publication_date != blank %}, {% endif %}{{ display_location }}{% endif %} + {% if derived_status != blank %}{% if publication_date != blank or display_location != blank %}, {% endif %}{{ derived_status }}{% endif %} + {% if entry.additional_info %}{{ entry.additional_info | markdownify | remove: '

' | remove: '

' }}{% endif %} + {% endcapture %} + {% capture hook_exists %}{% file_exists _includes/hook/bib.liquid %}{% endcapture %} {% if hook_exists == 'true' %} {% include hook/bib.liquid %} {% endif %} - {% assign entrytype_text = entrytype | strip_html | strip %} - {% capture periodical %}{{ entrytype }}{% if entrytype_text != "" and entryyear != "" %}, {% endif %}{{ entrymonth }}{{ entryyear }}{% endcapture %} +
{% if entry.abbr %} - {{ entry.abbr }} + + {{- entry.abbr -}} + {% endif %} - {{ periodical | strip }} + {{ primary_details | strip }}
+ {% if show_full_venue %} +
+ {{ full_venue }} +
+ {% endif %}
- {{ entry.note | strip }} + {{ display_note | strip }}
diff --git a/_plugins/publication_metadata.rb b/_plugins/publication_metadata.rb new file mode 100644 index 00000000..a9f292d6 --- /dev/null +++ b/_plugins/publication_metadata.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +# Deterministic helpers for keeping publication venue metadata concise without +# changing the bibliography source. The abbreviation remains the primary venue +# signal; a full venue is secondary only when it conveys information beyond it. +module Jekyll + module PublicationMetadata + GENERIC_WORDS = %w[ + annual conference international proceedings symposium workshop on of the + in for and volume vol edition + ].freeze + + module_function + + def plain_text(value) + value.to_s + .gsub(/\\[a-zA-Z]+\s*(?:\[[^\]]*\])?\s*\{([^{}]*)\}/, '\\1') + .gsub(/[{}]/, '') + .gsub(/<[^>]+>/, ' ') + .gsub(/&[a-zA-Z0-9#]+;/, ' ') + .gsub(/\s+/, ' ') + .strip + end + + def normalized(value) + plain_text(value).downcase.gsub(/[^a-z0-9]+/, '') + end + + def words(value) + plain_text(value).downcase.scan(/[a-z0-9]+/).reject { |word| word.match?(/\A\d+(?:st|nd|rd|th)?\z/) } + end + + def initialisms(value) + source_words = words(value) + variants = [source_words, source_words.reject { |word| GENERIC_WORDS.include?(word) }] + variants.filter_map { |items| items.map { |word| word[0] }.join if items.any? }.uniq + end + + def abbreviation_like?(value) + text = plain_text(value).gsub(/[^A-Za-z]/, '') + uppercase_count = text.scan(/[A-Z]/).length + text.length > 1 && (text == text.upcase || uppercase_count > 1) + end + + def venue_distinct(full_venue, abbreviation) + full = normalized(full_venue) + short = normalized(abbreviation) + return false if full.empty? + return true if short.empty? + return false if full == short + return false if abbreviation_like?(abbreviation) && full.include?(short) + + # Covers abbreviations formed from significant words (ICLR, NeurIPS) and + # mixed-case short names whose letters occur in the full title (ToS). + !initialisms(full_venue).any? { |initials| initials == short || initials.include?(short) } + end + + def oral_award?(award_name) + plain_text(award_name).match?(/\boral\b/i) + end + + # additional_info is intentionally never passed through this helper: it is + # author-supplied literal prose and must be preserved even with an ORAL button. + def visible_derived_status(value, award_name) + return value.to_s unless oral_award?(award_name) + + value.to_s + .gsub(/\b(?:selected\s+(?:as|for)\s+)?(?:an?\s+)?oral(?:\s+(?:paper|presentation))?\b/i, '') + .gsub(/\s+([,.;:])/, '\1') + .gsub(/([,;:])\s*\z/, '') + .gsub(/\s{2,}/, ' ') + .strip + end + end + + module PublicationMetadataFilters + def venue_distinct(full_venue, abbreviation) + PublicationMetadata.venue_distinct(full_venue, abbreviation) + end + + def visible_derived_status(value, award_name) + PublicationMetadata.visible_derived_status(value, award_name) + end + end +end + +Liquid::Template.register_filter(Jekyll::PublicationMetadataFilters) diff --git a/assets/css/main.scss b/assets/css/main.scss index 7dbfcc7c..573d2aed 100644 --- a/assets/css/main.scss +++ b/assets/css/main.scss @@ -2130,3 +2130,12 @@ body.layout-about .homepage-victoria .homepage-publications-list ol.bibliography color: var(--victoria-ink) !important; opacity: 1 !important; } + +// Full venue names are exceptional secondary context; the compact abbreviation +// above remains the primary venue signal. +.publication-venue-detail { + color: var(--victoria-muted, var(--homepage-muted, #6f6a63)); + font-size: 0.92em; + font-weight: 400; + opacity: 0.82; +} diff --git a/test/publication_metadata_test.rb b/test/publication_metadata_test.rb new file mode 100644 index 00000000..2903ebb0 --- /dev/null +++ b/test/publication_metadata_test.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'digest' +require 'minitest/autorun' + +module Liquid + class Template + def self.register_filter(_filter); end + end +end + +require_relative '../_plugins/publication_metadata' + +class PublicationMetadataTest < Minitest::Test + Metadata = Jekyll::PublicationMetadata + TEMPLATE = File.expand_path('../_layouts/bib.liquid', __dir__) + + def test_redundant_conference_expansions_are_suppressed + refute Metadata.venue_distinct('International Conference on Learning Representations, {ICLR}', 'ICLR') + refute Metadata.venue_distinct('Advances in Neural Information Processing Systems, {NeurIPS}', 'NeurIPS') + refute Metadata.venue_distinct('ICML 2026', 'ICML') + end + + def test_redundant_journal_and_preprint_names_are_suppressed + refute Metadata.venue_distinct('ArXiv', 'ArXiv') + refute Metadata.venue_distinct('IEEE Network', 'IEEENetwork') + refute Metadata.venue_distinct('ACM Transactions on Storage', 'ToS') + end + + def test_semantically_distinct_secondary_venue_is_retained + assert Metadata.venue_distinct('Proc. ACM Program. Lang.', 'OOPSLA') + assert Metadata.venue_distinct('Proceedings of the ACM Symposium on Cloud Computing Poster', 'Poster') + end + + def test_year_and_additional_info_are_rendered_without_filtering_literal_prose + template = File.read(TEMPLATE) + + assert_includes template, '{{ entry.year }}' + assert_includes template, "entry.additional_info | markdownify" + refute_includes template, 'entry.additional_info | visible_derived_status' + end + + def test_explicit_additional_info_oral_text_is_preserved_exactly + literal = '. Oral paper (explicit author note).' + + assert_equal literal, literal # The template appends additional_info directly. + assert_includes File.read(TEMPLATE), "{% if entry.additional_info %}{{ entry.additional_info | markdownify" + end + + def test_derived_oral_status_is_suppressed_when_oral_button_exists + assert_equal '', Metadata.visible_derived_status('Oral paper', 'Oral') + assert_equal '', Metadata.visible_derived_status('Selected for oral presentation', 'ORAL') + assert_equal 'ECCV', Metadata.visible_derived_status('ECCV Oral presentation', 'Oral') + assert_equal 'Spotlight', Metadata.visible_derived_status('Spotlight', 'Oral') + assert_equal 'Oral paper', Metadata.visible_derived_status('Oral paper', 'Best paper') + end + + def test_publication_button_markup_and_labels_remain_unchanged + template = File.read(TEMPLATE) + links = template[/ .*?(?= \{% if site\.enable_publication_badges %\})/m] + + assert_equal 'e066afb79731deff79de68a946081564c007cdccde8642749a8f2915909d6edd', Digest::SHA256.hexdigest(links) + %w[Abs arXiv Bib PAPER PDF Supp Video Blog Code Poster Slides Website].each { |label| assert_includes links, ">#{label}<" } + assert_includes links, "{%- if entry.award_name %}{{ entry.award_name }}{% else %}Awarded{% endif -%}" + end +end From c66f1e77ffe8b195059524d6119c7216e5daa223 Mon Sep 17 00:00:00 2001 From: OpenClaw Assistant Date: Mon, 13 Jul 2026 14:47:33 +0000 Subject: [PATCH 2/4] AI-assisted: simplify publication venue metadata --- _layouts/bib.liquid | 62 +++++++++--------- _plugins/publication_metadata.rb | 87 ------------------------- assets/css/main.scss | 9 --- test/publication_metadata_test.rb | 102 +++++++++++++++++++----------- 4 files changed, 93 insertions(+), 167 deletions(-) delete mode 100644 _plugins/publication_metadata.rb diff --git a/_layouts/bib.liquid b/_layouts/bib.liquid index 3f0ab007..ee77d892 100644 --- a/_layouts/bib.liquid +++ b/_layouts/bib.liquid @@ -160,31 +160,34 @@ {% endif %} - + {% assign proceedings = 'inproceedings,incollection' | split: ',' %} {% assign thesis = 'thesis,mastersthesis,phdthesis' | split: ',' %} - {% assign full_venue = '' %} - {% if entry.type == 'article' %} - {% assign full_venue = entry.journal %} - {% elsif proceedings contains entry.type %} - {% assign full_venue = entry.booktitle %} - {% elsif thesis contains entry.type %} - {% assign full_venue = entry.school %} + {% assign abbr_text = entry.abbr | default: '' | strip %} + {% assign location_text = entry.location | default: '' | strip %} + {% assign status_text = entry.status | default: '' | strip %} + {% assign additional_info_text = entry.additional_info | default: '' | strip %} + {% assign note_text = entry.note | default: '' | strip %} + {% assign award_text = entry.award | default: '' | strip %} + {% assign award_name_downcase = entry.award_name | default: '' | downcase %} + {% if award_text != '' and award_name_downcase contains 'oral' %} + {% assign status_text = '' %} {% endif %} - {% assign full_venue = full_venue | visible_derived_status: entry.award_name %} - {% assign show_full_venue = full_venue | venue_distinct: entry.abbr %} - {% assign derived_status = entry.status | visible_derived_status: entry.award_name %} - {% assign display_location = entry.location | visible_derived_status: entry.award_name %} - {% assign display_note = entry.note | visible_derived_status: entry.award_name %} - - {% capture venue_accessible_name %}{% if full_venue != blank %}{{ full_venue | strip }}{% else %}{{ entry.abbr }}{% endif %}{% endcapture %} - {% capture publication_date %}{% if entry.month %}{{ entry.month | capitalize }} {% endif %}{{ entry.year }}{% endcapture %} - {% capture primary_details %} - {{ publication_date | strip }} - {% if display_location != blank %}{% if publication_date != blank %}, {% endif %}{{ display_location }}{% endif %} - {% if derived_status != blank %}{% if publication_date != blank or display_location != blank %}, {% endif %}{{ derived_status }}{% endif %} - {% if entry.additional_info %}{{ entry.additional_info | markdownify | remove: '

' | remove: '

' }}{% endif %} - {% endcapture %} + {% capture publication_date -%}{%- if entry.month -%}{{ entry.month | capitalize }} {% endif -%}{{ entry.year }}{%- endcapture %} + {% assign publication_date_text = publication_date | strip %} + {% capture periodical -%} + {%- if abbr_text == '' -%} + {%- if entry.type == 'article' and entry.journal != blank -%}{{ entry.journal }}{%- assign has_full_venue = true -%} + {%- elsif proceedings contains entry.type and entry.booktitle != blank -%}In {{ entry.booktitle }}{%- assign has_full_venue = true -%} + {%- elsif thesis contains entry.type and entry.school != blank -%}{{ entry.school }}{%- assign has_full_venue = true -%} + {%- endif -%} + {%- endif -%} + {%- if has_full_venue and publication_date_text != '' -%}, {% endif -%}{{ publication_date_text }} + {%- if location_text != '' -%}{%- if has_full_venue or publication_date_text != '' -%}, {% endif -%}{{ entry.location }}{%- endif -%} + {%- if status_text != '' -%}{%- if has_full_venue or publication_date_text != '' or location_text != '' -%}, {% endif -%}{{ entry.status }}{%- endif -%} + {%- if additional_info_text != '' -%}{{ entry.additional_info | markdownify | remove: '

' | remove: '

' }}{%- endif -%} + {%- if entry.year != blank and location_text == '' and status_text == '' and additional_info_text == '' and note_text == '' -%}.{%- endif -%} + {%- endcapture %} {% capture hook_exists %}{% file_exists _includes/hook/bib.liquid %}{% endcapture %} {% if hook_exists == 'true' %} @@ -192,20 +195,13 @@ {% endif %}
- {% if entry.abbr %} - - {{- entry.abbr -}} - + {% if abbr_text != '' %} + {{ entry.abbr }} {% endif %} - {{ primary_details | strip }} + {{ periodical | strip }}
- {% if show_full_venue %} -
- {{ full_venue }} -
- {% endif %}
- {{ display_note | strip }} + {{ entry.note | strip }}
diff --git a/_plugins/publication_metadata.rb b/_plugins/publication_metadata.rb deleted file mode 100644 index a9f292d6..00000000 --- a/_plugins/publication_metadata.rb +++ /dev/null @@ -1,87 +0,0 @@ -# frozen_string_literal: true - -# Deterministic helpers for keeping publication venue metadata concise without -# changing the bibliography source. The abbreviation remains the primary venue -# signal; a full venue is secondary only when it conveys information beyond it. -module Jekyll - module PublicationMetadata - GENERIC_WORDS = %w[ - annual conference international proceedings symposium workshop on of the - in for and volume vol edition - ].freeze - - module_function - - def plain_text(value) - value.to_s - .gsub(/\\[a-zA-Z]+\s*(?:\[[^\]]*\])?\s*\{([^{}]*)\}/, '\\1') - .gsub(/[{}]/, '') - .gsub(/<[^>]+>/, ' ') - .gsub(/&[a-zA-Z0-9#]+;/, ' ') - .gsub(/\s+/, ' ') - .strip - end - - def normalized(value) - plain_text(value).downcase.gsub(/[^a-z0-9]+/, '') - end - - def words(value) - plain_text(value).downcase.scan(/[a-z0-9]+/).reject { |word| word.match?(/\A\d+(?:st|nd|rd|th)?\z/) } - end - - def initialisms(value) - source_words = words(value) - variants = [source_words, source_words.reject { |word| GENERIC_WORDS.include?(word) }] - variants.filter_map { |items| items.map { |word| word[0] }.join if items.any? }.uniq - end - - def abbreviation_like?(value) - text = plain_text(value).gsub(/[^A-Za-z]/, '') - uppercase_count = text.scan(/[A-Z]/).length - text.length > 1 && (text == text.upcase || uppercase_count > 1) - end - - def venue_distinct(full_venue, abbreviation) - full = normalized(full_venue) - short = normalized(abbreviation) - return false if full.empty? - return true if short.empty? - return false if full == short - return false if abbreviation_like?(abbreviation) && full.include?(short) - - # Covers abbreviations formed from significant words (ICLR, NeurIPS) and - # mixed-case short names whose letters occur in the full title (ToS). - !initialisms(full_venue).any? { |initials| initials == short || initials.include?(short) } - end - - def oral_award?(award_name) - plain_text(award_name).match?(/\boral\b/i) - end - - # additional_info is intentionally never passed through this helper: it is - # author-supplied literal prose and must be preserved even with an ORAL button. - def visible_derived_status(value, award_name) - return value.to_s unless oral_award?(award_name) - - value.to_s - .gsub(/\b(?:selected\s+(?:as|for)\s+)?(?:an?\s+)?oral(?:\s+(?:paper|presentation))?\b/i, '') - .gsub(/\s+([,.;:])/, '\1') - .gsub(/([,;:])\s*\z/, '') - .gsub(/\s{2,}/, ' ') - .strip - end - end - - module PublicationMetadataFilters - def venue_distinct(full_venue, abbreviation) - PublicationMetadata.venue_distinct(full_venue, abbreviation) - end - - def visible_derived_status(value, award_name) - PublicationMetadata.visible_derived_status(value, award_name) - end - end -end - -Liquid::Template.register_filter(Jekyll::PublicationMetadataFilters) diff --git a/assets/css/main.scss b/assets/css/main.scss index 573d2aed..7dbfcc7c 100644 --- a/assets/css/main.scss +++ b/assets/css/main.scss @@ -2130,12 +2130,3 @@ body.layout-about .homepage-victoria .homepage-publications-list ol.bibliography color: var(--victoria-ink) !important; opacity: 1 !important; } - -// Full venue names are exceptional secondary context; the compact abbreviation -// above remains the primary venue signal. -.publication-venue-detail { - color: var(--victoria-muted, var(--homepage-muted, #6f6a63)); - font-size: 0.92em; - font-weight: 400; - opacity: 0.82; -} diff --git a/test/publication_metadata_test.rb b/test/publication_metadata_test.rb index 2903ebb0..740ac6c6 100644 --- a/test/publication_metadata_test.rb +++ b/test/publication_metadata_test.rb @@ -2,65 +2,91 @@ require 'digest' require 'minitest/autorun' +require 'liquid' -module Liquid - class Template - def self.register_filter(_filter); end +class FileExistsTag < Liquid::Tag + def render(_context) + 'false' end end - -require_relative '../_plugins/publication_metadata' +Liquid::Template.register_tag('file_exists', FileExistsTag) class PublicationMetadataTest < Minitest::Test - Metadata = Jekyll::PublicationMetadata - TEMPLATE = File.expand_path('../_layouts/bib.liquid', __dir__) + TEMPLATE_PATH = File.expand_path('../_layouts/bib.liquid', __dir__) + TEMPLATE_SOURCE = File.read(TEMPLATE_PATH) + METADATA_SOURCE = TEMPLATE_SOURCE[/ .*?(?= )/m] + BUTTON_SOURCE = TEMPLATE_SOURCE[/ .*?(?= \{% if site\.enable_publication_badges %\})/m] + MASTER_BUTTON_SHA256 = 'e066afb79731deff79de68a946081564c007cdccde8642749a8f2915909d6edd' + + def render_metadata(entry) + Liquid::Template.parse(METADATA_SOURCE, error_mode: :strict).render!('entry' => entry) + end + + def visible_text(entry) + render_metadata(entry).gsub(/<[^>]*>/, ' ').gsub(/\s+/, ' ').strip + end - def test_redundant_conference_expansions_are_suppressed - refute Metadata.venue_distinct('International Conference on Learning Representations, {ICLR}', 'ICLR') - refute Metadata.venue_distinct('Advances in Neural Information Processing Systems, {NeurIPS}', 'NeurIPS') - refute Metadata.venue_distinct('ICML 2026', 'ICML') + def test_nonblank_abbr_unconditionally_suppresses_journal_and_booktitle + article = visible_text('type' => 'article', 'abbr' => 'OOPSLA', 'journal' => 'Proc. ACM Program. Lang.', 'year' => '2025') + proceedings = visible_text('type' => 'inproceedings', 'abbr' => 'WWW', 'booktitle' => 'Companion Proceedings of the ACM Web Conference', 'year' => '2024') + + assert_includes article, 'OOPSLA' + assert_includes proceedings, 'WWW' + refute_includes article, 'Proc. ACM Program. Lang.' + refute_includes proceedings, 'Companion Proceedings' end - def test_redundant_journal_and_preprint_names_are_suppressed - refute Metadata.venue_distinct('ArXiv', 'ArXiv') - refute Metadata.venue_distinct('IEEE Network', 'IEEENetwork') - refute Metadata.venue_distinct('ACM Transactions on Storage', 'ToS') + def test_blank_or_absent_abbr_uses_full_venue_fallback + article = render_metadata('type' => 'article', 'abbr' => ' ', 'journal' => 'Journal Name', 'year' => '2025') + proceedings = render_metadata('type' => 'inproceedings', 'booktitle' => 'Conference Name', 'year' => '2024') + + assert_includes article, 'Journal Name, 2025.' + assert_includes proceedings, 'In Conference Name, 2024.' end - def test_semantically_distinct_secondary_venue_is_retained - assert Metadata.venue_distinct('Proc. ACM Program. Lang.', 'OOPSLA') - assert Metadata.venue_distinct('Proceedings of the ACM Symposium on Cloud Computing Poster', 'Poster') + def test_year_only_metadata_ends_with_period + assert_match(/2025\.\z/, visible_text('type' => 'misc', 'abbr' => 'ArXiv', 'year' => '2025')) end - def test_year_and_additional_info_are_rendered_without_filtering_literal_prose - template = File.read(TEMPLATE) + def test_year_with_following_content_uses_separator_without_stray_period + text = visible_text('type' => 'inproceedings', 'abbr' => 'WWW', 'year' => '2024', 'location' => 'Singapore') - assert_includes template, '{{ entry.year }}' - assert_includes template, "entry.additional_info | markdownify" - refute_includes template, 'entry.additional_info | visible_derived_status' + assert_includes text, '2024, Singapore' + refute_includes text, '2024., Singapore' end - def test_explicit_additional_info_oral_text_is_preserved_exactly + def test_additional_info_is_retained_verbatim_without_period_injected_after_year literal = '. Oral paper (explicit author note).' + html = render_metadata('type' => 'inproceedings', 'abbr' => 'ICLR', 'year' => '2026', 'additional_info' => literal) - assert_equal literal, literal # The template appends additional_info directly. - assert_includes File.read(TEMPLATE), "{% if entry.additional_info %}{{ entry.additional_info | markdownify" + assert_includes html, "2026#{literal}" + refute_includes html, '2026..' end - def test_derived_oral_status_is_suppressed_when_oral_button_exists - assert_equal '', Metadata.visible_derived_status('Oral paper', 'Oral') - assert_equal '', Metadata.visible_derived_status('Selected for oral presentation', 'ORAL') - assert_equal 'ECCV', Metadata.visible_derived_status('ECCV Oral presentation', 'Oral') - assert_equal 'Spotlight', Metadata.visible_derived_status('Spotlight', 'Oral') - assert_equal 'Oral paper', Metadata.visible_derived_status('Oral paper', 'Best paper') + def test_oral_button_suppresses_derived_status_but_not_literal_additional_info + text = visible_text( + 'type' => 'inproceedings', 'abbr' => 'ICLR', 'year' => '2026', + 'award' => "ICLR'26 oral paper.", 'award_name' => 'ORAL', + 'status' => 'Selected for oral presentation', 'additional_info' => '. Oral paper (literal).' + ) + + refute_includes text, 'Selected for oral presentation' + assert_includes text, 'Oral paper (literal).' end - def test_publication_button_markup_and_labels_remain_unchanged - template = File.read(TEMPLATE) - links = template[/ .*?(?= \{% if site\.enable_publication_badges %\})/m] + def test_nonoral_status_and_note_remain_visible + html = render_metadata( + 'type' => 'article', 'abbr' => 'ArXiv', 'year' => '2025', + 'status' => 'To appear', 'note' => 'Useful note' + ) + + assert_includes html, '2025, To appear' + assert_includes html, 'Useful note' + end - assert_equal 'e066afb79731deff79de68a946081564c007cdccde8642749a8f2915909d6edd', Digest::SHA256.hexdigest(links) - %w[Abs arXiv Bib PAPER PDF Supp Video Blog Code Poster Slides Website].each { |label| assert_includes links, ">#{label}<" } - assert_includes links, "{%- if entry.award_name %}{{ entry.award_name }}{% else %}Awarded{% endif -%}" + def test_publication_buttons_are_byte_for_byte_unchanged_from_master + assert_equal MASTER_BUTTON_SHA256, Digest::SHA256.hexdigest(BUTTON_SOURCE) + %w[Abs arXiv Bib PAPER PDF Supp Video Blog Code Poster Slides Website].each { |label| assert_includes BUTTON_SOURCE, ">#{label}<" } + assert_includes BUTTON_SOURCE, '{%- if entry.award_name %}{{ entry.award_name }}{% else %}Awarded{% endif -%}' end end From 35b67f61479fc0bfda8a28bb8ec67bde2fe7c58a Mon Sep 17 00:00:00 2001 From: OpenClaw Assistant Date: Mon, 13 Jul 2026 15:15:32 +0000 Subject: [PATCH 3/4] AI-assisted: terminate publication metadata punctuation --- _layouts/bib.liquid | 26 +++++++++++--- test/publication_metadata_test.rb | 59 ++++++++++++------------------- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/_layouts/bib.liquid b/_layouts/bib.liquid index ee77d892..7db124c8 100644 --- a/_layouts/bib.liquid +++ b/_layouts/bib.liquid @@ -185,9 +185,17 @@ {%- if has_full_venue and publication_date_text != '' -%}, {% endif -%}{{ publication_date_text }} {%- if location_text != '' -%}{%- if has_full_venue or publication_date_text != '' -%}, {% endif -%}{{ entry.location }}{%- endif -%} {%- if status_text != '' -%}{%- if has_full_venue or publication_date_text != '' or location_text != '' -%}, {% endif -%}{{ entry.status }}{%- endif -%} - {%- if additional_info_text != '' -%}{{ entry.additional_info | markdownify | remove: '

' | remove: '

' }}{%- endif -%} - {%- if entry.year != blank and location_text == '' and status_text == '' and additional_info_text == '' and note_text == '' -%}.{%- endif -%} + {%- if additional_info_text != '' -%} + {%- assign additional_info_first_character = additional_info_text | slice: 0, 1 -%} + {%- unless additional_info_first_character == '.' or additional_info_first_character == '!' or additional_info_first_character == '?' or additional_info_first_character == ',' or additional_info_first_character == ';' or additional_info_first_character == ':' -%} + {%- if has_full_venue or publication_date_text != '' or location_text != '' or status_text != '' -%}. {% endif -%} + {%- endunless -%} + {{- entry.additional_info | markdownify | remove: '

' | remove: '

' -}} + {%- endif -%} {%- endcapture %} + {% assign periodical_text = periodical | strip_html | strip %} + {% assign periodical_last_character = periodical_text | slice: -1, 1 %} + {% assign note_last_character = note_text | slice: -1, 1 %} {% capture hook_exists %}{% file_exists _includes/hook/bib.liquid %}{% endcapture %} {% if hook_exists == 'true' %} @@ -198,10 +206,20 @@ {% if abbr_text != '' %} {{ entry.abbr }} {% endif %} - {{ periodical | strip }} + + {{- periodical | strip -}} + {%- unless periodical_text == '' + or periodical_last_character == '.' + or periodical_last_character == '!' + or periodical_last_character == '?' + -%} + . + {%- endunless -%} +
- {{ entry.note | strip }} + {{ entry.note | strip -}} + {%- unless note_text == '' or note_last_character == '.' or note_last_character == '!' or note_last_character == '?' %}.{% endunless %}
diff --git a/test/publication_metadata_test.rb b/test/publication_metadata_test.rb index 740ac6c6..f98953d4 100644 --- a/test/publication_metadata_test.rb +++ b/test/publication_metadata_test.rb @@ -26,44 +26,41 @@ def visible_text(entry) render_metadata(entry).gsub(/<[^>]*>/, ' ').gsub(/\s+/, ' ').strip end - def test_nonblank_abbr_unconditionally_suppresses_journal_and_booktitle + def test_nonblank_abbr_unconditionally_suppresses_full_venue article = visible_text('type' => 'article', 'abbr' => 'OOPSLA', 'journal' => 'Proc. ACM Program. Lang.', 'year' => '2025') - proceedings = visible_text('type' => 'inproceedings', 'abbr' => 'WWW', 'booktitle' => 'Companion Proceedings of the ACM Web Conference', 'year' => '2024') + proceedings = visible_text('type' => 'inproceedings', 'abbr' => 'WWW', 'booktitle' => 'ACM Web Conference', 'year' => '2024') - assert_includes article, 'OOPSLA' - assert_includes proceedings, 'WWW' - refute_includes article, 'Proc. ACM Program. Lang.' - refute_includes proceedings, 'Companion Proceedings' + assert_equal 'OOPSLA 2025.', article + assert_equal 'WWW 2024.', proceedings end - def test_blank_or_absent_abbr_uses_full_venue_fallback - article = render_metadata('type' => 'article', 'abbr' => ' ', 'journal' => 'Journal Name', 'year' => '2025') - proceedings = render_metadata('type' => 'inproceedings', 'booktitle' => 'Conference Name', 'year' => '2024') - - assert_includes article, 'Journal Name, 2025.' - assert_includes proceedings, 'In Conference Name, 2024.' - end - - def test_year_only_metadata_ends_with_period - assert_match(/2025\.\z/, visible_text('type' => 'misc', 'abbr' => 'ArXiv', 'year' => '2025')) + def test_year_ended_line_has_one_terminal_period_and_preserves_month_spacing + assert_equal 'OOPSLA Oct 2025.', visible_text('type' => 'article', 'abbr' => 'OOPSLA', 'month' => 'oct', 'year' => '2025') end - def test_year_with_following_content_uses_separator_without_stray_period - text = visible_text('type' => 'inproceedings', 'abbr' => 'WWW', 'year' => '2024', 'location' => 'Singapore') + def test_non_year_metadata_lines_also_end_with_one_period + location = visible_text('type' => 'inproceedings', 'abbr' => 'WWW', 'year' => '2024', 'location' => 'Singapore') + additional_info = visible_text( + 'type' => 'article', 'abbr' => 'OOPSLA', 'month' => 'oct', 'year' => '2025', + 'additional_info' => ' [Site](https://example.com), the ArXiv version' + ) + note = visible_text('type' => 'misc', 'abbr' => 'ArXiv', 'year' => '2025', 'note' => 'Useful note') - assert_includes text, '2024, Singapore' - refute_includes text, '2024., Singapore' + assert_equal 'WWW 2024, Singapore.', location + assert_equal 'OOPSLA Oct 2025. [Site](https://example.com), the ArXiv version.', additional_info + assert_equal 'ArXiv 2025. Useful note.', note end - def test_additional_info_is_retained_verbatim_without_period_injected_after_year - literal = '. Oral paper (explicit author note).' - html = render_metadata('type' => 'inproceedings', 'abbr' => 'ICLR', 'year' => '2026', 'additional_info' => literal) + def test_prepunctuated_metadata_is_not_double_punctuated + { '.' => 'Final.', '!' => 'Final!', '?' => 'Final?' }.each do |punctuation, info| + text = visible_text('type' => 'misc', 'abbr' => 'Test', 'year' => '2025', 'additional_info' => ". #{info}") - assert_includes html, "2026#{literal}" - refute_includes html, '2026..' + assert text.end_with?(punctuation), text + refute_match(/[.!?]{2}\z/, text) + end end - def test_oral_button_suppresses_derived_status_but_not_literal_additional_info + def test_oral_rule_and_nonvenue_metadata_are_preserved text = visible_text( 'type' => 'inproceedings', 'abbr' => 'ICLR', 'year' => '2026', 'award' => "ICLR'26 oral paper.", 'award_name' => 'ORAL', @@ -74,16 +71,6 @@ def test_oral_button_suppresses_derived_status_but_not_literal_additional_info assert_includes text, 'Oral paper (literal).' end - def test_nonoral_status_and_note_remain_visible - html = render_metadata( - 'type' => 'article', 'abbr' => 'ArXiv', 'year' => '2025', - 'status' => 'To appear', 'note' => 'Useful note' - ) - - assert_includes html, '2025, To appear' - assert_includes html, 'Useful note' - end - def test_publication_buttons_are_byte_for_byte_unchanged_from_master assert_equal MASTER_BUTTON_SHA256, Digest::SHA256.hexdigest(BUTTON_SOURCE) %w[Abs arXiv Bib PAPER PDF Supp Video Blog Code Poster Slides Website].each { |label| assert_includes BUTTON_SOURCE, ">#{label}<" } From c2df27d22107e0d30d369d07c67202f084ad6a54 Mon Sep 17 00:00:00 2001 From: OpenClaw Assistant Date: Mon, 13 Jul 2026 15:26:25 +0000 Subject: [PATCH 4/4] AI-assisted: preserve publication month punctuation --- _layouts/bib.liquid | 7 ++++++- test/publication_metadata_test.rb | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/_layouts/bib.liquid b/_layouts/bib.liquid index 7db124c8..815c2b62 100644 --- a/_layouts/bib.liquid +++ b/_layouts/bib.liquid @@ -173,7 +173,12 @@ {% if award_text != '' and award_name_downcase contains 'oral' %} {% assign status_text = '' %} {% endif %} - {% capture publication_date -%}{%- if entry.month -%}{{ entry.month | capitalize }} {% endif -%}{{ entry.year }}{%- endcapture %} + {% assign abbreviated_months = 'jan,feb,mar,apr,aug,sep,sept,oct,nov,dec' | split: ',' %} + {% assign month_text = entry.month | default: '' | strip %} + {% assign month_downcase = month_text | downcase %} + {% capture publication_date -%} + {%- if month_text != '' -%}{{ month_text | capitalize }}{%- if abbreviated_months contains month_downcase -%}.{%- endif %} {% endif -%}{{ entry.year }} + {%- endcapture %} {% assign publication_date_text = publication_date | strip %} {% capture periodical -%} {%- if abbr_text == '' -%} diff --git a/test/publication_metadata_test.rb b/test/publication_metadata_test.rb index f98953d4..da972e5c 100644 --- a/test/publication_metadata_test.rb +++ b/test/publication_metadata_test.rb @@ -34,8 +34,9 @@ def test_nonblank_abbr_unconditionally_suppresses_full_venue assert_equal 'WWW 2024.', proceedings end - def test_year_ended_line_has_one_terminal_period_and_preserves_month_spacing - assert_equal 'OOPSLA Oct 2025.', visible_text('type' => 'article', 'abbr' => 'OOPSLA', 'month' => 'oct', 'year' => '2025') + def test_year_ended_line_has_one_terminal_period_and_preserves_month_style + assert_equal 'OOPSLA Oct. 2025.', visible_text('type' => 'article', 'abbr' => 'OOPSLA', 'month' => 'oct', 'year' => '2025') + assert_equal 'Test July 2025.', visible_text('type' => 'misc', 'abbr' => 'Test', 'month' => 'July', 'year' => '2025') end def test_non_year_metadata_lines_also_end_with_one_period @@ -47,7 +48,7 @@ def test_non_year_metadata_lines_also_end_with_one_period note = visible_text('type' => 'misc', 'abbr' => 'ArXiv', 'year' => '2025', 'note' => 'Useful note') assert_equal 'WWW 2024, Singapore.', location - assert_equal 'OOPSLA Oct 2025. [Site](https://example.com), the ArXiv version.', additional_info + assert_equal 'OOPSLA Oct. 2025. [Site](https://example.com), the ArXiv version.', additional_info assert_equal 'ArXiv 2025. Useful note.', note end