coradoc 2.0.1 → 2.0.3

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +77 -146
  3. data/coradoc-adoc/lib/coradoc/asciidoc/model/base.rb +4 -3
  4. data/coradoc-adoc/lib/coradoc/asciidoc/model/document.rb +1 -1
  5. data/coradoc-adoc/lib/coradoc/asciidoc/model/include.rb +1 -1
  6. data/coradoc-adoc/lib/coradoc/asciidoc/model/resolver.rb +2 -2
  7. data/coradoc-adoc/lib/coradoc/asciidoc/model/serialization/asciidoc_transform.rb +3 -3
  8. data/coradoc-adoc/lib/coradoc/asciidoc/model/table_row.rb +1 -1
  9. data/coradoc-adoc/lib/coradoc/asciidoc/model/text_element.rb +4 -8
  10. data/coradoc-adoc/lib/coradoc/asciidoc/parse_error.rb +6 -6
  11. data/coradoc-adoc/lib/coradoc/asciidoc/serializer/adoc_serializer.rb +5 -10
  12. data/coradoc-adoc/lib/coradoc/asciidoc/serializer/formatter.rb +4 -3
  13. data/coradoc-adoc/lib/coradoc/asciidoc/serializer/serializers/base.rb +8 -20
  14. data/coradoc-adoc/lib/coradoc/asciidoc/serializer/serializers/block/core.rb +1 -1
  15. data/coradoc-adoc/lib/coradoc/asciidoc/serializer/serializers/document.rb +3 -6
  16. data/coradoc-adoc/lib/coradoc/asciidoc/serializer/serializers/inline/strikethrough.rb +1 -1
  17. data/coradoc-adoc/lib/coradoc/asciidoc/serializer/serializers/list/item.rb +5 -9
  18. data/coradoc-adoc/lib/coradoc/asciidoc/transform/from_core_model.rb +26 -34
  19. data/coradoc-adoc/lib/coradoc/asciidoc/transform/from_core_model_registrations.rb +18 -18
  20. data/coradoc-adoc/lib/coradoc/asciidoc/transform/to_core_model.rb +96 -123
  21. data/coradoc-adoc/lib/coradoc/asciidoc/transform/to_core_model_registrations.rb +10 -6
  22. data/coradoc-adoc/lib/coradoc/asciidoc/transformer/header_rules.rb +5 -5
  23. data/coradoc-adoc/lib/coradoc/asciidoc/transformer/list_rules.rb +2 -2
  24. data/coradoc-adoc/lib/coradoc/asciidoc/transformer/structural_rules.rb +1 -1
  25. data/coradoc-adoc/lib/coradoc/asciidoc/transformer.rb +5 -5
  26. data/coradoc-adoc/lib/coradoc/asciidoc.rb +1 -1
  27. data/coradoc-adoc/lib/coradoc/util/asciidoc.rb +4 -3
  28. data/coradoc-adoc/spec/coradoc/asciidoc/transform/from_core_model_spec.rb +4 -2
  29. data/coradoc-docx/lib/coradoc/docx/transform/context.rb +1 -1
  30. data/coradoc-docx/lib/coradoc/docx/transform/from_core_model.rb +13 -6
  31. data/coradoc-docx/lib/coradoc/docx/transform/numbering_resolver.rb +9 -7
  32. data/coradoc-docx/lib/coradoc/docx/transform/ordered_content.rb +2 -2
  33. data/coradoc-docx/lib/coradoc/docx/transform/rules/image_rule.rb +4 -1
  34. data/coradoc-docx/lib/coradoc/docx/transform/rules/math_rule.rb +2 -1
  35. data/coradoc-docx/lib/coradoc/docx/transform/rules/run_rule.rb +20 -30
  36. data/coradoc-docx/lib/coradoc/docx/transform/rules/simple_field_rule.rb +7 -5
  37. data/coradoc-docx/lib/coradoc/docx/transform/rules/table_rule.rb +3 -5
  38. data/coradoc-docx/lib/coradoc/docx/transform/style_resolver.rb +19 -24
  39. data/coradoc-docx/lib/coradoc/docx/transform/to_core_model.rb +18 -11
  40. data/coradoc-docx/lib/coradoc/docx.rb +6 -4
  41. data/coradoc-docx/spec/coradoc/docx/transform/from_core_model_spec.rb +5 -2
  42. data/coradoc-docx/spec/coradoc/docx/transform/rules/rule_unit_spec.rb +27 -7
  43. data/coradoc-docx/spec/coradoc/docx/transform/to_core_model_spec.rb +6 -2
  44. data/coradoc-html/lib/coradoc/html/base.rb +3 -7
  45. data/coradoc-html/lib/coradoc/html/converter_base.rb +5 -15
  46. data/coradoc-html/lib/coradoc/html/converters/base.rb +22 -28
  47. data/coradoc-html/lib/coradoc/html/converters/comment_line.rb +2 -2
  48. data/coradoc-html/lib/coradoc/html/converters/link.rb +1 -1
  49. data/coradoc-html/lib/coradoc/html/converters/list_item.rb +3 -3
  50. data/coradoc-html/lib/coradoc/html/converters/ordered.rb +1 -1
  51. data/coradoc-html/lib/coradoc/html/converters/span.rb +2 -2
  52. data/coradoc-html/lib/coradoc/html/converters/table.rb +3 -3
  53. data/coradoc-html/lib/coradoc/html/converters/table_cell.rb +14 -28
  54. data/coradoc-html/lib/coradoc/html/converters/table_row.rb +2 -2
  55. data/coradoc-html/lib/coradoc/html/converters/text_element.rb +1 -5
  56. data/coradoc-html/lib/coradoc/html/input/converters/a.rb +2 -2
  57. data/coradoc-html/lib/coradoc/html/input/converters/dl.rb +1 -1
  58. data/coradoc-html/lib/coradoc/html/input/converters/figure.rb +2 -2
  59. data/coradoc-html/lib/coradoc/html/input/converters/markup.rb +3 -3
  60. data/coradoc-html/lib/coradoc/html/input/converters/p.rb +1 -1
  61. data/coradoc-html/lib/coradoc/html/input/converters/td.rb +1 -1
  62. data/coradoc-html/lib/coradoc/html/input/converters.rb +1 -2
  63. data/coradoc-html/lib/coradoc/html/input/html_converter.rb +3 -3
  64. data/coradoc-html/lib/coradoc/html/input/plugin.rb +2 -2
  65. data/coradoc-html/lib/coradoc/html/renderer.rb +4 -6
  66. data/coradoc-html/lib/coradoc/html/theme/base.rb +2 -3
  67. data/coradoc-html/lib/coradoc/html/theme/classic_renderer.rb +9 -12
  68. data/coradoc-html/lib/coradoc/html/theme/modern/serializers/document_serializer.rb +3 -3
  69. data/coradoc-html/lib/coradoc/html.rb +1 -1
  70. data/coradoc-markdown/lib/coradoc/markdown/model/base.rb +6 -5
  71. data/coradoc-markdown/lib/coradoc/markdown/serializer.rb +2 -2
  72. data/coradoc-markdown/lib/coradoc/markdown/toc_generator.rb +4 -5
  73. data/coradoc-markdown/lib/coradoc/markdown/transform/from_core_model.rb +2 -2
  74. data/coradoc-markdown/lib/coradoc/markdown/transformer.rb +5 -3
  75. data/coradoc-markdown/lib/coradoc/markdown.rb +1 -1
  76. data/lib/coradoc/configurable.rb +6 -2
  77. data/lib/coradoc/coradoc.rb +18 -16
  78. data/lib/coradoc/core_model/base.rb +3 -3
  79. data/lib/coradoc/core_model/list_item.rb +3 -3
  80. data/lib/coradoc/core_model/toc_generator.rb +1 -1
  81. data/lib/coradoc/document_manipulator.rb +9 -13
  82. data/lib/coradoc/format_module.rb +16 -4
  83. data/lib/coradoc/input.rb +1 -1
  84. data/lib/coradoc/output.rb +1 -1
  85. data/lib/coradoc/query.rb +38 -186
  86. data/lib/coradoc/registry.rb +5 -7
  87. data/lib/coradoc/serializer/registry.rb +3 -5
  88. data/lib/coradoc/validation.rb +40 -21
  89. data/lib/coradoc/version.rb +1 -1
  90. metadata +1 -1
@@ -45,10 +45,8 @@ module Coradoc
45
45
  private
46
46
 
47
47
  def effective_props(run)
48
- if run.respond_to?(:effective_run_properties)
49
- ep = run.effective_run_properties
50
- return ep if ep
51
- end
48
+ ep = run.effective_run_properties
49
+ return ep if ep
52
50
 
53
51
  run.properties
54
52
  end
@@ -66,15 +64,12 @@ module Coradoc
66
64
  result << context.transform(drawing)
67
65
  end
68
66
 
69
- # Tab character
70
- result << "\t" if run.respond_to?(:tab) && run.tab
67
+ result << "\t" if run.tab
71
68
 
72
- # Inline math (m:oMath within a run)
73
- result << context.transform(run.o_math) if run.respond_to?(:o_math) && run.o_math
69
+ result << context.transform(run.o_math) if run.class.attributes.key?(:o_math) && run.o_math
74
70
 
75
- # Deleted text (tracked changes)
76
- if run.respond_to?(:del_text) && run.del_text
77
- text = run.del_text.respond_to?(:content) ? run.del_text.content.to_s : run.del_text.to_s
71
+ if run.del_text
72
+ text = run.del_text.is_a?(Uniword::Wordprocessingml::DeletedText) ? run.del_text.content.to_s : run.del_text.to_s
78
73
  unless text.empty?
79
74
  result << CoreModel::InlineElement.new(
80
75
  format_type: 'strikethrough',
@@ -83,42 +78,37 @@ module Coradoc
83
78
  end
84
79
  end
85
80
 
86
- # Symbol (w:sym)
87
- if run.respond_to?(:sym) && run.sym
88
- sym = run.sym
89
- char = sym.respond_to?(:char) ? sym.char : nil
81
+ if run.sym
82
+ char = run.sym.char
90
83
  result << char.to_s if char && !char.empty?
91
84
  end
92
85
 
93
- # No-break hyphen (U+2011)
94
- result << "\u2011" if run.respond_to?(:no_break_hyphen) && run.no_break_hyphen
86
+ result << "\u2011" if run.no_break_hyphen
95
87
 
96
- # Soft hyphen (U+00AD)
97
- result << "\u00AD" if run.respond_to?(:soft_hyphen) && run.soft_hyphen
88
+ result << "\u00AD" if run.class.attributes.key?(:soft_hyphen) && run.soft_hyphen
98
89
 
99
- # Carriage return (w:cr) treat as line break
100
- result << CoreModel::InlineElement.new(format_type: 'hard_line_break') if run.respond_to?(:carriage_return) && run.carriage_return
90
+ result << CoreModel::InlineElement.new(format_type: 'hard_line_break') if run.class.attributes.key?(:carriage_return) && run.carriage_return
101
91
 
102
- # Alternate content — extract preferred/fallback content
103
- result << extract_alternate_content(run.alternate_content, context) if run.respond_to?(:alternate_content) && run.alternate_content
92
+ if run.alternate_content
93
+ result << extract_alternate_content(run.alternate_content,
94
+ context)
95
+ end
104
96
 
105
97
  result.compact
106
98
  end
107
99
 
108
100
  def extract_alternate_content(ac, context)
109
- # Prefer the fallback content (wc:w:bdoContent or mc:Fallback)
110
- content = if ac.respond_to?(:fallback) && ac.fallback
101
+ content = if ac.fallback
111
102
  ac.fallback
112
- elsif ac.respond_to?(:choice) && ac.choice
103
+ elsif ac.choice
113
104
  ac.choice
114
105
  end
115
106
 
116
107
  return nil unless content
117
108
 
118
- # If the fallback/choice has paragraphs or runs, transform them
119
- if content.respond_to?(:runs)
109
+ if content.is_a?(Uniword::Wordprocessingml::Run)
120
110
  content.runs&.each { |r| context.transform(r) }
121
- elsif content.respond_to?(:paragraphs)
111
+ elsif content.is_a?(Uniword::Wordprocessingml::Paragraph)
122
112
  content.paragraphs&.flat_map { |p| context.transform(p) }
123
113
  end
124
114
  end
@@ -144,7 +134,7 @@ module Coradoc
144
134
  return nil unless props
145
135
 
146
136
  # Check rStyle for semantic role
147
- if context.style_resolver.respond_to?(:run_semantic_role)
137
+ if context.style_resolver.is_a?(Coradoc::Docx::Transform::StyleResolver)
148
138
  role = context.style_resolver.run_semantic_role(run)
149
139
  case role
150
140
  when :monospace then return 'monospace'
@@ -62,15 +62,17 @@ module Coradoc
62
62
  private
63
63
 
64
64
  def field_text(field)
65
- # SimpleField may have runs with resolved text
66
- return field.runs.map { |r| r.text&.content.to_s }.join if field.respond_to?(:runs) && field.runs && !field.runs.empty?
65
+ if field.runs && !field.runs.empty?
66
+ return field.runs.map do |r|
67
+ r.text&.content.to_s
68
+ end.join
69
+ end
67
70
 
68
- # Fall back to text attribute
69
- field.respond_to?(:text) ? field.text.to_s : nil
71
+ nil
70
72
  end
71
73
 
72
74
  def field_instruction(field)
73
- instr = field.respond_to?(:instr) ? field.instr : nil
75
+ instr = field.instr
74
76
  instr.to_s
75
77
  end
76
78
 
@@ -32,7 +32,7 @@ module Coradoc
32
32
  def transform_row(row, context)
33
33
  CoreModel::TableRow.new(
34
34
  cells: row.cells.map { |c| transform_cell(c, context) },
35
- header: row.respond_to?(:header?) ? row.header? : false
35
+ header: row.header?
36
36
  )
37
37
  end
38
38
 
@@ -69,17 +69,15 @@ module Coradoc
69
69
  end
70
70
 
71
71
  def cell_paragraphs(cell)
72
- cell.respond_to?(:paragraphs) ? (cell.paragraphs || []) : []
72
+ cell.paragraphs || []
73
73
  end
74
74
 
75
75
  def header_cell?(cell)
76
76
  return false unless cell.properties
77
- return false unless cell.properties.respond_to?(:v_merge)
78
77
 
79
78
  vm = cell.properties.v_merge
80
- vm.respond_to?(:value) ? vm.value.to_s == 'restart' : false
79
+ vm&.value.to_s == 'restart'
81
80
  end
82
-
83
81
  end
84
82
  end
85
83
  end
@@ -51,18 +51,16 @@ module Coradoc
51
51
  style_name = resolve_style_name(paragraph)
52
52
  return true if style_name && HEADING_PATTERN.match?(style_name)
53
53
 
54
- # Check outline_level on paragraph properties
55
54
  ol = paragraph.properties.outline_level
56
55
  if ol
57
- ol_level = ol.respond_to?(:value) ? ol.value.to_i : ol.to_i
56
+ ol_level = ol.is_a?(Uniword::Wordprocessingml::OutlineLevel) ? ol.value.to_i : ol.to_i
58
57
  return true if ol_level.positive?
59
58
  end
60
59
 
61
- # Check outline_level from style definition
62
60
  style = find_style_for_paragraph(paragraph)
63
- if style.respond_to?(:outline_level) && style.outline_level
61
+ if style&.outline_level
64
62
  ol_val = style.outline_level
65
- ol_val = ol_val.respond_to?(:value) ? ol_val.value.to_i : ol_val.to_i
63
+ ol_val = ol_val.is_a?(Uniword::Wordprocessingml::OutlineLevel) ? ol_val.value.to_i : ol_val.to_i
66
64
  return true if ol_val.positive?
67
65
  end
68
66
 
@@ -82,7 +80,7 @@ module Coradoc
82
80
  # Check outline_level on paragraph properties
83
81
  ol = paragraph.properties&.outline_level
84
82
  if ol
85
- level = ol.respond_to?(:value) ? ol.value.to_i : ol.to_i
83
+ level = ol.is_a?(Uniword::Wordprocessingml::OutlineLevel) ? ol.value.to_i : ol.to_i
86
84
  return level if level.positive?
87
85
  end
88
86
 
@@ -138,10 +136,9 @@ module Coradoc
138
136
  style_ref = paragraph.properties&.style
139
137
  return nil unless style_ref
140
138
 
141
- value = style_ref.respond_to?(:value) ? style_ref.value : style_ref.to_s
139
+ value = style_ref.is_a?(Uniword::Wordprocessingml::PStyle) ? style_ref.val : style_ref.to_s
142
140
  return nil unless value
143
141
 
144
- # Check local style map first (for custom style aliases)
145
142
  mapped = @style_map[value]
146
143
  return mapped if mapped
147
144
 
@@ -152,7 +149,7 @@ module Coradoc
152
149
  style_ref = run.properties.style
153
150
  return nil unless style_ref
154
151
 
155
- value = style_ref.respond_to?(:value) ? style_ref.value : style_ref.to_s
152
+ value = style_ref.is_a?(Uniword::Wordprocessingml::PStyle) ? style_ref.val : style_ref.to_s
156
153
  return nil unless value
157
154
 
158
155
  mapped = @style_map[value]
@@ -165,23 +162,21 @@ module Coradoc
165
162
  style_id = style_id_from_paragraph(paragraph)
166
163
  return nil unless style_id
167
164
 
168
- if @config.respond_to?(:style_by_id)
169
- @config.style_by_id(style_id)
170
- elsif @config.respond_to?(:styles)
171
- @config.styles.find { |s| s.styleId == style_id }
172
- end
165
+ return unless @config.is_a?(Uniword::Wordprocessingml::StylesConfiguration)
166
+
167
+ @config.style_by_id(style_id)
173
168
  end
174
169
 
175
170
  def style_id_from_paragraph(paragraph)
176
171
  style_ref = paragraph.properties&.style
177
172
  return nil unless style_ref
178
173
 
179
- style_ref.respond_to?(:value) ? style_ref.value : style_ref.to_s
174
+ style_ref.is_a?(Uniword::Wordprocessingml::PStyle) ? style_ref.val : style_ref.to_s
180
175
  end
181
176
 
182
177
  def build_style_map(config)
183
178
  return {} unless config
184
- return {} unless config.respond_to?(:styles)
179
+ return {} unless config.is_a?(Uniword::Wordprocessingml::StylesConfiguration)
185
180
 
186
181
  map = {}
187
182
  config.styles.each do |style|
@@ -191,7 +186,6 @@ module Coradoc
191
186
 
192
187
  map[id] = name
193
188
 
194
- # Detect custom heading styles by basedOn chain
195
189
  if heading_by_based_on?(config, style)
196
190
  level = heading_level_from_chain(config, style)
197
191
  map[id] = "Heading#{level}"
@@ -202,26 +196,27 @@ module Coradoc
202
196
  end
203
197
 
204
198
  def extract_style_name(style)
205
- return style.style_name if style.respond_to?(:style_name)
199
+ sn = style.style_name
200
+ return sn if sn
206
201
 
207
202
  name = style.name
208
203
  return nil unless name
209
204
 
210
- name.respond_to?(:val) ? name.val.to_s : name.to_s
205
+ name.is_a?(Uniword::Wordprocessingml::StyleName) ? name.val.to_s : name.to_s
211
206
  end
212
207
 
213
208
  def heading_by_based_on?(config, style)
214
- based_on = style.respond_to?(:based_on) ? style.based_on : nil
209
+ based_on = style.based_on
215
210
  return false unless based_on
216
211
 
217
212
  visited = Set.new
218
213
  current = style
219
214
  while current && !visited.include?(current.styleId)
220
215
  visited << current.styleId
221
- parent_id = current.respond_to?(:based_on) ? current.based_on : nil
216
+ parent_id = current.based_on
222
217
  return true if parent_id && HEADING_PATTERN.match?(parent_id)
223
218
 
224
- break unless parent_id && config.respond_to?(:styles)
219
+ break unless parent_id
225
220
 
226
221
  current = config.styles.find { |s| s.styleId == parent_id }
227
222
  end
@@ -240,8 +235,8 @@ module Coradoc
240
235
  return match[2].to_i if match
241
236
  end
242
237
 
243
- parent_id = current.respond_to?(:based_on) ? current.based_on : nil
244
- break unless parent_id && config.respond_to?(:styles)
238
+ parent_id = current.based_on
239
+ break unless parent_id
245
240
 
246
241
  current = config.styles.find { |s| s.styleId == parent_id }
247
242
  end
@@ -129,14 +129,13 @@ module Coradoc
129
129
  end
130
130
 
131
131
  def section_break?(paragraph)
132
- return false unless paragraph.respond_to?(:properties)
132
+ return false unless paragraph.is_a?(Uniword::Wordprocessingml::Paragraph)
133
133
  return false unless paragraph.properties
134
134
 
135
135
  sect_pr = paragraph.properties.section_properties
136
136
  return false unless sect_pr
137
137
 
138
- # A section break exists if sectPr has a type (nextPage, continuous, etc.)
139
- sect_pr.respond_to?(:type) && sect_pr.type
138
+ sect_pr.type ? true : false
140
139
  end
141
140
 
142
141
  def section_break_element(paragraph, context)
@@ -182,12 +181,12 @@ module Coradoc
182
181
  end
183
182
 
184
183
  def body_ordered_elements(body)
185
- order = body.respond_to?(:element_order) ? body.element_order : nil
184
+ order = body.is_a?(Uniword::Wordprocessingml::Body) ? body.element_order : nil
186
185
  return body.elements if order.nil? || order.empty?
187
186
 
188
187
  p_idx = tbl_idx = sdt_idx = 0
189
188
  order.filter_map do |entry|
190
- name = entry.respond_to?(:name) ? entry.name : entry.to_s
189
+ name = entry.is_a?(String) ? entry : entry.name
191
190
  case name
192
191
  when 'p'
193
192
  para = body.paragraphs[p_idx]
@@ -216,7 +215,7 @@ module Coradoc
216
215
  doc_footnotes = document.footnotes
217
216
  if doc_footnotes.is_a?(Hash)
218
217
  doc_footnotes.each do |id, fn|
219
- paragraphs = fn.respond_to?(:content) ? fn.content : []
218
+ paragraphs = fn.is_a?(Uniword::Wordprocessingml::Footnote) ? fn.paragraphs : fn[:content]
220
219
  footnotes[id.to_s] = Array(paragraphs)
221
220
  end
222
221
  end
@@ -280,7 +279,10 @@ module Coradoc
280
279
  end
281
280
 
282
281
  def extract_from_parts(document, method, prefix, core_doc)
283
- parts = document.respond_to?(method) ? document.send(method) : nil
282
+ parts = case method
283
+ when :headers then document.headers
284
+ when :footers then document.footers
285
+ end
284
286
  return unless parts
285
287
 
286
288
  Array(parts).each_with_index do |part, idx|
@@ -288,13 +290,18 @@ module Coradoc
288
290
  next if text.nil? || text.strip.empty?
289
291
  next if layout_only_text?(text)
290
292
 
291
- core_doc.set_metadata("docx.#{prefix}.#{part.respond_to?(:type) ? part.type : idx}",
292
- text.strip)
293
+ part_type = if part.is_a?(Uniword::Wordprocessingml::Header) ||
294
+ part.is_a?(Uniword::Wordprocessingml::Footer)
295
+ part.type
296
+ else
297
+ idx
298
+ end
299
+ core_doc.set_metadata("docx.#{prefix}.#{part_type}", text.strip)
293
300
  end
294
301
  end
295
302
 
296
303
  def extract_part_text(part)
297
- paragraphs = part.respond_to?(:paragraphs) ? part.paragraphs : []
304
+ paragraphs = part.paragraphs || []
298
305
  return nil unless paragraphs
299
306
 
300
307
  paragraphs.map do |para|
@@ -303,7 +310,7 @@ module Coradoc
303
310
  end
304
311
 
305
312
  def extract_paragraph_text_content(para)
306
- runs = para.respond_to?(:runs) ? para.runs : []
313
+ runs = para.runs || []
307
314
  return nil unless runs
308
315
 
309
316
  runs.map { |r| r.text&.content.to_s }.join
@@ -91,7 +91,9 @@ module Coradoc
91
91
  end
92
92
 
93
93
  # Auto-register :docx format with Coradoc when both gems are loaded
94
- Coradoc.register_format(:docx, Coradoc::Docx,
95
- aliases: %w[docx],
96
- extensions: %w[.docx],
97
- binary: true) unless Coradoc.registered_formats.include?(:docx)
94
+ unless Coradoc.registered_formats.include?(:docx)
95
+ Coradoc.register_format(:docx, Coradoc::Docx,
96
+ aliases: %w[docx],
97
+ extensions: %w[.docx],
98
+ binary: true)
99
+ end
@@ -120,7 +120,9 @@ RSpec.describe Coradoc::Docx::Transform::FromCoreModel do
120
120
  end
121
121
 
122
122
  context 'with Abbreviation' do
123
- let(:core_model) { Coradoc::CoreModel::Abbreviation.new(term: 'API', definition: 'Application Programming Interface') }
123
+ let(:core_model) do
124
+ Coradoc::CoreModel::Abbreviation.new(term: 'API', definition: 'Application Programming Interface')
125
+ end
124
126
 
125
127
  it 'produces a Paragraph with term and definition' do
126
128
  expect(result).to be_a(Uniword::Wordprocessingml::Paragraph)
@@ -245,7 +247,8 @@ RSpec.describe Coradoc::Docx::Transform::FromCoreModel do
245
247
  @tmpdir = Dir.mktmpdir
246
248
  img_path = File.join(@tmpdir, 'test.png')
247
249
  # Minimal 1x1 PNG
248
- File.binwrite(img_path, "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xAEB`\x82".b)
250
+ File.binwrite(img_path,
251
+ "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xAEB`\x82".b)
249
252
  Coradoc::CoreModel::Image.new(src: img_path, alt: 'Test image')
250
253
  end
251
254
 
@@ -148,7 +148,10 @@ RSpec.describe Coradoc::Docx::Transform::Rules::ParagraphRule do
148
148
  end
149
149
 
150
150
  describe '#apply' do
151
- let(:context) { build_context(registry: build_registry_with(described_class.new, Coradoc::Docx::Transform::Rules::RunRule.new, Coradoc::Docx::Transform::Rules::TextRule.new)) }
151
+ let(:context) do
152
+ build_context(registry: build_registry_with(described_class.new, Coradoc::Docx::Transform::Rules::RunRule.new,
153
+ Coradoc::Docx::Transform::Rules::TextRule.new))
154
+ end
152
155
 
153
156
  it 'produces a Block paragraph with text content' do
154
157
  para = build_paragraph('Hello')
@@ -188,7 +191,10 @@ RSpec.describe Coradoc::Docx::Transform::Rules::TableRule do
188
191
  end
189
192
 
190
193
  describe '#apply' do
191
- let(:context) { build_context(registry: build_registry_with(described_class.new, Coradoc::Docx::Transform::Rules::ParagraphRule.new, Coradoc::Docx::Transform::Rules::RunRule.new, Coradoc::Docx::Transform::Rules::TextRule.new)) }
194
+ let(:context) do
195
+ build_context(registry: build_registry_with(described_class.new, Coradoc::Docx::Transform::Rules::ParagraphRule.new,
196
+ Coradoc::Docx::Transform::Rules::RunRule.new, Coradoc::Docx::Transform::Rules::TextRule.new))
197
+ end
192
198
 
193
199
  it 'produces a CoreModel::Table with rows and cells' do
194
200
  table = build_table([%w[A B], %w[C D]])
@@ -213,7 +219,10 @@ RSpec.describe Coradoc::Docx::Transform::Rules::ListItemRule do
213
219
  end
214
220
 
215
221
  describe '#apply' do
216
- let(:context) { build_context(registry: build_registry_with(Coradoc::Docx::Transform::Rules::RunRule.new, Coradoc::Docx::Transform::Rules::TextRule.new)) }
222
+ let(:context) do
223
+ build_context(registry: build_registry_with(Coradoc::Docx::Transform::Rules::RunRule.new,
224
+ Coradoc::Docx::Transform::Rules::TextRule.new))
225
+ end
217
226
 
218
227
  it 'produces a ListItem with content' do
219
228
  para = build_list_paragraph('Item 1', num_id: 1)
@@ -242,7 +251,10 @@ RSpec.describe Coradoc::Docx::Transform::Rules::HeadingRule do
242
251
  end
243
252
 
244
253
  describe '#apply' do
245
- let(:context) { build_context(registry: build_registry_with(Coradoc::Docx::Transform::Rules::RunRule.new, Coradoc::Docx::Transform::Rules::TextRule.new)) }
254
+ let(:context) do
255
+ build_context(registry: build_registry_with(Coradoc::Docx::Transform::Rules::RunRule.new,
256
+ Coradoc::Docx::Transform::Rules::TextRule.new))
257
+ end
246
258
 
247
259
  it 'produces a StructuralElement section with title and level' do
248
260
  para = build_heading('My Title', level: 2)
@@ -363,7 +375,9 @@ RSpec.describe Coradoc::Docx::Transform::Rules::HyperlinkRule do
363
375
 
364
376
  describe '#apply' do
365
377
  let(:run_rule) { Coradoc::Docx::Transform::Rules::RunRule.new }
366
- let(:context) { build_context(registry: build_registry_with(run_rule, Coradoc::Docx::Transform::Rules::TextRule.new)) }
378
+ let(:context) do
379
+ build_context(registry: build_registry_with(run_rule, Coradoc::Docx::Transform::Rules::TextRule.new))
380
+ end
367
381
 
368
382
  it 'produces InlineElement link with external URL' do
369
383
  hl = build_hyperlink(
@@ -419,7 +433,10 @@ RSpec.describe Coradoc::Docx::Transform::Rules::SimpleFieldRule do
419
433
  end
420
434
 
421
435
  describe '#apply' do
422
- let(:context) { build_context(registry: build_registry_with(Coradoc::Docx::Transform::Rules::RunRule.new, Coradoc::Docx::Transform::Rules::TextRule.new)) }
436
+ let(:context) do
437
+ build_context(registry: build_registry_with(Coradoc::Docx::Transform::Rules::RunRule.new,
438
+ Coradoc::Docx::Transform::Rules::TextRule.new))
439
+ end
423
440
 
424
441
  it 'returns nil for PAGE field (print layout)' do
425
442
  field = Uniword::Wordprocessingml::SimpleField.new
@@ -449,7 +466,10 @@ RSpec.describe Coradoc::Docx::Transform::Rules::StructuredDocumentTagRule do
449
466
  end
450
467
 
451
468
  describe '#apply' do
452
- let(:context) { build_context(registry: build_registry_with(Coradoc::Docx::Transform::Rules::ParagraphRule.new, Coradoc::Docx::Transform::Rules::RunRule.new, Coradoc::Docx::Transform::Rules::TextRule.new)) }
469
+ let(:context) do
470
+ build_context(registry: build_registry_with(Coradoc::Docx::Transform::Rules::ParagraphRule.new,
471
+ Coradoc::Docx::Transform::Rules::RunRule.new, Coradoc::Docx::Transform::Rules::TextRule.new))
472
+ end
453
473
 
454
474
  it 'unwraps SDT content and delegates to paragraph rules' do
455
475
  sdt = Uniword::Wordprocessingml::StructuredDocumentTag.new
@@ -221,7 +221,9 @@ RSpec.describe Coradoc::Docx::Transform::ToCoreModel do
221
221
  core = transform_to_core(doc)
222
222
 
223
223
  blocks = core.children.select { |c| c.is_a?(Coradoc::CoreModel::Block) }
224
- inline = blocks.first.children.find { |c| c.is_a?(Coradoc::CoreModel::InlineElement) && c.format_type == 'subscript' }
224
+ inline = blocks.first.children.find do |c|
225
+ c.is_a?(Coradoc::CoreModel::InlineElement) && c.format_type == 'subscript'
226
+ end
225
227
  expect(inline).not_to be_nil
226
228
  expect(inline.content).to eq('2')
227
229
  end
@@ -241,7 +243,9 @@ RSpec.describe Coradoc::Docx::Transform::ToCoreModel do
241
243
  core = transform_to_core(doc)
242
244
 
243
245
  blocks = core.children.select { |c| c.is_a?(Coradoc::CoreModel::Block) }
244
- inline = blocks.first.children.find { |c| c.is_a?(Coradoc::CoreModel::InlineElement) && c.format_type == 'superscript' }
246
+ inline = blocks.first.children.find do |c|
247
+ c.is_a?(Coradoc::CoreModel::InlineElement) && c.format_type == 'superscript'
248
+ end
245
249
  expect(inline).not_to be_nil
246
250
  expect(inline.content).to eq('2')
247
251
  end
@@ -125,14 +125,10 @@ module Coradoc
125
125
  def extract_attributes(model)
126
126
  attrs = {}
127
127
 
128
- # Extract ID if available
129
- attrs[:id] = model.id if model.respond_to?(:id) && model.id
128
+ attrs[:id] = model.id if model.id
129
+ attrs[:title] = model.title if model.title
130
130
 
131
- # Extract title if available
132
- attrs[:title] = model.title if model.respond_to?(:title) && model.title
133
-
134
- # Extract class/role if available
135
- if model.respond_to?(:metadata) && model.metadata
131
+ if model.is_a?(Coradoc::CoreModel::StructuralElement) && model.metadata
136
132
  attrs[:class] = model.metadata[:class] || model.metadata[:role]
137
133
  attrs.merge!(model.metadata.except(:class, :role))
138
134
  end
@@ -118,25 +118,19 @@ module Coradoc
118
118
  # @param config [Hash, Object] Configuration options or object
119
119
  # @return [Object] Built configuration object
120
120
  def build_config(config)
121
- # If config is already a Configuration object, validate and return it
122
- if config.respond_to?(:validate!)
123
- config.validate! if config.respond_to?(:validate!)
121
+ if config.public_methods.include?(:validate!)
122
+ config.validate!
124
123
  return config
125
124
  end
126
125
 
127
- # Otherwise, build from hash (subclasses should override this)
128
126
  config
129
127
  end
130
128
 
131
- # Extract document title
132
- #
133
- # @return [String] Document title
134
129
  def extract_document_title
135
- # Handle CoreModel::StructuralElement (has title directly)
136
- if @document.respond_to?(:title) && @document.title
130
+ if @document.is_a?(Coradoc::CoreModel::StructuralElement) && @document.title
137
131
  title = @document.title
138
132
  return title if title.is_a?(String)
139
- return title.text if title.respond_to?(:text)
133
+ return title.text if title.is_a?(Coradoc::CoreModel::Base) && title.text
140
134
 
141
135
  return title.to_s
142
136
  end
@@ -144,10 +138,6 @@ module Coradoc
144
138
  'Untitled Document'
145
139
  end
146
140
 
147
- # Extract text from content (array of inline elements)
148
- #
149
- # @param content [Array] Content elements
150
- # @return [String] Extracted text
151
141
  def extract_text_from_content(content)
152
142
  case content
153
143
  when Array
@@ -157,7 +147,7 @@ module Coradoc
157
147
  when Coradoc::CoreModel::InlineElement
158
148
  content.content.to_s
159
149
  when Coradoc::CoreModel::Base
160
- if content.respond_to?(:content)
150
+ if content.content
161
151
  extract_text_from_content(content.content)
162
152
  else
163
153
  content.to_s
@@ -96,7 +96,10 @@ module Coradoc
96
96
 
97
97
  return render_core_bibliography(content, state) if content.is_a?(Coradoc::CoreModel::Bibliography)
98
98
 
99
- return render_core_bibliography_entry(content, state) if content.is_a?(Coradoc::CoreModel::BibliographyEntry)
99
+ if content.is_a?(Coradoc::CoreModel::BibliographyEntry)
100
+ return render_core_bibliography_entry(content,
101
+ state)
102
+ end
100
103
 
101
104
  # Handle unknown types gracefully
102
105
  handle_unknown_content(content, state)
@@ -431,37 +434,28 @@ module Coradoc
431
434
 
432
435
  # Extract text from unknown model types as a fallback
433
436
  def extract_text_fallback(content)
434
- # Try text attribute first
435
- if content.respond_to?(:text) && content.text
436
- text_val = content.text
437
- return text_val if text_val.is_a?(String)
438
- return text_val.to_s if text_val.respond_to?(:to_s)
439
- end
437
+ if content.is_a?(Coradoc::CoreModel::Base)
438
+ if content.class.attributes.key?(:text) && content.text
439
+ text_val = content.text
440
+ return text_val if text_val.is_a?(String)
440
441
 
441
- # Try content attribute
442
- if content.respond_to?(:content) && content.content
443
- content_val = content.content
444
- if content_val.is_a?(String)
445
- return content_val
446
- elsif content_val.is_a?(Array)
447
- return content_val.map { |item| convert_content_to_html(item, {}) }.join
442
+ return text_val.to_s
448
443
  end
449
- end
450
444
 
451
- # Try href attribute (for cross-references)
452
- return content.href.to_s if content.respond_to?(:href) && content.href
453
-
454
- # Try term attribute (for term references)
455
- return content.term.to_s if content.respond_to?(:term) && content.term
456
-
457
- # Try id attribute (for references with IDs)
458
- return content.id.to_s if content.respond_to?(:id) && content.id
459
-
460
- # Try name attribute
461
- return content.name.to_s if content.respond_to?(:name) && content.name
445
+ if content.class.attributes.key?(:content) && content.content
446
+ content_val = content.content
447
+ if content_val.is_a?(String)
448
+ return content_val
449
+ elsif content_val.is_a?(Array)
450
+ return content_val.map { |item| convert_content_to_html(item, {}) }.join
451
+ end
452
+ end
462
453
 
463
- # Try to_adoc if available (for AsciiDoc models)
464
- return content.to_adoc.to_s if content.respond_to?(:to_adoc)
454
+ return content.href.to_s if content.class.attributes.key?(:href) && content.href
455
+ return content.term.to_s if content.class.attributes.key?(:term) && content.term
456
+ return content.id.to_s if content.class.attributes.key?(:id) && content.id
457
+ return content.name.to_s if content.class.attributes.key?(:name) && content.name
458
+ end
465
459
 
466
460
  ''
467
461
  end
@@ -12,9 +12,9 @@ module Coradoc
12
12
  return '' unless options[:preserve_comments]
13
13
 
14
14
  # Get comment text - check for content or text attribute
15
- text = if comment.respond_to?(:content) && comment.content
15
+ text = if comment.content
16
16
  comment.content
17
- elsif comment.respond_to?(:text) && comment.text
17
+ elsif comment.text
18
18
  comment.text
19
19
  else
20
20
  ''