uniword 1.0.4 → 1.0.5

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/lib/uniword/accessibility/rules/descriptive_headings_rule.rb +7 -9
  3. data/lib/uniword/accessibility/rules/document_title_rule.rb +2 -2
  4. data/lib/uniword/accessibility/rules/heading_structure_rule.rb +4 -3
  5. data/lib/uniword/accessibility/rules/language_specification_rule.rb +2 -2
  6. data/lib/uniword/accessibility/rules/table_headers_rule.rb +1 -1
  7. data/lib/uniword/assembly/cross_reference_resolver.rb +6 -59
  8. data/lib/uniword/assembly/toc.rb +2 -2
  9. data/lib/uniword/assembly/variable_substitutor.rb +0 -24
  10. data/lib/uniword/batch/stages/compress_images_stage.rb +2 -2
  11. data/lib/uniword/batch/stages/normalize_styles_stage.rb +12 -12
  12. data/lib/uniword/batch/stages/update_metadata_stage.rb +26 -11
  13. data/lib/uniword/batch/stages/validate_links_stage.rb +1 -1
  14. data/lib/uniword/builder/bibliography_builder.rb +1 -1
  15. data/lib/uniword/builder/chart_builder.rb +2 -2
  16. data/lib/uniword/builder/has_borders.rb +1 -1
  17. data/lib/uniword/builder/image_builder.rb +1 -1
  18. data/lib/uniword/cli/main.rb +4 -4
  19. data/lib/uniword/diff/document_differ.rb +11 -7
  20. data/lib/uniword/document_factory.rb +3 -3
  21. data/lib/uniword/docx/document_statistics.rb +3 -3
  22. data/lib/uniword/docx/package_defaults.rb +2 -2
  23. data/lib/uniword/docx/reconciler.rb +3 -3
  24. data/lib/uniword/element_registry.rb +1 -1
  25. data/lib/uniword/endnote.rb +3 -1
  26. data/lib/uniword/footnote.rb +3 -1
  27. data/lib/uniword/format_converter.rb +7 -7
  28. data/lib/uniword/format_detector.rb +1 -1
  29. data/lib/uniword/generation/document_generator.rb +2 -2
  30. data/lib/uniword/infrastructure/zip_extractor.rb +1 -1
  31. data/lib/uniword/math_equation.rb +3 -1
  32. data/lib/uniword/mhtml/math_converter.rb +1 -1
  33. data/lib/uniword/mhtml/word_css.rb +14 -14
  34. data/lib/uniword/ooxml/schema/element_serializer.rb +14 -14
  35. data/lib/uniword/quality/document_checker.rb +2 -2
  36. data/lib/uniword/quality/rules/link_validation_rule.rb +4 -4
  37. data/lib/uniword/quality/rules/style_consistency_rule.rb +1 -1
  38. data/lib/uniword/quality/rules/table_header_rule.rb +1 -1
  39. data/lib/uniword/resource/color_transformer.rb +1 -1
  40. data/lib/uniword/resource/resource_resolver.rb +1 -1
  41. data/lib/uniword/resource/theme_processor.rb +1 -1
  42. data/lib/uniword/resource/theme_transition.rb +1 -1
  43. data/lib/uniword/review/interactive_review.rb +1 -1
  44. data/lib/uniword/spellcheck/spell_checker.rb +6 -6
  45. data/lib/uniword/styleset.rb +1 -1
  46. data/lib/uniword/template/helpers/conditional_helper.rb +3 -4
  47. data/lib/uniword/template/helpers/filter_helper.rb +1 -1
  48. data/lib/uniword/template/helpers/loop_helper.rb +1 -1
  49. data/lib/uniword/template/helpers/variable_helper.rb +1 -1
  50. data/lib/uniword/template/template_parser.rb +9 -7
  51. data/lib/uniword/template/template_renderer.rb +3 -3
  52. data/lib/uniword/template/variable_resolver.rb +5 -6
  53. data/lib/uniword/themes/theme_transformation.rb +1 -1
  54. data/lib/uniword/toc/toc_generator.rb +1 -1
  55. data/lib/uniword/transformation/mhtml_element_renderer.rb +5 -7
  56. data/lib/uniword/transformation/mhtml_metadata_builder.rb +17 -18
  57. data/lib/uniword/transformation/ooxml_to_html_converter.rb +1 -1
  58. data/lib/uniword/transformation/transformer.rb +6 -6
  59. data/lib/uniword/validation/checkers/external_link_checker.rb +1 -1
  60. data/lib/uniword/validation/checkers/file_reference_checker.rb +5 -5
  61. data/lib/uniword/validation/checkers/footnote_reference_checker.rb +12 -12
  62. data/lib/uniword/validation/checkers/internal_link_checker.rb +7 -9
  63. data/lib/uniword/validation/link_validator.rb +17 -23
  64. data/lib/uniword/validation/validation_report.rb +5 -3
  65. data/lib/uniword/validation/validation_result.rb +4 -4
  66. data/lib/uniword/validators/element_validator.rb +1 -7
  67. data/lib/uniword/version.rb +1 -1
  68. data/lib/uniword/visitor/text_extractor.rb +3 -3
  69. data/lib/uniword/warnings/warning_collector.rb +1 -1
  70. data/lib/uniword/watermark/manager.rb +1 -1
  71. data/lib/uniword/wordprocessingml/numbering_configuration.rb +1 -1
  72. data/lib/uniword/wordprocessingml/run.rb +4 -4
  73. data/lib/uniword/wordprocessingml/styles/style_definition.rb +1 -1
  74. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1de998a54b8e5d76ba39ef47f1021280ec260fa5065c77662be9865fc36910db
4
- data.tar.gz: 35f25b895be41d28d4df44b4226955653468e8ff67f190846dd6ec140a6862c5
3
+ metadata.gz: 6bf213950306833f5f1b647478fcbdf3d3567510a34ed6329e0d0d1ed6eedf6a
4
+ data.tar.gz: d9b9e0ef1afffe36923b9dcd55831ce4a35441f5bbd4277d821a491b0b0f008d
5
5
  SHA512:
6
- metadata.gz: 07b9e1a207a4c048a5cb92a9075f10ba46a4597b2737910be2b018f43468cab36e89a1590bcc1bf06d5196a289419b62c7918b6a2633c8ed37ba08539229f980
7
- data.tar.gz: 4a381bacb9c6f8207a6cc06bdd9d8987898664b8ee1f7b328efb992b044f76676f00e8aaa198e525094d123b0e1d23b601557a8a662179bfa32369dd302615a4
6
+ metadata.gz: ead9275e412cabb6d6f6ac359fec7d2ac4b2fcd298886ac8fa5e5623532d9bb8b8fb56fe957552fef12d296cd066c440ba225bff7816a690dc63c1323d4db5ce
7
+ data.tar.gz: ac185de3d3411ff440d9d2651f3d5d88c101c35d19a05cbe9e886e7afdb7f42cd8780f907bdfedcc17ab439a54a4ac926fb8a99286b844b59f0ad87c2d447143
@@ -69,16 +69,14 @@ module Uniword
69
69
  def heading_paragraph?(paragraph)
70
70
  return false unless paragraph.style
71
71
 
72
- paragraph.style.match?(/^Heading\s*\d+$/i) ||
73
- paragraph.style.match?(/^h\d+$/i)
72
+ style_str = paragraph.style.to_s
73
+ style_str.match?(/^Heading\s*\d+$/i) ||
74
+ style_str.match?(/^h\d+$/i)
74
75
  end
75
76
 
76
- # Extract heading level from paragraph
77
- #
78
- # @param paragraph [Paragraph] Paragraph to extract from
79
- # @return [Integer] Heading level
80
77
  def extract_heading_level(paragraph)
81
- match = paragraph.style.match(/(\d+)/)
78
+ style_str = paragraph.style.to_s
79
+ match = style_str.match(/(\d+)/)
82
80
  match ? match[1].to_i : 1
83
81
  end
84
82
 
@@ -87,9 +85,9 @@ module Uniword
87
85
  # @param paragraph [Paragraph] Paragraph to extract from
88
86
  # @return [String, nil] Heading text
89
87
  def extract_heading_text(paragraph)
90
- if paragraph.respond_to?(:text)
88
+ if paragraph.is_a?(Lutaml::Model::Serializable) && paragraph.class.attributes.key?(:text)
91
89
  paragraph.text
92
- elsif paragraph.respond_to?(:runs)
90
+ elsif paragraph.is_a?(Lutaml::Model::Serializable) && paragraph.class.attributes.key?(:runs)
93
91
  paragraph.runs.map(&:text).join
94
92
  end
95
93
  end
@@ -52,9 +52,9 @@ module Uniword
52
52
  # @return [String, nil] Document title
53
53
  def extract_title(document)
54
54
  # Try to get title from document metadata/properties
55
- if document.respond_to?(:title)
55
+ if document.is_a?(Lutaml::Model::Serializable) && document.class.attributes.key?(:title)
56
56
  document.title
57
- elsif document.respond_to?(:metadata)
57
+ elsif document.is_a?(Lutaml::Model::Serializable) && document.class.attributes.key?(:metadata)
58
58
  document.metadata&.title
59
59
  end
60
60
  end
@@ -65,8 +65,9 @@ module Uniword
65
65
  def heading_paragraph?(paragraph)
66
66
  return false unless paragraph.style
67
67
 
68
- paragraph.style.match?(/^Heading\s*\d+$/i) ||
69
- paragraph.style.match?(/^h\d+$/i)
68
+ style_str = paragraph.style.to_s
69
+ style_str.match?(/^Heading\s*\d+$/i) ||
70
+ style_str.match?(/^h\d+$/i)
70
71
  end
71
72
 
72
73
  # Extract heading level from paragraph
@@ -74,7 +75,7 @@ module Uniword
74
75
  # @param paragraph [Paragraph] Paragraph to extract from
75
76
  # @return [Integer] Heading level
76
77
  def extract_heading_level(paragraph)
77
- match = paragraph.style.match(/(\d+)/)
78
+ match = paragraph.style.to_s.match(/(\d+)/)
78
79
  match ? match[1].to_i : 1
79
80
  end
80
81
 
@@ -43,9 +43,9 @@ module Uniword
43
43
  # @return [String, nil] Document language code
44
44
  def extract_language(document)
45
45
  # Try to get language from document metadata/properties
46
- if document.respond_to?(:language)
46
+ if document.is_a?(Lutaml::Model::Serializable) && document.class.attributes.key?(:language)
47
47
  document.language
48
- elsif document.respond_to?(:metadata)
48
+ elsif document.is_a?(Lutaml::Model::Serializable) && document.class.attributes.key?(:metadata)
49
49
  document.metadata&.language
50
50
  end
51
51
  end
@@ -53,7 +53,7 @@ module Uniword
53
53
  # @return [Boolean] True if caption exists
54
54
  def table_has_caption?(table)
55
55
  # Tables may have a caption property or title
56
- table.respond_to?(:caption) && !table.caption.to_s.strip.empty?
56
+ table.is_a?(Lutaml::Model::Serializable) && table.class.attributes.key?(:caption) && !table.caption.to_s.strip.empty?
57
57
  end
58
58
  end
59
59
  end
@@ -104,33 +104,9 @@ module Uniword
104
104
  # @param document [Document] Source document
105
105
  # @return [void]
106
106
  def collect_bookmarks(document)
107
- # Process body paragraphs
108
107
  document.paragraphs.each do |paragraph|
109
108
  collect_paragraph_bookmarks(paragraph)
110
109
  end
111
-
112
- # Process sections (headers/footers)
113
- return unless document.respond_to?(:sections)
114
-
115
- document.sections.each do |section|
116
- # Process headers if section has them
117
- if section.respond_to?(:headers)
118
- section.headers.each do |header|
119
- header.paragraphs.each do |paragraph|
120
- collect_paragraph_bookmarks(paragraph)
121
- end
122
- end
123
- end
124
-
125
- # Process footers if section has them
126
- next unless section.respond_to?(:footers)
127
-
128
- section.footers.each do |footer|
129
- footer.paragraphs.each do |paragraph|
130
- collect_paragraph_bookmarks(paragraph)
131
- end
132
- end
133
- end
134
110
  end
135
111
 
136
112
  # Collect bookmarks from paragraph.
@@ -138,12 +114,7 @@ module Uniword
138
114
  # @param paragraph [Paragraph] Source paragraph
139
115
  # @return [void]
140
116
  def collect_paragraph_bookmarks(paragraph)
141
- paragraph.runs.each do |run|
142
- # Check if run contains bookmark
143
- next unless run.respond_to?(:bookmark_start)
144
- next unless run.bookmark_start
145
-
146
- bookmark = run.bookmark_start
117
+ paragraph.bookmark_starts.each do |bookmark|
147
118
  @bookmark_registry[bookmark.id] = bookmark
148
119
  end
149
120
  end
@@ -153,33 +124,9 @@ module Uniword
153
124
  # @param document [Document] Document to process
154
125
  # @return [void]
155
126
  def resolve_references(document)
156
- # Process body paragraphs
157
127
  document.paragraphs.each do |paragraph|
158
128
  resolve_paragraph_references(paragraph)
159
129
  end
160
-
161
- # Process sections (headers/footers)
162
- return unless document.respond_to?(:sections)
163
-
164
- document.sections.each do |section|
165
- # Process headers if section has them
166
- if section.respond_to?(:headers)
167
- section.headers.each do |header|
168
- header.paragraphs.each do |paragraph|
169
- resolve_paragraph_references(paragraph)
170
- end
171
- end
172
- end
173
-
174
- # Process footers if section has them
175
- next unless section.respond_to?(:footers)
176
-
177
- section.footers.each do |footer|
178
- footer.paragraphs.each do |paragraph|
179
- resolve_paragraph_references(paragraph)
180
- end
181
- end
182
- end
183
130
  end
184
131
 
185
132
  # Resolve references in paragraph.
@@ -187,12 +134,12 @@ module Uniword
187
134
  # @param paragraph [Paragraph] Source paragraph
188
135
  # @return [void]
189
136
  def resolve_paragraph_references(paragraph)
190
- paragraph.runs.each do |run|
191
- # Process hyperlinks
192
- resolve_hyperlink(run.hyperlink) if run.respond_to?(:hyperlink) && run.hyperlink
137
+ paragraph.hyperlinks.each do |hyperlink|
138
+ resolve_hyperlink(hyperlink)
139
+ end
193
140
 
194
- # Process fields (for cross-references)
195
- resolve_field(run.field) if run.respond_to?(:field) && run.field
141
+ paragraph.simple_fields.each do |field|
142
+ resolve_field(field)
196
143
  end
197
144
  end
198
145
 
@@ -174,7 +174,7 @@ module Uniword
174
174
  return match[1].to_i if match
175
175
 
176
176
  # Check for outline level in properties
177
- if paragraph.properties.respond_to?(:outline_level)
177
+ if paragraph.properties.is_a?(Uniword::Wordprocessingml::ParagraphProperties)
178
178
  outline = paragraph.properties.outline_level
179
179
  return outline if outline.is_a?(Integer)
180
180
  end
@@ -212,7 +212,7 @@ module Uniword
212
212
  run.text = @title
213
213
 
214
214
  # Set run properties
215
- if run.respond_to?(:properties=)
215
+ if run.is_a?(Uniword::Wordprocessingml::Run)
216
216
  run.properties = Wordprocessingml::RunProperties.new(
217
217
  bold: true,
218
218
  size: 48, # 24pt = 48 half-points
@@ -66,34 +66,10 @@ module Uniword
66
66
  # @example Process document
67
67
  # sub.substitute_document(document)
68
68
  def substitute_document(document)
69
- # Process all paragraphs
70
69
  document.paragraphs.each do |paragraph|
71
70
  substitute_paragraph(paragraph)
72
71
  end
73
72
 
74
- # Process sections (headers/footers)
75
- if document.respond_to?(:sections)
76
- document.sections.each do |section|
77
- # Process headers if section has them
78
- if section.respond_to?(:headers)
79
- section.headers.each do |header|
80
- header.paragraphs.each do |paragraph|
81
- substitute_paragraph(paragraph)
82
- end
83
- end
84
- end
85
-
86
- # Process footers if section has them
87
- next unless section.respond_to?(:footers)
88
-
89
- section.footers.each do |footer|
90
- footer.paragraphs.each do |paragraph|
91
- substitute_paragraph(paragraph)
92
- end
93
- end
94
- end
95
- end
96
-
97
73
  document
98
74
  end
99
75
 
@@ -155,8 +155,8 @@ module Uniword
155
155
  def get_image_dimensions(image)
156
156
  # Placeholder - would need to parse image data
157
157
  # For now, return dimensions from image properties if available
158
- width = image.respond_to?(:width) ? image.width : 800
159
- height = image.respond_to?(:height) ? image.height : 600
158
+ width = image.is_a?(Uniword::Image) ? image.width : 800
159
+ height = image.is_a?(Uniword::Image) ? image.height : 600
160
160
  [width || 800, height || 600]
161
161
  end
162
162
 
@@ -89,7 +89,7 @@ module Uniword
89
89
  props = paragraph.properties
90
90
 
91
91
  # Check outline level for headings
92
- if props.respond_to?(:outline_level)
92
+ if props&.outline_level
93
93
  case props.outline_level
94
94
  when 0, 1
95
95
  return :heading1
@@ -137,16 +137,16 @@ module Uniword
137
137
  props = run.properties
138
138
 
139
139
  # Preserve bold for emphasis
140
- bold = props.respond_to?(:bold) ? props.bold : nil
140
+ bold = props&.bold
141
141
  # Preserve italic for emphasis
142
- italic = props.respond_to?(:italic) ? props.italic : nil
142
+ italic = props&.italic
143
143
 
144
144
  # Clear all properties
145
145
  clear_all_run_properties(run)
146
146
 
147
147
  # Restore semantic properties
148
- props.bold = bold if props.respond_to?(:bold=) && bold
149
- return unless props.respond_to?(:italic=) && italic
148
+ props.bold = bold if bold
149
+ return unless italic
150
150
 
151
151
  props.italic = italic
152
152
  end
@@ -159,15 +159,15 @@ module Uniword
159
159
  return unless props
160
160
 
161
161
  # Reset font properties
162
- props.font_size = nil if props.respond_to?(:font_size=)
163
- props.font_name = nil if props.respond_to?(:font_name=)
164
- props.color = nil if props.respond_to?(:color=)
162
+ props.font_size = nil
163
+ props.font_name = nil
164
+ props.color = nil
165
165
 
166
166
  # Reset text effects (keep if you want to preserve semantic formatting)
167
- # props.bold = nil if props.respond_to?(:bold=)
168
- # props.italic = nil if props.respond_to?(:italic=)
169
- props.underline = nil if props.respond_to?(:underline=)
170
- props.strike = nil if props.respond_to?(:strike=)
167
+ # props.bold = nil
168
+ # props.italic = nil
169
+ props.underline = nil
170
+ props.strike = nil
171
171
  end
172
172
  end
173
173
  end
@@ -62,30 +62,45 @@ module Uniword
62
62
  #
63
63
  # @param document [Document] Document to update
64
64
  def update_core_properties(document)
65
- # NOTE: Actual implementation depends on Document API
66
- # This is a placeholder that shows the intended structure
65
+ return unless document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
67
66
 
68
- document.author = @author || current_user if @update_author && document.respond_to?(:author=)
67
+ cp = document.core_properties
69
68
 
70
- document.modified_date = Time.now if @update_modified_date && document.respond_to?(:modified_date=)
69
+ if @update_author
70
+ cp.creator = Uniword::Ooxml::Types::DcCreatorType.new(
71
+ @author || current_user,
72
+ )
73
+ end
74
+
75
+ if @update_modified_date
76
+ cp.modified = Uniword::Ooxml::Types::DctermsModifiedType.new(
77
+ value: Time.now.to_s,
78
+ type: "dcterms:W3CDTF",
79
+ )
80
+ end
71
81
 
72
- if @update_revision_number && document.respond_to?(:revision=)
73
- current_revision = document.respond_to?(:revision) ? document.revision : 0
74
- document.revision = (current_revision.to_i + 1).to_s
82
+ if @update_revision_number
83
+ current = cp.revision.to_i
84
+ cp.revision = Uniword::Ooxml::Types::CpRevisionType.new(
85
+ (current + 1).to_s,
86
+ )
75
87
  end
76
88
 
77
- return unless @title && document.respond_to?(:core_properties)
89
+ return unless @title
78
90
 
79
- document.core_properties.title = @title
91
+ cp.title = Uniword::Ooxml::Types::DcTitleType.new(@title)
80
92
  end
81
93
 
82
94
  # Update extended document properties
83
95
  #
84
96
  # @param document [Document] Document to update
85
97
  def update_extended_properties(document)
86
- return unless @company && document.respond_to?(:company=)
98
+ return unless @company && document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
99
+
100
+ app = document.app_properties
101
+ return unless app
87
102
 
88
- document.company = @company
103
+ app.company = @company
89
104
  end
90
105
 
91
106
  # Get current user name
@@ -160,7 +160,7 @@ module Uniword
160
160
  # @param document [Document] Document to check
161
161
  # @return [Boolean] true if bookmark exists
162
162
  def bookmark_exists?(bookmark_name, document)
163
- return true unless document.respond_to?(:bookmarks)
163
+ return true unless document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
164
164
  return true if document.bookmarks.nil?
165
165
 
166
166
  document.bookmarks.any? { |b| b.name == bookmark_name }
@@ -35,7 +35,7 @@ module Uniword
35
35
  # @param document [DocumentBuilder, DocumentRoot] Target document
36
36
  # @return [self]
37
37
  def attach(document)
38
- root = document.respond_to?(:model) ? document.model : document
38
+ root = document.is_a?(Uniword::Builder::DocumentBuilder) ? document.model : document
39
39
  root.bibliography_sources = @sources
40
40
  self
41
41
  end
@@ -118,7 +118,7 @@ module Uniword
118
118
  # @param document [DocumentBuilder, DocumentRoot] Target document
119
119
  # @return [Wordprocessingml::Drawing]
120
120
  def build_drawing(document)
121
- root = document.respond_to?(:model) ? document.model : document
121
+ root = document.is_a?(Uniword::Builder::DocumentBuilder) ? document.model : document
122
122
  root.chart_parts ||= {}
123
123
 
124
124
  r_id = "rIdChart#{root.chart_parts.size + 1}"
@@ -265,7 +265,7 @@ module Uniword
265
265
 
266
266
  # Build a chart axis
267
267
  def build_axis(xml, tag, id, position, cross_id)
268
- xml["c"].send(tag) do
268
+ xml["c"].public_send(tag) do
269
269
  xml["c"].axId("val" => id)
270
270
  xml["c"].scaling do
271
271
  xml["c"].orientation("val" => "minMax")
@@ -17,7 +17,7 @@ module Uniword
17
17
  Properties::Border.new(color: value, style: "single",
18
18
  size: 4)
19
19
  end
20
- props.borders.send("#{side}=", border)
20
+ props.borders.public_send("#{side}=", border)
21
21
  end
22
22
  self
23
23
  end
@@ -33,7 +33,7 @@ module Uniword
33
33
  # @param path [String] Path to image file
34
34
  # @return [String] Relationship ID (e.g., 'rIdImg1')
35
35
  def self.register_image(document, path)
36
- root = document.respond_to?(:model) ? document.model : document
36
+ root = document.is_a?(Uniword::Builder::DocumentBuilder) ? document.model : document
37
37
  if root
38
38
  root.image_parts ||= {}
39
39
  r_id = "rIdImg#{root.image_parts.size + 1}"
@@ -211,7 +211,7 @@ module Uniword
211
211
  require "json"
212
212
  output = reports.transform_values do |r|
213
213
  { valid: r.valid?,
214
- issues: r.respond_to?(:issues) ? r.issues.count : 0 }
214
+ issues: r.is_a?(Uniword::Quality::CheckReport) ? r.issues.count : 0 }
215
215
  end
216
216
  puts JSON.pretty_generate(output)
217
217
  else
@@ -461,9 +461,9 @@ module Uniword
461
461
  if report.valid?
462
462
  say("#{label}: No issues found", :green)
463
463
  else
464
- issue_count = report.respond_to?(:issues) ? report.issues.count : "?"
464
+ issue_count = report.is_a?(Uniword::Quality::CheckReport) ? report.issues.count : "?"
465
465
  say("#{label}: #{issue_count} issue(s) found", :yellow)
466
- if options[:verbose] && report.respond_to?(:issues)
466
+ if options[:verbose] && report.is_a?(Uniword::Quality::CheckReport)
467
467
  report.issues.each do |issue|
468
468
  say(" - #{issue}", :yellow)
469
469
  end
@@ -511,7 +511,7 @@ module Uniword
511
511
  setters.each do |opt, setter|
512
512
  next unless options[opt]
513
513
 
514
- core_properties&.send(setter, options[opt])
514
+ core_properties&.public_send(setter, options[opt])
515
515
  updated = true
516
516
  end
517
517
  updated
@@ -442,8 +442,8 @@ module Uniword
442
442
  def format_value(props, method)
443
443
  return nil unless props
444
444
 
445
- val = props.send(method)
446
- val.respond_to?(:value) ? val.value : val
445
+ val = props.public_send(method)
446
+ unwrap_value(val)
447
447
  end
448
448
 
449
449
  # Extract a value from run properties.
@@ -454,10 +454,10 @@ module Uniword
454
454
  def run_prop_value(props, method)
455
455
  return nil unless props
456
456
 
457
- val = props.send(method)
457
+ val = props.public_send(method)
458
458
  return nil if val.nil?
459
459
 
460
- val.respond_to?(:value) ? val.value : val
460
+ unwrap_value(val)
461
461
  end
462
462
 
463
463
  # Extract a metadata field value from core properties.
@@ -468,10 +468,10 @@ module Uniword
468
468
  def metadata_value(cp, field)
469
469
  return nil unless cp
470
470
 
471
- val = cp.send(field)
471
+ val = cp.public_send(field)
472
472
  return nil if val.nil?
473
473
 
474
- val.respond_to?(:value) ? val.value.to_s : val.to_s
474
+ unwrap_value(val).to_s
475
475
  end
476
476
 
477
477
  # Extract styles from a document as a hash of style_id => name.
@@ -489,13 +489,17 @@ module Uniword
489
489
  styles.each do |style|
490
490
  style_id = style.styleId&.to_s
491
491
  name_obj = style.name
492
- name = name_obj.respond_to?(:val) ? name_obj.val : name_obj.to_s
492
+ name = name_obj.is_a?(Lutaml::Model::Serializable) && name_obj.class.attributes.key?(:val) ? name_obj.val : name_obj.to_s
493
493
  result[style_id] = name if style_id
494
494
  end
495
495
 
496
496
  result
497
497
  end
498
498
 
499
+ def unwrap_value(val)
500
+ val.is_a?(Lutaml::Model::Serializable) && val.class.attributes.key?(:value) ? val.value : val
501
+ end
502
+
499
503
  # Build a text change hash.
500
504
  #
501
505
  # @param change [Symbol] :added, :removed, or :modified
@@ -199,8 +199,8 @@ module Uniword
199
199
  return unless document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
200
200
 
201
201
  PACKAGE_PART_MAPPINGS.each do |pkg_attr, doc_attr|
202
- value = package.send(pkg_attr)
203
- document.send(:"#{doc_attr}=", value) if value
202
+ value = package.public_send(pkg_attr)
203
+ document.public_send(:"#{doc_attr}=", value) if value
204
204
  end
205
205
  end
206
206
 
@@ -232,7 +232,7 @@ module Uniword
232
232
  return if path.is_a?(IO) || path.is_a?(StringIO)
233
233
 
234
234
  # For strings, validate
235
- if path.respond_to?(:empty?) && path.empty?
235
+ if path.is_a?(String) && path.empty?
236
236
  raise ArgumentError,
237
237
  "Path cannot be empty"
238
238
  end
@@ -50,13 +50,13 @@ module Uniword
50
50
 
51
51
  # Recursively collect text from paragraphs, tables, and SDTs.
52
52
  def collect_text(container, text_per_paragraph)
53
- if container.respond_to?(:paragraphs)
53
+ if container.is_a?(Uniword::Wordprocessingml::Body) || container.is_a?(Uniword::Wordprocessingml::DocumentRoot) || container.is_a?(Uniword::Wordprocessingml::TableCell)
54
54
  container.paragraphs.each do |para|
55
55
  text_per_paragraph << para.text
56
56
  end
57
57
  end
58
58
 
59
- if container.respond_to?(:tables)
59
+ if container.is_a?(Uniword::Wordprocessingml::Body) || container.is_a?(Uniword::Wordprocessingml::DocumentRoot)
60
60
  container.tables.each do |table|
61
61
  table.rows.each do |row|
62
62
  row.cells.each do |cell|
@@ -66,7 +66,7 @@ module Uniword
66
66
  end
67
67
  end
68
68
 
69
- if container.respond_to?(:structured_document_tags)
69
+ if container.is_a?(Uniword::Wordprocessingml::Body)
70
70
  container.structured_document_tags.each do |sdt|
71
71
  sdt.content&.paragraphs&.each do |para|
72
72
  text = para.text
@@ -41,8 +41,8 @@ module Uniword
41
41
  return unless document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
42
42
 
43
43
  DOCUMENT_TO_PACKAGE_MAPPINGS.each do |doc_attr, pkg_attr|
44
- value = document.send(doc_attr)
45
- package.send(:"#{pkg_attr}=", value) if value
44
+ value = document.public_send(doc_attr)
45
+ package.public_send(:"#{pkg_attr}=", value) if value
46
46
  end
47
47
 
48
48
  package.numbering = document.numbering_configuration if document.numbering_configuration_loaded?
@@ -479,7 +479,7 @@ module Uniword
479
479
  package.numbering.instances.each do |inst|
480
480
  next unless inst.abstract_num_id
481
481
 
482
- abs_id = inst.abstract_num_id.respond_to?(:val) ? inst.abstract_num_id.val : inst.abstract_num_id
482
+ abs_id = inst.abstract_num_id.is_a?(Uniword::Wordprocessingml::AbstractNumId) ? inst.abstract_num_id.val : inst.abstract_num_id
483
483
  defn = package.numbering.definitions.find do |d|
484
484
  d.abstract_num_id == abs_id
485
485
  end
@@ -985,8 +985,8 @@ module Uniword
985
985
  # 4. East Asian light font, 5. Major font (headings)
986
986
  names << fs&.minor_font if fs&.minor_font
987
987
 
988
- ea_font = loc.respond_to?(:east_asian_font) && loc.east_asian_font
989
- ea_light = loc.respond_to?(:east_asian_light_font) && loc.east_asian_light_font
988
+ ea_font = loc&.east_asian_font
989
+ ea_light = loc&.east_asian_light_font
990
990
 
991
991
  # Default East Asian fonts for zh-CN when locale profile omits them
992
992
  if loc.east_asia_lang == "zh-CN"
@@ -15,7 +15,7 @@ module Uniword
15
15
  # @param element_class [Class] The element class to register
16
16
  # @return [void]
17
17
  def register(element_class)
18
- return if element_class.respond_to?(:abstract?) && element_class.abstract?
18
+ return if element_class <= Uniword::Element && element_class.abstract?
19
19
 
20
20
  key = element_class_key(element_class)
21
21
  return if key.nil? # Skip anonymous classes
@@ -50,7 +50,9 @@ module Uniword
50
50
  #
51
51
  # @return [String] The combined text of all paragraphs
52
52
  def text
53
- @content.map { |p| p.respond_to?(:text) ? p.text : p.to_s }.join("\n")
53
+ @content.map do |p|
54
+ p.is_a?(Uniword::Wordprocessingml::Paragraph) ? p.text : p.to_s
55
+ end.join("\n")
54
56
  end
55
57
 
56
58
  # Convert to hash representation.
@@ -49,7 +49,9 @@ module Uniword
49
49
  #
50
50
  # @return [String] The combined text of all paragraphs
51
51
  def text
52
- @content.map { |p| p.respond_to?(:text) ? p.text : p.to_s }.join("\n")
52
+ @content.map do |p|
53
+ p.is_a?(Uniword::Wordprocessingml::Paragraph) ? p.text : p.to_s
54
+ end.join("\n")
53
55
  end
54
56
 
55
57
  # Convert to hash representation.