uniword 1.0.9 → 1.0.11

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 (68) 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/document_factory.rb +1 -0
  7. data/lib/uniword/docx/package.rb +18 -0
  8. data/lib/uniword/docx/package_defaults.rb +1 -0
  9. data/lib/uniword/docx/package_serialization.rb +38 -0
  10. data/lib/uniword/docx/reconciler.rb +195 -13
  11. data/lib/uniword/lazy_loader.rb +1 -1
  12. data/lib/uniword/properties/boolean_element_factory.rb +8 -15
  13. data/lib/uniword/schema/schema_loader.rb +1 -1
  14. data/lib/uniword/transformation/mhtml_style_builder.rb +9 -2
  15. data/lib/uniword/transformation/ooxml_to_mhtml_converter.rb +7 -15
  16. data/lib/uniword/transformation/yaml_css_generator.rb +815 -0
  17. data/lib/uniword/transformation.rb +2 -0
  18. data/lib/uniword/version.rb +1 -1
  19. data/lib/uniword/wordprocessingml/attached_template.rb +18 -0
  20. data/lib/uniword/wordprocessingml/body.rb +34 -34
  21. data/lib/uniword/wordprocessingml/brk_bin.rb +17 -0
  22. data/lib/uniword/wordprocessingml/brk_bin_sub.rb +17 -0
  23. data/lib/uniword/wordprocessingml/character_spacing_control.rb +17 -0
  24. data/lib/uniword/wordprocessingml/clr_scheme_mapping.rb +39 -0
  25. data/lib/uniword/wordprocessingml/compat.rb +16 -0
  26. data/lib/uniword/wordprocessingml/decimal_symbol.rb +17 -0
  27. data/lib/uniword/wordprocessingml/def_jc.rb +17 -0
  28. data/lib/uniword/wordprocessingml/default_tab_stop.rb +17 -0
  29. data/lib/uniword/wordprocessingml/disp_def.rb +14 -0
  30. data/lib/uniword/wordprocessingml/do_not_display_page_boundaries.rb +14 -0
  31. data/lib/uniword/wordprocessingml/do_not_include_subdocs_in_stats.rb +17 -0
  32. data/lib/uniword/wordprocessingml/doc_vars.rb +36 -0
  33. data/lib/uniword/wordprocessingml/document_root.rb +8 -5
  34. data/lib/uniword/wordprocessingml/endnote_pr.rb +18 -0
  35. data/lib/uniword/wordprocessingml/endnotes.rb +18 -0
  36. data/lib/uniword/wordprocessingml/even_and_odd_headers.rb +17 -0
  37. data/lib/uniword/wordprocessingml/footnote_pos.rb +17 -0
  38. data/lib/uniword/wordprocessingml/footnote_pr.rb +20 -0
  39. data/lib/uniword/wordprocessingml/footnotes.rb +18 -0
  40. data/lib/uniword/wordprocessingml/hdr_shape_defaults.rb +19 -0
  41. data/lib/uniword/wordprocessingml/hyphenation_zone.rb +20 -0
  42. data/lib/uniword/wordprocessingml/int_lim.rb +17 -0
  43. data/lib/uniword/wordprocessingml/l_margin.rb +17 -0
  44. data/lib/uniword/wordprocessingml/list_separator.rb +17 -0
  45. data/lib/uniword/wordprocessingml/math_font.rb +17 -0
  46. data/lib/uniword/wordprocessingml/math_pr.rb +49 -0
  47. data/lib/uniword/wordprocessingml/mirror_margins.rb +17 -0
  48. data/lib/uniword/wordprocessingml/nary_lim.rb +17 -0
  49. data/lib/uniword/wordprocessingml/proof_state.rb +19 -0
  50. data/lib/uniword/wordprocessingml/r_margin.rb +17 -0
  51. data/lib/uniword/wordprocessingml/rsid.rb +17 -0
  52. data/lib/uniword/wordprocessingml/rsid_root.rb +17 -0
  53. data/lib/uniword/wordprocessingml/rsids.rb +22 -0
  54. data/lib/uniword/wordprocessingml/settings.rb +45 -511
  55. data/lib/uniword/wordprocessingml/shape_defaults.rb +21 -0
  56. data/lib/uniword/wordprocessingml/small_frac.rb +17 -0
  57. data/lib/uniword/wordprocessingml/style_pane_format_filter.rb +50 -0
  58. data/lib/uniword/wordprocessingml/style_pane_sort_method.rb +20 -0
  59. data/lib/uniword/wordprocessingml/table_cell.rb +6 -0
  60. data/lib/uniword/wordprocessingml/table_row_properties.rb +2 -0
  61. data/lib/uniword/wordprocessingml/theme_font_lang.rb +19 -0
  62. data/lib/uniword/wordprocessingml/w14_doc_id.rb +17 -0
  63. data/lib/uniword/wordprocessingml/w15_chart_tracking_ref_based.rb +14 -0
  64. data/lib/uniword/wordprocessingml/w15_doc_id.rb +17 -0
  65. data/lib/uniword/wordprocessingml/wrap_indent.rb +17 -0
  66. data/lib/uniword/wordprocessingml.rb +42 -56
  67. data/lib/uniword.rb +12 -5
  68. metadata +50 -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: bdd8529445dab8a8008addcef94857f4dc453cea19ece485baaac0ef1c4701f2
4
+ data.tar.gz: cbba0e318b8fde04e96cf0717f82e1a4a827ac4c73e47b8c4af853ba4dbd77be
5
5
  SHA512:
6
- metadata.gz: 1c6c8785a7fe56480f0d7bdda19ec56e50704653016d4146c561e401c8e98b856cf99a1205411521b78cd19b701e794dfbc75c4d4c844b87e2b8fe3d44fb514e
7
- data.tar.gz: 9275ea155ee7af2ee9cd0e24d6534a0124f0f8501583206fb98eb3c63e7736c280552336a47bad97a31e50b235805c5fba904d1a80c0cee9bbe6f216f7e49f02
6
+ metadata.gz: 92dc72fbc93219b74f58bb901aca4d1bda56786c761a04ceb724ffad3417406a13e92f962808613d495f8f68a0955278a9f9a2fcfef29848f6d803f88beab907
7
+ data.tar.gz: 59f335ab79d4cb3b72c80c9b07ffecaf3705285017ab494490c1d6f331fa310205a50ccc2b1f3bb2397c313cb392ee33fa99aec477edaac4cba0b11b4fed3f0f
@@ -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
@@ -188,6 +188,7 @@ module Uniword
188
188
  custom_xml_items: :custom_xml_items,
189
189
  footnotes: :footnotes,
190
190
  endnotes: :endnotes,
191
+ embeddings: :embeddings,
191
192
  }.freeze
192
193
 
193
194
  # Copy package parts to document for round-trip preservation
@@ -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
 
@@ -50,6 +50,7 @@ module Uniword
50
50
  package.chart_parts = document.chart_parts if document.chart_parts
51
51
  package.custom_xml_items = document.custom_xml_items if document.custom_xml_items
52
52
  package.bibliography_sources = document.bibliography_sources if document.bibliography_sources
53
+ package.embeddings = document.embeddings if document.embeddings
53
54
  end
54
55
 
55
56
  # Create minimal content types for a valid DOCX
@@ -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
@@ -17,6 +17,16 @@ module Uniword
17
17
  class Reconciler
18
18
  CONFIG_DIR = File.join(__dir__, "../../../config")
19
19
 
20
+ # All w1x/w16x extension namespace prefixes declared by namespace_scope
21
+ # on wordprocessingml parts. These MUST be listed in mc:Ignorable so
22
+ # Word doesn't reject them as unknown extensions.
23
+ IGNORABLE_PREFIXES = %w[
24
+ w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du
25
+ ].freeze
26
+
27
+ # Extension prefixes used in document.xml (adds wp14 for drawing)
28
+ IGNORABLE_PREFIXES_DOCUMENT = (IGNORABLE_PREFIXES + %w[wp14]).freeze
29
+
20
30
  def initialize(package, profile: nil)
21
31
  @package = package
22
32
  @profile = profile
@@ -28,6 +38,7 @@ module Uniword
28
38
  reconcile_section_properties
29
39
  reconcile_footnotes
30
40
  reconcile_endnotes
41
+ reconcile_note_references
31
42
  repair_theme
32
43
 
33
44
  # Group 2: Support parts (profile-dependent)
@@ -85,13 +96,11 @@ module Uniword
85
96
  ].compact
86
97
 
87
98
  parts.each do |part|
88
- part.pending_namespace_data = nil
89
99
  part.import_declaration_plan = nil
90
100
  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
101
  end
102
+
103
+ parts.each(&:clear_xml_parse_state!)
95
104
  end
96
105
 
97
106
  def reconcile_section_properties
@@ -174,6 +183,114 @@ module Uniword
174
183
  ensure_separators(package.endnotes, :endnote) if package.endnotes
175
184
  end
176
185
 
186
+ # -- Note reference consistency --
187
+
188
+ def reconcile_note_references
189
+ reconcile_footnote_references
190
+ reconcile_endnote_references
191
+ reconcile_note_definition_integrity
192
+ end
193
+
194
+ def reconcile_footnote_references
195
+ return unless package.document&.body
196
+
197
+ referenced = collect_note_ids(:footnote)
198
+ return if referenced.empty?
199
+
200
+ ensure_footnotes_part
201
+ defined = package.footnotes.footnote_entries.each_with_object(Set.new) do |fn, s|
202
+ s << fn.id
203
+ end
204
+
205
+ missing = referenced.to_set - defined
206
+ return if missing.empty?
207
+
208
+ missing.each do |id|
209
+ package.footnotes.footnote_entries << Wordprocessingml::Footnote.new(
210
+ id: id, paragraphs: [Wordprocessingml::Paragraph.new],
211
+ )
212
+ end
213
+ record_fix("R10",
214
+ "Added #{missing.size} missing footnote definition(s) " \
215
+ "for orphaned footnoteReference IDs: #{missing.sort.join(', ')}")
216
+ end
217
+
218
+ def reconcile_endnote_references
219
+ return unless package.document&.body
220
+
221
+ referenced = collect_note_ids(:endnote)
222
+ return if referenced.empty?
223
+
224
+ ensure_endnotes_part
225
+ defined = package.endnotes.endnote_entries.each_with_object(Set.new) do |en, s|
226
+ s << en.id
227
+ end
228
+
229
+ missing = referenced.to_set - defined
230
+ return if missing.empty?
231
+
232
+ missing.each do |id|
233
+ package.endnotes.endnote_entries << Wordprocessingml::Endnote.new(
234
+ id: id, paragraphs: [Wordprocessingml::Paragraph.new],
235
+ )
236
+ end
237
+ record_fix("R10",
238
+ "Added #{missing.size} missing endnote definition(s) " \
239
+ "for orphaned endnoteReference IDs: #{missing.sort.join(', ')}")
240
+ end
241
+
242
+ # Validates structural integrity of footnote/endnote definitions.
243
+ def reconcile_note_definition_integrity
244
+ strip_invalid_note_types(:footnote)
245
+ strip_invalid_note_types(:endnote)
246
+ deduplicate_note_ids(:footnote)
247
+ deduplicate_note_ids(:endnote)
248
+ end
249
+
250
+ # R15: Regular footnotes/endnotes must NOT have w:type.
251
+ # Only separator (id=-1) and continuationSeparator (id=0) use w:type.
252
+ def strip_invalid_note_types(type)
253
+ notes = package.public_send(:"#{type}s")
254
+ return unless notes
255
+
256
+ entries = notes.public_send(:"#{type}_entries")
257
+ invalid = entries.select do |e|
258
+ e.type && e.id != "-1" && e.id != "0"
259
+ end
260
+ return if invalid.empty?
261
+
262
+ invalid.each { |e| e.type = nil }
263
+ record_fix("R15",
264
+ "Removed invalid w:type from #{invalid.size} " \
265
+ "regular #{type} definition(s): " \
266
+ "IDs #{invalid.map(&:id).sort.join(', ')}")
267
+ end
268
+
269
+ # R16: Duplicate IDs in footnotes.xml/endnotes.xml are invalid.
270
+ # Keeps the first occurrence, removes subsequent duplicates.
271
+ def deduplicate_note_ids(type)
272
+ notes = package.public_send(:"#{type}s")
273
+ return unless notes
274
+
275
+ entries = notes.public_send(:"#{type}_entries")
276
+ seen = Set.new
277
+ dupes = []
278
+ entries.reject! do |e|
279
+ if seen.include?(e.id)
280
+ dupes << e
281
+ true
282
+ else
283
+ seen << e.id
284
+ false
285
+ end
286
+ end
287
+ return if dupes.empty?
288
+
289
+ record_fix("R16",
290
+ "Removed #{dupes.size} duplicate #{type} ID(s): " \
291
+ "#{dupes.map(&:id).sort.join(', ')}")
292
+ end
293
+
177
294
  # -- Builders --
178
295
 
179
296
  def minimal_footnotes
@@ -216,6 +333,53 @@ module Uniword
216
333
  entries.unshift(continuation_entry(type)) unless ids.include?("0")
217
334
  end
218
335
 
336
+ def collect_note_ids(type)
337
+ ref_attr = type == :footnote ? :footnote_reference : :endnote_reference
338
+ ids = []
339
+ collect_note_ids_from_element(package.document.body, ref_attr, ids)
340
+ ids
341
+ end
342
+
343
+ def collect_note_ids_from_element(element, ref_attr, ids)
344
+ return unless element
345
+
346
+ collect_note_ids_from_paragraphs(element.paragraphs, ref_attr, ids)
347
+
348
+ tables = element.tables
349
+ return unless tables
350
+ tables.each do |tbl|
351
+ tbl.rows.each do |row|
352
+ row.cells.each do |cell|
353
+ collect_note_ids_from_paragraphs(cell.paragraphs, ref_attr, ids)
354
+ end
355
+ end
356
+ end
357
+ end
358
+
359
+ def collect_note_ids_from_paragraphs(paragraphs, ref_attr, ids)
360
+ return unless paragraphs
361
+ paragraphs.each do |p|
362
+ (p.runs || []).each do |r|
363
+ ref = r.public_send(ref_attr)
364
+ ids << ref.id if ref&.id
365
+ end
366
+ end
367
+ end
368
+
369
+ def ensure_footnotes_part
370
+ return if package.footnotes
371
+
372
+ package.footnotes = minimal_footnotes
373
+ record_fix("R9", "Created footnotes.xml for orphaned references")
374
+ end
375
+
376
+ def ensure_endnotes_part
377
+ return if package.endnotes
378
+
379
+ package.endnotes = minimal_endnotes
380
+ record_fix("R9", "Created endnotes.xml for orphaned references")
381
+ end
382
+
219
383
  # -- Group 2: Support parts (profile-dependent) --
220
384
 
221
385
  def reconcile_theme
@@ -353,11 +517,16 @@ module Uniword
353
517
 
354
518
  rsid = generate_rsid
355
519
 
520
+ # Only inject settings defaults for newly created documents.
521
+ # If settings already has substantial content (zoom + compat + rsids),
522
+ # the document was loaded from a real DOCX and defaults would alter
523
+ # its round-trip fidelity.
524
+ loaded_doc = settings.zoom && settings.compat && settings.rsids
525
+
356
526
  settings.zoom ||= Wordprocessingml::Zoom.new(percent: 100)
357
- settings.do_not_display_page_boundaries ||= Wordprocessingml::DoNotDisplayPageBoundaries.new
358
527
  settings.proof_state ||= Wordprocessingml::ProofState.new(
359
528
  spelling: "clean", grammar: "clean",
360
- )
529
+ ) unless loaded_doc
361
530
  settings.default_tab_stop ||= Wordprocessingml::DefaultTabStop.new(val: "720")
362
531
  settings.character_spacing_control ||= Wordprocessingml::CharacterSpacingControl.new(
363
532
  val: "doNotCompress",
@@ -391,7 +560,9 @@ module Uniword
391
560
  record_fix("R2", "Generated w15:docId in GUID format")
392
561
  end
393
562
 
394
- settings.mc_ignorable = nil
563
+ settings.mc_ignorable = Ooxml::Types::McIgnorable.new(
564
+ IGNORABLE_PREFIXES.join(" "),
565
+ )
395
566
  end
396
567
 
397
568
  def reconcile_font_table
@@ -432,7 +603,9 @@ module Uniword
432
603
  font_table.fonts << font
433
604
  end
434
605
 
435
- font_table.mc_ignorable = nil
606
+ font_table.mc_ignorable = Ooxml::Types::McIgnorable.new(
607
+ IGNORABLE_PREFIXES.join(" "),
608
+ )
436
609
  record_fix("R13",
437
610
  "Populated font table with profile fonts and signatures")
438
611
  end
@@ -451,7 +624,9 @@ module Uniword
451
624
 
452
625
  ensure_default_styles(styles)
453
626
 
454
- styles.mc_ignorable = nil
627
+ styles.mc_ignorable = Ooxml::Types::McIgnorable.new(
628
+ IGNORABLE_PREFIXES.join(" "),
629
+ )
455
630
  record_fix("R10",
456
631
  "Ensured styles have docDefaults, latentStyles, and default styles")
457
632
  end
@@ -460,7 +635,9 @@ module Uniword
460
635
  return unless profile
461
636
  return unless package.numbering
462
637
 
463
- package.numbering.mc_ignorable = nil
638
+ package.numbering.mc_ignorable = Ooxml::Types::McIgnorable.new(
639
+ IGNORABLE_PREFIXES.join(" "),
640
+ )
464
641
 
465
642
  # Validate instance → definition references
466
643
  package.numbering.instances.each do |inst|
@@ -486,8 +663,11 @@ module Uniword
486
663
  package.web_settings
487
664
  end
488
665
 
489
- ws.mc_ignorable = nil
490
- record_fix("R1", "Cleared mc:Ignorable on webSettings")
666
+ ws.mc_ignorable = Ooxml::Types::McIgnorable.new(
667
+ IGNORABLE_PREFIXES.join(" "),
668
+ )
669
+ ws.allow_png ||= Wordprocessingml::AllowPng.new
670
+ record_fix("R1", "Set mc:Ignorable and allowPNG on webSettings")
491
671
  end
492
672
 
493
673
  def reconcile_app_properties
@@ -581,7 +761,9 @@ module Uniword
581
761
  return unless package.document&.body
582
762
 
583
763
  doc = package.document
584
- doc.mc_ignorable = Ooxml::Types::McIgnorable.new("w14")
764
+ doc.mc_ignorable = Ooxml::Types::McIgnorable.new(
765
+ IGNORABLE_PREFIXES_DOCUMENT.join(" "),
766
+ )
585
767
 
586
768
  record_fix("R1", "Added mc:Ignorable to document body")
587
769
  record_fix("R12", "Assigned rsid and paraId to paragraphs")
@@ -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