uniword 1.5.2 → 1.5.4

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/lib/uniword/batch/operation/file_result.rb +39 -0
  4. data/lib/uniword/batch/operation/repair_task.rb +34 -0
  5. data/lib/uniword/batch/operation/report.rb +66 -0
  6. data/lib/uniword/batch/operation/runner.rb +30 -0
  7. data/lib/uniword/batch/operation/task.rb +40 -0
  8. data/lib/uniword/batch/operation/verify_task.rb +27 -0
  9. data/lib/uniword/batch/operation.rb +22 -0
  10. data/lib/uniword/batch.rb +1 -0
  11. data/lib/uniword/builder/image_builder.rb +1 -1
  12. data/lib/uniword/caption/caption_builder.rb +120 -0
  13. data/lib/uniword/caption/counter.rb +61 -0
  14. data/lib/uniword/caption/cross_reference.rb +44 -0
  15. data/lib/uniword/caption.rb +24 -0
  16. data/lib/uniword/diff/semantic/change.rb +66 -0
  17. data/lib/uniword/diff/semantic/engine.rb +31 -0
  18. data/lib/uniword/diff/semantic/paragraph_comparator.rb +195 -0
  19. data/lib/uniword/diff/semantic/result.rb +53 -0
  20. data/lib/uniword/diff/semantic.rb +26 -0
  21. data/lib/uniword/diff.rb +1 -0
  22. data/lib/uniword/docx/package_defaults.rb +1 -1
  23. data/lib/uniword/docx/package_serialization.rb +1 -1
  24. data/lib/uniword/docx/reconciler/helpers.rb +1 -1
  25. data/lib/uniword/docx/reconciler/referential_integrity.rb +1 -1
  26. data/lib/uniword/find_replace/scope.rb +9 -11
  27. data/lib/uniword/ooxml/package_file.rb +1 -1
  28. data/lib/uniword/picture/fill_rect.rb +2 -2
  29. data/lib/uniword/picture/picture_source_rect.rb +3 -2
  30. data/lib/uniword/picture/picture_stretch.rb +3 -2
  31. data/lib/uniword/picture/tile.rb +3 -2
  32. data/lib/uniword/plugin/cli_command.rb +30 -0
  33. data/lib/uniword/plugin/loader.rb +43 -0
  34. data/lib/uniword/plugin/registry.rb +83 -0
  35. data/lib/uniword/plugin/transformer.rb +50 -0
  36. data/lib/uniword/plugin/validator.rb +34 -0
  37. data/lib/uniword/plugin.rb +25 -0
  38. data/lib/uniword/properties/word2010_id_value.rb +2 -2
  39. data/lib/uniword/version.rb +1 -1
  40. data/lib/uniword/wordprocessingml/body.rb +54 -6
  41. data/lib/uniword/wordprocessingml/document_styling.rb +40 -0
  42. data/lib/uniword/wordprocessingml/styles_configuration.rb +1 -1
  43. data/lib/uniword.rb +6 -0
  44. metadata +26 -4
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uniword
4
+ module Plugin
5
+ # Base class for plugin-provided validation rules. Subclasses
6
+ # implement `#check(document)` (yields findings).
7
+ #
8
+ # Open/closed: adding a new validator = subclassing + calling
9
+ # `Registry.register_validator`.
10
+ class Validator
11
+ # @return [Symbol] validator name (matches the registry key)
12
+ attr_reader :name
13
+
14
+ # @return [Symbol] default severity (:error, :warning, :info)
15
+ attr_reader :severity
16
+
17
+ # @param name [Symbol]
18
+ # @param severity [Symbol]
19
+ def initialize(name:, severity: :warning)
20
+ @name = name
21
+ @severity = severity
22
+ end
23
+
24
+ # Walk the document and yield each finding as a hash.
25
+ #
26
+ # @param document [Wordprocessingml::DocumentRoot]
27
+ # @yieldparam finding [Hash] { message:, path: }
28
+ # @return [void]
29
+ def check(document)
30
+ raise NotImplementedError
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uniword
4
+ # Plugin system: extend uniword without forking.
5
+ #
6
+ # Three extension surfaces, each with a base class:
7
+ #
8
+ # - `Plugin::Validator` — register custom validation rules
9
+ # - `Plugin::Transformer` — mutate documents at defined pipeline
10
+ # stages (load, pre-save, post-reconcile)
11
+ # - `Plugin::CliCommand` — register new Thor subcommands
12
+ #
13
+ # Discovery: `Plugin::Loader.load_all` walks installed gems via
14
+ # `Gem.find_files("uniword/plugin/*.rb")` and loads each.
15
+ #
16
+ # Configuration: `Uniword.configuration.plugins = [:my_plugin]`
17
+ # (default `:all`).
18
+ module Plugin
19
+ autoload :Registry, "#{__dir__}/plugin/registry"
20
+ autoload :Validator, "#{__dir__}/plugin/validator"
21
+ autoload :Transformer, "#{__dir__}/plugin/transformer"
22
+ autoload :CliCommand, "#{__dir__}/plugin/cli_command"
23
+ autoload :Loader, "#{__dir__}/plugin/loader"
24
+ end
25
+ end
@@ -4,13 +4,13 @@ require "lutaml/model"
4
4
 
5
5
  module Uniword
6
6
  module Properties
7
- # Namespaced custom type for Word 2010 (wp14) attributes
7
+ # Namespaced custom type for Word 2010 Drawing (wp14) attributes
8
8
  # Used for wp14:anchorId and wp14:editId attributes
9
9
  class Word2010IdValue < Lutaml::Model::Type::String
10
10
  include Lutaml::Xml::Type::Configurable
11
11
 
12
12
  xml do
13
- namespace Uniword::Ooxml::Namespaces::Word2010
13
+ namespace Uniword::Ooxml::Namespaces::Word2010Drawing
14
14
  end
15
15
  end
16
16
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uniword
4
- VERSION = "1.5.2"
4
+ VERSION = "1.5.4"
5
5
  end
@@ -32,15 +32,54 @@ module Uniword
32
32
  map_element "bookmarkEnd", to: :bookmark_ends, render_nil: false
33
33
  end
34
34
 
35
- # Get all elements in body
35
+ # Get all elements in body, in document order.
36
36
  #
37
- # @return [Array<Paragraph, Table, SectionProperties, StructuredDocumentTag>] All block-level content
37
+ # Walks `element_order` (populated by lutaml-model when mixed_content
38
+ # is parsed) and dispatches each tag to its typed collection —
39
+ # `p` -> paragraphs, `tbl` -> tables, `sdt` -> structured_document_tags,
40
+ # `bookmarkStart` -> bookmark_starts, `bookmarkEnd` -> bookmark_ends,
41
+ # `sectPr` -> section_properties. Whitespace/comment entries are
42
+ # skipped. Elements not represented in `element_order` are appended
43
+ # at the end in collection order (the only order available when
44
+ # `element_order` is empty or nil).
45
+ #
46
+ # @return [Array] Block-level content in document order
38
47
  def elements
48
+ return concatenate_typed_collections if element_order.nil? || element_order.empty?
49
+
39
50
  result = []
40
- result.concat(paragraphs || [])
41
- result.concat(structured_document_tags || [])
42
- result.concat(tables || [])
43
- result << section_properties if section_properties
51
+ counters = Hash.new(0)
52
+ sectpr_seen = false
53
+ collections = {
54
+ "p" => paragraphs,
55
+ "tbl" => tables,
56
+ "sdt" => structured_document_tags,
57
+ "bookmarkStart" => bookmark_starts,
58
+ "bookmarkEnd" => bookmark_ends,
59
+ }
60
+
61
+ element_order.each do |entry|
62
+ name = entry.name
63
+ if collections.key?(name)
64
+ collection = collections[name]
65
+ index = counters[name]
66
+ if collection && index < collection.size
67
+ result << collection[index]
68
+ counters[name] = index + 1
69
+ end
70
+ elsif name == "sectPr"
71
+ sectpr_seen = true
72
+ result << section_properties if section_properties
73
+ end
74
+ end
75
+
76
+ collections.each do |name, collection|
77
+ next unless collection && counters[name] < collection.size
78
+
79
+ result.concat(collection[counters[name]..])
80
+ end
81
+
82
+ result << section_properties if section_properties && !sectpr_seen
44
83
  result
45
84
  end
46
85
 
@@ -80,6 +119,15 @@ module Uniword
80
119
 
81
120
  private
82
121
 
122
+ def concatenate_typed_collections
123
+ result = []
124
+ result.concat(paragraphs || [])
125
+ result.concat(structured_document_tags || [])
126
+ result.concat(tables || [])
127
+ result << section_properties if section_properties
128
+ result
129
+ end
130
+
83
131
  def sync_element_order
84
132
  return if element_order.nil? || element_order.empty?
85
133
 
@@ -195,6 +195,46 @@ module Uniword
195
195
  Lint::Engine.new(document: self, ruleset: ruleset).run
196
196
  end
197
197
 
198
+ # Counter for auto-numbered captions. Persisted across the
199
+ # document's lifetime; reset on document load.
200
+ #
201
+ # @return [Caption::Counter]
202
+ def caption_counter
203
+ @caption_counter ||= Caption::Counter.new
204
+ end
205
+
206
+ # Add an auto-numbered caption paragraph to the body and
207
+ # return the bookmark name (for use in cross-references).
208
+ #
209
+ # The caption is appended as a new paragraph with style
210
+ # "Caption" and a SEQ field. The bookmark wraps the entire
211
+ # paragraph so cross-references resolve to the caption text.
212
+ #
213
+ # @param label [String] "Figure", "Table", "Equation", or any
214
+ # other category — each gets its own counter
215
+ # @param text [String] caption body
216
+ # @param separator [String] between the label/number and the
217
+ # body (default ": ")
218
+ # @return [String] bookmark name (e.g. "_Figure1")
219
+ def add_caption(label:, text:, separator: ": ")
220
+ builder = Caption::CaptionBuilder.new(caption_counter)
221
+ paragraph, bookmark_name = builder.build(label: label,
222
+ text: text,
223
+ separator: separator)
224
+ body.paragraphs << paragraph
225
+ bookmark_name
226
+ end
227
+
228
+ # Build a cross-reference run targeting a bookmark. Returns a
229
+ # Run containing a SimpleField with a REF instruction. The
230
+ # caller decides where to place it.
231
+ #
232
+ # @param bookmark_name [String] target bookmark
233
+ # @return [Wordprocessingml::SimpleField]
234
+ def cross_reference_to(bookmark_name)
235
+ Caption::CrossReference.new(bookmark_name).build
236
+ end
237
+
198
238
  # Apply uniform page setup to every section of the document
199
239
  #
200
240
  # Mirrors Word's Layout dialog: named paper sizes, orientation
@@ -157,7 +157,7 @@ module Uniword
157
157
  #
158
158
  # @return [Array<Style>] Custom styles
159
159
  def custom_styles
160
- styles.select(&:custom)
160
+ styles.select(&:custom?)
161
161
  end
162
162
 
163
163
  # Get the number of styles
data/lib/uniword.rb CHANGED
@@ -140,6 +140,12 @@ module Uniword
140
140
  # Document lint (style-guide enforcement).
141
141
  autoload :Lint, "uniword/lint"
142
142
 
143
+ # Auto-numbered captions + cross-references.
144
+ autoload :Caption, "uniword/caption"
145
+
146
+ # Plugin system (validators, transformers, CLI commands).
147
+ autoload :Plugin, "uniword/plugin"
148
+
143
149
  # CLI
144
150
  autoload :CLI, "uniword/cli/main"
145
151
  autoload :GenerateCLI, "uniword/cli/generate_cli"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uniword
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-31 00:00:00.000000000 Z
11
+ date: 2026-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logger
@@ -92,14 +92,14 @@ dependencies:
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '2.3'
95
+ version: '3.4'
96
96
  type: :runtime
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '2.3'
102
+ version: '3.4'
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: thor
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -592,6 +592,13 @@ files:
592
592
  - lib/uniword/batch.rb
593
593
  - lib/uniword/batch/batch_result.rb
594
594
  - lib/uniword/batch/document_processor.rb
595
+ - lib/uniword/batch/operation.rb
596
+ - lib/uniword/batch/operation/file_result.rb
597
+ - lib/uniword/batch/operation/repair_task.rb
598
+ - lib/uniword/batch/operation/report.rb
599
+ - lib/uniword/batch/operation/runner.rb
600
+ - lib/uniword/batch/operation/task.rb
601
+ - lib/uniword/batch/operation/verify_task.rb
595
602
  - lib/uniword/batch/processing_stage.rb
596
603
  - lib/uniword/batch/stages/compress_images_stage.rb
597
604
  - lib/uniword/batch/stages/convert_format_stage.rb
@@ -656,6 +663,10 @@ files:
656
663
  - lib/uniword/builder/theme_builder.rb
657
664
  - lib/uniword/builder/toc_builder.rb
658
665
  - lib/uniword/builder/watermark_builder.rb
666
+ - lib/uniword/caption.rb
667
+ - lib/uniword/caption/caption_builder.rb
668
+ - lib/uniword/caption/counter.rb
669
+ - lib/uniword/caption/cross_reference.rb
659
670
  - lib/uniword/chart.rb
660
671
  - lib/uniword/chart/area3_d_chart.rb
661
672
  - lib/uniword/chart/area_chart.rb
@@ -800,6 +811,11 @@ files:
800
811
  - lib/uniword/diff/formatter.rb
801
812
  - lib/uniword/diff/package_diff_result.rb
802
813
  - lib/uniword/diff/package_differ.rb
814
+ - lib/uniword/diff/semantic.rb
815
+ - lib/uniword/diff/semantic/change.rb
816
+ - lib/uniword/diff/semantic/engine.rb
817
+ - lib/uniword/diff/semantic/paragraph_comparator.rb
818
+ - lib/uniword/diff/semantic/result.rb
803
819
  - lib/uniword/document_factory.rb
804
820
  - lib/uniword/document_input.rb
805
821
  - lib/uniword/document_variables.rb
@@ -1205,6 +1221,12 @@ files:
1205
1221
  - lib/uniword/picture/picture_source_rect.rb
1206
1222
  - lib/uniword/picture/picture_stretch.rb
1207
1223
  - lib/uniword/picture/tile.rb
1224
+ - lib/uniword/plugin.rb
1225
+ - lib/uniword/plugin/cli_command.rb
1226
+ - lib/uniword/plugin/loader.rb
1227
+ - lib/uniword/plugin/registry.rb
1228
+ - lib/uniword/plugin/transformer.rb
1229
+ - lib/uniword/plugin/validator.rb
1208
1230
  - lib/uniword/presentationml.rb
1209
1231
  - lib/uniword/presentationml/audio.rb
1210
1232
  - lib/uniword/presentationml/body_properties.rb