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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +313 -0
- data/CONTRIBUTING.md +1 -1
- data/config/ooxml/schemas/shared_types.yml +5 -4
- data/data/schemas/ecma/dc.xsd +118 -0
- data/data/schemas/ecma/dcmitype.xsd +50 -0
- data/data/schemas/ecma/dcterms.xsd +331 -0
- data/data/schemas/ecma/opc-coreProperties.xsd +2 -2
- data/data/schemas/ecma/xml.xsd +117 -0
- data/lib/uniword/builder/bibliography_builder.rb +1 -1
- data/lib/uniword/builder/chart_builder.rb +22 -14
- data/lib/uniword/builder/comment_anchorer.rb +221 -0
- data/lib/uniword/builder/document_builder.rb +97 -6
- data/lib/uniword/builder/image_builder.rb +11 -10
- data/lib/uniword/builder/run_utils.rb +2 -0
- data/lib/uniword/builder.rb +1 -0
- data/lib/uniword/cli/fonts_cli.rb +52 -0
- data/lib/uniword/cli/main.rb +110 -11
- data/lib/uniword/cli/page_cli.rb +85 -0
- data/lib/uniword/cli/styles_cli.rb +204 -0
- data/lib/uniword/cli/theme_cli.rb +88 -0
- data/lib/uniword/cli/toc_cli.rb +22 -2
- data/lib/uniword/comment.rb +20 -15
- data/lib/uniword/comment_range.rb +3 -3
- data/lib/uniword/comments_part.rb +51 -19
- data/lib/uniword/configuration/configuration_loader.rb +5 -3
- data/lib/uniword/configuration.rb +93 -1
- data/lib/uniword/content_types.rb +19 -39
- data/lib/uniword/document_factory.rb +12 -18
- data/lib/uniword/document_writer.rb +16 -5
- data/lib/uniword/docx/chart_part.rb +34 -0
- data/lib/uniword/docx/custom_xml_item.rb +91 -0
- data/lib/uniword/docx/header_footer_part.rb +140 -0
- data/lib/uniword/docx/header_footer_part_collection.rb +135 -0
- data/lib/uniword/docx/header_footer_view.rb +225 -0
- data/lib/uniword/docx/id_allocator.rb +130 -36
- data/lib/uniword/docx/image_part.rb +83 -0
- data/lib/uniword/docx/package.rb +141 -333
- data/lib/uniword/docx/package_defaults.rb +83 -99
- data/lib/uniword/docx/package_integrity_checker.rb +312 -0
- data/lib/uniword/docx/package_serialization.rb +144 -233
- data/lib/uniword/docx/part.rb +125 -0
- data/lib/uniword/docx/part_collection.rb +131 -0
- data/lib/uniword/docx/part_loader/chart_loader.rb +50 -0
- data/lib/uniword/docx/part_loader/custom_xml_loader.rb +53 -0
- data/lib/uniword/docx/part_loader/embedding_loader.rb +26 -0
- data/lib/uniword/docx/part_loader/header_footer_loader.rb +101 -0
- data/lib/uniword/docx/part_loader/image_loader.rb +105 -0
- data/lib/uniword/docx/part_loader/load_context.rb +85 -0
- data/lib/uniword/docx/part_loader/raw_part_loader.rb +183 -0
- data/lib/uniword/docx/part_loader/theme_media_loader.rb +39 -0
- data/lib/uniword/docx/part_loader/xml_model_loader.rb +56 -0
- data/lib/uniword/docx/part_loader.rb +92 -0
- data/lib/uniword/docx/raw_part.rb +75 -0
- data/lib/uniword/docx/reconciler/body.rb +59 -68
- data/lib/uniword/docx/reconciler/fix.rb +46 -0
- data/lib/uniword/docx/reconciler/fix_codes.rb +37 -30
- data/lib/uniword/docx/reconciler/helpers.rb +18 -31
- data/lib/uniword/docx/reconciler/notes.rb +17 -8
- data/lib/uniword/docx/reconciler/package_structure.rb +168 -221
- data/lib/uniword/docx/reconciler/parts.rb +36 -13
- data/lib/uniword/docx/reconciler/referential_integrity.rb +222 -123
- data/lib/uniword/docx/reconciler/tables.rb +78 -16
- data/lib/uniword/docx/reconciler/theme.rb +7 -4
- data/lib/uniword/docx/reconciler.rb +36 -14
- data/lib/uniword/docx.rb +13 -0
- data/lib/uniword/drawingml/blip.rb +0 -1
- data/lib/uniword/drawingml/color_scheme.rb +5 -6
- data/lib/uniword/drawingml/font_scheme.rb +4 -8
- data/lib/uniword/drawingml/theme.rb +12 -6
- data/lib/uniword/errors.rb +8 -1
- data/lib/uniword/footer.rb +4 -0
- data/lib/uniword/header.rb +4 -0
- data/lib/uniword/hyperlink.rb +1 -1
- data/lib/uniword/image.rb +6 -6
- data/lib/uniword/images/image_manager.rb +13 -13
- data/lib/uniword/lazy_loader.rb +1 -2
- data/lib/uniword/model_attribute_access.rb +20 -4
- data/lib/uniword/ooxml/dotx_package.rb +42 -2
- data/lib/uniword/ooxml/element_order.rb +94 -0
- data/lib/uniword/ooxml/part_definition.rb +329 -0
- data/lib/uniword/ooxml/part_registry.rb +504 -0
- data/lib/uniword/ooxml/relationships/image_relationship.rb +6 -3
- data/lib/uniword/ooxml/relationships/package_relationships.rb +20 -22
- data/lib/uniword/ooxml/schema/element_serializer.rb +8 -7
- data/lib/uniword/ooxml/types/hex_color_value.rb +38 -0
- data/lib/uniword/ooxml/types/ooxml_boolean.rb +27 -14
- data/lib/uniword/ooxml/types/ooxml_boolean_optional.rb +21 -16
- data/lib/uniword/ooxml/types/theme_color_value.rb +22 -0
- data/lib/uniword/ooxml/types/unsigned_decimal_number.rb +30 -0
- data/lib/uniword/ooxml/types.rb +10 -0
- data/lib/uniword/ooxml.rb +3 -0
- data/lib/uniword/properties/alignment.rb +8 -3
- data/lib/uniword/properties/border.rb +52 -6
- data/lib/uniword/properties/cell_vertical_align.rb +4 -1
- data/lib/uniword/properties/color_value.rb +8 -3
- data/lib/uniword/properties/highlight.rb +9 -5
- data/lib/uniword/properties/shading.rb +4 -3
- data/lib/uniword/properties/tab_stop.rb +6 -1
- data/lib/uniword/properties/table_justification.rb +4 -1
- data/lib/uniword/properties/underline.rb +4 -2
- data/lib/uniword/properties/vertical_align.rb +6 -3
- data/lib/uniword/review/review_manager.rb +23 -9
- data/lib/uniword/revision.rb +9 -9
- data/lib/uniword/schema/model_generator.rb +31 -15
- data/lib/uniword/shared_types/hex_color.rb +4 -1
- data/lib/uniword/shared_types/pixel_measure.rb +4 -1
- data/lib/uniword/shared_types/point_measure.rb +4 -1
- data/lib/uniword/shared_types/text_alignment.rb +6 -1
- data/lib/uniword/shared_types/twips_measure.rb +4 -1
- data/lib/uniword/spreadsheetml/phonetic_pr.rb +1 -1
- data/lib/uniword/spreadsheetml/shared_string_table.rb +2 -2
- data/lib/uniword/spreadsheetml/table_formula.rb +1 -1
- data/lib/uniword/template/variable_resolver.rb +40 -7
- data/lib/uniword/themes/theme_transformation.rb +55 -41
- data/lib/uniword/toc/toc_generator.rb +6 -5
- data/lib/uniword/transformation/mhtml_element_renderer.rb +1 -5
- data/lib/uniword/transformation/mhtml_metadata_builder.rb +6 -6
- data/lib/uniword/validation/engine.rb +35 -0
- data/lib/uniword/validation/link_checker.rb +1 -1
- data/lib/uniword/validation/opc_validator.rb +16 -2
- data/lib/uniword/validation/report/layer_result.rb +0 -1
- data/lib/uniword/validation/report/terminal_formatter.rb +0 -1
- data/lib/uniword/validation/report/verification_report.rb +0 -2
- data/lib/uniword/validation/report.rb +20 -0
- data/lib/uniword/validation/rules/base.rb +10 -2
- data/lib/uniword/validation/rules/bookmark_pairing_rule.rb +38 -0
- data/lib/uniword/validation/rules/bookmark_uniqueness_rule.rb +48 -0
- data/lib/uniword/validation/rules/bookmarks_rule.rb +0 -2
- data/lib/uniword/validation/rules/content_types_coverage_rule.rb +0 -2
- data/lib/uniword/validation/rules/core_properties_namespace_rule.rb +0 -2
- data/lib/uniword/validation/rules/document_body_rule.rb +25 -0
- data/lib/uniword/validation/rules/document_context.rb +7 -0
- data/lib/uniword/validation/rules/empty_paragraphs_rule.rb +27 -0
- data/lib/uniword/validation/rules/font_table_signature_rule.rb +0 -2
- data/lib/uniword/validation/rules/fonts_rule.rb +0 -2
- data/lib/uniword/validation/rules/footnotes_rule.rb +0 -2
- data/lib/uniword/validation/rules/headers_footers_rule.rb +0 -2
- data/lib/uniword/validation/rules/images_rule.rb +0 -2
- data/lib/uniword/validation/rules/mc_ignorable_namespace_rule.rb +0 -2
- data/lib/uniword/validation/rules/model_context.rb +32 -0
- data/lib/uniword/validation/rules/model_rule.rb +34 -0
- data/lib/uniword/validation/rules/numbering_preservation_rule.rb +0 -2
- data/lib/uniword/validation/rules/numbering_rule.rb +0 -2
- data/lib/uniword/validation/rules/relationship_integrity_rule.rb +0 -1
- data/lib/uniword/validation/rules/rsid_rule.rb +0 -2
- data/lib/uniword/validation/rules/section_properties_rule.rb +0 -2
- data/lib/uniword/validation/rules/settings_rule.rb +0 -2
- data/lib/uniword/validation/rules/settings_values_rule.rb +0 -2
- data/lib/uniword/validation/rules/style_references_rule.rb +0 -2
- data/lib/uniword/validation/rules/table_grid_rule.rb +27 -0
- data/lib/uniword/validation/rules/table_properties_rule.rb +27 -0
- data/lib/uniword/validation/rules/tables_rule.rb +0 -2
- data/lib/uniword/validation/rules/theme_completeness_rule.rb +0 -2
- data/lib/uniword/validation/rules/theme_rule.rb +0 -2
- data/lib/uniword/validation/rules.rb +61 -23
- data/lib/uniword/validation/schema_registry.rb +11 -0
- data/lib/uniword/validation/validators/document_semantics_validator.rb +1 -9
- data/lib/uniword/validation/validators/xml_schema_validator.rb +46 -2
- data/lib/uniword/validation/validators.rb +0 -9
- data/lib/uniword/validation/verify_orchestrator.rb +0 -7
- data/lib/uniword/validation.rb +7 -2
- data/lib/uniword/version.rb +1 -1
- data/lib/uniword/vml/imagedata.rb +0 -1
- data/lib/uniword/wordprocessingml/attached_template.rb +0 -1
- data/lib/uniword/wordprocessingml/deleted_text.rb +1 -1
- data/lib/uniword/wordprocessingml/document_root.rb +121 -122
- data/lib/uniword/wordprocessingml/document_styling.rb +246 -0
- data/lib/uniword/wordprocessingml/endnotes.rb +4 -1
- data/lib/uniword/wordprocessingml/font_replacer.rb +147 -0
- data/lib/uniword/wordprocessingml/footnotes.rb +4 -1
- data/lib/uniword/wordprocessingml/hdr_shape_defaults.rb +0 -1
- data/lib/uniword/wordprocessingml/hyperlink.rb +0 -1
- data/lib/uniword/wordprocessingml/level.rb +6 -20
- data/lib/uniword/wordprocessingml/math_pr.rb +0 -11
- data/lib/uniword/wordprocessingml/numbering_definition.rb +16 -8
- data/lib/uniword/wordprocessingml/numbering_elements.rb +0 -15
- data/lib/uniword/wordprocessingml/page_setup.rb +181 -0
- data/lib/uniword/wordprocessingml/paragraph_properties.rb +33 -28
- data/lib/uniword/wordprocessingml/rsids.rb +0 -2
- data/lib/uniword/wordprocessingml/run.rb +3 -0
- data/lib/uniword/wordprocessingml/section_properties.rb +2 -2
- data/lib/uniword/wordprocessingml/settings.rb +8 -34
- data/lib/uniword/wordprocessingml/shape_defaults.rb +0 -1
- data/lib/uniword/wordprocessingml/style.rb +2 -1
- data/lib/uniword/wordprocessingml/style_cleanup.rb +198 -0
- data/lib/uniword/wordprocessingml/table_cell_properties.rb +6 -6
- data/lib/uniword/wordprocessingml/update_fields.rb +27 -0
- data/lib/uniword/wordprocessingml/w14_attributes.rb +30 -2
- data/lib/uniword/wordprocessingml.rb +5 -2
- data/lib/uniword.rb +27 -2
- metadata +52 -19
- data/config/validation_rules.yml +0 -60
- data/config/warning_rules.yml +0 -57
- data/lib/uniword/validation/document_validator.rb +0 -272
- data/lib/uniword/validation/structural_validator.rb +0 -116
- data/lib/uniword/validation/validators/content_type_validator.rb +0 -165
- data/lib/uniword/validation/validators/file_structure_validator.rb +0 -104
- data/lib/uniword/validation/validators/ooxml_part_validator.rb +0 -128
- data/lib/uniword/validation/validators/relationship_validator.rb +0 -147
- data/lib/uniword/validation/validators/zip_integrity_validator.rb +0 -110
- data/lib/uniword/validators/element_validator.rb +0 -93
- data/lib/uniword/validators/paragraph_validator.rb +0 -116
- data/lib/uniword/validators/table_validator.rb +0 -134
- data/lib/uniword/validators.rb +0 -9
- data/lib/uniword/warnings/warning.rb +0 -130
- data/lib/uniword/warnings/warning_collector.rb +0 -234
- data/lib/uniword/warnings/warning_report.rb +0 -159
- 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
|
data/lib/uniword/validators.rb
DELETED
|
@@ -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
|