uniword 1.2.5 → 1.3.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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +223 -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 +19 -9
  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 +6 -5
  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 +9 -1
  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 +83 -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/package.rb +223 -35
  38. data/lib/uniword/docx/package_defaults.rb +58 -78
  39. data/lib/uniword/docx/package_integrity_checker.rb +312 -0
  40. data/lib/uniword/docx/package_serialization.rb +103 -231
  41. data/lib/uniword/docx/part.rb +106 -0
  42. data/lib/uniword/docx/part_collection.rb +123 -0
  43. data/lib/uniword/docx/reconciler/body.rb +59 -68
  44. data/lib/uniword/docx/reconciler/fix.rb +46 -0
  45. data/lib/uniword/docx/reconciler/fix_codes.rb +37 -30
  46. data/lib/uniword/docx/reconciler/helpers.rb +9 -1
  47. data/lib/uniword/docx/reconciler/notes.rb +17 -8
  48. data/lib/uniword/docx/reconciler/package_structure.rb +169 -221
  49. data/lib/uniword/docx/reconciler/parts.rb +36 -13
  50. data/lib/uniword/docx/reconciler/referential_integrity.rb +297 -123
  51. data/lib/uniword/docx/reconciler/tables.rb +78 -16
  52. data/lib/uniword/docx/reconciler/theme.rb +7 -4
  53. data/lib/uniword/docx/reconciler.rb +36 -14
  54. data/lib/uniword/docx.rb +10 -0
  55. data/lib/uniword/drawingml/blip.rb +0 -1
  56. data/lib/uniword/drawingml/color_scheme.rb +5 -6
  57. data/lib/uniword/drawingml/font_scheme.rb +4 -8
  58. data/lib/uniword/drawingml/theme.rb +12 -6
  59. data/lib/uniword/errors.rb +8 -1
  60. data/lib/uniword/footer.rb +4 -0
  61. data/lib/uniword/header.rb +4 -0
  62. data/lib/uniword/hyperlink.rb +1 -1
  63. data/lib/uniword/image.rb +6 -6
  64. data/lib/uniword/lazy_loader.rb +1 -2
  65. data/lib/uniword/model_attribute_access.rb +20 -4
  66. data/lib/uniword/ooxml/dotx_package.rb +42 -2
  67. data/lib/uniword/ooxml/part_definition.rb +171 -0
  68. data/lib/uniword/ooxml/part_registry.rb +311 -0
  69. data/lib/uniword/ooxml/relationships/image_relationship.rb +1 -1
  70. data/lib/uniword/ooxml/relationships/package_relationships.rb +20 -22
  71. data/lib/uniword/ooxml/schema/element_serializer.rb +8 -7
  72. data/lib/uniword/ooxml/types/hex_color_value.rb +38 -0
  73. data/lib/uniword/ooxml/types/ooxml_boolean.rb +27 -14
  74. data/lib/uniword/ooxml/types/ooxml_boolean_optional.rb +21 -16
  75. data/lib/uniword/ooxml/types/theme_color_value.rb +22 -0
  76. data/lib/uniword/ooxml/types/unsigned_decimal_number.rb +30 -0
  77. data/lib/uniword/ooxml/types.rb +10 -0
  78. data/lib/uniword/ooxml.rb +2 -0
  79. data/lib/uniword/properties/alignment.rb +8 -3
  80. data/lib/uniword/properties/border.rb +52 -6
  81. data/lib/uniword/properties/cell_vertical_align.rb +4 -1
  82. data/lib/uniword/properties/color_value.rb +8 -3
  83. data/lib/uniword/properties/highlight.rb +9 -5
  84. data/lib/uniword/properties/shading.rb +4 -3
  85. data/lib/uniword/properties/tab_stop.rb +6 -1
  86. data/lib/uniword/properties/table_justification.rb +4 -1
  87. data/lib/uniword/properties/underline.rb +4 -2
  88. data/lib/uniword/properties/vertical_align.rb +6 -3
  89. data/lib/uniword/review/review_manager.rb +23 -9
  90. data/lib/uniword/revision.rb +9 -9
  91. data/lib/uniword/schema/model_generator.rb +31 -15
  92. data/lib/uniword/shared_types/hex_color.rb +4 -1
  93. data/lib/uniword/shared_types/pixel_measure.rb +4 -1
  94. data/lib/uniword/shared_types/point_measure.rb +4 -1
  95. data/lib/uniword/shared_types/text_alignment.rb +6 -1
  96. data/lib/uniword/shared_types/twips_measure.rb +4 -1
  97. data/lib/uniword/spreadsheetml/phonetic_pr.rb +1 -1
  98. data/lib/uniword/spreadsheetml/shared_string_table.rb +2 -2
  99. data/lib/uniword/spreadsheetml/table_formula.rb +1 -1
  100. data/lib/uniword/template/variable_resolver.rb +40 -7
  101. data/lib/uniword/themes/theme_transformation.rb +55 -41
  102. data/lib/uniword/toc/toc_generator.rb +2 -0
  103. data/lib/uniword/validation/engine.rb +35 -0
  104. data/lib/uniword/validation/link_checker.rb +1 -1
  105. data/lib/uniword/validation/opc_validator.rb +0 -1
  106. data/lib/uniword/validation/report/layer_result.rb +0 -1
  107. data/lib/uniword/validation/report/terminal_formatter.rb +0 -1
  108. data/lib/uniword/validation/report/verification_report.rb +0 -2
  109. data/lib/uniword/validation/report.rb +20 -0
  110. data/lib/uniword/validation/rules/base.rb +10 -2
  111. data/lib/uniword/validation/rules/bookmark_pairing_rule.rb +38 -0
  112. data/lib/uniword/validation/rules/bookmark_uniqueness_rule.rb +48 -0
  113. data/lib/uniword/validation/rules/bookmarks_rule.rb +0 -2
  114. data/lib/uniword/validation/rules/content_types_coverage_rule.rb +0 -2
  115. data/lib/uniword/validation/rules/core_properties_namespace_rule.rb +0 -2
  116. data/lib/uniword/validation/rules/document_body_rule.rb +25 -0
  117. data/lib/uniword/validation/rules/document_context.rb +7 -0
  118. data/lib/uniword/validation/rules/empty_paragraphs_rule.rb +27 -0
  119. data/lib/uniword/validation/rules/font_table_signature_rule.rb +0 -2
  120. data/lib/uniword/validation/rules/fonts_rule.rb +0 -2
  121. data/lib/uniword/validation/rules/footnotes_rule.rb +0 -2
  122. data/lib/uniword/validation/rules/headers_footers_rule.rb +0 -2
  123. data/lib/uniword/validation/rules/images_rule.rb +0 -2
  124. data/lib/uniword/validation/rules/mc_ignorable_namespace_rule.rb +0 -2
  125. data/lib/uniword/validation/rules/model_context.rb +32 -0
  126. data/lib/uniword/validation/rules/model_rule.rb +34 -0
  127. data/lib/uniword/validation/rules/numbering_preservation_rule.rb +0 -2
  128. data/lib/uniword/validation/rules/numbering_rule.rb +0 -2
  129. data/lib/uniword/validation/rules/relationship_integrity_rule.rb +0 -1
  130. data/lib/uniword/validation/rules/rsid_rule.rb +0 -2
  131. data/lib/uniword/validation/rules/section_properties_rule.rb +0 -2
  132. data/lib/uniword/validation/rules/settings_rule.rb +0 -2
  133. data/lib/uniword/validation/rules/settings_values_rule.rb +0 -2
  134. data/lib/uniword/validation/rules/style_references_rule.rb +0 -2
  135. data/lib/uniword/validation/rules/table_grid_rule.rb +27 -0
  136. data/lib/uniword/validation/rules/table_properties_rule.rb +27 -0
  137. data/lib/uniword/validation/rules/tables_rule.rb +0 -2
  138. data/lib/uniword/validation/rules/theme_completeness_rule.rb +0 -2
  139. data/lib/uniword/validation/rules/theme_rule.rb +0 -2
  140. data/lib/uniword/validation/rules.rb +61 -23
  141. data/lib/uniword/validation/schema_registry.rb +11 -0
  142. data/lib/uniword/validation/validators/document_semantics_validator.rb +1 -9
  143. data/lib/uniword/validation/validators/xml_schema_validator.rb +0 -2
  144. data/lib/uniword/validation/validators.rb +0 -9
  145. data/lib/uniword/validation/verify_orchestrator.rb +0 -7
  146. data/lib/uniword/validation.rb +7 -2
  147. data/lib/uniword/version.rb +1 -1
  148. data/lib/uniword/vml/imagedata.rb +0 -1
  149. data/lib/uniword/wordprocessingml/attached_template.rb +0 -1
  150. data/lib/uniword/wordprocessingml/deleted_text.rb +1 -1
  151. data/lib/uniword/wordprocessingml/document_root.rb +211 -21
  152. data/lib/uniword/wordprocessingml/endnotes.rb +4 -0
  153. data/lib/uniword/wordprocessingml/font_replacer.rb +147 -0
  154. data/lib/uniword/wordprocessingml/footnotes.rb +4 -0
  155. data/lib/uniword/wordprocessingml/hdr_shape_defaults.rb +0 -1
  156. data/lib/uniword/wordprocessingml/hyperlink.rb +0 -1
  157. data/lib/uniword/wordprocessingml/level.rb +6 -20
  158. data/lib/uniword/wordprocessingml/math_pr.rb +0 -11
  159. data/lib/uniword/wordprocessingml/numbering_definition.rb +16 -8
  160. data/lib/uniword/wordprocessingml/page_setup.rb +181 -0
  161. data/lib/uniword/wordprocessingml/paragraph_properties.rb +33 -28
  162. data/lib/uniword/wordprocessingml/rsids.rb +0 -2
  163. data/lib/uniword/wordprocessingml/run.rb +3 -0
  164. data/lib/uniword/wordprocessingml/section_properties.rb +2 -2
  165. data/lib/uniword/wordprocessingml/settings.rb +8 -34
  166. data/lib/uniword/wordprocessingml/shape_defaults.rb +0 -1
  167. data/lib/uniword/wordprocessingml/style.rb +2 -1
  168. data/lib/uniword/wordprocessingml/style_cleanup.rb +198 -0
  169. data/lib/uniword/wordprocessingml/table_cell_properties.rb +6 -6
  170. data/lib/uniword/wordprocessingml/update_fields.rb +27 -0
  171. data/lib/uniword/wordprocessingml/w14_attributes.rb +30 -2
  172. data/lib/uniword/wordprocessingml.rb +4 -0
  173. data/lib/uniword.rb +27 -2
  174. metadata +38 -19
  175. data/config/validation_rules.yml +0 -60
  176. data/config/warning_rules.yml +0 -57
  177. data/lib/uniword/validation/document_validator.rb +0 -272
  178. data/lib/uniword/validation/structural_validator.rb +0 -116
  179. data/lib/uniword/validation/validators/content_type_validator.rb +0 -165
  180. data/lib/uniword/validation/validators/file_structure_validator.rb +0 -104
  181. data/lib/uniword/validation/validators/ooxml_part_validator.rb +0 -128
  182. data/lib/uniword/validation/validators/relationship_validator.rb +0 -147
  183. data/lib/uniword/validation/validators/zip_integrity_validator.rb +0 -110
  184. data/lib/uniword/validators/element_validator.rb +0 -93
  185. data/lib/uniword/validators/paragraph_validator.rb +0 -116
  186. data/lib/uniword/validators/table_validator.rb +0 -134
  187. data/lib/uniword/validators.rb +0 -9
  188. data/lib/uniword/warnings/warning.rb +0 -130
  189. data/lib/uniword/warnings/warning_collector.rb +0 -234
  190. data/lib/uniword/warnings/warning_report.rb +0 -159
  191. data/lib/uniword/warnings.rb +0 -9
@@ -1,128 +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 OOXML part structure (required/optional parts).
10
- #
11
- # Responsibility: Validate OOXML parts exist per ISO/IEC 29500.
12
- # Single Responsibility: Only validates part existence.
13
- #
14
- # This is Layer 3 validation - validates that all required OOXML
15
- # parts are present in the document package.
16
- #
17
- # Checks:
18
- # - Required parts exist (Content Types, document, relationships)
19
- # - Optional but common parts (styles, numbering)
20
- #
21
- # @example Validate OOXML parts
22
- # validator = OoxmlPartValidator.new(config)
23
- # result = validator.validate('/path/to/document.docx')
24
- # puts result.valid? # => true
25
- class OoxmlPartValidator < LayerValidator
26
- # Required parts per ISO/IEC 29500
27
- REQUIRED_PARTS = {
28
- "[Content_Types].xml" => "Content Types definition",
29
- "word/document.xml" => "Main document content",
30
- "_rels/.rels" => "Package relationships",
31
- }.freeze
32
-
33
- # Optional but commonly used parts
34
- OPTIONAL_PARTS = {
35
- "word/styles.xml" => "Styles definition",
36
- "word/numbering.xml" => "Numbering definition",
37
- "word/_rels/document.xml.rels" => "Document relationships",
38
- "word/settings.xml" => "Document settings",
39
- "word/fontTable.xml" => "Font table",
40
- "word/theme/theme1.xml" => "Theme definition",
41
- }.freeze
42
-
43
- def layer_name
44
- "OOXML Parts"
45
- end
46
-
47
- def validate(path)
48
- result = LayerValidationResult.new(layer_name)
49
-
50
- Zip::File.open(path) do |zip|
51
- validate_required_parts(zip, result)
52
- validate_optional_parts(zip, result) if warn_missing_optional?
53
- end
54
-
55
- result
56
- rescue Zip::Error => e
57
- result.add_error(
58
- "Cannot open ZIP file: #{e.message}",
59
- critical: true,
60
- )
61
- result
62
- end
63
-
64
- private
65
-
66
- def validate_required_parts(zip, result)
67
- return unless require_content_types? ||
68
- require_document? ||
69
- require_relationships?
70
-
71
- REQUIRED_PARTS.each do |part_name, description|
72
- next unless should_check_part?(part_name)
73
-
74
- next if zip.find_entry(part_name)
75
-
76
- result.add_error(
77
- "Missing #{description}: #{part_name}",
78
- critical: true,
79
- )
80
- end
81
- end
82
-
83
- def validate_optional_parts(zip, result)
84
- OPTIONAL_PARTS.each do |part_name, description|
85
- next if zip.find_entry(part_name)
86
-
87
- result.add_info(
88
- "Missing #{description}: #{part_name} (optional)",
89
- )
90
- end
91
- end
92
-
93
- def should_check_part?(part_name)
94
- case part_name
95
- when "[Content_Types].xml"
96
- require_content_types?
97
- when "word/document.xml"
98
- require_document?
99
- when "_rels/.rels"
100
- require_relationships?
101
- else
102
- true
103
- end
104
- end
105
-
106
- def require_content_types?
107
- layer_config[:require_content_types] != false
108
- end
109
-
110
- def require_document?
111
- layer_config[:require_document] != false
112
- end
113
-
114
- def require_relationships?
115
- layer_config[:require_relationships] != false
116
- end
117
-
118
- def warn_missing_optional?
119
- layer_config[:warn_missing_optional] != false
120
- end
121
-
122
- def layer_config
123
- @config[:ooxml_parts] || {}
124
- end
125
- end
126
- end
127
- end
128
- end
@@ -1,147 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "zip"
4
- require "nokogiri"
5
- # LayerValidator autoloaded via lib/uniword/validation.rb
6
-
7
- module Uniword
8
- module Validation
9
- module Validators
10
- # Validates relationship integrity (targets exist, no broken refs).
11
- #
12
- # Responsibility: Validate relationship files and targets.
13
- # Single Responsibility: Only validates relationships.
14
- #
15
- # This is Layer 5 validation - validates that all relationship
16
- # files are valid and their targets exist.
17
- #
18
- # Checks:
19
- # - Relationship files are well-formed
20
- # - Relationship targets exist
21
- # - Relationship types are valid
22
- #
23
- # @example Validate relationships
24
- # validator = RelationshipValidator.new(config)
25
- # result = validator.validate('/path/to/document.docx')
26
- # puts result.valid? # => true
27
- class RelationshipValidator < LayerValidator
28
- RELATIONSHIP_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships"
29
-
30
- def layer_name
31
- "Relationships"
32
- end
33
-
34
- def validate(path)
35
- result = LayerValidationResult.new(layer_name)
36
-
37
- Zip::File.open(path) do |zip|
38
- validate_relationships(zip, result)
39
- end
40
-
41
- result
42
- rescue Zip::Error => e
43
- result.add_error(
44
- "Cannot open ZIP file: #{e.message}",
45
- critical: true,
46
- )
47
- result
48
- end
49
-
50
- private
51
-
52
- def validate_relationships(zip, result)
53
- rel_entries = zip.entries.select { |e| e.name.end_with?(".rels") }
54
-
55
- if rel_entries.empty?
56
- result.add_warning("No relationship files found")
57
- return
58
- end
59
-
60
- rel_entries.each do |entry|
61
- validate_relationship_file(zip, entry, result)
62
- end
63
- end
64
-
65
- def validate_relationship_file(zip, entry, result)
66
- xml_content = entry.get_input_stream.read
67
- doc = Nokogiri::XML(xml_content)
68
-
69
- # Get the base directory for this .rels file
70
- base_dir = File.dirname(entry.name.sub("/_rels/", "/"))
71
- base_dir = "" if base_dir == "."
72
-
73
- doc.xpath("//xmlns:Relationship",
74
- "xmlns" => RELATIONSHIP_NAMESPACE).each do |rel|
75
- validate_relationship(zip, rel, base_dir, result)
76
- end
77
- rescue Nokogiri::XML::SyntaxError => e
78
- result.add_error(
79
- "Malformed relationship file #{entry.name}: #{e.message}",
80
- )
81
- rescue StandardError => e
82
- result.add_error(
83
- "Failed to validate #{entry.name}: #{e.message}",
84
- )
85
- end
86
-
87
- def validate_relationship(zip, rel_node, base_dir, result)
88
- target = rel_node["Target"]
89
- type = rel_node["Type"]
90
- target_mode = rel_node["TargetMode"]
91
-
92
- # Skip external relationships
93
- return if target_mode == "External"
94
-
95
- # Check if target exists
96
- if check_targets_exist? && target && !target.start_with?("#")
97
- target_path = resolve_target_path(base_dir, target)
98
-
99
- unless zip.find_entry(target_path)
100
- result.add_error(
101
- "Relationship target not found: #{target_path}",
102
- )
103
- end
104
- end
105
-
106
- # Validate relationship type
107
- return unless validate_types? && type
108
-
109
- validate_relationship_type(type, result)
110
- end
111
-
112
- def resolve_target_path(base_dir, target)
113
- # Handle absolute paths (starting with /)
114
- return target[1..] if target.start_with?("/")
115
-
116
- # Handle relative paths
117
- if base_dir.empty?
118
- target
119
- else
120
- File.join(base_dir, target)
121
- end
122
- end
123
-
124
- def validate_relationship_type(type, result)
125
- # Just validate it's a URI-like string
126
- return if type.include?("/")
127
-
128
- result.add_warning(
129
- "Unusual relationship type: #{type}",
130
- )
131
- end
132
-
133
- def check_targets_exist?
134
- layer_config[:check_targets_exist] != false
135
- end
136
-
137
- def validate_types?
138
- layer_config[:validate_types] != false
139
- end
140
-
141
- def layer_config
142
- @config[:relationships] || {}
143
- end
144
- end
145
- end
146
- end
147
- end
@@ -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