uniword 1.2.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +313 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/config/ooxml/schemas/shared_types.yml +5 -4
  5. data/data/schemas/ecma/dc.xsd +118 -0
  6. data/data/schemas/ecma/dcmitype.xsd +50 -0
  7. data/data/schemas/ecma/dcterms.xsd +331 -0
  8. data/data/schemas/ecma/opc-coreProperties.xsd +2 -2
  9. data/data/schemas/ecma/xml.xsd +117 -0
  10. data/lib/uniword/builder/bibliography_builder.rb +1 -1
  11. data/lib/uniword/builder/chart_builder.rb +22 -14
  12. data/lib/uniword/builder/comment_anchorer.rb +221 -0
  13. data/lib/uniword/builder/document_builder.rb +97 -6
  14. data/lib/uniword/builder/image_builder.rb +11 -10
  15. data/lib/uniword/builder/run_utils.rb +2 -0
  16. data/lib/uniword/builder.rb +1 -0
  17. data/lib/uniword/cli/fonts_cli.rb +52 -0
  18. data/lib/uniword/cli/main.rb +110 -11
  19. data/lib/uniword/cli/page_cli.rb +85 -0
  20. data/lib/uniword/cli/styles_cli.rb +204 -0
  21. data/lib/uniword/cli/theme_cli.rb +88 -0
  22. data/lib/uniword/cli/toc_cli.rb +22 -2
  23. data/lib/uniword/comment.rb +20 -15
  24. data/lib/uniword/comment_range.rb +3 -3
  25. data/lib/uniword/comments_part.rb +51 -19
  26. data/lib/uniword/configuration/configuration_loader.rb +5 -3
  27. data/lib/uniword/configuration.rb +93 -1
  28. data/lib/uniword/content_types.rb +19 -39
  29. data/lib/uniword/document_factory.rb +12 -18
  30. data/lib/uniword/document_writer.rb +16 -5
  31. data/lib/uniword/docx/chart_part.rb +34 -0
  32. data/lib/uniword/docx/custom_xml_item.rb +91 -0
  33. data/lib/uniword/docx/header_footer_part.rb +140 -0
  34. data/lib/uniword/docx/header_footer_part_collection.rb +135 -0
  35. data/lib/uniword/docx/header_footer_view.rb +225 -0
  36. data/lib/uniword/docx/id_allocator.rb +130 -36
  37. data/lib/uniword/docx/image_part.rb +83 -0
  38. data/lib/uniword/docx/package.rb +141 -333
  39. data/lib/uniword/docx/package_defaults.rb +83 -99
  40. data/lib/uniword/docx/package_integrity_checker.rb +312 -0
  41. data/lib/uniword/docx/package_serialization.rb +144 -233
  42. data/lib/uniword/docx/part.rb +125 -0
  43. data/lib/uniword/docx/part_collection.rb +131 -0
  44. data/lib/uniword/docx/part_loader/chart_loader.rb +50 -0
  45. data/lib/uniword/docx/part_loader/custom_xml_loader.rb +53 -0
  46. data/lib/uniword/docx/part_loader/embedding_loader.rb +26 -0
  47. data/lib/uniword/docx/part_loader/header_footer_loader.rb +101 -0
  48. data/lib/uniword/docx/part_loader/image_loader.rb +105 -0
  49. data/lib/uniword/docx/part_loader/load_context.rb +85 -0
  50. data/lib/uniword/docx/part_loader/raw_part_loader.rb +183 -0
  51. data/lib/uniword/docx/part_loader/theme_media_loader.rb +39 -0
  52. data/lib/uniword/docx/part_loader/xml_model_loader.rb +56 -0
  53. data/lib/uniword/docx/part_loader.rb +92 -0
  54. data/lib/uniword/docx/raw_part.rb +75 -0
  55. data/lib/uniword/docx/reconciler/body.rb +59 -68
  56. data/lib/uniword/docx/reconciler/fix.rb +46 -0
  57. data/lib/uniword/docx/reconciler/fix_codes.rb +37 -30
  58. data/lib/uniword/docx/reconciler/helpers.rb +18 -31
  59. data/lib/uniword/docx/reconciler/notes.rb +17 -8
  60. data/lib/uniword/docx/reconciler/package_structure.rb +168 -221
  61. data/lib/uniword/docx/reconciler/parts.rb +36 -13
  62. data/lib/uniword/docx/reconciler/referential_integrity.rb +222 -123
  63. data/lib/uniword/docx/reconciler/tables.rb +78 -16
  64. data/lib/uniword/docx/reconciler/theme.rb +7 -4
  65. data/lib/uniword/docx/reconciler.rb +36 -14
  66. data/lib/uniword/docx.rb +13 -0
  67. data/lib/uniword/drawingml/blip.rb +0 -1
  68. data/lib/uniword/drawingml/color_scheme.rb +5 -6
  69. data/lib/uniword/drawingml/font_scheme.rb +4 -8
  70. data/lib/uniword/drawingml/theme.rb +12 -6
  71. data/lib/uniword/errors.rb +8 -1
  72. data/lib/uniword/footer.rb +4 -0
  73. data/lib/uniword/header.rb +4 -0
  74. data/lib/uniword/hyperlink.rb +1 -1
  75. data/lib/uniword/image.rb +6 -6
  76. data/lib/uniword/images/image_manager.rb +13 -13
  77. data/lib/uniword/lazy_loader.rb +1 -2
  78. data/lib/uniword/model_attribute_access.rb +20 -4
  79. data/lib/uniword/ooxml/dotx_package.rb +42 -2
  80. data/lib/uniword/ooxml/element_order.rb +94 -0
  81. data/lib/uniword/ooxml/part_definition.rb +329 -0
  82. data/lib/uniword/ooxml/part_registry.rb +504 -0
  83. data/lib/uniword/ooxml/relationships/image_relationship.rb +6 -3
  84. data/lib/uniword/ooxml/relationships/package_relationships.rb +20 -22
  85. data/lib/uniword/ooxml/schema/element_serializer.rb +8 -7
  86. data/lib/uniword/ooxml/types/hex_color_value.rb +38 -0
  87. data/lib/uniword/ooxml/types/ooxml_boolean.rb +27 -14
  88. data/lib/uniword/ooxml/types/ooxml_boolean_optional.rb +21 -16
  89. data/lib/uniword/ooxml/types/theme_color_value.rb +22 -0
  90. data/lib/uniword/ooxml/types/unsigned_decimal_number.rb +30 -0
  91. data/lib/uniword/ooxml/types.rb +10 -0
  92. data/lib/uniword/ooxml.rb +3 -0
  93. data/lib/uniword/properties/alignment.rb +8 -3
  94. data/lib/uniword/properties/border.rb +52 -6
  95. data/lib/uniword/properties/cell_vertical_align.rb +4 -1
  96. data/lib/uniword/properties/color_value.rb +8 -3
  97. data/lib/uniword/properties/highlight.rb +9 -5
  98. data/lib/uniword/properties/shading.rb +4 -3
  99. data/lib/uniword/properties/tab_stop.rb +6 -1
  100. data/lib/uniword/properties/table_justification.rb +4 -1
  101. data/lib/uniword/properties/underline.rb +4 -2
  102. data/lib/uniword/properties/vertical_align.rb +6 -3
  103. data/lib/uniword/review/review_manager.rb +23 -9
  104. data/lib/uniword/revision.rb +9 -9
  105. data/lib/uniword/schema/model_generator.rb +31 -15
  106. data/lib/uniword/shared_types/hex_color.rb +4 -1
  107. data/lib/uniword/shared_types/pixel_measure.rb +4 -1
  108. data/lib/uniword/shared_types/point_measure.rb +4 -1
  109. data/lib/uniword/shared_types/text_alignment.rb +6 -1
  110. data/lib/uniword/shared_types/twips_measure.rb +4 -1
  111. data/lib/uniword/spreadsheetml/phonetic_pr.rb +1 -1
  112. data/lib/uniword/spreadsheetml/shared_string_table.rb +2 -2
  113. data/lib/uniword/spreadsheetml/table_formula.rb +1 -1
  114. data/lib/uniword/template/variable_resolver.rb +40 -7
  115. data/lib/uniword/themes/theme_transformation.rb +55 -41
  116. data/lib/uniword/toc/toc_generator.rb +6 -5
  117. data/lib/uniword/transformation/mhtml_element_renderer.rb +1 -5
  118. data/lib/uniword/transformation/mhtml_metadata_builder.rb +6 -6
  119. data/lib/uniword/validation/engine.rb +35 -0
  120. data/lib/uniword/validation/link_checker.rb +1 -1
  121. data/lib/uniword/validation/opc_validator.rb +16 -2
  122. data/lib/uniword/validation/report/layer_result.rb +0 -1
  123. data/lib/uniword/validation/report/terminal_formatter.rb +0 -1
  124. data/lib/uniword/validation/report/verification_report.rb +0 -2
  125. data/lib/uniword/validation/report.rb +20 -0
  126. data/lib/uniword/validation/rules/base.rb +10 -2
  127. data/lib/uniword/validation/rules/bookmark_pairing_rule.rb +38 -0
  128. data/lib/uniword/validation/rules/bookmark_uniqueness_rule.rb +48 -0
  129. data/lib/uniword/validation/rules/bookmarks_rule.rb +0 -2
  130. data/lib/uniword/validation/rules/content_types_coverage_rule.rb +0 -2
  131. data/lib/uniword/validation/rules/core_properties_namespace_rule.rb +0 -2
  132. data/lib/uniword/validation/rules/document_body_rule.rb +25 -0
  133. data/lib/uniword/validation/rules/document_context.rb +7 -0
  134. data/lib/uniword/validation/rules/empty_paragraphs_rule.rb +27 -0
  135. data/lib/uniword/validation/rules/font_table_signature_rule.rb +0 -2
  136. data/lib/uniword/validation/rules/fonts_rule.rb +0 -2
  137. data/lib/uniword/validation/rules/footnotes_rule.rb +0 -2
  138. data/lib/uniword/validation/rules/headers_footers_rule.rb +0 -2
  139. data/lib/uniword/validation/rules/images_rule.rb +0 -2
  140. data/lib/uniword/validation/rules/mc_ignorable_namespace_rule.rb +0 -2
  141. data/lib/uniword/validation/rules/model_context.rb +32 -0
  142. data/lib/uniword/validation/rules/model_rule.rb +34 -0
  143. data/lib/uniword/validation/rules/numbering_preservation_rule.rb +0 -2
  144. data/lib/uniword/validation/rules/numbering_rule.rb +0 -2
  145. data/lib/uniword/validation/rules/relationship_integrity_rule.rb +0 -1
  146. data/lib/uniword/validation/rules/rsid_rule.rb +0 -2
  147. data/lib/uniword/validation/rules/section_properties_rule.rb +0 -2
  148. data/lib/uniword/validation/rules/settings_rule.rb +0 -2
  149. data/lib/uniword/validation/rules/settings_values_rule.rb +0 -2
  150. data/lib/uniword/validation/rules/style_references_rule.rb +0 -2
  151. data/lib/uniword/validation/rules/table_grid_rule.rb +27 -0
  152. data/lib/uniword/validation/rules/table_properties_rule.rb +27 -0
  153. data/lib/uniword/validation/rules/tables_rule.rb +0 -2
  154. data/lib/uniword/validation/rules/theme_completeness_rule.rb +0 -2
  155. data/lib/uniword/validation/rules/theme_rule.rb +0 -2
  156. data/lib/uniword/validation/rules.rb +61 -23
  157. data/lib/uniword/validation/schema_registry.rb +11 -0
  158. data/lib/uniword/validation/validators/document_semantics_validator.rb +1 -9
  159. data/lib/uniword/validation/validators/xml_schema_validator.rb +46 -2
  160. data/lib/uniword/validation/validators.rb +0 -9
  161. data/lib/uniword/validation/verify_orchestrator.rb +0 -7
  162. data/lib/uniword/validation.rb +7 -2
  163. data/lib/uniword/version.rb +1 -1
  164. data/lib/uniword/vml/imagedata.rb +0 -1
  165. data/lib/uniword/wordprocessingml/attached_template.rb +0 -1
  166. data/lib/uniword/wordprocessingml/deleted_text.rb +1 -1
  167. data/lib/uniword/wordprocessingml/document_root.rb +121 -122
  168. data/lib/uniword/wordprocessingml/document_styling.rb +246 -0
  169. data/lib/uniword/wordprocessingml/endnotes.rb +4 -1
  170. data/lib/uniword/wordprocessingml/font_replacer.rb +147 -0
  171. data/lib/uniword/wordprocessingml/footnotes.rb +4 -1
  172. data/lib/uniword/wordprocessingml/hdr_shape_defaults.rb +0 -1
  173. data/lib/uniword/wordprocessingml/hyperlink.rb +0 -1
  174. data/lib/uniword/wordprocessingml/level.rb +6 -20
  175. data/lib/uniword/wordprocessingml/math_pr.rb +0 -11
  176. data/lib/uniword/wordprocessingml/numbering_definition.rb +16 -8
  177. data/lib/uniword/wordprocessingml/numbering_elements.rb +0 -15
  178. data/lib/uniword/wordprocessingml/page_setup.rb +181 -0
  179. data/lib/uniword/wordprocessingml/paragraph_properties.rb +33 -28
  180. data/lib/uniword/wordprocessingml/rsids.rb +0 -2
  181. data/lib/uniword/wordprocessingml/run.rb +3 -0
  182. data/lib/uniword/wordprocessingml/section_properties.rb +2 -2
  183. data/lib/uniword/wordprocessingml/settings.rb +8 -34
  184. data/lib/uniword/wordprocessingml/shape_defaults.rb +0 -1
  185. data/lib/uniword/wordprocessingml/style.rb +2 -1
  186. data/lib/uniword/wordprocessingml/style_cleanup.rb +198 -0
  187. data/lib/uniword/wordprocessingml/table_cell_properties.rb +6 -6
  188. data/lib/uniword/wordprocessingml/update_fields.rb +27 -0
  189. data/lib/uniword/wordprocessingml/w14_attributes.rb +30 -2
  190. data/lib/uniword/wordprocessingml.rb +5 -2
  191. data/lib/uniword.rb +27 -2
  192. metadata +52 -19
  193. data/config/validation_rules.yml +0 -60
  194. data/config/warning_rules.yml +0 -57
  195. data/lib/uniword/validation/document_validator.rb +0 -272
  196. data/lib/uniword/validation/structural_validator.rb +0 -116
  197. data/lib/uniword/validation/validators/content_type_validator.rb +0 -165
  198. data/lib/uniword/validation/validators/file_structure_validator.rb +0 -104
  199. data/lib/uniword/validation/validators/ooxml_part_validator.rb +0 -128
  200. data/lib/uniword/validation/validators/relationship_validator.rb +0 -147
  201. data/lib/uniword/validation/validators/zip_integrity_validator.rb +0 -110
  202. data/lib/uniword/validators/element_validator.rb +0 -93
  203. data/lib/uniword/validators/paragraph_validator.rb +0 -116
  204. data/lib/uniword/validators/table_validator.rb +0 -134
  205. data/lib/uniword/validators.rb +0 -9
  206. data/lib/uniword/warnings/warning.rb +0 -130
  207. data/lib/uniword/warnings/warning_collector.rb +0 -234
  208. data/lib/uniword/warnings/warning_report.rb +0 -159
  209. data/lib/uniword/warnings.rb +0 -9
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "zip"
4
- # LayerValidator autoloaded via lib/uniword/validation.rb
5
-
6
- module Uniword
7
- module Validation
8
- module Validators
9
- # Validates ZIP archive integrity.
10
- #
11
- # Responsibility: Validate ZIP structure and contents.
12
- # Single Responsibility: Only validates ZIP integrity.
13
- #
14
- # This is Layer 2 validation - validates the ZIP container that
15
- # holds all OOXML parts.
16
- #
17
- # Checks:
18
- # - File opens as valid ZIP
19
- # - ZIP is not empty
20
- # - Required entries exist
21
- # - Entries are not corrupted
22
- #
23
- # @example Validate ZIP integrity
24
- # validator = ZipIntegrityValidator.new(config)
25
- # result = validator.validate('/path/to/document.docx')
26
- # puts result.valid? # => true
27
- class ZipIntegrityValidator < LayerValidator
28
- def layer_name
29
- "ZIP Integrity"
30
- end
31
-
32
- def validate(path)
33
- result = LayerValidationResult.new(layer_name)
34
-
35
- begin
36
- validate_zip_structure(path, result)
37
- rescue Zip::Error => e
38
- result.add_error(
39
- "Invalid ZIP file: #{e.message}",
40
- critical: true,
41
- )
42
- end
43
-
44
- result
45
- end
46
-
47
- private
48
-
49
- def validate_zip_structure(path, result)
50
- Zip::File.open(path) do |zip_file|
51
- # Check ZIP is not empty
52
- if zip_file.entries.empty?
53
- result.add_error("ZIP archive is empty", critical: true)
54
- return
55
- end
56
-
57
- # Check required entries
58
- validate_required_entries(zip_file, result) if check_entries?
59
-
60
- # Check for corrupted entries
61
- validate_entry_integrity(zip_file, result) if check_corruption?
62
- end
63
- end
64
-
65
- def validate_required_entries(zip_file, result)
66
- required_entries = [
67
- "[Content_Types].xml",
68
- "word/document.xml",
69
- ]
70
-
71
- required_entries.each do |entry_name|
72
- next if zip_file.find_entry(entry_name)
73
-
74
- result.add_error(
75
- "Missing required file: #{entry_name}",
76
- critical: true,
77
- )
78
- end
79
- end
80
-
81
- def validate_entry_integrity(zip_file, result)
82
- zip_file.entries.each do |entry|
83
- next if entry.directory?
84
-
85
- begin
86
- # Try to read first byte to check for corruption
87
- entry.get_input_stream { |io| io.read(1) }
88
- rescue StandardError => e
89
- result.add_error(
90
- "Corrupted entry: #{entry.name} (#{e.message})",
91
- )
92
- end
93
- end
94
- end
95
-
96
- def check_entries?
97
- layer_config[:check_entries] != false
98
- end
99
-
100
- def check_corruption?
101
- layer_config[:check_corruption] != false
102
- end
103
-
104
- def layer_config
105
- @config[:zip_integrity] || {}
106
- end
107
- end
108
- end
109
- end
110
- end
@@ -1,93 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Uniword
4
- module Validators
5
- # Base validator for element validation
6
- # Responsibility: Provide validation interface and registry for element validators
7
- #
8
- # This class follows the Single Responsibility Principle by separating
9
- # validation logic from the element classes themselves.
10
- #
11
- # @example Using the validator
12
- # validator = Uniword::Validators::ElementValidator.for(Paragraph)
13
- # validator.valid?(paragraph) # => true or false
14
- #
15
- # @example Registering a custom validator
16
- # Uniword::Validators::ElementValidator.register(MyElement, MyValidator)
17
- class ElementValidator
18
- class << self
19
- # Get the appropriate validator for an element class
20
- #
21
- # @param element_class [Class] The element class to get validator for
22
- # @return [ElementValidator] An instance of the appropriate validator
23
- # @raise [ArgumentError] if element_class is not a valid Element class
24
- def for(element_class)
25
- unless element_class.is_a?(Class) &&
26
- element_class.ancestors.any? do |a|
27
- a.to_s.include?("Serializable")
28
- end
29
- raise ArgumentError,
30
- "element_class must be a lutaml-model serializable class"
31
- end
32
-
33
- validator_class = validator_registry[element_class] || self
34
- validator_class.new
35
- end
36
-
37
- # Register a validator for a specific element class
38
- #
39
- # @param element_class [Class] The element class
40
- # @param validator_class [Class] The validator class
41
- # @return [void]
42
- def register(element_class, validator_class)
43
- unless validator_class.ancestors.include?(ElementValidator)
44
- raise ArgumentError,
45
- "validator_class must inherit from ElementValidator"
46
- end
47
-
48
- validator_registry[element_class] = validator_class
49
- end
50
-
51
- # Get the validator registry
52
- #
53
- # @return [Hash] The registry mapping element classes to validators
54
- def validator_registry
55
- @validator_registry ||= {}
56
- end
57
-
58
- # Reset the validator registry (useful for testing)
59
- #
60
- # @return [void]
61
- def reset_registry
62
- @validator_registry = {}
63
- end
64
- end
65
-
66
- # Validate an element
67
- #
68
- # @param element [Element] The element to validate
69
- # @return [Boolean] true if valid, false otherwise
70
- def valid?(element)
71
- return false if element.nil?
72
- # v2.0: Check if element is a serializable object (has lutaml-model ancestry)
73
- return false unless element.class.ancestors.any? do |a|
74
- a.to_s.include?("Serializable")
75
- end
76
-
77
- true
78
- end
79
-
80
- # Get validation errors for an element
81
- #
82
- # @param element [Element] The element to validate
83
- # @return [Array<String>] Array of error messages
84
- def errors(element)
85
- return ["Element is nil"] if element.nil?
86
- return ["Element must be a Uniword::Element"] unless element.class.ancestors.any? { |a| a.to_s.include?("Serializable") }
87
- return [] if valid?(element)
88
-
89
- ["Element validation failed"]
90
- end
91
- end
92
- end
93
- end
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # NOTE: element_validator is already loaded by uniword.rb
4
- # No need to require it here to avoid circular dependencies
5
-
6
- module Uniword
7
- module Validators
8
- # Validator for Paragraph elements
9
- # Responsibility: Validate paragraph-specific constraints
10
- #
11
- # A valid paragraph:
12
- # - Must be a Paragraph instance
13
- # - Can have zero or more runs (empty paragraphs are valid)
14
- # - All runs must be valid Run instances
15
- # - Properties, if present, must be valid ParagraphProperties
16
- #
17
- # @example Using the paragraph validator
18
- # validator = Uniword::Validators::ParagraphValidator.new
19
- # validator.valid?(paragraph) # => true or false
20
- # validator.errors(paragraph) # => ["error message", ...]
21
- class ParagraphValidator < ElementValidator
22
- # Validate a paragraph element
23
- #
24
- # @param element [Paragraph] The paragraph to validate
25
- # @return [Boolean] true if valid, false otherwise
26
- def valid?(element)
27
- return false unless super
28
- return false unless element.is_a?(Uniword::Wordprocessingml::Paragraph)
29
-
30
- validate_runs(element) &&
31
- validate_properties(element)
32
- end
33
-
34
- # Get validation errors for a paragraph
35
- #
36
- # @param element [Paragraph] The paragraph to validate
37
- # @return [Array<String>] Array of error messages
38
- def errors(element)
39
- errors = []
40
-
41
- # Check if element is nil
42
- return ["Element is nil"] if element.nil?
43
-
44
- # Check paragraph type first (more specific than base check)
45
- return ["Element must be a Paragraph"] unless element.is_a?(Uniword::Wordprocessingml::Paragraph)
46
-
47
- # Validate runs - collect all specific errors
48
- errors.concat(run_errors(element))
49
-
50
- # Validate properties - collect all specific errors
51
- errors.concat(property_errors(element))
52
-
53
- errors
54
- end
55
-
56
- private
57
-
58
- # Validate that all runs are valid Run instances
59
- #
60
- # @param paragraph [Paragraph] The paragraph to validate
61
- # @return [Boolean] true if all runs are valid
62
- def validate_runs(paragraph)
63
- return true if paragraph.runs.nil? || paragraph.runs.empty?
64
-
65
- paragraph.runs.all?(Uniword::Wordprocessingml::Run)
66
- end
67
-
68
- # Get errors related to runs
69
- #
70
- # @param paragraph [Paragraph] The paragraph to validate
71
- # @return [Array<String>] Array of error messages
72
- def run_errors(paragraph)
73
- errors = []
74
-
75
- return errors if paragraph.runs.nil? || paragraph.runs.empty?
76
-
77
- paragraph.runs.each_with_index do |run, index|
78
- errors << "Run at index #{index} must be a Run instance" unless run.is_a?(Uniword::Wordprocessingml::Run)
79
- end
80
-
81
- errors
82
- end
83
-
84
- # Validate properties if present
85
- #
86
- # @param paragraph [Paragraph] The paragraph to validate
87
- # @return [Boolean] true if properties are valid or nil
88
- def validate_properties(paragraph)
89
- return true if paragraph.properties.nil?
90
-
91
- paragraph.properties.is_a?(Uniword::Wordprocessingml::ParagraphProperties)
92
- end
93
-
94
- # Get errors related to properties
95
- #
96
- # @param paragraph [Paragraph] The paragraph to validate
97
- # @return [Array<String>] Array of error messages
98
- def property_errors(paragraph)
99
- return [] if paragraph.properties.nil?
100
-
101
- return ["Properties must be a ParagraphProperties instance"] unless paragraph.properties.is_a?(Uniword::Wordprocessingml::ParagraphProperties)
102
-
103
- []
104
- end
105
- end
106
- end
107
- end
108
-
109
- # Register this validator when the file is loaded
110
- # This ensures the validator is available regardless of load order
111
- if defined?(Uniword::Validators::ElementValidator)
112
- Uniword::Validators::ElementValidator.register(
113
- Uniword::Wordprocessingml::Paragraph,
114
- Uniword::Validators::ParagraphValidator,
115
- )
116
- end
@@ -1,134 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # NOTE: element_validator is already loaded by uniword.rb
4
- # No need to require it here to avoid circular dependencies
5
-
6
- module Uniword
7
- module Validators
8
- # Validator for Table elements
9
- # Responsibility: Validate table-specific constraints
10
- #
11
- # A valid table:
12
- # - Must be a Table instance
13
- # - Can have zero or more rows (empty tables are valid)
14
- # - All rows must be valid TableRow instances
15
- # - All rows should have consistent column counts (warning, not error)
16
- # - Properties, if present, must be valid TableProperties
17
- #
18
- # @example Using the table validator
19
- # validator = Uniword::Validators::TableValidator.new
20
- # validator.valid?(table) # => true or false
21
- # validator.errors(table) # => ["error message", ...]
22
- class TableValidator < ElementValidator
23
- # Validate a table element
24
- #
25
- # @param element [Table] The table to validate
26
- # @return [Boolean] true if valid, false otherwise
27
- def valid?(element)
28
- return false unless super
29
- return false unless element.is_a?(Uniword::Wordprocessingml::Table)
30
-
31
- validate_rows(element) &&
32
- validate_properties(element)
33
- end
34
-
35
- # Get validation errors for a table
36
- #
37
- # @param element [Table] The table to validate
38
- # @return [Array<String>] Array of error messages
39
- def errors(element)
40
- errors = []
41
-
42
- # Check if element is nil
43
- return ["Element is nil"] if element.nil?
44
-
45
- # Check table type first (more specific than base check)
46
- return ["Element must be a Table"] unless element.is_a?(Uniword::Wordprocessingml::Table)
47
-
48
- # Validate rows - collect all specific errors
49
- errors.concat(row_errors(element))
50
-
51
- # Validate properties - collect all specific errors
52
- errors.concat(property_errors(element))
53
-
54
- errors
55
- end
56
-
57
- # Get validation warnings (non-critical issues)
58
- #
59
- # @param element [Table] The table to validate
60
- # @return [Array<String>] Array of warning messages
61
- def warnings(element)
62
- warnings = []
63
-
64
- return warnings unless element.is_a?(Uniword::Wordprocessingml::Table)
65
- return warnings if element.rows.empty?
66
-
67
- # Check for inconsistent column counts
68
- column_counts = element.rows.map { |row| row.cells.count }.uniq
69
- warnings << "Table has inconsistent column counts: #{column_counts.join(', ')}" if column_counts.size > 1
70
-
71
- warnings
72
- end
73
-
74
- private
75
-
76
- # Validate that all rows are valid TableRow instances
77
- #
78
- # @param table [Table] The table to validate
79
- # @return [Boolean] true if all rows are valid
80
- def validate_rows(table)
81
- return true if table.rows.nil? || table.rows.empty?
82
-
83
- table.rows.all?(Uniword::Wordprocessingml::TableRow)
84
- end
85
-
86
- # Get errors related to rows
87
- #
88
- # @param table [Table] The table to validate
89
- # @return [Array<String>] Array of error messages
90
- def row_errors(table)
91
- errors = []
92
-
93
- return errors if table.rows.nil? || table.rows.empty?
94
-
95
- table.rows.each_with_index do |row, index|
96
- errors << "Row at index #{index} must be a TableRow instance" unless row.is_a?(Uniword::Wordprocessingml::TableRow)
97
- end
98
-
99
- errors
100
- end
101
-
102
- # Validate properties if present
103
- #
104
- # @param table [Table] The table to validate
105
- # @return [Boolean] true if properties are valid or nil
106
- def validate_properties(table)
107
- return true if table.properties.nil?
108
-
109
- table.properties.is_a?(Uniword::Wordprocessingml::TableProperties)
110
- end
111
-
112
- # Get errors related to properties
113
- #
114
- # @param table [Table] The table to validate
115
- # @return [Array<String>] Array of error messages
116
- def property_errors(table)
117
- return [] if table.properties.nil?
118
-
119
- return ["Properties must be a TableProperties instance"] unless table.properties.is_a?(Uniword::Wordprocessingml::TableProperties)
120
-
121
- []
122
- end
123
- end
124
- end
125
- end
126
-
127
- # Register this validator when the file is loaded
128
- # This ensures the validator is available regardless of load order
129
- if defined?(Uniword::Validators::ElementValidator)
130
- Uniword::Validators::ElementValidator.register(
131
- Uniword::Wordprocessingml::Table,
132
- Uniword::Validators::TableValidator,
133
- )
134
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Uniword
4
- module Validators
5
- autoload :ElementValidator, "#{__dir__}/validators/element_validator"
6
- autoload :ParagraphValidator, "#{__dir__}/validators/paragraph_validator"
7
- autoload :TableValidator, "#{__dir__}/validators/table_validator"
8
- end
9
- end
@@ -1,130 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Uniword
4
- module Warnings
5
- # Represents a single warning about an unsupported element or feature.
6
- #
7
- # Responsibility: Store information about one warning.
8
- # Single Responsibility: Only represents warning data.
9
- #
10
- # A warning includes:
11
- # - Type (unsupported_element, unsupported_attribute, etc.)
12
- # - Severity (error, warning, info)
13
- # - Element/attribute name
14
- # - Context where it was encountered
15
- # - Location in document
16
- # - Suggestion for user
17
- #
18
- # @example Create a warning
19
- # warning = Warning.new(
20
- # type: :unsupported_element,
21
- # severity: :warning,
22
- # element: 'chart',
23
- # message: 'Unsupported element: chart',
24
- # context: 'While parsing paragraph',
25
- # suggestion: 'Chart will be preserved but not editable'
26
- # )
27
- class Warning
28
- # @return [Symbol] Type of warning
29
- attr_reader :type
30
-
31
- # @return [Symbol] Severity level (:error, :warning, :info)
32
- attr_reader :severity
33
-
34
- # @return [String] Element name
35
- attr_reader :element
36
-
37
- # @return [String, nil] Attribute name (for attribute warnings)
38
- attr_reader :attribute
39
-
40
- # @return [String] Warning message
41
- attr_reader :message
42
-
43
- # @return [String, nil] Context where warning occurred
44
- attr_reader :context
45
-
46
- # @return [String, nil] Location in document
47
- attr_reader :location
48
-
49
- # @return [String, nil] Suggestion for user
50
- attr_reader :suggestion
51
-
52
- # Initialize a new warning.
53
- #
54
- # @param attributes [Hash] Warning attributes
55
- # @option attributes [Symbol] :type Warning type
56
- # @option attributes [Symbol] :severity Severity level
57
- # @option attributes [String] :element Element name
58
- # @option attributes [String] :attribute Attribute name (optional)
59
- # @option attributes [String] :message Warning message
60
- # @option attributes [String] :context Context (optional)
61
- # @option attributes [String] :location Location (optional)
62
- # @option attributes [String] :suggestion Suggestion (optional)
63
- #
64
- # @example Create a warning
65
- # warning = Warning.new(
66
- # type: :unsupported_element,
67
- # severity: :error,
68
- # element: 'chart',
69
- # message: 'Unsupported element: chart'
70
- # )
71
- def initialize(attributes)
72
- @type = attributes[:type]
73
- @severity = attributes[:severity]
74
- @element = attributes[:element]
75
- @attribute = attributes[:attribute]
76
- @message = attributes[:message]
77
- @context = attributes[:context]
78
- @location = attributes[:location]
79
- @suggestion = attributes[:suggestion]
80
- end
81
-
82
- # Check if this is an error-level warning.
83
- #
84
- # @return [Boolean] true if severity is :error
85
- def error?
86
- @severity == :error
87
- end
88
-
89
- # Check if this is a warning-level warning.
90
- #
91
- # @return [Boolean] true if severity is :warning
92
- def warning?
93
- @severity == :warning
94
- end
95
-
96
- # Check if this is an info-level warning.
97
- #
98
- # @return [Boolean] true if severity is :info
99
- def info?
100
- @severity == :info
101
- end
102
-
103
- # Convert to hash representation.
104
- #
105
- # @return [Hash] Hash representation
106
- def to_h
107
- {
108
- type: @type,
109
- severity: @severity,
110
- element: @element,
111
- attribute: @attribute,
112
- message: @message,
113
- context: @context,
114
- location: @location,
115
- suggestion: @suggestion,
116
- }.compact
117
- end
118
-
119
- # Convert to string for display.
120
- #
121
- # @return [String] String representation
122
- def to_s
123
- severity_str = @severity.to_s.upcase
124
- element_str = @attribute ? "#{@element}/@#{@attribute}" : @element
125
- msg_str = @message
126
- "[#{severity_str}] #{element_str}: #{msg_str}"
127
- end
128
- end
129
- end
130
- end