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
@@ -9,34 +9,39 @@ module Uniword
9
9
  #
10
10
  # Key behavior:
11
11
  # - cast(nil) -> nil (doesn't convert to false like OoxmlBoolean)
12
- # - cast("1") -> true, cast("0") -> false
12
+ # - cast("1"/"true"/"on") -> true, cast("0"/"false"/"off") -> false
13
+ # - cast of any other value raises
14
+ # Lutaml::Model::Type::InvalidValueError instead of passing
15
+ # through unchanged
13
16
  # - to_xml(true) -> "1"
14
17
  # - to_xml(false) -> "0" (explicit false in original)
15
18
  # - to_xml(nil) -> nil (attribute absent, omit from output)
16
19
  #
17
20
  # This is the right type for attributes like w:locked, w:semiHidden
18
- # on lsdException where absent = false = omit, but explicit "0" = render "0".
19
- # Note: Due to cast(nil) = nil, absent attributes also serialize as omitted.
21
+ # on lsdException where absent = false = omit, but explicit
22
+ # "0" = render "0".
23
+ # Note: Due to cast(nil) = nil, absent attributes also serialize
24
+ # as omitted.
20
25
  class OoxmlBooleanOptional < Lutaml::Model::Type::Boolean
21
26
  def self.cast(value, _options = {})
22
- case value
23
- when true, "1", 1
24
- true
25
- when false, "0", 0
26
- false
27
- when nil
28
- nil
29
- else
30
- value
31
- end
27
+ return value if Lutaml::Model::Utils.uninitialized?(value)
28
+ return nil if value.nil?
29
+ return true if OoxmlBoolean::TRUE_VALUES.include?(value)
30
+ return false if OoxmlBoolean::FALSE_VALUES.include?(value)
31
+
32
+ raise Lutaml::Model::Type::InvalidValueError.new(
33
+ value, OoxmlBoolean::ON_OFF_VALUES
34
+ )
32
35
  end
33
36
 
34
37
  def self.serialize(value)
35
38
  return nil if value.nil?
36
- return "1" if value == true || value.to_s.match?(/^(true|t|yes|y|1)$/i)
37
- return "0" if value == false || value.to_s.match?(/^(false|f|no|n|0)$/i)
39
+ return "1" if OoxmlBoolean::TRUE_VALUES.include?(value)
40
+ return "0" if OoxmlBoolean::FALSE_VALUES.include?(value)
38
41
 
39
- value
42
+ raise Lutaml::Model::Type::InvalidValueError.new(
43
+ value, OoxmlBoolean::ON_OFF_VALUES
44
+ )
40
45
  end
41
46
 
42
47
  # Override instance to_xml:
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Ooxml
7
+ module Types
8
+ # ST_ThemeColor (ECMA-376, wml.xsd): theme color reference
9
+ #
10
+ # Carries the full ST_ThemeColor enumeration so models can declare
11
+ # `values: ThemeColorValue::VALUES` on theme color attributes.
12
+ class ThemeColorValue < Lutaml::Model::Type::String
13
+ # Full ST_ThemeColor enumeration from ECMA-376 (wml.xsd)
14
+ VALUES = %w[
15
+ dark1 light1 dark2 light2 accent1 accent2 accent3 accent4
16
+ accent5 accent6 hyperlink followedHyperlink none background1
17
+ text1 background2 text2
18
+ ].freeze
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Ooxml
7
+ module Types
8
+ # ST_UnsignedDecimalNumber (ECMA-376): non-negative integer
9
+ #
10
+ # Constrained integer type shared by unsigned OOXML measures
11
+ # (ST_TwipsMeasure, ST_PointMeasure, ST_PixelsMeasure). Negative
12
+ # values raise Lutaml::Model::Type::MinBoundError at cast time
13
+ # (attribute assignment and XML parsing).
14
+ class UnsignedDecimalNumber < Lutaml::Model::Type::Integer
15
+ # Cast a value to a non-negative Integer
16
+ #
17
+ # @param value [Object] the raw value
18
+ # @param options [Hash] cast options (unused)
19
+ # @return [Integer, nil] the non-negative integer value
20
+ # @raise [Lutaml::Model::Type::MinBoundError] when negative
21
+ def self.cast(value, options = {})
22
+ casted = super
23
+ return casted unless casted.is_a?(::Integer) && casted.negative?
24
+
25
+ raise Lutaml::Model::Type::MinBoundError.new(casted, 0)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -40,6 +40,16 @@ module Uniword
40
40
  # Serializes true -> "1", false/nil -> omitted
41
41
  autoload :OoxmlBooleanOptional, "#{__dir__}/types/ooxml_boolean_optional"
42
42
 
43
+ # ST_HexColor constrained string type ("auto" or 6 hex digits)
44
+ autoload :HexColorValue, "#{__dir__}/types/hex_color_value"
45
+
46
+ # ST_ThemeColor enumeration carrier type
47
+ autoload :ThemeColorValue, "#{__dir__}/types/theme_color_value"
48
+
49
+ # ST_UnsignedDecimalNumber constrained integer type (non-negative)
50
+ autoload :UnsignedDecimalNumber,
51
+ "#{__dir__}/types/unsigned_decimal_number"
52
+
43
53
  # Relationships namespace type for r:embed/r:link cross-namespace attrs
44
54
  autoload :RelationshipId, "#{__dir__}/types/relationship_id"
45
55
 
data/lib/uniword/ooxml.rb CHANGED
@@ -29,6 +29,8 @@ module Uniword
29
29
  autoload :Types, "#{__dir__}/ooxml/types"
30
30
  autoload :PackageFile, "#{__dir__}/ooxml/package_file"
31
31
  autoload :ContentTypes, "#{__dir__}/content_types"
32
+ autoload :PartDefinition, "#{__dir__}/ooxml/part_definition"
33
+ autoload :PartRegistry, "#{__dir__}/ooxml/part_registry"
32
34
 
33
35
  # OOXML namespaces - autoload for runtime access
34
36
  autoload :Namespaces, "#{__dir__}/ooxml/namespaces"
@@ -6,14 +6,19 @@ module Uniword
6
6
  module Properties
7
7
  # Namespaced custom type for alignment value
8
8
  class AlignmentValue < Lutaml::Model::Type::String
9
+ # Full ST_Jc enumeration from ECMA-376 (wml.xsd)
10
+ VALUES = %w[
11
+ start center end both mediumKashida distribute numTab
12
+ highKashida lowKashida thaiDistribute left right
13
+ ].freeze
9
14
  end
10
15
 
11
16
  # Paragraph alignment element
12
17
  #
13
- # Represents <w:jc w:val="..."/> where value is:
14
- # - left, center, right, both (justified), distribute
18
+ # Represents <w:jc w:val="..."/> where value is from ST_Jc
19
+ # (ECMA-376), e.g. left, center, right, both (justified), distribute
15
20
  class Alignment < Lutaml::Model::Serializable
16
- attribute :value, AlignmentValue
21
+ attribute :value, AlignmentValue, values: AlignmentValue::VALUES
17
22
 
18
23
  xml do
19
24
  element "jc"
@@ -8,31 +8,77 @@ module Uniword
8
8
  #
9
9
  # Represents border line styles from OOXML specification
10
10
  class BorderStyleValue < Lutaml::Model::Type::String
11
+ # Full ST_Border enumeration from ECMA-376 (wml.xsd)
12
+ VALUES = %w[nil none single thick double dotted dashed dotDash dotDotDash
13
+ triple thinThickSmallGap thickThinSmallGap
14
+ thinThickThinSmallGap thinThickMediumGap thickThinMediumGap
15
+ thinThickThinMediumGap thinThickLargeGap thickThinLargeGap
16
+ thinThickThinLargeGap wave doubleWave dashSmallGap
17
+ dashDotStroked threeDEmboss threeDEngrave outset inset apples
18
+ archedScallops babyPacifier babyRattle balloons3Colors
19
+ balloonsHotAir basicBlackDashes basicBlackDots
20
+ basicBlackSquares basicThinLines basicWhiteDashes
21
+ basicWhiteDots basicWhiteSquares basicWideInline
22
+ basicWideMidline basicWideOutline bats birds birdsFlight
23
+ cabins cakeSlice candyCorn celticKnotwork certificateBanner
24
+ chainLink champagneBottle checkedBarBlack checkedBarColor
25
+ checkered christmasTree circlesLines circlesRectangles
26
+ classicalWave clocks compass confetti confettiGrays
27
+ confettiOutline confettiStreamers confettiWhite
28
+ cornerTriangles couponCutoutDashes couponCutoutDots crazyMaze
29
+ creaturesButterfly creaturesFish creaturesInsects
30
+ creaturesLadyBug crossStitch cup decoArch decoArchColor
31
+ decoBlocks diamondsGray doubleD doubleDiamonds earth1 earth2
32
+ earth3 eclipsingSquares1 eclipsingSquares2 eggsBlack fans
33
+ film firecrackers flowersBlockPrint flowersDaisies
34
+ flowersModern1 flowersModern2 flowersPansy flowersRedRose
35
+ flowersRoses flowersTeacup flowersTiny gems gingerbreadMan
36
+ gradient handmade1 handmade2 heartBalloon heartGray hearts
37
+ heebieJeebies holly houseFunky hypnotic iceCreamCones
38
+ lightBulb lightning1 lightning2 mapPins mapleLeaf
39
+ mapleMuffins marquee marqueeToothed moons mosaic musicNotes
40
+ northwest ovals packages palmsBlack palmsColor paperClips
41
+ papyrus partyFavor partyGlass pencils people peopleWaving
42
+ peopleHats poinsettias postageStamp pumpkin1 pushPinNote2
43
+ pushPinNote1 pyramids pyramidsAbove quadrants rings safari
44
+ sawtooth sawtoothGray scaredCat seattle shadowedSquares
45
+ sharksTeeth shorebirdTracks skyrocket snowflakeFancy
46
+ snowflakes sombrero southwest stars starsTop stars3d
47
+ starsBlack starsShadowed sun swirligig tornPaper
48
+ tornPaperBlack trees triangleParty triangles triangle1
49
+ triangle2 triangleCircle1 triangleCircle2 shapes1 shapes2
50
+ twistedLines1 twistedLines2 vine waveline weavingAngles
51
+ weavingBraid weavingRibbon weavingStrips whiteFlowers
52
+ woodwork xIllusions zanyTriangles zigZag zigZagStitch
53
+ custom].freeze
11
54
  end
12
55
 
13
56
  # Individual border definition
14
57
  #
15
58
  # Represents a single border (top, bottom, left, right) with style,
16
59
  # size, spacing, and color attributes.
60
+ # style is ST_Border, color is ST_HexColor, themeColor is
61
+ # ST_ThemeColor (ECMA-376).
17
62
  #
18
63
  # @example Creating a border
19
64
  # border = Border.new(
20
- # style: 'single',
65
+ # style: "single",
21
66
  # size: 4,
22
67
  # space: 1,
23
- # color: 'auto'
68
+ # color: "auto"
24
69
  # )
25
70
  class Border < Lutaml::Model::Serializable
26
71
  # Pattern 0: ATTRIBUTES FIRST
27
- attribute :style, BorderStyleValue
72
+ attribute :style, BorderStyleValue, values: BorderStyleValue::VALUES
28
73
  attribute :size, :integer # Size in eighths of a point (1-96)
29
74
  attribute :space, :integer # Spacing offset in points (0-31)
30
- attribute :color, :string # RGB hex or 'auto'
31
- attribute :theme_color, :string
75
+ attribute :color, Ooxml::Types::HexColorValue
76
+ attribute :theme_color, Ooxml::Types::ThemeColorValue,
77
+ values: Ooxml::Types::ThemeColorValue::VALUES
32
78
  attribute :theme_shade, :string
33
79
 
34
80
  xml do
35
- # Use 'border' as element name for standalone parsing
81
+ # Use "border" as element name for standalone parsing
36
82
  element "border"
37
83
  namespace Ooxml::Namespaces::WordProcessingML
38
84
 
@@ -8,6 +8,8 @@ module Uniword
8
8
  #
9
9
  # Represents vertical alignment types from OOXML specification
10
10
  class CellVerticalAlignValue < Lutaml::Model::Type::String
11
+ # Full ST_VerticalJc enumeration from ECMA-376 (wml.xsd)
12
+ VALUES = %w[top center both bottom].freeze
11
13
  end
12
14
 
13
15
  # Table cell vertical alignment
@@ -19,7 +21,8 @@ module Uniword
19
21
  # valign = CellVerticalAlign.new(value: 'center')
20
22
  class CellVerticalAlign < Lutaml::Model::Serializable
21
23
  # Pattern 0: ATTRIBUTES FIRST
22
- attribute :value, CellVerticalAlignValue # Alignment: top, center, bottom
24
+ attribute :value, CellVerticalAlignValue,
25
+ values: CellVerticalAlignValue::VALUES
23
26
 
24
27
  xml do
25
28
  element "vAlign"
@@ -5,18 +5,23 @@ require "lutaml/model"
5
5
  module Uniword
6
6
  module Properties
7
7
  # Namespaced custom type for color value
8
- class ColorValueType < Lutaml::Model::Type::String
8
+ #
9
+ # Inherits the ST_HexColor constraint ("auto" or six hex digits)
10
+ # from Ooxml::Types::HexColorValue.
11
+ class ColorValueType < Ooxml::Types::HexColorValue
9
12
  end
10
13
 
11
14
  # Color value element
12
15
  #
13
16
  # Represents <w:color w:val="..." w:themeColor="..." w:themeShade="..."/>
14
- # where value is RGB hex (e.g., "FF0000") and themeColor references theme (e.g., "background1")
17
+ # where value is RGB hex (e.g., "FF0000") and themeColor references
18
+ # theme (e.g., "background1")
15
19
  # themeShade is a hex tint value (e.g., "BF")
16
20
  class ColorValue < Lutaml::Model::Serializable
17
21
  # Pattern 0: ATTRIBUTES FIRST
18
22
  attribute :value, ColorValueType
19
- attribute :theme_color, :string
23
+ attribute :theme_color, Ooxml::Types::ThemeColorValue,
24
+ values: Ooxml::Types::ThemeColorValue::VALUES
20
25
  attribute :theme_shade, :string
21
26
  attribute :theme_tint, :string
22
27
 
@@ -6,16 +6,20 @@ module Uniword
6
6
  module Properties
7
7
  # Namespaced custom type for highlight value
8
8
  class HighlightValue < Lutaml::Model::Type::String
9
+ # Full ST_HighlightColor enumeration from ECMA-376 (wml.xsd)
10
+ VALUES = %w[
11
+ black blue cyan green magenta red yellow white darkBlue
12
+ darkCyan darkGreen darkMagenta darkRed darkYellow darkGray
13
+ lightGray none
14
+ ].freeze
9
15
  end
10
16
 
11
17
  # Text highlight element
12
18
  #
13
- # Represents <w:highlight w:val="..."/> where value is:
14
- # - yellow, green, cyan, magenta, blue, red, darkBlue, darkCyan,
15
- # darkGreen, darkMagenta, darkRed, darkYellow, darkGray,
16
- # lightGray, black, white, none
19
+ # Represents <w:highlight w:val="..."/> where value is from
20
+ # ST_HighlightColor (ECMA-376)
17
21
  class Highlight < Lutaml::Model::Serializable
18
- attribute :value, HighlightValue
22
+ attribute :value, HighlightValue, values: HighlightValue::VALUES
19
23
 
20
24
  xml do
21
25
  element "highlight"
@@ -23,9 +23,10 @@ module Uniword
23
23
  class Shading < Lutaml::Model::Serializable
24
24
  # Pattern 0: ATTRIBUTES FIRST
25
25
  attribute :pattern, ShadingPatternValue
26
- attribute :color, :string # Foreground color (RGB hex or 'auto')
27
- attribute :fill, :string # Background fill color (RGB hex)
28
- attribute :theme_fill, :string # Theme color reference (e.g., 'accent1')
26
+ attribute :color, Ooxml::Types::HexColorValue
27
+ attribute :fill, Ooxml::Types::HexColorValue
28
+ attribute :theme_fill, Ooxml::Types::ThemeColorValue,
29
+ values: Ooxml::Types::ThemeColorValue::VALUES
29
30
 
30
31
  xml do
31
32
  element "shd"
@@ -8,6 +8,10 @@ module Uniword
8
8
  #
9
9
  # Represents alignment types for tab stops
10
10
  class TabAlignmentValue < Lutaml::Model::Type::String
11
+ # Full ST_TabJc enumeration from ECMA-376 (wml.xsd)
12
+ VALUES = %w[
13
+ clear start center end decimal bar num left right
14
+ ].freeze
11
15
  end
12
16
 
13
17
  # Tab leader enumeration
@@ -28,7 +32,8 @@ module Uniword
28
32
  # )
29
33
  class TabStop < Lutaml::Model::Serializable
30
34
  # Pattern 0: ATTRIBUTES FIRST
31
- attribute :alignment, TabAlignmentValue
35
+ attribute :alignment, TabAlignmentValue,
36
+ values: TabAlignmentValue::VALUES
32
37
  attribute :position, :integer # Position in twips
33
38
  attribute :leader, TabLeaderValue
34
39
 
@@ -9,7 +9,10 @@ module Uniword
9
9
  # Represents <w:jc> element with val attribute for table alignment.
10
10
  # Used in table properties (w:tblPr) for table positioning.
11
11
  class TableJustification < Lutaml::Model::Serializable
12
- attribute :value, :string
12
+ # Full ST_JcTable enumeration from ECMA-376 (wml.xsd)
13
+ VALUES = %w[center end left right start].freeze
14
+
15
+ attribute :value, :string, values: VALUES
13
16
 
14
17
  xml do
15
18
  element "jc"
@@ -12,10 +12,12 @@ module Uniword
12
12
  #
13
13
  # Represents <w:u w:val="..."/> where value is:
14
14
  # - single, double, thick, dotted, dashed, wave, none, etc.
15
+ # color is ST_HexColor, themeColor is ST_ThemeColor (ECMA-376)
15
16
  class Underline < Lutaml::Model::Serializable
16
17
  attribute :value, UnderlineValue
17
- attribute :color, :string
18
- attribute :theme_color, :string
18
+ attribute :color, Ooxml::Types::HexColorValue
19
+ attribute :theme_color, Ooxml::Types::ThemeColorValue,
20
+ values: Ooxml::Types::ThemeColorValue::VALUES
19
21
  attribute :theme_shade, :string
20
22
 
21
23
  xml do
@@ -6,14 +6,17 @@ module Uniword
6
6
  module Properties
7
7
  # Namespaced custom type for vertical alignment value
8
8
  class VerticalAlignValue < Lutaml::Model::Type::String
9
+ # Full ST_VerticalAlignRun enumeration from ECMA-376 (wml.xsd)
10
+ VALUES = %w[baseline superscript subscript].freeze
9
11
  end
10
12
 
11
13
  # Text vertical alignment element
12
14
  #
13
- # Represents <w:vertAlign w:val="..."/> where value is:
14
- # - baseline, superscript, subscript
15
+ # Represents <w:vertAlign w:val="..."/> where value is from
16
+ # ST_VerticalAlignRun (ECMA-376): baseline, superscript, subscript
15
17
  class VerticalAlign < Lutaml::Model::Serializable
16
- attribute :value, VerticalAlignValue
18
+ attribute :value, VerticalAlignValue,
19
+ values: VerticalAlignValue::VALUES
17
20
 
18
21
  xml do
19
22
  element "vertAlign"
@@ -40,6 +40,10 @@ module Uniword
40
40
 
41
41
  # Add a new comment to the document.
42
42
  #
43
+ # The comment is registered in the document's CommentsPart, which
44
+ # the save path serializes to word/comments.xml. The collection
45
+ # assigns a sequential decimal ID (OOXML ST_DecimalNumber).
46
+ #
43
47
  # @param text [String] Comment text
44
48
  # @param author [String] Author name
45
49
  # @param initials [String, nil] Author initials
@@ -50,6 +54,7 @@ module Uniword
50
54
  author: author,
51
55
  initials: initials,
52
56
  )
57
+ comment.comment_id = nil
53
58
  comments_part.add_comment(comment)
54
59
  end
55
60
 
@@ -74,6 +79,7 @@ module Uniword
74
79
  text: text,
75
80
  author: author,
76
81
  )
82
+ comment.comment_id = nil
77
83
  comments_part.add_comment(comment)
78
84
  end
79
85
 
@@ -216,15 +222,23 @@ module Uniword
216
222
  #
217
223
  # @return [Uniword::CommentsPart] The comments collection
218
224
  def comments_part
219
- @comments_part ||= begin
220
- existing = document.comments
221
- if existing.is_a?(CommentsPart)
222
- existing
223
- else
224
- part = CommentsPart.new
225
- document.comments = part
226
- part
227
- end
225
+ @comments_part ||= migrate_comments(document.comments)
226
+ end
227
+
228
+ # Migrate legacy Array storage into a CommentsPart, preserving
229
+ # its entries; initialize the collection when absent.
230
+ #
231
+ # @param existing [CommentsPart, Array, nil] current value
232
+ # @return [Uniword::CommentsPart] The comments collection
233
+ def migrate_comments(existing)
234
+ case existing
235
+ when CommentsPart then existing
236
+ when Array
237
+ part = CommentsPart.new
238
+ existing.each { |c| part.add_comment(c) }
239
+ document.comments = part
240
+ else
241
+ document.comments = CommentsPart.new
228
242
  end
229
243
  end
230
244
 
@@ -34,6 +34,15 @@ module Uniword
34
34
  #
35
35
  # @see TrackedChanges For revision collection management
36
36
  class Revision < Lutaml::Model::Serializable
37
+ # Unique revision identifier
38
+ attribute :revision_id, :string
39
+
40
+ # Author name
41
+ attribute :author, :string
42
+
43
+ # Revision date/time
44
+ attribute :date, :string
45
+
37
46
  # OOXML namespace configuration
38
47
  xml do
39
48
  element "ins"
@@ -44,15 +53,6 @@ module Uniword
44
53
  map_attribute "date", to: :date
45
54
  end
46
55
 
47
- # Unique revision identifier
48
- attribute :revision_id, :string
49
-
50
- # Author name
51
- attribute :author, :string
52
-
53
- # Revision date/time
54
- attribute :date, :string
55
-
56
56
  # Revision type (:insert, :delete, :format_change)
57
57
  attr_accessor :type
58
58
 
@@ -62,8 +62,6 @@ module Uniword
62
62
  file_path
63
63
  end
64
64
 
65
- private
66
-
67
65
  # Generate complete class code
68
66
  #
69
67
  # @param element_name [String] XML element name
@@ -97,6 +95,8 @@ module Uniword
97
95
  RUBY
98
96
  end
99
97
 
98
+ private
99
+
100
100
  # Generate attribute declarations
101
101
  # CRITICAL: These MUST come BEFORE xml mapping (Pattern 0)
102
102
  #
@@ -105,21 +105,37 @@ module Uniword
105
105
  def generate_attributes(attributes)
106
106
  return "" if attributes.empty?
107
107
 
108
- lines = attributes.map do |attr|
109
- # Convert type to symbol for primitive types
110
- type_str = if %w[String
111
- Integer].include?(attr["type"])
112
- ":#{attr['type'].downcase}"
113
- else
114
- attr["type"]
115
- end
116
- attr_code = " attribute :#{attr['name']}, #{type_str}"
117
- attr_code += ", collection: true" if attr["collection"]
118
- attr_code += ", initialize_empty: true" if attr["collection"]
119
- attr_code
108
+ attributes.map { |attr| generate_attribute(attr) }.join("\n")
109
+ end
110
+
111
+ # Generate one attribute declaration
112
+ #
113
+ # @param attr [Hash] Attribute definition
114
+ # @return [String] Attribute declaration code
115
+ def generate_attribute(attr)
116
+ attr_code = " attribute :#{attr['name']}, " \
117
+ "#{attribute_type(attr['type'])}"
118
+ attr_code += ", collection: true" if attr["collection"]
119
+ attr_code += ", initialize_empty: true" if attr["collection"]
120
+ if attr["values"]
121
+ attr_code += ", values: %w[#{attr['values'].join(' ')}]"
120
122
  end
123
+ attr_code += ", pattern: /#{attr['pattern']}/" if attr["pattern"]
124
+ attr_code
125
+ end
121
126
 
122
- lines.join("\n")
127
+ # Normalize an attribute type from YAML to Ruby code
128
+ # Primitive types become symbols (":string"), custom type class
129
+ # names are passed through verbatim
130
+ #
131
+ # @param type [String, Symbol] Type from YAML attribute definition
132
+ # @return [String] Ruby type expression
133
+ def attribute_type(type)
134
+ type = type.to_s
135
+ return ":#{type.downcase}" if %w[String Integer].include?(type)
136
+ return type if type.start_with?(":") || type.include?("::")
137
+
138
+ ":#{type}"
123
139
  end
124
140
 
125
141
  # Generate XML mapping block
@@ -8,8 +8,11 @@ module Uniword
8
8
  #
9
9
  # Generated from OOXML schema: shared_types.yml
10
10
  # Element: <st:hex_color>
11
+ #
12
+ # Value constrained to ST_HexColor (ECMA-376): "auto" or six
13
+ # hexadecimal digits; see Ooxml::Types::HexColorValue.
11
14
  class HexColor < Lutaml::Model::Serializable
12
- attribute :val, :string
15
+ attribute :val, Uniword::Ooxml::Types::HexColorValue
13
16
 
14
17
  xml do
15
18
  element "hex_color"
@@ -8,8 +8,11 @@ module Uniword
8
8
  #
9
9
  # Generated from OOXML schema: shared_types.yml
10
10
  # Element: <st:pixel_measure>
11
+ #
12
+ # Value constrained to ST_PixelsMeasure (ECMA-376): non-negative
13
+ # integer; see Ooxml::Types::UnsignedDecimalNumber.
11
14
  class PixelMeasure < Lutaml::Model::Serializable
12
- attribute :val, :integer
15
+ attribute :val, Uniword::Ooxml::Types::UnsignedDecimalNumber
13
16
 
14
17
  xml do
15
18
  element "pixel_measure"
@@ -8,8 +8,11 @@ module Uniword
8
8
  #
9
9
  # Generated from OOXML schema: shared_types.yml
10
10
  # Element: <st:point_measure>
11
+ #
12
+ # Value constrained to ST_PointMeasure (ECMA-376): non-negative
13
+ # integer; see Ooxml::Types::UnsignedDecimalNumber.
11
14
  class PointMeasure < Lutaml::Model::Serializable
12
- attribute :val, :integer
15
+ attribute :val, Uniword::Ooxml::Types::UnsignedDecimalNumber
13
16
 
14
17
  xml do
15
18
  element "point_measure"
@@ -8,8 +8,13 @@ module Uniword
8
8
  #
9
9
  # Generated from OOXML schema: shared_types.yml
10
10
  # Element: <st:text_alignment>
11
+ #
12
+ # Value constrained to ST_TextAlignment (ECMA-376, wml.xsd).
11
13
  class TextAlignment < Lutaml::Model::Serializable
12
- attribute :val, :string
14
+ # Full ST_TextAlignment enumeration from ECMA-376 (wml.xsd)
15
+ VALUES = %w[top center baseline bottom auto].freeze
16
+
17
+ attribute :val, :string, values: VALUES
13
18
 
14
19
  xml do
15
20
  element "text_alignment"
@@ -8,8 +8,11 @@ module Uniword
8
8
  #
9
9
  # Generated from OOXML schema: shared_types.yml
10
10
  # Element: <st:twips_measure>
11
+ #
12
+ # Value constrained to ST_TwipsMeasure (ECMA-376): non-negative
13
+ # integer; see Ooxml::Types::UnsignedDecimalNumber.
11
14
  class TwipsMeasure < Lutaml::Model::Serializable
12
- attribute :val, :integer
15
+ attribute :val, Uniword::Ooxml::Types::UnsignedDecimalNumber
13
16
 
14
17
  xml do
15
18
  element "twips_measure"
@@ -26,7 +26,7 @@ module Uniword
26
26
  map_attribute "panose", to: :panose, render_nil: false
27
27
  map_attribute "pitchFamily", to: :pitch_family, render_nil: false
28
28
  map_attribute "charset", to: :charset, render_nil: false
29
- map_attribute "altText", to: :AltText, render_nil: false
29
+ map_attribute "altText", to: :alt_text, render_nil: false
30
30
  map_attribute "combinationId", to: :combination_id, render_nil: false
31
31
  map_attribute "growingEditing", to: :growing_editing, render_nil: false
32
32
  end