simple_inline_text_annotation 2.0.0 → 2.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d8d14f48a850c835dbf62083dcda9ea3f2588e7f2544cacf3125e54481fca37
4
- data.tar.gz: 50ebe7c23e5d841697f455586c9ed031d7df4d0da8e625630872948506145b2b
3
+ metadata.gz: 3c4a6b74cd54c5a3ef2263ee7e591cc1f5ef2acab8f36b991fdd3a7f84fc887c
4
+ data.tar.gz: a436a2add0c95d4484f096a4c56ca1dc283f5e54178377ebeb50f53c4cbe9ddb
5
5
  SHA512:
6
- metadata.gz: 99e205823a643812b22d835d9b9eae0041e320430b07175ab95a8b87cfb6287d37c749b18eac48beada081c39bb89a3aa16179cbb930e1ef4b426ce5b4ed0334
7
- data.tar.gz: ccd90e14e4b0247428bc271e1663c995126f7361c60c59dfa6a2805fe3d255ef82b73f02ca19ed4beefe0486f3075697a96e389a7577b454d29bdbf19bbe6488
6
+ metadata.gz: a34913353fd069aab8ad9e19955fcfaf50e8856d8e49c91034eb271c5eb9e156f3cf03371f4b053a9f4fc85d5f3d4e5fc62afaefbc7d5da0b70e93f72acee825
7
+ data.tar.gz: 4eddd143142df13d5b4653805bac281e9b2043fd3ec9ab4ac4d2bf1c104eb43ffa2b2f2491435a31e6a0f8ff0b1145ba29ba246df94d6aee7f93aea28fe20ab2
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 3.1
2
+ TargetRubyVersion: 3.4.4
3
3
  NewCops: enable
4
4
 
5
5
  Style/StringLiterals:
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.4
@@ -15,7 +15,11 @@ class SimpleInlineTextAnnotation
15
15
  private
16
16
 
17
17
  def remove_duplicates_from(denotations)
18
- denotations.uniq(&:span)
18
+ # Deduplicate on the FULL identity (span + obj + id), not just span —
19
+ # two denotations sharing a span but pointing at different objs are the
20
+ # multi-label case (e.g. `[eye][UBERON_0000019|UBERON_0000955]`) and
21
+ # Generator pipe-joins them. Only truly identical entries are dropped.
22
+ denotations.uniq { |d| [d.span, d.obj, d.id] }
19
23
  end
20
24
 
21
25
  def remove_non_integer_positions_from(denotations)
@@ -40,12 +44,19 @@ class SimpleInlineTextAnnotation
40
44
  result = []
41
45
 
42
46
  sorted_denotations.each do |denotation|
43
- result << denotation unless result.any? { |outer| denotation.nested_within?(outer) }
47
+ # Preserve denotations that share an EXACT span with an already-accepted
48
+ # one (the multi-label case — Generator will pipe-join their labels).
49
+ # Only drop those STRICTLY nested inside a larger outer span.
50
+ result << denotation unless result.any? { |outer| strictly_nested?(denotation, outer) }
44
51
  end
45
52
 
46
53
  result
47
54
  end
48
55
 
56
+ def strictly_nested?(inner, outer)
57
+ inner.nested_within?(outer) && (inner.begin_pos != outer.begin_pos || inner.end_pos != outer.end_pos)
58
+ end
59
+
49
60
  def remove_boundary_crosses_from(denotations)
50
61
  denotations.reject do |denotation|
51
62
  denotations.any? { |existing| denotation.boundary_crossing?(existing) }
@@ -34,27 +34,46 @@ class SimpleInlineTextAnnotation
34
34
  end
35
35
 
36
36
  def annotate_text(text, denotations, relations)
37
+ # Group denotations by span position so multiple denotations on the
38
+ # SAME span emit ONE annotation with a pipe-joined label
39
+ # (e.g. `[eye][UBERON_0000019|UBERON_0000955]`) — the SIAF spec's
40
+ # extension for multi-labelled spans. Individual URL resolution via
41
+ # `entity types` config still applies to each label independently, so
42
+ # the tail reference block lists every underlying URL.
43
+ grouped = denotations.group_by { |d| [d.begin_pos, d.end_pos] }
44
+
37
45
  # Annotate text from the end to ensure position calculation.
38
- denotations.sort_by(&:begin_pos).reverse_each do |denotation|
39
- text = annotate_text_with_denotation(text, denotation, relations)
46
+ grouped.sort_by { |(b, _e), _| b }.reverse_each do |(_b, _e), ds|
47
+ text = annotate_text_with_denotations(text, ds, relations)
40
48
  end
41
49
 
42
50
  text
43
51
  end
44
52
 
45
- def annotate_text_with_denotation(text, denotation, relations)
46
- begin_pos = denotation.begin_pos
47
- end_pos = denotation.end_pos
48
- annotation = if denotation.id && !denotation.id.empty?
49
- get_annotations(denotation, relations)
50
- else
51
- get_obj(denotation.obj)
52
- end
53
+ def annotate_text_with_denotations(text, denotations, relations)
54
+ begin_pos = denotations.first.begin_pos
55
+ end_pos = denotations.first.end_pos
56
+ composite = compose_label(denotations, relations)
53
57
 
54
- annotated_text = "[#{text[begin_pos...end_pos]}][#{annotation}]"
58
+ annotated_text = "[#{text[begin_pos...end_pos]}][#{composite}]"
55
59
  text[0...begin_pos] + annotated_text + text[end_pos..]
56
60
  end
57
61
 
62
+ # Compose each denotation's label independently (honors get_obj URL →
63
+ # short-label lookup and per-denotation relation composition), then
64
+ # pipe-join with dedupe to preserve insertion order.
65
+ def compose_label(denotations, relations)
66
+ denotations.map { |d| label_for(d, relations) }.uniq.join("|")
67
+ end
68
+
69
+ def label_for(denotation, relations)
70
+ if denotation.id && !denotation.id.empty?
71
+ get_annotations(denotation, relations)
72
+ else
73
+ get_obj(denotation.obj)
74
+ end
75
+ end
76
+
58
77
  def labeled_entity_types
59
78
  return nil unless @config
60
79
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "entity_type_collection"
4
4
  require_relative "denotation"
5
+ require_relative "relation_without_denotation_error"
5
6
 
6
7
  class SimpleInlineTextAnnotation
7
8
  class Parser
@@ -21,12 +22,10 @@ class SimpleInlineTextAnnotation
21
22
 
22
23
  process_annotations(full_text)
23
24
 
24
- SimpleInlineTextAnnotation.new(
25
- full_text,
26
- @denotations,
27
- @relations,
28
- @entity_type_collection
29
- )
25
+ SimpleInlineTextAnnotation.new full_text,
26
+ @denotations,
27
+ @relations,
28
+ @entity_type_collection
30
29
  end
31
30
 
32
31
  private
@@ -58,6 +57,7 @@ class SimpleInlineTextAnnotation
58
57
  return match.end(0) unless process_annotation(match[2], begin_pos, end_pos)
59
58
 
60
59
  full_text[match.begin(0)...match.end(0)] = target_text
60
+
61
61
  end_pos
62
62
  end
63
63
 
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SimpleInlineTextAnnotation
4
+ class RelationWithoutDenotationError < StandardError; end
5
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SimpleInlineTextAnnotation
4
- VERSION = "2.0.0"
4
+ VERSION = "2.2.0"
5
5
  end
@@ -25,6 +25,7 @@ class SimpleInlineTextAnnotation
25
25
  @denotations = denotations
26
26
  @relations = relations
27
27
  @entity_type_collection = entity_type_collection
28
+ check_denotations_and_relations
28
29
  end
29
30
 
30
31
  def self.parse(source)
@@ -70,4 +71,19 @@ class SimpleInlineTextAnnotation
70
71
 
71
72
  { "entity types" => @entity_type_collection.to_config }
72
73
  end
74
+
75
+ def check_denotations_and_relations
76
+ @relations.each do |relation|
77
+ unless referable_to?(relation, @denotations)
78
+ raise SimpleInlineTextAnnotation::RelationWithoutDenotationError,
79
+ "Relation #{relation.inspect} refers to missing denotation."
80
+ end
81
+ end
82
+ end
83
+
84
+ def referable_to?(relation, denotations)
85
+ denotation_ids = denotations.map(&:id)
86
+
87
+ denotation_ids.include?(relation["subj"]) && denotation_ids.include?(relation["obj"])
88
+ end
73
89
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_inline_text_annotation
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - xaiBUh29wX
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-07-30 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: This gem provides inline text annotation functionality, extracted from
14
13
  PubAnnotation, with support for denotations, entity types, and nested spans.
@@ -20,6 +19,7 @@ extra_rdoc_files: []
20
19
  files:
21
20
  - ".rspec"
22
21
  - ".rubocop.yml"
22
+ - ".ruby-version"
23
23
  - CHANGELOG.md
24
24
  - CODE_OF_CONDUCT.md
25
25
  - LICENSE.txt
@@ -33,6 +33,7 @@ files:
33
33
  - lib/simple_inline_text_annotation/generator_error.rb
34
34
  - lib/simple_inline_text_annotation/parser.rb
35
35
  - lib/simple_inline_text_annotation/relation_validator.rb
36
+ - lib/simple_inline_text_annotation/relation_without_denotation_error.rb
36
37
  - lib/simple_inline_text_annotation/version.rb
37
38
  - sig/simple_inline_text_annotation.rbs
38
39
  homepage: https://github.com/pubannotation/simple_inline_annotation_format
@@ -42,7 +43,6 @@ metadata:
42
43
  homepage_uri: https://github.com/pubannotation/simple_inline_annotation_format
43
44
  changelog_uri: https://github.com/pubannotation/simple_inline_annotation_format/blob/master/ruby/CHANGELOG.md
44
45
  rubygems_uri: https://rubygems.org/gems/simple_inline_text_annotation
45
- post_install_message:
46
46
  rdoc_options: []
47
47
  require_paths:
48
48
  - lib
@@ -50,15 +50,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 3.1.0
53
+ version: 3.4.4
54
54
  required_rubygems_version: !ruby/object:Gem::Requirement
55
55
  requirements:
56
56
  - - ">="
57
57
  - !ruby/object:Gem::Version
58
58
  version: '0'
59
59
  requirements: []
60
- rubygems_version: 3.4.20
61
- signing_key:
60
+ rubygems_version: 3.6.7
62
61
  specification_version: 4
63
62
  summary: A Ruby gem for inline text annotation with denotations and entity types.
64
63
  test_files: []