uniword 1.0.9 → 1.0.10

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/data/themes/office_format_scheme.xml +137 -0
  3. data/data/themes/office_theme.xml +2 -0
  4. data/lib/uniword/builder/footnote_builder.rb +4 -2
  5. data/lib/uniword/builder/run_builder.rb +5 -0
  6. data/lib/uniword/docx/package.rb +18 -0
  7. data/lib/uniword/docx/package_serialization.rb +38 -0
  8. data/lib/uniword/docx/reconciler.rb +158 -4
  9. data/lib/uniword/lazy_loader.rb +1 -1
  10. data/lib/uniword/properties/boolean_element_factory.rb +8 -15
  11. data/lib/uniword/schema/schema_loader.rb +1 -1
  12. data/lib/uniword/version.rb +1 -1
  13. data/lib/uniword/wordprocessingml/attached_template.rb +18 -0
  14. data/lib/uniword/wordprocessingml/body.rb +18 -36
  15. data/lib/uniword/wordprocessingml/brk_bin.rb +17 -0
  16. data/lib/uniword/wordprocessingml/brk_bin_sub.rb +17 -0
  17. data/lib/uniword/wordprocessingml/character_spacing_control.rb +17 -0
  18. data/lib/uniword/wordprocessingml/clr_scheme_mapping.rb +39 -0
  19. data/lib/uniword/wordprocessingml/compat.rb +16 -0
  20. data/lib/uniword/wordprocessingml/decimal_symbol.rb +17 -0
  21. data/lib/uniword/wordprocessingml/def_jc.rb +17 -0
  22. data/lib/uniword/wordprocessingml/default_tab_stop.rb +17 -0
  23. data/lib/uniword/wordprocessingml/disp_def.rb +14 -0
  24. data/lib/uniword/wordprocessingml/do_not_display_page_boundaries.rb +14 -0
  25. data/lib/uniword/wordprocessingml/do_not_include_subdocs_in_stats.rb +17 -0
  26. data/lib/uniword/wordprocessingml/doc_vars.rb +36 -0
  27. data/lib/uniword/wordprocessingml/document_root.rb +6 -5
  28. data/lib/uniword/wordprocessingml/endnote_pr.rb +18 -0
  29. data/lib/uniword/wordprocessingml/endnotes.rb +18 -0
  30. data/lib/uniword/wordprocessingml/even_and_odd_headers.rb +17 -0
  31. data/lib/uniword/wordprocessingml/footnote_pos.rb +17 -0
  32. data/lib/uniword/wordprocessingml/footnote_pr.rb +20 -0
  33. data/lib/uniword/wordprocessingml/footnotes.rb +18 -0
  34. data/lib/uniword/wordprocessingml/hdr_shape_defaults.rb +19 -0
  35. data/lib/uniword/wordprocessingml/hyphenation_zone.rb +20 -0
  36. data/lib/uniword/wordprocessingml/int_lim.rb +17 -0
  37. data/lib/uniword/wordprocessingml/l_margin.rb +17 -0
  38. data/lib/uniword/wordprocessingml/list_separator.rb +17 -0
  39. data/lib/uniword/wordprocessingml/math_font.rb +17 -0
  40. data/lib/uniword/wordprocessingml/math_pr.rb +49 -0
  41. data/lib/uniword/wordprocessingml/mirror_margins.rb +17 -0
  42. data/lib/uniword/wordprocessingml/nary_lim.rb +17 -0
  43. data/lib/uniword/wordprocessingml/proof_state.rb +19 -0
  44. data/lib/uniword/wordprocessingml/r_margin.rb +17 -0
  45. data/lib/uniword/wordprocessingml/rsid.rb +17 -0
  46. data/lib/uniword/wordprocessingml/rsid_root.rb +17 -0
  47. data/lib/uniword/wordprocessingml/rsids.rb +22 -0
  48. data/lib/uniword/wordprocessingml/settings.rb +45 -511
  49. data/lib/uniword/wordprocessingml/shape_defaults.rb +21 -0
  50. data/lib/uniword/wordprocessingml/small_frac.rb +17 -0
  51. data/lib/uniword/wordprocessingml/style_pane_format_filter.rb +50 -0
  52. data/lib/uniword/wordprocessingml/style_pane_sort_method.rb +20 -0
  53. data/lib/uniword/wordprocessingml/table_cell.rb +6 -0
  54. data/lib/uniword/wordprocessingml/table_row_properties.rb +2 -0
  55. data/lib/uniword/wordprocessingml/theme_font_lang.rb +19 -0
  56. data/lib/uniword/wordprocessingml/w14_doc_id.rb +17 -0
  57. data/lib/uniword/wordprocessingml/w15_chart_tracking_ref_based.rb +14 -0
  58. data/lib/uniword/wordprocessingml/w15_doc_id.rb +17 -0
  59. data/lib/uniword/wordprocessingml/wrap_indent.rb +17 -0
  60. data/lib/uniword/wordprocessingml.rb +42 -56
  61. data/lib/uniword.rb +12 -5
  62. metadata +49 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b3b37b9a1642f94a634c42b754630ca35cd7167ddc1415223dd65848a379c48
4
- data.tar.gz: 011ddc8e381521e2d161cd0cf9fd0dd9eee791e9f28026fd63e89cf9386bfa03
3
+ metadata.gz: a7feae85851d80946524005c3cc6419419d492c717e559628093494ee7532cd9
4
+ data.tar.gz: eba8d3d3d433aa158b5a4214659e6afd0e12d74e3fada13997da066021a78f9d
5
5
  SHA512:
6
- metadata.gz: 1c6c8785a7fe56480f0d7bdda19ec56e50704653016d4146c561e401c8e98b856cf99a1205411521b78cd19b701e794dfbc75c4d4c844b87e2b8fe3d44fb514e
7
- data.tar.gz: 9275ea155ee7af2ee9cd0e24d6534a0124f0f8501583206fb98eb3c63e7736c280552336a47bad97a31e50b235805c5fba904d1a80c0cee9bbe6f216f7e49f02
6
+ metadata.gz: 940e11bb305505cd3e6acdc6e9cca98e002d151a0f75e7c8872d613353b4c5d85b424062a661572b14474c342b62db3072d2cc081cb9e924f95bb8fdf80e6c39
7
+ data.tar.gz: 3ca3c9bea8133f7a18e64897b687ef8dd10843c85aa2e4422dabceed5152ae5a5f9cce56fdff8999f03e734f8510b919732f9f5b5b0d5ac0e9470c8dd0db11fb
@@ -0,0 +1,137 @@
1
+ <a:fmtScheme name="Office">
2
+ <a:fillStyleLst>
3
+ <a:solidFill>
4
+ <a:schemeClr val="phClr"/>
5
+ </a:solidFill>
6
+ <a:gradFill rotWithShape="1">
7
+ <a:gsLst>
8
+ <a:gs pos="0">
9
+ <a:schemeClr val="phClr">
10
+ <a:lumMod val="110000"/>
11
+ <a:satMod val="105000"/>
12
+ <a:tint val="67000"/>
13
+ </a:schemeClr>
14
+ </a:gs>
15
+ <a:gs pos="50000">
16
+ <a:schemeClr val="phClr">
17
+ <a:lumMod val="105000"/>
18
+ <a:satMod val="103000"/>
19
+ <a:tint val="73000"/>
20
+ </a:schemeClr>
21
+ </a:gs>
22
+ <a:gs pos="100000">
23
+ <a:schemeClr val="phClr">
24
+ <a:lumMod val="105000"/>
25
+ <a:satMod val="109000"/>
26
+ <a:tint val="81000"/>
27
+ </a:schemeClr>
28
+ </a:gs>
29
+ </a:gsLst>
30
+ <a:lin ang="5400000" scaled="0"/>
31
+ </a:gradFill>
32
+ <a:gradFill rotWithShape="1">
33
+ <a:gsLst>
34
+ <a:gs pos="0">
35
+ <a:schemeClr val="phClr">
36
+ <a:satMod val="103000"/>
37
+ <a:lumMod val="102000"/>
38
+ <a:tint val="94000"/>
39
+ </a:schemeClr>
40
+ </a:gs>
41
+ <a:gs pos="50000">
42
+ <a:schemeClr val="phClr">
43
+ <a:satMod val="110000"/>
44
+ <a:lumMod val="100000"/>
45
+ <a:shade val="100000"/>
46
+ </a:schemeClr>
47
+ </a:gs>
48
+ <a:gs pos="100000">
49
+ <a:schemeClr val="phClr">
50
+ <a:lumMod val="99000"/>
51
+ <a:satMod val="120000"/>
52
+ <a:shade val="78000"/>
53
+ </a:schemeClr>
54
+ </a:gs>
55
+ </a:gsLst>
56
+ <a:lin ang="5400000" scaled="0"/>
57
+ </a:gradFill>
58
+ </a:fillStyleLst>
59
+ <a:lnStyleLst>
60
+ <a:ln w="12700" cap="flat" cmpd="sng" algn="ctr">
61
+ <a:solidFill>
62
+ <a:schemeClr val="phClr"/>
63
+ </a:solidFill>
64
+ <a:prstDash val="solid"/>
65
+ <a:miter lim="800000"/>
66
+ </a:ln>
67
+ <a:ln w="19050" cap="flat" cmpd="sng" algn="ctr">
68
+ <a:solidFill>
69
+ <a:schemeClr val="phClr"/>
70
+ </a:solidFill>
71
+ <a:prstDash val="solid"/>
72
+ <a:miter lim="800000"/>
73
+ </a:ln>
74
+ <a:ln w="25400" cap="flat" cmpd="sng" algn="ctr">
75
+ <a:solidFill>
76
+ <a:schemeClr val="phClr"/>
77
+ </a:solidFill>
78
+ <a:prstDash val="solid"/>
79
+ <a:miter lim="800000"/>
80
+ </a:ln>
81
+ </a:lnStyleLst>
82
+ <a:effectStyleLst>
83
+ <a:effectStyle>
84
+ <a:effectLst/>
85
+ </a:effectStyle>
86
+ <a:effectStyle>
87
+ <a:effectLst/>
88
+ </a:effectStyle>
89
+ <a:effectStyle>
90
+ <a:effectLst>
91
+ <a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0">
92
+ <a:srgbClr val="000000">
93
+ <a:alpha val="63000"/>
94
+ </a:srgbClr>
95
+ </a:outerShdw>
96
+ </a:effectLst>
97
+ </a:effectStyle>
98
+ </a:effectStyleLst>
99
+ <a:bgFillStyleLst>
100
+ <a:solidFill>
101
+ <a:schemeClr val="phClr"/>
102
+ </a:solidFill>
103
+ <a:solidFill>
104
+ <a:schemeClr val="phClr">
105
+ <a:tint val="95000"/>
106
+ <a:satMod val="170000"/>
107
+ </a:schemeClr>
108
+ </a:solidFill>
109
+ <a:gradFill rotWithShape="1">
110
+ <a:gsLst>
111
+ <a:gs pos="0">
112
+ <a:schemeClr val="phClr">
113
+ <a:tint val="93000"/>
114
+ <a:satMod val="150000"/>
115
+ <a:shade val="98000"/>
116
+ <a:lumMod val="102000"/>
117
+ </a:schemeClr>
118
+ </a:gs>
119
+ <a:gs pos="50000">
120
+ <a:schemeClr val="phClr">
121
+ <a:tint val="98000"/>
122
+ <a:satMod val="130000"/>
123
+ <a:shade val="90000"/>
124
+ <a:lumMod val="103000"/>
125
+ </a:schemeClr>
126
+ </a:gs>
127
+ <a:gs pos="100000">
128
+ <a:schemeClr val="phClr">
129
+ <a:shade val="63000"/>
130
+ <a:satMod val="120000"/>
131
+ </a:schemeClr>
132
+ </a:gs>
133
+ </a:gsLst>
134
+ <a:lin ang="5400000" scaled="0"/>
135
+ </a:gradFill>
136
+ </a:bgFillStyleLst>
137
+ </a:fmtScheme>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
+ <a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="0E2841"/></a:dk2><a:lt2><a:srgbClr val="E8E8E8"/></a:lt2><a:accent1><a:srgbClr val="156082"/></a:accent1><a:accent2><a:srgbClr val="E97132"/></a:accent2><a:accent3><a:srgbClr val="196B24"/></a:accent3><a:accent4><a:srgbClr val="0F9ED5"/></a:accent4><a:accent5><a:srgbClr val="A02B93"/></a:accent5><a:accent6><a:srgbClr val="4EA72E"/></a:accent6><a:hlink><a:srgbClr val="467886"/></a:hlink><a:folHlink><a:srgbClr val="96607D"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Aptos Display" panose="02110004020202020204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Angsana New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:majorFont><a:minorFont><a:latin typeface="Aptos" panose="02110004020202020204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游明朝"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Cordia New"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/><a:font script="Armn" typeface="Arial"/><a:font script="Bugi" typeface="Leelawadee UI"/><a:font script="Bopo" typeface="Microsoft JhengHei"/><a:font script="Java" typeface="Javanese Text"/><a:font script="Lisu" typeface="Segoe UI"/><a:font script="Mymr" typeface="Myanmar Text"/><a:font script="Nkoo" typeface="Ebrima"/><a:font script="Olck" typeface="Nirmala UI"/><a:font script="Osma" typeface="Ebrima"/><a:font script="Phag" typeface="Phagspa"/><a:font script="Syrn" typeface="Estrangelo Edessa"/><a:font script="Syrj" typeface="Estrangelo Edessa"/><a:font script="Syre" typeface="Estrangelo Edessa"/><a:font script="Sora" typeface="Nirmala UI"/><a:font script="Tale" typeface="Microsoft Tai Le"/><a:font script="Talu" typeface="Microsoft New Tai Lue"/><a:font script="Tfng" typeface="Ebrima"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults><a:lnDef><a:spPr/><a:bodyPr/><a:lstStyle/><a:style><a:lnRef idx="2"><a:schemeClr val="accent1"/></a:lnRef><a:fillRef idx="0"><a:schemeClr val="accent1"/></a:fillRef><a:effectRef idx="1"><a:schemeClr val="accent1"/></a:effectRef><a:fontRef idx="minor"><a:schemeClr val="tx1"/></a:fontRef></a:style></a:lnDef></a:objectDefaults><a:extraClrSchemeLst/><a:extLst><a:ext uri="{05A4C25C-085E-4340-85A3-A5531E510DB2}"><thm15:themeFamily xmlns:thm15="http://schemas.microsoft.com/office/thememl/2012/main" name="Office Theme" id="{2E142A2C-CD16-42D6-873A-C26D2A0506FA}" vid="{1BDDFF52-6CD6-40A5-AB3C-68EB2F1E4D0A}"/></a:ext></a:extLst></a:theme>
@@ -75,7 +75,9 @@ module Uniword
75
75
  end
76
76
 
77
77
  def create_footnote_entry(id, text, &block)
78
- entry = Wordprocessingml::Footnote.new(id: id.to_s, type: "normal")
78
+ # OOXML spec: regular footnotes must NOT have w:type.
79
+ # Only separator/continuationSeparator use w:type.
80
+ entry = Wordprocessingml::Footnote.new(id: id.to_s)
79
81
  para = ParagraphBuilder.new
80
82
  if block_given?
81
83
  block.call(para)
@@ -87,7 +89,7 @@ module Uniword
87
89
  end
88
90
 
89
91
  def create_endnote_entry(id, text, &block)
90
- entry = Wordprocessingml::Endnote.new(id: id.to_s, type: "normal")
92
+ entry = Wordprocessingml::Endnote.new(id: id.to_s)
91
93
  para = ParagraphBuilder.new
92
94
  if block_given?
93
95
  block.call(para)
@@ -22,6 +22,11 @@ module Uniword
22
22
  self
23
23
  end
24
24
 
25
+ def character_style(value)
26
+ ensure_properties.style = Properties::RunStyleReference.new(value: value)
27
+ self
28
+ end
29
+
25
30
  def bold(value = true)
26
31
  ensure_properties.bold = Properties::Bold.new(value: value)
27
32
  self
@@ -87,6 +87,7 @@ module Uniword
87
87
 
88
88
  # Non-serialized attributes (DOCX packaging helpers)
89
89
  attr_accessor :chart_parts, :bibliography_sources, :profile
90
+ attr_accessor :settings_rels, :embeddings
90
91
 
91
92
  # Load DOCX package from file
92
93
  #
@@ -194,6 +195,13 @@ module Uniword
194
195
  )
195
196
  end
196
197
 
198
+ if zip_content["word/_rels/settings.xml.rels"]
199
+ package.settings_rels =
200
+ Ooxml::Relationships::PackageRelationships.from_xml(
201
+ zip_content["word/_rels/settings.xml.rels"]
202
+ )
203
+ end
204
+
197
205
  if zip_content["word/fontTable.xml"]
198
206
  package.font_table = Uniword::Wordprocessingml::FontTable.from_xml(
199
207
  zip_content["word/fontTable.xml"]
@@ -272,6 +280,16 @@ module Uniword
272
280
  # Extract image parts from word/media/ directory
273
281
  extract_image_parts(zip_content, package, zip_path)
274
282
 
283
+ # Extract OLE/embedded object binaries from word/embeddings/
284
+ embedding_files = zip_content.keys.grep(%r{^word/embeddings/.+$})
285
+ if embedding_files.any?
286
+ package.embeddings = {}
287
+ embedding_files.each do |emb_path|
288
+ target = emb_path.sub("word/", "")
289
+ package.embeddings[target] = zip_content[emb_path]
290
+ end
291
+ end
292
+
275
293
  package
276
294
  end
277
295
 
@@ -26,6 +26,7 @@ document_rels)
26
26
  inject_notes(content_types, document_rels)
27
27
  inject_theme(content_types, document_rels)
28
28
  inject_numbering(content_types, document_rels)
29
+ inject_embeddings(content_types, document_rels)
29
30
  end
30
31
 
31
32
  # Serialize all package parts to XML and add to content hash
@@ -88,6 +89,10 @@ document_rels)
88
89
  content["word/settings.xml"] =
89
90
  serialize_part(settings)
90
91
  end
92
+ if settings_rels
93
+ content["word/_rels/settings.xml.rels"] =
94
+ serialize_infrastructure(settings_rels)
95
+ end
91
96
  if font_table
92
97
  content["word/fontTable.xml"] =
93
98
  serialize_part(font_table)
@@ -131,6 +136,9 @@ document_rels)
131
136
  serialize_headers(content)
132
137
  serialize_footers(content)
133
138
  serialize_header_footer_parts(content)
139
+
140
+ # OLE/embedded object binaries
141
+ serialize_embeddings(content)
134
142
  end
135
143
 
136
144
  # Serialize an OOXML document part with standard encoding
@@ -409,6 +417,28 @@ document_rels)
409
417
  )
410
418
  end
411
419
 
420
+ def inject_embeddings(content_types, document_rels)
421
+ return unless embeddings && !embeddings.empty?
422
+
423
+ embeddings.each_with_index do |(target, _data), idx|
424
+ part_name = "/word/#{target}"
425
+ next if content_types.overrides.any? { |o| o.part_name == part_name }
426
+
427
+ content_types.overrides << Uniword::ContentTypes::Override.new(
428
+ part_name: part_name,
429
+ content_type: "application/vnd.openxmlformats-officedocument.oleObject",
430
+ )
431
+
432
+ next if document_rels.relationships.any? { |r| r.target == target }
433
+
434
+ document_rels.relationships << Ooxml::Relationships::Relationship.new(
435
+ id: "rIdEmbedding#{idx + 1}",
436
+ type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject",
437
+ target: target,
438
+ )
439
+ end
440
+ end
441
+
412
442
  def wire_header_reference(type, r_id)
413
443
  return unless document&.body
414
444
 
@@ -467,6 +497,14 @@ document_rels)
467
497
  serialize_part(part[:content])
468
498
  end
469
499
  end
500
+
501
+ def serialize_embeddings(content)
502
+ return unless embeddings && !embeddings.empty?
503
+
504
+ embeddings.each do |target, data|
505
+ content["word/#{target}"] = data
506
+ end
507
+ end
470
508
  end
471
509
  end
472
510
  end
@@ -28,6 +28,7 @@ module Uniword
28
28
  reconcile_section_properties
29
29
  reconcile_footnotes
30
30
  reconcile_endnotes
31
+ reconcile_note_references
31
32
  repair_theme
32
33
 
33
34
  # Group 2: Support parts (profile-dependent)
@@ -85,13 +86,11 @@ module Uniword
85
86
  ].compact
86
87
 
87
88
  parts.each do |part|
88
- part.pending_namespace_data = nil
89
89
  part.import_declaration_plan = nil
90
90
  part.pending_plan_root_element = nil
91
- # TODO: @xml_input_namespaces has no public accessor in lutaml-model.
92
- # Replace with public API once lutaml-model exposes one.
93
- part.instance_variable_set(:@xml_input_namespaces, nil)
94
91
  end
92
+
93
+ parts.each(&:clear_xml_parse_state!)
95
94
  end
96
95
 
97
96
  def reconcile_section_properties
@@ -174,6 +173,114 @@ module Uniword
174
173
  ensure_separators(package.endnotes, :endnote) if package.endnotes
175
174
  end
176
175
 
176
+ # -- Note reference consistency --
177
+
178
+ def reconcile_note_references
179
+ reconcile_footnote_references
180
+ reconcile_endnote_references
181
+ reconcile_note_definition_integrity
182
+ end
183
+
184
+ def reconcile_footnote_references
185
+ return unless package.document&.body
186
+
187
+ referenced = collect_note_ids(:footnote)
188
+ return if referenced.empty?
189
+
190
+ ensure_footnotes_part
191
+ defined = package.footnotes.footnote_entries.each_with_object(Set.new) do |fn, s|
192
+ s << fn.id
193
+ end
194
+
195
+ missing = referenced.to_set - defined
196
+ return if missing.empty?
197
+
198
+ missing.each do |id|
199
+ package.footnotes.footnote_entries << Wordprocessingml::Footnote.new(
200
+ id: id, paragraphs: [Wordprocessingml::Paragraph.new],
201
+ )
202
+ end
203
+ record_fix("R10",
204
+ "Added #{missing.size} missing footnote definition(s) " \
205
+ "for orphaned footnoteReference IDs: #{missing.sort.join(', ')}")
206
+ end
207
+
208
+ def reconcile_endnote_references
209
+ return unless package.document&.body
210
+
211
+ referenced = collect_note_ids(:endnote)
212
+ return if referenced.empty?
213
+
214
+ ensure_endnotes_part
215
+ defined = package.endnotes.endnote_entries.each_with_object(Set.new) do |en, s|
216
+ s << en.id
217
+ end
218
+
219
+ missing = referenced.to_set - defined
220
+ return if missing.empty?
221
+
222
+ missing.each do |id|
223
+ package.endnotes.endnote_entries << Wordprocessingml::Endnote.new(
224
+ id: id, paragraphs: [Wordprocessingml::Paragraph.new],
225
+ )
226
+ end
227
+ record_fix("R10",
228
+ "Added #{missing.size} missing endnote definition(s) " \
229
+ "for orphaned endnoteReference IDs: #{missing.sort.join(', ')}")
230
+ end
231
+
232
+ # Validates structural integrity of footnote/endnote definitions.
233
+ def reconcile_note_definition_integrity
234
+ strip_invalid_note_types(:footnote)
235
+ strip_invalid_note_types(:endnote)
236
+ deduplicate_note_ids(:footnote)
237
+ deduplicate_note_ids(:endnote)
238
+ end
239
+
240
+ # R15: Regular footnotes/endnotes must NOT have w:type.
241
+ # Only separator (id=-1) and continuationSeparator (id=0) use w:type.
242
+ def strip_invalid_note_types(type)
243
+ notes = package.public_send(:"#{type}s")
244
+ return unless notes
245
+
246
+ entries = notes.public_send(:"#{type}_entries")
247
+ invalid = entries.select do |e|
248
+ e.type && e.id != "-1" && e.id != "0"
249
+ end
250
+ return if invalid.empty?
251
+
252
+ invalid.each { |e| e.type = nil }
253
+ record_fix("R15",
254
+ "Removed invalid w:type from #{invalid.size} " \
255
+ "regular #{type} definition(s): " \
256
+ "IDs #{invalid.map(&:id).sort.join(', ')}")
257
+ end
258
+
259
+ # R16: Duplicate IDs in footnotes.xml/endnotes.xml are invalid.
260
+ # Keeps the first occurrence, removes subsequent duplicates.
261
+ def deduplicate_note_ids(type)
262
+ notes = package.public_send(:"#{type}s")
263
+ return unless notes
264
+
265
+ entries = notes.public_send(:"#{type}_entries")
266
+ seen = Set.new
267
+ dupes = []
268
+ entries.reject! do |e|
269
+ if seen.include?(e.id)
270
+ dupes << e
271
+ true
272
+ else
273
+ seen << e.id
274
+ false
275
+ end
276
+ end
277
+ return if dupes.empty?
278
+
279
+ record_fix("R16",
280
+ "Removed #{dupes.size} duplicate #{type} ID(s): " \
281
+ "#{dupes.map(&:id).sort.join(', ')}")
282
+ end
283
+
177
284
  # -- Builders --
178
285
 
179
286
  def minimal_footnotes
@@ -216,6 +323,53 @@ module Uniword
216
323
  entries.unshift(continuation_entry(type)) unless ids.include?("0")
217
324
  end
218
325
 
326
+ def collect_note_ids(type)
327
+ ref_attr = type == :footnote ? :footnote_reference : :endnote_reference
328
+ ids = []
329
+ collect_note_ids_from_element(package.document.body, ref_attr, ids)
330
+ ids
331
+ end
332
+
333
+ def collect_note_ids_from_element(element, ref_attr, ids)
334
+ return unless element
335
+
336
+ collect_note_ids_from_paragraphs(element.paragraphs, ref_attr, ids)
337
+
338
+ tables = element.tables
339
+ return unless tables
340
+ tables.each do |tbl|
341
+ tbl.rows.each do |row|
342
+ row.cells.each do |cell|
343
+ collect_note_ids_from_paragraphs(cell.paragraphs, ref_attr, ids)
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ def collect_note_ids_from_paragraphs(paragraphs, ref_attr, ids)
350
+ return unless paragraphs
351
+ paragraphs.each do |p|
352
+ (p.runs || []).each do |r|
353
+ ref = r.public_send(ref_attr)
354
+ ids << ref.id if ref&.id
355
+ end
356
+ end
357
+ end
358
+
359
+ def ensure_footnotes_part
360
+ return if package.footnotes
361
+
362
+ package.footnotes = minimal_footnotes
363
+ record_fix("R9", "Created footnotes.xml for orphaned references")
364
+ end
365
+
366
+ def ensure_endnotes_part
367
+ return if package.endnotes
368
+
369
+ package.endnotes = minimal_endnotes
370
+ record_fix("R9", "Created endnotes.xml for orphaned references")
371
+ end
372
+
219
373
  # -- Group 2: Support parts (profile-dependent) --
220
374
 
221
375
  def reconcile_theme
@@ -105,7 +105,7 @@ module Uniword
105
105
 
106
106
  # If not loaded, try to get count without loading
107
107
  # This is a hook for subclasses to override
108
- send(name).size
108
+ public_send(name).size
109
109
  end
110
110
  end
111
111
  end
@@ -14,23 +14,16 @@ module Uniword
14
14
  end
15
15
 
16
16
  # Helper to define val= override after attribute declaration.
17
- # Uses alias_method to save the generated setter, then overrides it.
18
17
  module BooleanValSetter
19
18
  def self.included(base)
20
- base.class_eval do
21
- alias_method :__original_val_setter=, :val= if method_defined?(:val=)
22
- define_method(:val=) do |v|
23
- @val = if v.nil?
24
- nil
25
- elsif v == false || v.to_s == "false"
26
- "false"
27
- elsif v == true || v.to_s == "true"
28
- nil
29
- else
30
- v
31
- end
32
- value_set_for(:val)
33
- end
19
+ base.define_method(:val=) do |v|
20
+ @val = case v
21
+ when nil then nil
22
+ when false, "false" then "false"
23
+ when true, "true" then nil
24
+ else v
25
+ end
26
+ value_set_for(:val)
34
27
  end
35
28
  end
36
29
  end
@@ -85,6 +85,6 @@ module Uniword
85
85
  end
86
86
 
87
87
  # Raised when a schema file cannot be found
88
- class SchemaNotFoundError < StandardError; end
88
+ class SchemaNotFoundError < Uniword::Error; end
89
89
  end
90
90
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uniword
4
- VERSION = "1.0.9"
4
+ VERSION = "1.0.10"
5
5
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "../properties/relationship_id"
5
+
6
+ module Uniword
7
+ module Wordprocessingml
8
+ class AttachedTemplate < Lutaml::Model::Serializable
9
+ attribute :r_id, Properties::RelationshipIdValue
10
+
11
+ xml do
12
+ element "attachedTemplate"
13
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
14
+ map_attribute "id", to: :r_id
15
+ end
16
+ end
17
+ end
18
+ end
@@ -71,48 +71,30 @@ module Uniword
71
71
  def sync_element_order
72
72
  return if element_order.nil? || element_order.empty?
73
73
 
74
- # Count how many p/tbl/sdt entries exist in element_order
75
- ordered_p_count = element_order.count { |e| e.name == "p" }
76
- ordered_tbl_count = element_order.count { |e| e.name == "tbl" }
77
- ordered_sdt_count = element_order.count { |e| e.name == "sdt" }
78
- ordered_bookmark_start_count = element_order.count do |e|
79
- e.name == "bookmarkStart"
80
- end
81
- ordered_bookmark_end_count = element_order.count do |e|
82
- e.name == "bookmarkEnd"
83
- end
84
-
85
- # Add missing paragraphs
86
- (paragraphs.size - ordered_p_count).times do
87
- element_order << Lutaml::Xml::Element.new("Element", "p")
74
+ counts = element_order.each_with_object(Hash.new(0)) do |e, h|
75
+ h[e.name] += 1
88
76
  end
89
77
 
90
- # Add missing tables
91
- (tables.size - ordered_tbl_count).times do
92
- element_order << Lutaml::Xml::Element.new("Element", "tbl")
93
- end
78
+ append_missing_elements("p", paragraphs.size - counts["p"])
79
+ append_missing_elements("tbl", tables.size - counts["tbl"])
80
+ append_missing_elements("sdt",
81
+ structured_document_tags.size - counts["sdt"])
82
+ append_missing_elements("bookmarkStart",
83
+ bookmark_starts.size - counts["bookmarkStart"])
84
+ append_missing_elements("bookmarkEnd",
85
+ bookmark_ends.size - counts["bookmarkEnd"])
94
86
 
95
- # Add missing structured document tags
96
- (structured_document_tags.size - ordered_sdt_count).times do
97
- element_order << Lutaml::Xml::Element.new("Element", "sdt")
98
- end
87
+ return unless section_properties && counts["sectPr"].zero?
99
88
 
100
- # Add missing bookmark starts
101
- (bookmark_starts.size - ordered_bookmark_start_count).times do
102
- element_order << Lutaml::Xml::Element.new("Element", "bookmarkStart")
103
- end
104
-
105
- # Add missing bookmark ends
106
- (bookmark_ends.size - ordered_bookmark_end_count).times do
107
- element_order << Lutaml::Xml::Element.new("Element", "bookmarkEnd")
108
- end
89
+ element_order << build_order_element("sectPr")
90
+ end
109
91
 
110
- # Ensure section_properties is in element_order if present
111
- return unless section_properties && element_order.none? do |e|
112
- e.name == "sectPr"
113
- end
92
+ def append_missing_elements(name, count)
93
+ count.times { element_order << build_order_element(name) }
94
+ end
114
95
 
115
- element_order << Lutaml::Xml::Element.new("Element", "sectPr")
96
+ def build_order_element(name)
97
+ Lutaml::Xml::Element.new("Element", name)
116
98
  end
117
99
  end
118
100
  end