ttl2html 2.3.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a699bd6e103ee6b88c617b6421fc7968fc63141c78d2dfff8aa75a64e7f271d
4
- data.tar.gz: bfc5ccd8c37e16939d1cd1fcb2fe80a6b4902b408d2def30750f10bd71a1b1be
3
+ metadata.gz: d7254a4cc200e719bbb09c33d88dea4a22a8360e1c6453085f5331d541320b82
4
+ data.tar.gz: 43385ec41709d6b8a552dfe7bb2c2d7186b1142b0655bd8480b2fc0b91b336d8
5
5
  SHA512:
6
- metadata.gz: 88d8e9f520c749186b33001e4102b711ed75c3f9ee8c0dbcdab5c86896b68ce141d19800aa8fc6307926dfc3f65451d0ee0f674f80052ba606ef7815f32d120e
7
- data.tar.gz: 2899b38935cc874ca678913830089f514b805ceb0ff55a64fd7cb2cda850a9238ad348b000aceb3be44bfc0bf5482e525050236d409d818eff16ec0b1019a655
6
+ metadata.gz: 0be4d1d1550a1eb410ffb8747eeffbd8b3154a297966cb99cde5ecb6c249973a8c345e88b01ab8cd7c1c791064ea78b6708ad7bc1579231b2f5424b8643af709
7
+ data.tar.gz: a5ec01247b296e2be84d110e4742412152465f9925223950211ca99a5c9f6112ebe81820fa6a422589f8cf45f5d41ecfb3b619c33e833694b9811cb80fb86541
@@ -9,7 +9,6 @@ require "nokogiri"
9
9
 
10
10
  module TTL2HTML
11
11
  class Template
12
- attr_reader :param
13
12
  include ERB::Util
14
13
  include I18n::Base
15
14
  include ActionView::Helpers::NumberHelper
@@ -20,15 +19,15 @@ module TTL2HTML
20
19
  @template_path << File.join(File.dirname(__FILE__), "..", "..", "templates")
21
20
  I18n.load_path << Dir[File.join(File.dirname(__FILE__), "..", "..", "locales") + "/*.yml"]
22
21
  I18n.load_path << Dir[File.expand_path("locales") + "/*.yml"]
23
- I18n.locale = @param[:locale] if @param[:locale]
22
+ I18n.locale = @param[:locale].to_sym if @param[:locale]
24
23
  end
25
24
  def output_to(file, param = {})
26
- @param.update(param)
27
- @param[:output_file] = file
25
+ param = @param.merge(param)
26
+ param[:output_file] = file
28
27
  dir = File.dirname(file)
29
28
  FileUtils.mkdir_p(dir) if not File.exist?(dir)
30
- open(file, "w") do |io|
31
- io.print to_html(@param)
29
+ File.open(file, "w") do |io|
30
+ io.print to_html(param)
32
31
  end
33
32
  end
34
33
  def to_html(param)
@@ -37,9 +36,9 @@ module TTL2HTML
37
36
  to_html_raw(layout_fname, param)
38
37
  end
39
38
  def to_html_raw(template, param)
40
- @param.update(param)
39
+ param = @param.merge(param)
41
40
  template = find_template_path(template)
42
- tmpl = open(template){|io| io.read }
41
+ tmpl = File.open(template) { |io| io.read }
43
42
  erb = ERB.new(tmpl, trim_mode: "-")
44
43
  erb.filename = template
45
44
  erb.result(binding)
@@ -121,13 +120,12 @@ module TTL2HTML
121
120
 
122
121
  # helper method:
123
122
  include TTL2HTML::Util
124
- def relative_path(dest)
123
+ def relative_path(src, dest)
125
124
  path = nil
126
125
  dest_uri = RDF::IRI.parse(dest)
127
126
  if dest_uri.absolute?
128
127
  path = dest
129
128
  else
130
- src = @param[:output_file]
131
129
  src = Pathname.new(src).relative_path_from(Pathname.new(@param[:output_dir])) if @param[:output_dir]
132
130
  path = Pathname(dest).relative_path_from(Pathname(File.dirname src))
133
131
  if @param[:output_dir] and File.directory?(Pathname.new(@param[:output_dir]) + path)
@@ -139,11 +137,11 @@ module TTL2HTML
139
137
  #p [ :relative_path, path, dest, src ]
140
138
  path
141
139
  end
142
- def relative_path_uri(dest_uri, base_uri = @param[:base_uri])
140
+ def relative_path_uri(src, dest_uri, base_uri = @param[:base_uri])
143
141
  if dest_uri.start_with? base_uri
144
142
  dest = dest_uri.sub(base_uri, "")
145
143
  dest = uri_mapping_to_path(dest, @param, "")
146
- relative_path(dest)
144
+ relative_path(src, dest)
147
145
  else
148
146
  dest_uri
149
147
  end
@@ -196,20 +194,23 @@ module TTL2HTML
196
194
  object
197
195
  end
198
196
  end
199
- def format_property(property, labels = {}, subject = nil)
200
- subject = @param[:blank_subject] if not subject and @param[:blank_subject]
201
- subject_class = @param[:data_global][subject][RDF.type.to_s]&.first if subject
202
- if subject_class and @param[:labels_with_class][subject_class] and @param[:labels_with_class][subject_class][property]
203
- @param[:labels_with_class][subject_class][property]
204
- elsif labels and labels[property]
205
- labels[property]
197
+ def format_property(property, param, subject = nil)
198
+ param = @param.merge(param)
199
+ subject = param[:blank_subject] if not subject and param[:blank_subject]
200
+ subject_class = param[:data_global][subject][RDF.type.to_s]&.first if subject
201
+ if subject_class and param[:labels_with_class][subject_class] and param[:labels_with_class][subject_class][property]
202
+ param[:labels_with_class][subject_class][property]
203
+ elsif param[:labels] and param[:labels][property]
204
+ param[:labels][property]
206
205
  else
207
206
  property.split(/[\/\#]/).last.capitalize
208
207
  end
209
208
  end
210
- def format_object(object, data, type = {})
209
+ def format_object(object, param)
210
+ type = param[:type] || {}
211
+ data = param[:data] || {}
211
212
  if /\Ahttps?:\/\// =~ object.to_s
212
- rel_path = relative_path_uri(object, param[:base_uri])
213
+ rel_path = relative_path_uri(param[:output_file], object)
213
214
  if param[:data_global][object]
214
215
  result = "<a href=\"#{rel_path}\">#{get_title(param[:data_global][object]) or ERB::Util.html_escape(object)}</a>"
215
216
  subtitle = get_subtitle(param[:data_global][object])
@@ -223,16 +224,17 @@ module TTL2HTML
223
224
  end
224
225
  elsif /\A_:/ =~ object.to_s and param[:data_global][object]
225
226
  if type[:inverse] and param[:data_inverse_global][object]
226
- format_triples(param[:data_inverse_global][object], inverse: true, blank: true)
227
+ format_triples(param[:data_inverse_global][object], param, inverse: true, blank: true)
227
228
  else
228
- format_triples(param[:data_global][object], blank: true)
229
+ format_triples(param[:data_global][object], param, blank: true)
229
230
  end
230
231
  else
231
232
  ERB::Util.html_escape object
232
233
  end
233
234
  end
234
- def format_triples(triples, type = {})
235
- param_local = @param.dup.merge(data: triples)
235
+ def format_triples(triples, param, type = {})
236
+ param_local = @param.dup.merge(param)
237
+ param_local = param_local.merge(data: triples)
236
238
  param_local[:type] = type
237
239
  if @param[:labels_with_class] and triples["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"]
238
240
  @param[:labels_with_class].reverse_each do |k, v|
@@ -324,6 +326,7 @@ module TTL2HTML
324
326
  value && !(value.respond_to?(:empty?) && value.empty?)
325
327
  end
326
328
  def sort_criteria(val, data_global)
329
+ raise "sort_criteria got nil." if data_global.nil?
327
330
  resource = data_global[val]
328
331
  results = []
329
332
  [
@@ -350,4 +353,4 @@ module TTL2HTML
350
353
  results
351
354
  end
352
355
  end
353
- end
356
+ end
data/lib/ttl2html/util.rb CHANGED
@@ -1,29 +1,44 @@
1
1
  module TTL2HTML
2
2
  module Util
3
- def uri_mapping_to_path(uri, param, suffix = ".html")
4
- path = nil
3
+ def make_mapping_uris_cache(param)
4
+ @path_cache = Set.new
5
+ data = @data || @param[:data_global] || @param[:data] || {}
6
+ data.keys.each do |uri|
7
+ local_path = _uri_mapping_to_path(uri, param)
8
+ # 親パスをすべて抽出してSetに追加
9
+ idx = 0
10
+ while (idx = local_path.index("/", idx))
11
+ parent = local_path[0...idx]
12
+ @path_cache << parent unless parent.empty?
13
+ idx += 1
14
+ end
15
+ end
16
+ @path_cache
17
+ end
18
+ def _uri_mapping_to_path(uri, param, suffix = ".html")
19
+ local_path = uri.sub(param[:base_uri], "")
5
20
  if param[:uri_mappings]
6
21
  param[:uri_mappings].each do |mapping|
7
- local_file = uri.sub(param[:base_uri], "")
8
- if mapping["regexp"] =~ local_file
9
- path = local_file.sub(mapping["regexp"], mapping["path"])
22
+ if mapping["regexp"] =~ local_path
23
+ #p [mapping["regexp"], local_path]
24
+ local_path = local_path.sub(mapping["regexp"], mapping["path"])
25
+ #p [mapping["regexp"], local_path]
10
26
  end
11
27
  end
12
28
  end
13
- if path.nil?
14
- if suffix == ".html"
15
- if @data.keys.find{|e| e.start_with?(uri + "/") }
16
- path = uri + "/index"
17
- elsif uri.end_with?("/")
18
- path = uri + "index"
19
- else
20
- path = uri
21
- end
22
- else
23
- path = uri
29
+ local_path
30
+ end
31
+ def uri_mapping_to_path(uri, param, suffix = ".html")
32
+ path = nil
33
+ @path_cache ||= make_mapping_uris_cache(param)
34
+ path = _uri_mapping_to_path(uri, param, suffix)
35
+ if suffix == ".html"
36
+ if @path_cache.include? path
37
+ path += "/index"
38
+ elsif path.end_with?("/")
39
+ path += "index"
24
40
  end
25
41
  end
26
- path = path.sub(param[:base_uri], "")
27
42
  path << suffix
28
43
  #p [uri, path]
29
44
  path
@@ -1 +1 @@
1
- TTL2HTML::VERSION = "2.3.1"
1
+ TTL2HTML::VERSION = "3.1.0"
data/lib/ttl2html.rb CHANGED
@@ -27,7 +27,7 @@ module TTL2HTML
27
27
 
28
28
  def load_config(file)
29
29
  config = { output_turtle: true }
30
- open(file) do |io|
30
+ File.open(file) do |io|
31
31
  YAML.safe_load(io, permitted_classes: [Regexp]).each do |k, v|
32
32
  config[k.intern] = v
33
33
  end
@@ -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)
@@ -135,7 +232,9 @@ module TTL2HTML
135
232
  format: "(%t) %a %e %P% Processed: %c from %C"
136
233
  }
137
234
  data = @data.keys.sort_by do|uri|
138
- [ uri.count("/"), uri.size, uri ]
235
+ local_path = uri_mapping_to_path(uri, @config, ".html")
236
+ #p [ local_path.size, local_path.count("/"), local_path ]
237
+ [ local_path.size, local_path.count("/"), local_path ]
139
238
  end.reverse
140
239
  Parallel.each(data, progress: progressbar_options) do |uri|
141
240
  next if not uri.start_with? @config[:base_uri]
@@ -168,8 +267,8 @@ module TTL2HTML
168
267
  end
169
268
  @config[:orders_with_class] = shapes2orders(shapes)
170
269
  Dir.mkdir @config[:output_dir] if @config[:output_dir] and not File.exist? @config[:output_dir]
270
+ template = Template.new("default.html.erb", @config)
171
271
  each_data(:output_html_files) do |uri, v|
172
- template = Template.new("default.html.erb", @config)
173
272
  param = @config.dup
174
273
  param[:uri] = uri
175
274
  param[:turtle_uri] = uri + ".ttl"
@@ -185,6 +284,7 @@ module TTL2HTML
185
284
  param[:breadcrumbs_items] = build_breadcrumbs(uri, template)
186
285
  end
187
286
  file = uri_mapping_to_path(uri, @config, ".html")
287
+ #p [:each_data, uri, file]
188
288
  if @config[:output_dir]
189
289
  file = File.join(@config[:output_dir], file)
190
290
  end
@@ -262,7 +362,7 @@ module TTL2HTML
262
362
  label = template.get_title(@data[target_class], nil)
263
363
  comment = template.get_language_literal(@data[target_class]["http://www.w3.org/2000/01/rdf-schema#comment"]) if @data[target_class]["http://www.w3.org/2000/01/rdf-schema#comment"]
264
364
  else
265
- label = template.format_property(target_class)
365
+ label = template.format_property(target_class, param)
266
366
  end
267
367
  else
268
368
  label = template.get_title(@data[subject.to_s])
@@ -518,6 +618,8 @@ module TTL2HTML
518
618
  end
519
619
  dir = File.dirname(file)
520
620
  FileUtils.mkdir_p(dir) if not File.exist?(dir)
621
+ @cache ||= {}
622
+ @cache[:output_turtle_files] = Set.new
521
623
  str = format_turtle(uri)
522
624
  str << format_turtle_inverse(uri)
523
625
  open(file, "w") do |io|
@@ -532,6 +634,7 @@ module TTL2HTML
532
634
  end
533
635
 
534
636
  def cleanup
637
+ dirs = []
535
638
  @data.select do |uri, v|
536
639
  uri.start_with? @config[:base_uri]
537
640
  end.sort_by do |uri, v|
@@ -539,19 +642,31 @@ module TTL2HTML
539
642
  end.each do |uri, v|
540
643
  html_file = uri_mapping_to_path(uri, @config, ".html")
541
644
  html_file = File.join(@config[:output_dir], html_file) if @config[:output_dir]
645
+ dirs << File.dirname(html_file)
542
646
  File.unlink html_file if File.exist? html_file
543
647
  ttl_file = uri_mapping_to_path(uri, @config, ".ttl")
544
648
  ttl_file = File.join(@config[:output_dir], ttl_file) if @config[:output_dir]
545
649
  File.unlink ttl_file if File.exist? ttl_file
546
650
  dir = uri.sub(@config[:base_uri], "")
547
651
  dir = File.join(@config[:output_dir], dir) if @config[:output_dir]
548
- Dir.rmdir dir if File.exist? dir
652
+ dirs << dir
549
653
  end
550
654
  index_html = "index.html"
551
655
  index_html = File.join(@config[:output_dir], "index.html") if @config[:output_dir]
552
656
  if @config[:top_class] and File.exist? index_html
553
657
  File.unlink index_html
554
658
  end
659
+ about_html = (@config[:about_file] || "about.html")
660
+ about_html = File.join(@config[:output_dir], about_html) if @config[:output_dir]
661
+ File.unlink about_html if File.exist? about_html
662
+
663
+ dirs = dirs.uniq.sort_by{|e| -(e.size) }
664
+ #p dirs
665
+ dirs.each do |dir|
666
+ next if dir == "." # failsafe...
667
+ next if dir == @config[:output_dir] # failsafe...
668
+ FileUtils.remove_entry_secure(dir) if File.exist? dir
669
+ end
555
670
  end
556
671
  end
557
672
 
data/locales/en.yml CHANGED
@@ -16,6 +16,7 @@ en:
16
16
  default:
17
17
  details: Details
18
18
  inverse_data: Referred resources
19
+ copy-link: "Copy Link"
19
20
  index:
20
21
  derivedfrom-text: "This dataset is created by editing <a href=\"%{url}\">\"%{label}\"</a>."
21
22
  list: "List of %{resource}"
data/locales/ja.yml CHANGED
@@ -16,6 +16,7 @@ ja:
16
16
  default:
17
17
  details: 詳細情報
18
18
  inverse_data: 被参照情報
19
+ copy-link: "リンクをコピー"
19
20
  index:
20
21
  derivedfrom-text: "このデータセットは<a href=\"%{url}\">「%{label}」</a>を加工して作成。"
21
22
  list: "%{resource} 一覧"
@@ -36,7 +36,7 @@
36
36
  <%- if param[:shapes].size > 0 -%>
37
37
  <h2 id="shapes"><%=h t("about.shape-heading") %></h2>
38
38
  <%- param[:shapes].keys.sort_by{|k| param[:shapes][k][:order] }.each do |shape| -%>
39
- <h3 id="<%=h relative_path_uri(shape) %>"><%=h param[:shapes][shape][:label] %></h3>
39
+ <h3 id="<%=h relative_path_uri(param[:output_file], shape) %>"><%=h param[:shapes][shape][:label] %></h3>
40
40
  <%- if param[:shapes][shape][:target_class] -%>
41
41
  <ul><li><%= t("about.shape-target") %>: <a href="<%=h param[:shapes][shape][:target_class] %>"><%=h param[:shapes][shape][:target_class] %></a></li></ul>
42
42
  <%- end -%>
@@ -4,7 +4,69 @@
4
4
  <%- if param[:subtitle] -%>
5
5
  <p class="lead"><%= param[:subtitle] %></p>
6
6
  <%- end -%>
7
- <p><i class="bi bi-link-45deg"></i> <a href="<%=h param[:uri] %>"><%=h param[:uri] %></a></p>
7
+ <p>
8
+ <i class="bi bi-link-45deg"></i> <a href="<%=h param[:uri] %>"><%=h param[:uri] %></a>
9
+ <%- if param[:enable_copy_link_button] -%>
10
+ <button id="copy-link" class="btn btn-sm btn-outline-secondary" aria-live="polite">
11
+ <i class="bi bi-copy"></i> <span class="copy-link-text"><%=h t("default.copy-link") %></span>
12
+ </button>
13
+ <%- end -%>
14
+ </p>
15
+ <%- if param[:enable_copy_link_button] -%>
16
+ <script>
17
+ (() => {
18
+ const btn = document.getElementById("copy-link");
19
+ const url = '<%=h param[:uri] %>';
20
+ const originalHTML = btn.innerHTML;
21
+ let timerId = null;
22
+ async function copyText(text) {
23
+ // Clipboard API, or fallback
24
+ if (navigator.clipboard && window.isSecureContext) {
25
+ await navigator.clipboard.writeText(text);
26
+ return;
27
+ }
28
+ const ta = document.createElement("textarea");
29
+ ta.value = text;
30
+ ta.style.position = "fixed";
31
+ ta.style.left = "-9999px";
32
+ document.body.appendChild(ta);
33
+ ta.select();
34
+ document.execCommand("copy");
35
+ document.body.removeChild(ta);
36
+ }
37
+ function setCopiedState() {
38
+ btn.innerHTML = `<i class="bi bi-check2"></i> <span class="btn-label">Copied!</span>`;
39
+ btn.classList.remove("btn-outline-secondary");
40
+ btn.classList.add("btn-outline-success");
41
+ }
42
+ function resetState() {
43
+ btn.innerHTML = originalHTML;
44
+ btn.classList.remove("btn-outline-success");
45
+ btn.classList.add("btn-outline-secondary");
46
+ }
47
+ btn.addEventListener("click", async () => {
48
+ btn.disabled = true;
49
+ try {
50
+ await copyText(url);
51
+ setCopiedState();
52
+ clearTimeout(timerId);
53
+ timerId = setTimeout(() => {
54
+ resetState();
55
+ btn.disabled = false;
56
+ }, 1500);
57
+ } catch (e) {
58
+ // fail...
59
+ btn.innerHTML = `<span class="btn-label">Copy failed...</span>`;
60
+ clearTimeout(timerId);
61
+ timerId = setTimeout(() => {
62
+ resetState();
63
+ btn.disabled = false;
64
+ }, 1500);
65
+ }
66
+ });
67
+ })();
68
+ </script>
69
+ <%- end -%>
8
70
  </div>
9
71
  </div>
10
72
  <div class="container">
@@ -13,11 +75,11 @@
13
75
  <div class="col-md-12">
14
76
  <nav aria-label="breadcrumb">
15
77
  <ol class="breadcrumb">
16
- <li class="breadcrumb-item"><a href="<%=h relative_path_uri(param[:base_uri]) %>"><i class="bi bi-house-door-fill"></i> Home</a></li>
78
+ <li class="breadcrumb-item"><a href="<%=h relative_path_uri(param[:output_file], param[:base_uri]) %>"><i class="bi bi-house-door-fill"></i> Home</a></li>
17
79
  <%- param[:breadcrumbs_items].reverse.each_with_index do |e, i| -%>
18
80
  <li class="breadcrumb-item<%= ' active' if i == param[:breadcrumbs_items].size-1 %>" aria-current="page">
19
81
  <%- if e[:uri] -%>
20
- <a href="<%=h relative_path_uri(e[:uri]) %>"><%=h e[:label] %></a>
82
+ <a href="<%=h relative_path_uri(param[:output_file], e[:uri]) %>"><%=h e[:label] %></a>
21
83
  <%- else -%>
22
84
  <%=h e[:label] %>
23
85
  <%- end -%>
@@ -31,7 +93,7 @@
31
93
  <div class="row">
32
94
  <div class="col-md-12">
33
95
  <h2><%=h t("default.details") %></h2>
34
- <%= format_triples(param[:data]) %>
96
+ <%= format_triples(param[:data], param) %>
35
97
  </div>
36
98
  </div>
37
99
  <%- if param[:additional_content] -%>
@@ -41,7 +103,7 @@
41
103
  <div class="row inverse">
42
104
  <div class="col-md-12">
43
105
  <h2><%=h t("default.inverse_data") %></h2>
44
- <%= format_triples(param[:data_inverse], inverse: true) %>
106
+ <%= format_triples(param[:data_inverse], param, inverse: true) %>
45
107
  </div>
46
108
  </div>
47
109
  <%- end -%>
@@ -4,11 +4,11 @@
4
4
  <ul>
5
5
  <%- param[:index_data].each do |e| -%>
6
6
  <%- e.each do |s, o| -%>
7
- <li><%= format_object(s, param[:data_global]) %>
7
+ <li><%= format_object(s, param) %>
8
8
  <%- if o and o.size > 0 -%>
9
9
  <ul>
10
10
  <%- o.each do |o2| -%>
11
- <li><%= format_object(o2.to_s, param[:data_global]) %></li>
11
+ <li><%= format_object(o2.to_s, param) %></li>
12
12
  <%- end -%>
13
13
  </ul>
14
14
  <%- end -%>
@@ -9,7 +9,7 @@
9
9
  <%- end -%>
10
10
  <p><i class="bi bi-link-45deg"></i> <a href="<%=h param[:base_uri] %>"><%=h param[:base_uri] %></a></p>
11
11
  <%- if param[:about_file] -%>
12
- <p><a class="btn btn-info" href="<%=h relative_path(param[:about_file]) %>"><%=h t("about.title", title: param[:site_title]) %> &raquo;</a></p>
12
+ <p><a class="btn btn-info" href="<%=h relative_path(param[:output_file], param[:about_file]) %>"><%=h t("about.title", title: param[:site_title]) %> &raquo;</a></p>
13
13
  <%- end -%>
14
14
  </div>
15
15
  </div>
@@ -23,7 +23,7 @@
23
23
  <%= format_version_info(param[:versions].last) %>
24
24
  </dl>
25
25
  <%- if param[:about_file] and param[:versions].size > 1 -%>
26
- <p><a href="<%=h relative_path(param[:about_file]) %>#versions">&raquo; <%=h t("index.past-versions") %></a></p>
26
+ <p><a href="<%=h relative_path(param[:output_file], param[:about_file]) %>#versions">&raquo; <%=h t("index.past-versions") %></a></p>
27
27
  <%- end -%>
28
28
  <%- if param[:toplevel] and not param[:toplevel][:license].empty? -%>
29
29
  <p class="license">
@@ -9,7 +9,7 @@
9
9
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/afeld/bootstrap-toc/dist/bootstrap-toc.min.css">
10
10
  <%- end -%>
11
11
  <%- Array(param[:css_file]).each do |file| -%>
12
- <link rel="stylesheet" href="<%=h relative_path(file) %>">
12
+ <link rel="stylesheet" href="<%=h relative_path(param[:output_file], file) %>">
13
13
  <%- end -%>
14
14
  <%- if param[:custom_css] -%>
15
15
  <style type="text/css"><%=h param[:custom_css] %></style>
@@ -51,8 +51,8 @@
51
51
  <body<%= ' data-spy="scroll" data-target="#toc"' if @template == "about.html.erb" and param[:about_toc] %>>
52
52
  <nav class="navbar navbar-expand-lg <%=h param[:navbar_class] || "navbar-light" %>">
53
53
  <%- if param[:logo] -%>
54
- <a class="navbar-brand" href="<%=h relative_path_uri(param[:base_uri]) %>">
55
- <img src="<%=h relative_path(param[:logo]) %>" style="max-height: 54px" alt="<%=h param[:site_title] %>">
54
+ <a class="navbar-brand" href="<%=h relative_path_uri(param[:output_file], param[:base_uri]) %>">
55
+ <img src="<%=h relative_path(param[:output_file], param[:logo]) %>" style="max-height: 54px" alt="<%=h param[:site_title] %>">
56
56
  </a>
57
57
  <%- end -%>
58
58
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
@@ -61,16 +61,16 @@
61
61
  <div class="collapse navbar-collapse" id="navbarSupportedContent">
62
62
  <ul class="navbar-nav mr-auto">
63
63
  <li class="nav-item<%= ' active' if @template == "index.html.erb" %>">
64
- <a class="nav-link" href="<%=h relative_path_uri(param[:base_uri]) %>">Home</a>
64
+ <a class="nav-link" href="<%=h relative_path_uri(param[:output_file], param[:base_uri]) %>">Home</a>
65
65
  </li>
66
66
  <%- if param[:about_file] -%>
67
67
  <li class="nav-item<%= ' active' if @template == "about.html.erb" %>">
68
- <a class="nav-link" href="<%=h relative_path(param[:about_file]) %>">About</a>
68
+ <a class="nav-link" href="<%=h relative_path(param[:output_file], param[:about_file]) %>">About</a>
69
69
  </li>
70
70
  <%- end -%>
71
71
  <%- if param[:additional_link] -%>
72
72
  <%- param[:additional_link].each do |link| -%>
73
- <li class="nav-item"><a class="nav-link" href="<%=h link["href"] %>"><%=h link["label"] %></a></li>
73
+ <li class="nav-item"><a class="nav-link" href="<%=h relative_path_uri(param[:output_file], link["href"]) %>"><%=h link["label"] %></a></li>
74
74
  <%- end -%>
75
75
  <%- end -%>
76
76
  </ul>
@@ -103,7 +103,7 @@
103
103
  <script src="https://cdn.jsdelivr.net/gh/afeld/bootstrap-toc/dist/bootstrap-toc.min.js"></script>
104
104
  <%- end -%>
105
105
  <%- Array(param[:javascript_file]).each do |file| -%>
106
- <script src="<%=h relative_path(file) %>"></script>
106
+ <script src="<%=h relative_path(param[:output_file], file) %>"></script>
107
107
  <%- end -%>
108
108
  </body>
109
109
  </html>
@@ -1,4 +1,4 @@
1
1
  <dl class="row border">
2
- <dt class="col-sm-3"><%= format_object(param[:blank_subject], param[:data]) %></dt>
3
- <dd class="col-sm-9"><%= format_triples(param[:blank_triples]) %></dd>
2
+ <dt class="col-sm-3"><%= format_object(param[:blank_subject], param) %></dt>
3
+ <dd class="col-sm-9"><%= format_triples(param[:blank_triples], param) %></dd>
4
4
  </dl>
@@ -4,17 +4,17 @@
4
4
  order = Float::INFINITY if order.nil?
5
5
  [ order, k ]
6
6
  }.each do |k, v| -%>
7
- <dt class="col-sm-3"><%=h t('triples.inverse_refered', property: format_property(k, param[:labels], v.first)) %></dt>
7
+ <dt class="col-sm-3"><%=h t('triples.inverse_refered', property: format_property(k, param, v.first)) %></dt>
8
8
  <%- if v.respond_to? :has_key? -%>
9
9
  <% v.each_with_index do |v2, idx| %>
10
- <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>" lang="<%=h v2[0] %>"><%= format_object v2[1], param[:data], param[:type] %></dd>
10
+ <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>" lang="<%=h v2[0] %>"><%= format_object v2[1], param %></dd>
11
11
  <% end %>
12
12
  <%- elsif v.size > 1 -%>
13
13
  <%- v.each_with_index do |v2, idx| -%>
14
- <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>"><%= format_object v2, param[:data], param[:type] %></dd>
14
+ <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>"><%= format_object v2, param %></dd>
15
15
  <%- end -%>
16
16
  <%- else -%>
17
- <dd class="col-sm-9"><%= format_object v.first, param[:data], param[:type] %></dd>
17
+ <dd class="col-sm-9"><%= format_object v.first, param %></dd>
18
18
  <%- end -%>
19
19
  <%- end -%>
20
20
  </dl>
@@ -9,15 +9,15 @@
9
9
  order = Float::INFINITY if order.nil?
10
10
  [ order, k ]
11
11
  }.each do |k, v| -%>
12
- <dt class="col-sm-3"><%=h format_property(k, param[:labels]) %></dt>
12
+ <dt class="col-sm-3"><%=h format_property(k, param) %></dt>
13
13
  <%- v.sort_by{|e| sort_criteria(e, param[:data_global]) }.each_with_index do |v2, idx| -%>
14
14
  <%- if v2.respond_to? :language and v2.language? -%>
15
- <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>" lang="<%=h v2.language %>" itemprop="<%=h k %>"><%= format_object v2, param[:data], param[:type] %></dd>
15
+ <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>" lang="<%=h v2.language %>" itemprop="<%=h k %>"><%= format_object v2, param %></dd>
16
16
  <%- elsif /\Ahttps?:\/\// =~ v2.to_s -%>
17
17
  <%- itemtypes = param[:data_global][v2.to_s] ? param[:data_global][v2.to_s][RDF.type.to_s].to_a.join(" ") : "" -%>
18
- <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>" itemscope itemprop="<%=h k %>" itemid="<%=h v2.to_s %>"<% unless itemtypes.empty? %> itemtype="<%=h itemtypes %>"<% end %>><%= format_object v2, param[:data], param[:type] %></dd>
18
+ <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>" itemscope itemprop="<%=h k %>" itemid="<%=h v2.to_s %>"<% unless itemtypes.empty? %> itemtype="<%=h itemtypes %>"<% end %>><%= format_object v2, param %></dd>
19
19
  <%- else -%>
20
- <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>" itemprop="<%=h k %>"><%= format_object v2, param[:data], param[:type] %></dd>
20
+ <dd class="col-sm-9<%= ' offset-sm-3' if idx > 0 %>" itemprop="<%=h k %>"><%= format_object v2, param %></dd>
21
21
  <%- end -%>
22
22
  <%- end -%>
23
23
  <%- end -%>
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: 2.3.1
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-01-24 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