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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +223 -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 +19 -9
- 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 +6 -5
- 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 +9 -1
- 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 +83 -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/package.rb +223 -35
- data/lib/uniword/docx/package_defaults.rb +58 -78
- data/lib/uniword/docx/package_integrity_checker.rb +312 -0
- data/lib/uniword/docx/package_serialization.rb +103 -231
- data/lib/uniword/docx/part.rb +106 -0
- data/lib/uniword/docx/part_collection.rb +123 -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 +9 -1
- data/lib/uniword/docx/reconciler/notes.rb +17 -8
- data/lib/uniword/docx/reconciler/package_structure.rb +169 -221
- data/lib/uniword/docx/reconciler/parts.rb +36 -13
- data/lib/uniword/docx/reconciler/referential_integrity.rb +297 -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 +10 -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/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/part_definition.rb +171 -0
- data/lib/uniword/ooxml/part_registry.rb +311 -0
- data/lib/uniword/ooxml/relationships/image_relationship.rb +1 -1
- 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 +2 -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 +2 -0
- data/lib/uniword/validation/engine.rb +35 -0
- data/lib/uniword/validation/link_checker.rb +1 -1
- data/lib/uniword/validation/opc_validator.rb +0 -1
- 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 +0 -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 +211 -21
- data/lib/uniword/wordprocessingml/endnotes.rb +4 -0
- data/lib/uniword/wordprocessingml/font_replacer.rb +147 -0
- data/lib/uniword/wordprocessingml/footnotes.rb +4 -0
- 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/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 +4 -0
- data/lib/uniword.rb +27 -2
- metadata +38 -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,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
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Uniword
|
|
4
|
-
module Warnings
|
|
5
|
-
# Collects warnings during document processing.
|
|
6
|
-
#
|
|
7
|
-
# Responsibility: Track unsupported elements and features.
|
|
8
|
-
# Single Responsibility: Only collects warnings.
|
|
9
|
-
#
|
|
10
|
-
# This class is used during deserialization to track all unsupported
|
|
11
|
-
# elements and attributes encountered, allowing users to understand
|
|
12
|
-
# what features may be lost in round-trip operations.
|
|
13
|
-
#
|
|
14
|
-
# External configuration: config/warning_rules.yml
|
|
15
|
-
#
|
|
16
|
-
# @example Use during deserialization
|
|
17
|
-
# collector = WarningCollector.new
|
|
18
|
-
# collector.record_unsupported('chart', context: 'In paragraph 5')
|
|
19
|
-
# report = collector.report
|
|
20
|
-
# puts report.summary
|
|
21
|
-
class WarningCollector
|
|
22
|
-
# @return [Array<Warning>] Collected warnings
|
|
23
|
-
attr_reader :warnings
|
|
24
|
-
|
|
25
|
-
# @return [Hash<String, Integer>] Element occurrence counts
|
|
26
|
-
attr_reader :element_counts
|
|
27
|
-
|
|
28
|
-
# Initialize a new warning collector.
|
|
29
|
-
#
|
|
30
|
-
# @param config_file [String] Path to configuration file
|
|
31
|
-
#
|
|
32
|
-
# @example Create with default config
|
|
33
|
-
# collector = WarningCollector.new
|
|
34
|
-
#
|
|
35
|
-
# @example Create with custom config
|
|
36
|
-
# collector = WarningCollector.new(
|
|
37
|
-
# config_file: 'custom_warnings.yml'
|
|
38
|
-
# )
|
|
39
|
-
def initialize(config_file: nil)
|
|
40
|
-
@config = load_config(config_file)
|
|
41
|
-
@warnings = []
|
|
42
|
-
@element_counts = Hash.new(0)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Record an unsupported element.
|
|
46
|
-
#
|
|
47
|
-
# @param element_tag [String] Element tag name
|
|
48
|
-
# @param context [String] Context where element was found
|
|
49
|
-
# @param location [String] Location in document (optional)
|
|
50
|
-
# @return [void]
|
|
51
|
-
#
|
|
52
|
-
# @example Record unsupported element
|
|
53
|
-
# collector.record_unsupported(
|
|
54
|
-
# 'chart',
|
|
55
|
-
# context: 'While parsing paragraph',
|
|
56
|
-
# location: '/document/body/p[5]'
|
|
57
|
-
# )
|
|
58
|
-
def record_unsupported(element_tag, context:, location: nil)
|
|
59
|
-
return unless enabled?
|
|
60
|
-
return if max_warnings_reached?
|
|
61
|
-
|
|
62
|
-
@element_counts[element_tag] += 1
|
|
63
|
-
|
|
64
|
-
warning = Warning.new(
|
|
65
|
-
type: :unsupported_element,
|
|
66
|
-
severity: determine_severity(element_tag),
|
|
67
|
-
element: element_tag,
|
|
68
|
-
message: "Unsupported element: #{element_tag}",
|
|
69
|
-
context: context,
|
|
70
|
-
location: location,
|
|
71
|
-
suggestion: get_suggestion(element_tag),
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
@warnings << warning
|
|
75
|
-
log_warning(warning) if should_log?
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Record an unsupported attribute.
|
|
79
|
-
#
|
|
80
|
-
# @param element_tag [String] Element tag name
|
|
81
|
-
# @param attribute_name [String] Attribute name
|
|
82
|
-
# @param context [String] Context where attribute was found
|
|
83
|
-
# @return [void]
|
|
84
|
-
#
|
|
85
|
-
# @example Record unsupported attribute
|
|
86
|
-
# collector.record_unsupported_attribute(
|
|
87
|
-
# 'w:p',
|
|
88
|
-
# 'customAttr',
|
|
89
|
-
# context: 'In paragraph properties'
|
|
90
|
-
# )
|
|
91
|
-
def record_unsupported_attribute(element_tag, attribute_name, context:)
|
|
92
|
-
return unless enabled?
|
|
93
|
-
return if max_warnings_reached?
|
|
94
|
-
|
|
95
|
-
warning = Warning.new(
|
|
96
|
-
type: :unsupported_attribute,
|
|
97
|
-
severity: :info,
|
|
98
|
-
element: element_tag,
|
|
99
|
-
attribute: attribute_name,
|
|
100
|
-
message: "Unsupported attribute: #{element_tag}/@#{attribute_name}",
|
|
101
|
-
context: context,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
@warnings << warning
|
|
105
|
-
log_warning(warning) if should_log?
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Generate warning report.
|
|
109
|
-
#
|
|
110
|
-
# @return [WarningReport] Aggregated warning report
|
|
111
|
-
#
|
|
112
|
-
# @example Get report
|
|
113
|
-
# report = collector.report
|
|
114
|
-
# puts "Total warnings: #{report.total_count}"
|
|
115
|
-
def report
|
|
116
|
-
WarningReport.new(
|
|
117
|
-
warnings: @warnings,
|
|
118
|
-
element_counts: @element_counts,
|
|
119
|
-
config: @config,
|
|
120
|
-
)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Check if warnings were collected.
|
|
124
|
-
#
|
|
125
|
-
# @return [Boolean] true if warnings exist
|
|
126
|
-
def any?
|
|
127
|
-
@warnings.any?
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Clear all collected warnings.
|
|
131
|
-
#
|
|
132
|
-
# @return [void]
|
|
133
|
-
def clear
|
|
134
|
-
@warnings.clear
|
|
135
|
-
@element_counts.clear
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
private
|
|
139
|
-
|
|
140
|
-
def load_config(config_file)
|
|
141
|
-
if config_file
|
|
142
|
-
Configuration::ConfigurationLoader.load_file(config_file)
|
|
143
|
-
else
|
|
144
|
-
# Try to load default config
|
|
145
|
-
default_path = File.join(
|
|
146
|
-
Configuration::ConfigurationLoader::CONFIG_DIR,
|
|
147
|
-
"warning_rules.yml",
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
if File.exist?(default_path)
|
|
151
|
-
Configuration::ConfigurationLoader.load("warning_rules")
|
|
152
|
-
else
|
|
153
|
-
# Use empty config with defaults
|
|
154
|
-
default_config
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
rescue Configuration::ConfigurationError
|
|
158
|
-
# If config fails to load, use defaults
|
|
159
|
-
default_config
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def default_config
|
|
163
|
-
{
|
|
164
|
-
warning_system: {
|
|
165
|
-
enabled: true,
|
|
166
|
-
log_warnings: false,
|
|
167
|
-
log_level: :warn,
|
|
168
|
-
critical_elements: [],
|
|
169
|
-
warning_elements: [],
|
|
170
|
-
element_suggestions: {},
|
|
171
|
-
reporting: {
|
|
172
|
-
max_warnings: 100,
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
}
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def warning_config
|
|
179
|
-
@config[:warning_system] || {}
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def enabled?
|
|
183
|
-
warning_config[:enabled] != false
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def should_log?
|
|
187
|
-
warning_config[:log_warnings] == true
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def max_warnings_reached?
|
|
191
|
-
max = warning_config.dig(:reporting, :max_warnings) || 100
|
|
192
|
-
@warnings.count >= max
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def determine_severity(element_tag)
|
|
196
|
-
critical = warning_config[:critical_elements] || []
|
|
197
|
-
warning_els = warning_config[:warning_elements] || []
|
|
198
|
-
|
|
199
|
-
if critical.include?(element_tag)
|
|
200
|
-
:error
|
|
201
|
-
elsif warning_els.include?(element_tag)
|
|
202
|
-
:warning
|
|
203
|
-
else
|
|
204
|
-
:info
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def get_suggestion(element_tag)
|
|
209
|
-
suggestions = warning_config[:element_suggestions] || {}
|
|
210
|
-
suggestions[element_tag] ||
|
|
211
|
-
suggestions[element_tag.to_sym] ||
|
|
212
|
-
"This element is not yet supported. Data may be lost in round-trip."
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def log_warning(warning)
|
|
216
|
-
level = warning_config[:log_level] || :warn
|
|
217
|
-
message = "[WARNING] #{warning}"
|
|
218
|
-
|
|
219
|
-
# Use Uniword logger if available
|
|
220
|
-
if defined?(Uniword::Logger)
|
|
221
|
-
case level
|
|
222
|
-
when :debug then Uniword::Logger.debug(message)
|
|
223
|
-
when :info then Uniword::Logger.info(message)
|
|
224
|
-
when :warn then Uniword::Logger.warn(message)
|
|
225
|
-
when :error then Uniword::Logger.error(message)
|
|
226
|
-
end
|
|
227
|
-
elsif %i[warn error].include?(level)
|
|
228
|
-
# Fall back to standard output
|
|
229
|
-
puts message
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
end
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Uniword
|
|
6
|
-
module Warnings
|
|
7
|
-
# Aggregated warning report.
|
|
8
|
-
#
|
|
9
|
-
# Responsibility: Aggregate and report warnings.
|
|
10
|
-
# Single Responsibility: Only manages warning reporting.
|
|
11
|
-
#
|
|
12
|
-
# A warning report includes:
|
|
13
|
-
# - Collection of all warnings
|
|
14
|
-
# - Element occurrence counts
|
|
15
|
-
# - Grouping by severity
|
|
16
|
-
# - Summary statistics
|
|
17
|
-
#
|
|
18
|
-
# @example Create a report
|
|
19
|
-
# report = WarningReport.new(
|
|
20
|
-
# warnings: warnings,
|
|
21
|
-
# element_counts: { 'chart' => 5, 'smartArt' => 2 }
|
|
22
|
-
# )
|
|
23
|
-
# puts report.summary
|
|
24
|
-
class WarningReport
|
|
25
|
-
# @return [Array<Warning>] All warnings
|
|
26
|
-
attr_reader :warnings
|
|
27
|
-
|
|
28
|
-
# @return [Hash<String, Integer>] Element occurrence counts
|
|
29
|
-
attr_reader :element_counts
|
|
30
|
-
|
|
31
|
-
# Initialize a new warning report.
|
|
32
|
-
#
|
|
33
|
-
# @param warnings [Array<Warning>] Warnings to include
|
|
34
|
-
# @param element_counts [Hash<String, Integer>] Element counts
|
|
35
|
-
# @param config [Hash] Configuration
|
|
36
|
-
#
|
|
37
|
-
# @example Create a report
|
|
38
|
-
# report = WarningReport.new(
|
|
39
|
-
# warnings: [warning1, warning2],
|
|
40
|
-
# element_counts: { 'chart' => 3 }
|
|
41
|
-
# )
|
|
42
|
-
def initialize(warnings:, element_counts:, config: {})
|
|
43
|
-
@warnings = warnings
|
|
44
|
-
@element_counts = element_counts
|
|
45
|
-
@config = config
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Check if report has any warnings.
|
|
49
|
-
#
|
|
50
|
-
# @return [Boolean] true if warnings exist
|
|
51
|
-
def any?
|
|
52
|
-
@warnings.any?
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Get error-level warnings.
|
|
56
|
-
#
|
|
57
|
-
# @return [Array<Warning>] Error warnings
|
|
58
|
-
def errors
|
|
59
|
-
@warnings.select(&:error?)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Get warning-level warnings.
|
|
63
|
-
#
|
|
64
|
-
# @return [Array<Warning>] Warning-level warnings
|
|
65
|
-
def warnings_only
|
|
66
|
-
@warnings.select(&:warning?)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Get info-level warnings.
|
|
70
|
-
#
|
|
71
|
-
# @return [Array<Warning>] Info warnings
|
|
72
|
-
def infos
|
|
73
|
-
@warnings.select(&:info?)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Get count of error warnings.
|
|
77
|
-
#
|
|
78
|
-
# @return [Integer] Error count
|
|
79
|
-
def error_count
|
|
80
|
-
errors.count
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Get count of warning-level warnings.
|
|
84
|
-
#
|
|
85
|
-
# @return [Integer] Warning count
|
|
86
|
-
def warning_count
|
|
87
|
-
warnings_only.count
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Get count of info warnings.
|
|
91
|
-
#
|
|
92
|
-
# @return [Integer] Info count
|
|
93
|
-
def info_count
|
|
94
|
-
infos.count
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Get total warning count.
|
|
98
|
-
#
|
|
99
|
-
# @return [Integer] Total count
|
|
100
|
-
def total_count
|
|
101
|
-
@warnings.count
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# Generate summary text.
|
|
105
|
-
#
|
|
106
|
-
# @return [String] Summary text
|
|
107
|
-
def summary
|
|
108
|
-
return "No warnings" unless any?
|
|
109
|
-
|
|
110
|
-
lines = []
|
|
111
|
-
lines << "Found #{total_count} warning(s):"
|
|
112
|
-
lines << " Errors: #{error_count}"
|
|
113
|
-
lines << " Warnings: #{warning_count}"
|
|
114
|
-
lines << " Info: #{info_count}"
|
|
115
|
-
|
|
116
|
-
if @element_counts.any?
|
|
117
|
-
lines << ""
|
|
118
|
-
lines << "Unsupported elements encountered:"
|
|
119
|
-
|
|
120
|
-
@element_counts.sort_by { |_, count| -count }.each do |element, count|
|
|
121
|
-
lines << " #{element}: #{count} occurrence(s)"
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
lines.join("\n")
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Export to JSON string.
|
|
129
|
-
#
|
|
130
|
-
# @return [String] JSON representation
|
|
131
|
-
def to_json(*_args)
|
|
132
|
-
JSON.pretty_generate(to_h)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Convert to hash representation.
|
|
136
|
-
#
|
|
137
|
-
# @return [Hash] Hash representation
|
|
138
|
-
def to_h
|
|
139
|
-
{
|
|
140
|
-
total: total_count,
|
|
141
|
-
by_severity: {
|
|
142
|
-
errors: error_count,
|
|
143
|
-
warnings: warning_count,
|
|
144
|
-
infos: info_count,
|
|
145
|
-
},
|
|
146
|
-
element_counts: @element_counts,
|
|
147
|
-
warnings: @warnings.map(&:to_h),
|
|
148
|
-
}
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# Convert to string for display.
|
|
152
|
-
#
|
|
153
|
-
# @return [String] String representation
|
|
154
|
-
def to_s
|
|
155
|
-
summary
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
data/lib/uniword/warnings.rb
DELETED