ttl2html 3.0.0 → 3.1.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ttl2html/version.rb +1 -1
  3. data/lib/ttl2html.rb +111 -12
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8120a06f3f763ca13d8b9f35c920f9129d57876a5dde434b264c052bd1aa9151
4
- data.tar.gz: 9d1cd9d09f5a14a5d56856c00099b38abe059a9c27f775df2074b42db99e9a7a
3
+ metadata.gz: d7254a4cc200e719bbb09c33d88dea4a22a8360e1c6453085f5331d541320b82
4
+ data.tar.gz: 43385ec41709d6b8a552dfe7bb2c2d7186b1142b0655bd8480b2fc0b91b336d8
5
5
  SHA512:
6
- metadata.gz: 4935a3824f19a696ea4657d652c4afd482af6a8a2e33aa98725d4739375480654b86dd4a9781dae357d816d422dc7807a974bb7dfb38fbe1d6cb5d84a84a3792
7
- data.tar.gz: 9e561cb7abbfc195e8a13f09af9161a0b88f301e73b040ab149707e5f47905728a9ed603210b9f2818771350fd11670072941df4b58d92e4f5c268c608845274
6
+ metadata.gz: 0be4d1d1550a1eb410ffb8747eeffbd8b3154a297966cb99cde5ecb6c249973a8c345e88b01ab8cd7c1c791064ea78b6708ad7bc1579231b2f5424b8643af709
7
+ data.tar.gz: a5ec01247b296e2be84d110e4742412152465f9925223950211ca99a5c9f6112ebe81820fa6a422589f8cf45f5d41ecfb3b619c33e833694b9811cb80fb86541
@@ -1 +1 @@
1
- TTL2HTML::VERSION = "3.0.0"
1
+ TTL2HTML::VERSION = "3.1.0"
data/lib/ttl2html.rb CHANGED
@@ -72,9 +72,11 @@ module TTL2HTML
72
72
  $stderr.puts "#{count} triples. #{subjects.size} subjects."
73
73
  @data
74
74
  end
75
- def format_turtle(subject, depth = 1)
75
+ def format_turtle(subject, depth = 1, force = false)
76
76
  turtle = RDF::Turtle::Writer.new
77
77
  result = ""
78
+ #p [:format_turtle, subject, depth, force]
79
+ return result if !force && @cache[:output_turtle_files].include?(subject)
78
80
  if subject =~ /^_:/
79
81
  result << "[\n#{" "*depth}"
80
82
  else
@@ -92,14 +94,14 @@ module TTL2HTML
92
94
  [ @data[object.to_s][schema_position] ? @data[object.to_s][schema_position].first.to_i : Float::INFINITY,
93
95
  @data[object.to_s][qb_order] ? @data[object.to_s][qb_order].first.to_i : Float::INFINITY,
94
96
  @data[object.to_s][shacl_order] ? @data[object.to_s][shacl_order].first.to_i : Float::INFINITY,
95
- format_turtle(object, depth + 1)
97
+ format_turtle(object, depth + 1, true)
96
98
  ]
97
99
  else
98
100
  [Float::INFINITY, Float::INFINITY, Float::INFINITY, object.to_s]
99
101
  end
100
102
  end.map do |object|
101
103
  if /^_:/ =~ object.to_s # blank node:
102
- format_turtle(object, depth + 1)
104
+ format_turtle(object, depth + 1, force)
103
105
  elsif RDF::URI::IRI =~ object.to_s
104
106
  turtle.format_uri(RDF::URI.new object)
105
107
  elsif object.respond_to?(:first) and object.first.kind_of?(Symbol)
@@ -110,22 +112,117 @@ module TTL2HTML
110
112
  end.join(", ")
111
113
  str
112
114
  end.join(";\n#{" "*depth}")
113
- result << " ." if not subject =~ /^_:/
115
+ result << "." if not subject =~ /^_:/
114
116
  result << "\n"
115
117
  result << "#{" "*(depth-1)}]" if subject =~ /^_:/
118
+ @cache[:output_turtle_files] << subject unless force
116
119
  result
117
120
  end
118
121
  def format_turtle_inverse(object)
119
- result = ""
120
- return result if not object.start_with? @config[:base_uri]
121
- return result if not @data_inverse.has_key? object
122
- @data_inverse[object].keys.sort.each do |predicate|
123
- @data_inverse[object.to_s][predicate].sort.each do |subject|
124
- next if subject =~ /^_:/
125
- result << "<#{subject}> <#{predicate}> <#{object}>.\n"
122
+ triples = collect_inverse_triples(object)
123
+ return "" if triples.empty?
124
+ by_subject = build_subject_index(triples)
125
+ ref_count = build_object_ref_count(triples)
126
+ roots = find_inverse_roots(by_subject)
127
+ roots.map do |root|
128
+ "#{format_inverse_subject(root, by_subject, ref_count, Set.new, 1)}.\n"
129
+ end.join
130
+ end
131
+ def collect_inverse_triples(object, triples = Set.new, visited = Set.new)
132
+ return triples if object.to_s.start_with?("_:")
133
+ return triples unless object.to_s.start_with?(@config[:base_uri].to_s)
134
+ return triples unless @data_inverse.key?(object.to_s)
135
+ return triples if visited.include?(object.to_s)
136
+ visited << object.to_s
137
+ @data_inverse[object.to_s].each do |predicate, subjects|
138
+ subjects.each do |subject|
139
+ triples << [subject.to_s, predicate.to_s, object.to_s]
140
+ collect_inverse_triples_for_bnode(subject.to_s, triples, visited) if subject.to_s.start_with?("_:")
126
141
  end
127
142
  end
128
- result
143
+ triples
144
+ end
145
+ def collect_inverse_triples_for_bnode(node, triples, visited)
146
+ return triples unless @data_inverse.key?(node)
147
+ return triples if visited.include?(node)
148
+ visited << node
149
+ @data_inverse[node].each do |predicate, subjects|
150
+ subjects.each do |subject|
151
+ triples << [subject.to_s, predicate.to_s, node]
152
+ collect_inverse_triples_for_bnode(subject.to_s, triples, visited) if subject.to_s.start_with?("_:")
153
+ end
154
+ end
155
+ triples
156
+ end
157
+ def build_subject_index(triples)
158
+ by_subject = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = [] } }
159
+ triples.each do |subject, predicate, object|
160
+ by_subject[subject][predicate] << object
161
+ end
162
+ by_subject.each_value do |predicates|
163
+ predicates.each_value(&:uniq!)
164
+ end
165
+ by_subject
166
+ end
167
+ def build_object_ref_count(triples)
168
+ count = Hash.new(0)
169
+ triples.each do |_subject, _predicate, object|
170
+ count[object] += 1 if object.to_s.start_with?("_:")
171
+ end
172
+ count
173
+ end
174
+ def find_inverse_roots(by_subject)
175
+ all_subjects = by_subject.keys
176
+ all_objects = by_subject.values.flat_map { |preds| preds.values.flatten }.uniq
177
+ all_subjects.reject do |subject|
178
+ subject.start_with?("_:") || all_objects.include?(subject)
179
+ end.sort
180
+ end
181
+ def format_inverse_subject(subject, by_subject, ref_count, visited, depth = 1)
182
+ props = by_subject[subject]
183
+ return format_node(subject) if props.nil? || props.empty?
184
+ indent = " " * (depth - 1)
185
+ inner = " " * depth
186
+ if subject.start_with?("_:")
187
+ return "[]" if visited.include?(subject)
188
+ visited = visited.dup
189
+ visited << subject
190
+ head = "[\n#{inner}"
191
+ tail = "\n#{indent}]"
192
+ else
193
+ head = "<#{subject}> "
194
+ tail = ""
195
+ end
196
+ body = props.keys.sort.map do |predicate|
197
+ objects = props[predicate].sort.map do |object|
198
+ format_inverse_object(object, by_subject, ref_count, visited, depth + 1)
199
+ end.join(", ")
200
+ "<#{predicate}> #{objects}"
201
+ end.join(";\n#{inner}")
202
+ head + body + tail
203
+ end
204
+ def format_inverse_object(object, by_subject, ref_count, visited, depth = 1)
205
+ if object.to_s.start_with?("_:") && by_subject.key?(object.to_s)
206
+ if ref_count[object.to_s] <= 1
207
+ format_inverse_subject(object.to_s, by_subject, ref_count, visited, depth)
208
+ else
209
+ object.to_s
210
+ end
211
+ else
212
+ format_node(object)
213
+ end
214
+ end
215
+ def format_node(value)
216
+ turtle = RDF::Turtle::Writer.new
217
+ if value.to_s.start_with?("_:")
218
+ value.to_s
219
+ elsif RDF::URI::IRI =~ value.to_s
220
+ "<#{value}>"
221
+ elsif value.respond_to?(:first) && value.first.kind_of?(Symbol)
222
+ turtle.format_literal(RDF::Literal.new(value[1], language: value[0]))
223
+ else
224
+ turtle.format_literal(value)
225
+ end
129
226
  end
130
227
 
131
228
  def each_data(label = :each_data)
@@ -521,6 +618,8 @@ module TTL2HTML
521
618
  end
522
619
  dir = File.dirname(file)
523
620
  FileUtils.mkdir_p(dir) if not File.exist?(dir)
621
+ @cache ||= {}
622
+ @cache[:output_turtle_files] = Set.new
524
623
  str = format_turtle(uri)
525
624
  str << format_turtle_inverse(uri)
526
625
  open(file, "w") do |io|
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ttl2html
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masao Takaku
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-04-08 00:00:00.000000000 Z
10
+ date: 2026-04-12 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: nokogiri