uniword 1.0.4 → 1.0.6

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 +10 -28
  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
@@ -178,10 +178,10 @@ module Uniword
178
178
  def remove_template_comments(document)
179
179
  # Remove comments from paragraphs
180
180
  document.paragraphs.each do |para|
181
- next unless para.respond_to?(:comments)
181
+ next unless para.is_a?(Uniword::CommentsPart)
182
182
 
183
183
  # Filter out template comments
184
- next unless para.respond_to?(:attached_comments)
184
+ next unless para.is_a?(Uniword::CommentsPart)
185
185
 
186
186
  para.attached_comments.reject! do |c|
187
187
  template_comment?(c)
@@ -194,7 +194,7 @@ module Uniword
194
194
  # @param comment [Comment] Comment to check
195
195
  # @return [Boolean] true if template comment
196
196
  def template_comment?(comment)
197
- return false unless comment.respond_to?(:text)
197
+ return false unless comment.is_a?(Uniword::Comment)
198
198
 
199
199
  text = comment.text
200
200
  text.match?(/^\{\{.+\}\}$/)
@@ -108,9 +108,8 @@ module Uniword
108
108
  # For hashes, use key access
109
109
  if object.is_a?(Hash)
110
110
  object[property.to_sym] || object[property]
111
- # For objects with methods, call the method
112
- elsif object.respond_to?(property.to_sym)
113
- object.send(property.to_sym)
111
+ else
112
+ object.public_send(property.to_sym)
114
113
  end
115
114
  end
116
115
 
@@ -176,7 +175,7 @@ module Uniword
176
175
 
177
176
  return false if left_num.nil? || right_num.nil?
178
177
 
179
- left_num.send(operator, right_num)
178
+ left_num.public_send(operator, right_num)
180
179
  end
181
180
 
182
181
  # Convert value to number
@@ -185,8 +184,8 @@ module Uniword
185
184
  # @return [Numeric, nil] Numeric value or nil
186
185
  def to_number(value)
187
186
  return value if value.is_a?(Numeric)
188
- return value.to_i if value.respond_to?(:to_i) && value.to_s.match?(/^\d+$/)
189
- return value.to_f if value.respond_to?(:to_f) && value.to_s.match?(/^\d+\.\d+$/)
187
+ return value.to_i if value.to_s.match?(/^\d+$/)
188
+ return value.to_f if value.to_s.match?(/^\d+\.\d+$/)
190
189
 
191
190
  nil
192
191
  end
@@ -182,7 +182,7 @@ module Uniword
182
182
 
183
183
  colors = {}
184
184
  COLOR_KEYS.each do |key|
185
- if (color_ref = word_colors.send(key))
185
+ if (color_ref = word_colors.public_send(key))
186
186
  colors[key] = extract_hex_color(color_ref)
187
187
  end
188
188
  end
@@ -145,7 +145,7 @@ module Uniword
145
145
  style_ref = paragraph.properties&.style
146
146
  return nil unless style_ref
147
147
 
148
- if style_ref.respond_to?(:value)
148
+ if style_ref.is_a?(Uniword::Properties::StyleReference)
149
149
  style_ref.value
150
150
  else
151
151
  style_ref.to_s
@@ -239,7 +239,7 @@ module Uniword
239
239
  result = %(<span style="font-size:#{size_pt}pt">#{result}</span>)
240
240
  end
241
241
 
242
- if props.font.respond_to?(:ascii) && props.font.ascii
242
+ if props.font.is_a?(Uniword::Properties::RunFonts) && props.font.ascii
243
243
  result = %(<span style="font-family:'#{props.font.ascii}'">#{result}</span>)
244
244
  elsif props.font.is_a?(String) && !props.font.empty?
245
245
  result = %(<span style="font-family:'#{props.font}'">#{result}</span>)
@@ -582,7 +582,7 @@ module Uniword
582
582
 
583
583
  attrs = []
584
584
 
585
- attrs << %(w:id="#{props.id.value}") if props.id.respond_to?(:value) && props.id.value
585
+ attrs << %(w:id="#{props.id.value}") if props.id&.value
586
586
 
587
587
  attrs << 'w:showingPlcHdr="t"' if props.showing_placeholder_header
588
588
 
@@ -590,14 +590,12 @@ module Uniword
590
590
 
591
591
  if props.placeholder&.doc_part
592
592
  doc_part = props.placeholder.doc_part
593
- attrs << %(w:docPart="#{doc_part.value}") if doc_part.respond_to?(:value) && doc_part.value
593
+ attrs << %(w:docPart="#{doc_part.value}") if doc_part&.value
594
594
  end
595
595
 
596
- attrs << %(w:text="#{props.text.value}") if props.text.respond_to?(:value) && props.text.value
596
+ attrs << %(w:tag="#{escape_xml(props.tag.value)}") if props.tag&.value
597
597
 
598
- attrs << %(w:tag="#{escape_xml(props.tag.value)}") if props.tag.respond_to?(:value) && props.tag.value
599
-
600
- attrs << %(w:alias="#{escape_xml(props.alias_name.value)}") if props.alias_name.respond_to?(:value) && props.alias_name.value
598
+ attrs << %(w:alias="#{escape_xml(props.alias_name.value)}") if props.alias_name&.value
601
599
 
602
600
  attrs.empty? ? "" : " #{attrs.join(' ')}"
603
601
  end
@@ -49,14 +49,11 @@ module Uniword
49
49
  props = Uniword::Mhtml::Metadata::DocumentProperties.new
50
50
  cp = core_properties
51
51
 
52
- if cp
53
- props.author = cp.creator.to_s if cp.respond_to?(:creator) && cp.creator
54
- props.created = cp.created.value.to_s if cp.respond_to?(:created) && cp.created
55
- props.last_author = cp.last_modified_by.to_s if cp.respond_to?(:last_modified_by) && cp.last_modified_by
56
- props.last_saved = cp.modified.value.to_s if cp.respond_to?(:modified) && cp.modified
57
- props.pages = cp.pages.to_s if cp.respond_to?(:pages) && cp.pages
58
- props.words = cp.words.to_s if cp.respond_to?(:words) && cp.words
59
- props.characters = cp.characters.to_s if cp.respond_to?(:characters) && cp.characters
52
+ if cp.is_a?(Uniword::Ooxml::CoreProperties)
53
+ props.author = cp.creator.to_s if cp.creator
54
+ props.created = cp.created.value.to_s if cp.created
55
+ props.last_author = cp.last_modified_by.to_s if cp.last_modified_by
56
+ props.last_saved = cp.modified.value.to_s if cp.modified
60
57
  props.application = "Microsoft Word"
61
58
  end
62
59
 
@@ -109,11 +106,11 @@ module Uniword
109
106
  props = core_properties
110
107
  return "" unless props
111
108
 
112
- author = props.respond_to?(:creator) ? props.creator : nil
113
- last_author = props.respond_to?(:last_modified_by) ? props.last_modified_by : nil
114
- revision = props.respond_to?(:revision) ? props.revision : nil
115
- created = props.respond_to?(:created) && props.created ? props.created.value : nil
116
- last_saved = props.respond_to?(:modified) && props.modified ? props.modified.value : nil
109
+ author = props.is_a?(Uniword::Ooxml::CoreProperties) ? props.creator : nil
110
+ last_author = props.is_a?(Uniword::Ooxml::CoreProperties) ? props.last_modified_by : nil
111
+ revision = props.is_a?(Uniword::Ooxml::CoreProperties) ? props.revision : nil
112
+ created = props.is_a?(Uniword::Ooxml::CoreProperties) && props.created ? props.created.value : nil
113
+ last_saved = props.is_a?(Uniword::Ooxml::CoreProperties) && props.modified ? props.modified.value : nil
117
114
 
118
115
  stats = calculate_document_stats
119
116
 
@@ -212,11 +209,13 @@ module Uniword
212
209
 
213
210
  # Get a custom document property by name
214
211
  def custom_property(name)
215
- if @document.respond_to?(:core_properties) && @document.core_properties
216
- cp = @document.core_properties
217
- return cp.custom_properties[name] if cp.respond_to?(:custom_properties) && cp.custom_properties
218
- end
219
- nil
212
+ cp = @document.custom_properties
213
+ return nil unless cp
214
+
215
+ prop = cp.properties.find { |p| p.name == name }
216
+ return nil unless prop
217
+
218
+ prop.lpwstr || prop.lpstr || prop.bstr
220
219
  end
221
220
 
222
221
  # Escape XML special characters
@@ -110,7 +110,7 @@ module Uniword
110
110
  def self.wrap_html(body_html, document)
111
111
  title = document.title ? escape_html(document.title) : "Document"
112
112
  core_props = document.core_properties
113
- author = core_props.respond_to?(:creator) ? core_props.creator : nil
113
+ author = core_props.is_a?(Uniword::Ooxml::CoreProperties) ? core_props.creator : nil
114
114
 
115
115
  meta_tags = []
116
116
  meta_tags << "<meta name=\"author\" content=\"#{escape_html(author)}\">" if author
@@ -166,7 +166,7 @@ module Uniword
166
166
  ooxml_doc = Wordprocessingml::DocumentRoot.new
167
167
 
168
168
  # Get HTML content - use raw_html which is the correct method on Mhtml::Document
169
- html_content = source.raw_html if source.respond_to?(:raw_html) && source.raw_html
169
+ html_content = source.raw_html if source.is_a?(Uniword::Mhtml::Document) && source.raw_html
170
170
  return ooxml_doc if html_content.nil? || html_content.empty?
171
171
 
172
172
  # Convert HTML to OOXML paragraphs
@@ -185,15 +185,15 @@ module Uniword
185
185
  doc_props = source.document_properties
186
186
  if doc_props
187
187
  ooxml_doc.core_properties ||= Uniword::Ooxml::CoreProperties.new
188
- ooxml_doc.core_properties.creator = doc_props.author if doc_props.respond_to?(:author) && doc_props.author
189
- ooxml_doc.core_properties.creator = doc_props.author if doc_props.respond_to?(:author) && doc_props.author
190
- if doc_props.respond_to?(:created) && doc_props.created
188
+ ooxml_doc.core_properties.creator = doc_props.author if doc_props.author
189
+ ooxml_doc.core_properties.creator = doc_props.author if doc_props.author
190
+ if doc_props.created
191
191
  ooxml_doc.core_properties.created = Uniword::Ooxml::Types::DctermsCreatedType.new(
192
192
  value: doc_props.created, type: "dcterms:W3CDTF",
193
193
  )
194
194
  end
195
- ooxml_doc.core_properties.last_modified_by = doc_props.last_author if doc_props.respond_to?(:last_author) && doc_props.last_author
196
- if doc_props.respond_to?(:last_saved) && doc_props.last_saved
195
+ ooxml_doc.core_properties.last_modified_by = doc_props.last_author if doc_props.last_author
196
+ if doc_props.last_saved
197
197
  ooxml_doc.core_properties.modified = Uniword::Ooxml::Types::DctermsModifiedType.new(
198
198
  value: doc_props.last_saved, type: "dcterms:W3CDTF",
199
199
  )
@@ -55,7 +55,7 @@ module Uniword
55
55
  # checker.can_check?(hyperlink) # => true
56
56
  def can_check?(link)
57
57
  return false unless enabled?
58
- return false unless link.respond_to?(:url)
58
+ return false unless link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:url)
59
59
 
60
60
  url = link.url
61
61
  url&.to_s&.match?(%r{^https?://})
@@ -47,10 +47,10 @@ module Uniword
47
47
  # Check if link is a file path (string or has path attribute)
48
48
  if link.is_a?(String)
49
49
  looks_like_file_path?(link)
50
- elsif link.respond_to?(:url)
50
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:url)
51
51
  url = link.url
52
52
  url && looks_like_file_path?(url)
53
- elsif link.respond_to?(:path)
53
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:path)
54
54
  true
55
55
  else
56
56
  false
@@ -135,9 +135,9 @@ module Uniword
135
135
  def extract_file_path(link)
136
136
  if link.is_a?(String)
137
137
  link
138
- elsif link.respond_to?(:path)
138
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:path)
139
139
  link.path
140
- elsif link.respond_to?(:url)
140
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:url)
141
141
  url = link.url
142
142
  # Extract file path from file:// URLs
143
143
  if url&.start_with?("file://")
@@ -170,7 +170,7 @@ module Uniword
170
170
  # @return [String] Base path
171
171
  def determine_base_path(document)
172
172
  # Try to get document's directory
173
- if document.respond_to?(:file_path)
173
+ if document.is_a?(Lutaml::Model::Serializable) && document.class.attributes.key?(:file_path)
174
174
  File.dirname(document.file_path)
175
175
  else
176
176
  # Use configured base path
@@ -39,10 +39,10 @@ module Uniword
39
39
  return false unless enabled?
40
40
 
41
41
  # Check if link is a footnote reference
42
- link.respond_to?(:footnote_id) ||
43
- link.respond_to?(:endnote_id) ||
44
- (link.respond_to?(:type) && %i[footnote
45
- endnote].include?(link.type))
42
+ (link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:footnote_id)) ||
43
+ (link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:endnote_id)) ||
44
+ (link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:type) && %i[footnote
45
+ endnote].include?(link.type))
46
46
  end
47
47
 
48
48
  # Validate the footnote reference.
@@ -117,11 +117,11 @@ module Uniword
117
117
  # @param link [Object] The link object
118
118
  # @return [String, Integer, nil] Note ID
119
119
  def extract_note_id(link)
120
- if link.respond_to?(:footnote_id)
120
+ if link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:footnote_id)
121
121
  link.footnote_id
122
- elsif link.respond_to?(:endnote_id)
122
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:endnote_id)
123
123
  link.endnote_id
124
- elsif link.respond_to?(:id)
124
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:id)
125
125
  link.id
126
126
  end
127
127
  end
@@ -131,9 +131,9 @@ module Uniword
131
131
  # @param link [Object] The link object
132
132
  # @return [Symbol] Note type (:footnote or :endnote)
133
133
  def determine_note_type(link)
134
- if link.respond_to?(:type)
134
+ if link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:type)
135
135
  link.type
136
- elsif link.respond_to?(:endnote_id)
136
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:endnote_id)
137
137
  :endnote
138
138
  else
139
139
  :footnote
@@ -151,8 +151,8 @@ module Uniword
151
151
  # Try to get notes from document
152
152
  collection_name = note_type == :endnote ? :endnotes : :footnotes
153
153
 
154
- if document.respond_to?(collection_name)
155
- collection = document.send(collection_name)
154
+ if document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
155
+ collection = document.public_send(collection_name)
156
156
 
157
157
  case collection
158
158
  when Hash
@@ -160,7 +160,7 @@ module Uniword
160
160
  when Array
161
161
  # Convert array to hash indexed by ID
162
162
  collection.each do |note|
163
- id = note.respond_to?(:id) ? note.id : note.to_s
163
+ id = note.is_a?(Lutaml::Model::Serializable) && note.class.attributes.key?(:id) ? note.id : note.to_s
164
164
  notes[id] = note
165
165
  end
166
166
  end
@@ -40,12 +40,10 @@ module Uniword
40
40
  # checker.can_check?(hyperlink) # => true
41
41
  def can_check?(link)
42
42
  return false unless enabled?
43
- return false unless link.respond_to?(:anchor)
43
+ return false unless link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:anchor)
44
44
 
45
45
  # Internal links have anchor but no URL
46
- link.anchor && !link.respond_to?(:url).then do |has_url|
47
- has_url ? !link.url : true
48
- end
46
+ link.anchor && !(link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:url) && link.url)
49
47
  end
50
48
 
51
49
  # Validate the internal link.
@@ -108,13 +106,13 @@ module Uniword
108
106
  bookmarks = []
109
107
 
110
108
  # Extract from document.bookmarks if available
111
- if document.respond_to?(:bookmarks)
109
+ if document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
112
110
  case document.bookmarks
113
111
  when Hash
114
112
  bookmarks.concat(document.bookmarks.keys.map(&:to_s))
115
113
  when Array
116
114
  bookmarks.concat(document.bookmarks.map do |b|
117
- b.respond_to?(:name) ? b.name : b.to_s
115
+ b.is_a?(Lutaml::Model::Serializable) && b.class.attributes.key?(:name) ? b.name : b.to_s
118
116
  end)
119
117
  end
120
118
  end
@@ -132,13 +130,13 @@ module Uniword
132
130
  # @param document [Object] The document
133
131
  # @return [Array<String>] Heading bookmark names
134
132
  def extract_heading_bookmarks(document)
135
- return [] unless document.respond_to?(:paragraphs)
133
+ return [] unless document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
136
134
 
137
135
  document.paragraphs.select do |p|
138
- p.respond_to?(:style) && p.style&.match?(/^Heading/)
136
+ p.is_a?(Lutaml::Model::Serializable) && p.class.attributes.key?(:style) && p.style&.match?(/^Heading/)
139
137
  end.filter_map do |p|
140
138
  # Generate bookmark from heading text
141
- p.respond_to?(:text) ? heading_to_bookmark(p.text) : nil
139
+ p.is_a?(Lutaml::Model::Serializable) && p.class.attributes.key?(:text) ? heading_to_bookmark(p.text) : nil
142
140
  end
143
141
  end
144
142
 
@@ -193,27 +193,27 @@ module Uniword
193
193
  links = []
194
194
 
195
195
  # Extract from paragraphs
196
- if document.respond_to?(:paragraphs)
196
+ if document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
197
197
  document.paragraphs.each do |para|
198
198
  links.concat(extract_links_from_paragraph(para))
199
199
  end
200
200
  end
201
201
 
202
202
  # Extract from tables
203
- if document.respond_to?(:tables)
203
+ if document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
204
204
  document.tables.each do |table|
205
205
  links.concat(extract_links_from_table(table))
206
206
  end
207
207
  end
208
208
 
209
209
  # Extract from headers/footers
210
- if document.respond_to?(:headers) && document.headers
210
+ if document.is_a?(Uniword::Wordprocessingml::DocumentRoot) && document.headers
211
211
  document.headers.each do |header|
212
212
  links.concat(extract_links_from_section(header))
213
213
  end
214
214
  end
215
215
 
216
- if document.respond_to?(:footers) && document.footers
216
+ if document.is_a?(Uniword::Wordprocessingml::DocumentRoot) && document.footers
217
217
  document.footers.each do |footer|
218
218
  links.concat(extract_links_from_section(footer))
219
219
  end
@@ -230,14 +230,12 @@ module Uniword
230
230
  links = []
231
231
 
232
232
  # Check paragraph runs for hyperlinks
233
- if para.respond_to?(:runs)
234
- para.runs.each do |run|
235
- links << run if run.is_a?(Wordprocessingml::Hyperlink)
236
- end
233
+ para.runs.each do |run|
234
+ links << run if run.is_a?(Wordprocessingml::Hyperlink)
237
235
  end
238
236
 
239
237
  # Check for hyperlinks collection
240
- links.concat(para.hyperlinks) if para.respond_to?(:hyperlinks)
238
+ links.concat(para.hyperlinks) if para.hyperlinks
241
239
 
242
240
  links
243
241
  end
@@ -249,16 +247,14 @@ module Uniword
249
247
  def extract_links_from_table(table)
250
248
  links = []
251
249
 
252
- if table.respond_to?(:rows)
253
- table.rows.each do |row|
254
- next unless row.respond_to?(:cells)
250
+ table.rows&.each do |row|
251
+ next unless row.is_a?(Uniword::Wordprocessingml::TableRow)
255
252
 
256
- row.cells.each do |cell|
257
- next unless cell.respond_to?(:paragraphs)
253
+ row.cells.each do |cell|
254
+ next unless cell.is_a?(Uniword::Wordprocessingml::TableCell)
258
255
 
259
- cell.paragraphs.each do |para|
260
- links.concat(extract_links_from_paragraph(para))
261
- end
256
+ cell.paragraphs.each do |para|
257
+ links.concat(extract_links_from_paragraph(para))
262
258
  end
263
259
  end
264
260
  end
@@ -273,10 +269,8 @@ module Uniword
273
269
  def extract_links_from_section(section)
274
270
  links = []
275
271
 
276
- if section.respond_to?(:paragraphs)
277
- section.paragraphs.each do |para|
278
- links.concat(extract_links_from_paragraph(para))
279
- end
272
+ section.paragraphs&.each do |para|
273
+ links.concat(extract_links_from_paragraph(para))
280
274
  end
281
275
 
282
276
  links
@@ -290,7 +284,7 @@ module Uniword
290
284
  refs = []
291
285
 
292
286
  # Check if document has footnotes
293
- if document.respond_to?(:footnotes)
287
+ if document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
294
288
  footnotes = document.footnotes
295
289
  case footnotes
296
290
  when Hash
@@ -301,7 +295,7 @@ module Uniword
301
295
  end
302
296
 
303
297
  # Check if document has endnotes
304
- if document.respond_to?(:endnotes)
298
+ if document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
305
299
  endnotes = document.endnotes
306
300
  case endnotes
307
301
  when Hash
@@ -330,11 +330,13 @@ module Uniword
330
330
  # @param link [Object] The link object
331
331
  # @return [Symbol] Link type
332
332
  def classify_link_type(link)
333
- if link.respond_to?(:url) && link.url
333
+ if link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:url) && link.url
334
334
  :external
335
- elsif link.respond_to?(:anchor) && link.anchor
335
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:anchor) && link.anchor
336
336
  :internal
337
- elsif link.respond_to?(:id)
337
+ elsif link.is_a?(Uniword::Wordprocessingml::Hyperlink) && link.id
338
+ :external
339
+ elsif link.is_a?(Lutaml::Model::Serializable) && link.class.attributes.key?(:id)
338
340
  :footnote
339
341
  elsif link.is_a?(String) && File.exist?(link)
340
342
  :file
@@ -152,13 +152,13 @@ module Uniword
152
152
  # @example
153
153
  # result.link_identifier # => "https://example.com"
154
154
  def link_identifier
155
- if @link.respond_to?(:url) && @link.url
155
+ if @link.is_a?(Lutaml::Model::Serializable) && @link.class.attributes.key?(:url) && @link.url
156
156
  @link.url
157
- elsif @link.respond_to?(:anchor) && @link.anchor
157
+ elsif @link.is_a?(Lutaml::Model::Serializable) && @link.class.attributes.key?(:anchor) && @link.anchor
158
158
  "##{@link.anchor}"
159
- elsif @link.respond_to?(:id)
159
+ elsif @link.is_a?(Lutaml::Model::Serializable) && @link.class.attributes.key?(:id)
160
160
  @link.id.to_s
161
- elsif @link.respond_to?(:name)
161
+ elsif @link.is_a?(Lutaml::Model::Serializable) && @link.class.attributes.key?(:name)
162
162
  @link.name
163
163
  else
164
164
  @link.to_s
@@ -74,13 +74,7 @@ module Uniword
74
74
  a.to_s.include?("Serializable")
75
75
  end
76
76
 
77
- # v2.0: All lutaml-model objects are valid by default
78
- # v1.x: Elements have a valid? method
79
- if element.respond_to?(:valid?)
80
- element.valid?
81
- else
82
- true
83
- end
77
+ true
84
78
  end
85
79
 
86
80
  # Get validation errors for an element
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uniword
4
- VERSION = "1.0.4"
4
+ VERSION = "1.0.6"
5
5
  end
@@ -37,14 +37,14 @@ module Uniword
37
37
  # @return [void]
38
38
  def visit_document(document)
39
39
  # Visit both paragraphs and tables from body
40
- if document.body.respond_to?(:elements)
40
+ if document.body.is_a?(Uniword::Wordprocessingml::Body)
41
41
  document.body.elements.each do |element|
42
- element.accept(self) if element.respond_to?(:accept)
42
+ element.accept(self) if element.is_a?(Lutaml::Model::Serializable)
43
43
  end
44
44
  else
45
45
  # Legacy support: visit paragraphs only
46
46
  document.elements.each do |element|
47
- element.accept(self) if element.respond_to?(:accept)
47
+ element.accept(self) if element.is_a?(Lutaml::Model::Serializable)
48
48
  end
49
49
  end
50
50
  end
@@ -218,7 +218,7 @@ module Uniword
218
218
 
219
219
  # Use Uniword logger if available
220
220
  if defined?(Uniword::Logger)
221
- Uniword::Logger.send(level, message)
221
+ Uniword::Logger.public_send(level, message)
222
222
  elsif %i[warn error].include?(level)
223
223
  # Fall back to standard output
224
224
  puts message
@@ -106,7 +106,7 @@ module Uniword
106
106
 
107
107
  def watermark?(paragraph)
108
108
  return false unless paragraph.properties
109
- return false unless paragraph.properties.respond_to?(:style)
109
+ return false unless paragraph.properties.is_a?(Uniword::Wordprocessingml::ParagraphProperties)
110
110
 
111
111
  paragraph.properties.style.to_s == "UniwordWatermark"
112
112
  end
@@ -127,7 +127,7 @@ module Uniword
127
127
 
128
128
  # Get a definition by abstract_num_id (accepts Integer or AbstractNumId)
129
129
  def get_definition(abstract_num_id)
130
- target_id = abstract_num_id.respond_to?(:val) ? abstract_num_id.val : abstract_num_id
130
+ target_id = abstract_num_id.is_a?(AbstractNumId) ? abstract_num_id.val : abstract_num_id
131
131
  definitions.find { |d| d.abstract_num_id == target_id }
132
132
  end
133
133
 
@@ -180,8 +180,8 @@ module Uniword
180
180
 
181
181
  # Get all attribute names from RunProperties class
182
182
  RunProperties.attributes.each_key do |attr_name|
183
- override_val = override.send(attr_name)
184
- base_val = base.send(attr_name)
183
+ override_val = override.public_send(attr_name)
184
+ base_val = base.public_send(attr_name)
185
185
 
186
186
  # Use override if it's non-nil AND not using default value
187
187
  # For boolean properties like Bold, check if it was explicitly set
@@ -197,9 +197,9 @@ module Uniword
197
197
 
198
198
  begin
199
199
  if use_override
200
- merged.send(:"#{attr_name}=", override_val)
200
+ merged.public_send(:"#{attr_name}=", override_val)
201
201
  elsif base_val
202
- merged.send(:"#{attr_name}=", base_val)
202
+ merged.public_send(:"#{attr_name}=", base_val)
203
203
  end
204
204
  rescue StandardError => e
205
205
  Uniword.logger&.debug do
@@ -25,7 +25,7 @@ module Uniword
25
25
  def resolve_inheritance(library = nil)
26
26
  return properties unless @base_style && library
27
27
 
28
- base_def = library.send(style_type, @base_style)
28
+ base_def = library.public_send(style_type, @base_style)
29
29
  base_props = base_def.resolve_inheritance(library)
30
30
 
31
31
  merge_properties(base_props, properties)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uniword
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-06 00:00:00.000000000 Z
11
+ date: 2026-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logger