goldendocx 0.2.2 → 0.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +8 -2
  5. data/Gemfile.lock +3 -1
  6. data/README.md +10 -2
  7. data/Rakefile +14 -3
  8. data/demo/tables/create_embed_image_table.rb +3 -3
  9. data/demo/templates/generate_codes.rb +10 -0
  10. data/demo/templates/xml_to_class.rb +97 -0
  11. data/demo/texts/append_plain_text.rb +1 -1
  12. data/demo/texts/append_styled_text.rb +1 -2
  13. data/demo/texts/create_text.rb +14 -0
  14. data/lib/extensions/active_support_extensions.rb +16 -0
  15. data/lib/extensions/nokogiri_extensions.rb +41 -0
  16. data/lib/extensions/ox_extensions.rb +35 -0
  17. data/lib/extensions/xml_serialize_extensions.rb +45 -0
  18. data/lib/goldendocx/charts/properties.rb +1 -1
  19. data/lib/goldendocx/charts/series.rb +2 -2
  20. data/lib/goldendocx/charts.rb +1 -1
  21. data/lib/goldendocx/components/bar_chart.rb +2 -2
  22. data/lib/goldendocx/components/chart.rb +6 -6
  23. data/lib/goldendocx/components/column_chart.rb +2 -2
  24. data/lib/goldendocx/components/doughnut_chart.rb +2 -2
  25. data/lib/goldendocx/components/line_chart.rb +2 -2
  26. data/lib/goldendocx/components/properties/font_property.rb +19 -0
  27. data/lib/goldendocx/components/properties/language_property.rb +18 -0
  28. data/lib/goldendocx/components/properties/run_property.rb +2 -0
  29. data/lib/goldendocx/components/properties.rb +1 -1
  30. data/lib/goldendocx/components/table.rb +2 -2
  31. data/lib/goldendocx/components.rb +1 -1
  32. data/lib/goldendocx/content_types/default.rb +2 -6
  33. data/lib/goldendocx/content_types/override.rb +2 -6
  34. data/lib/goldendocx/document.rb +9 -28
  35. data/lib/goldendocx/documents/body.rb +23 -14
  36. data/lib/goldendocx/documents/document.rb +2 -4
  37. data/lib/goldendocx/documents/latent_styles.rb +12 -0
  38. data/lib/goldendocx/documents/properties/default_style_property.rb +17 -0
  39. data/lib/goldendocx/documents/properties/page_margin_property.rb +37 -0
  40. data/lib/goldendocx/documents/properties/page_size_property.rb +17 -0
  41. data/lib/goldendocx/documents/properties/paragraph_default_style_property.rb +16 -0
  42. data/lib/goldendocx/documents/properties/run_default_style_property.rb +22 -0
  43. data/lib/goldendocx/documents/properties/section_property.rb +17 -0
  44. data/lib/goldendocx/documents/properties/style_name_property.rb +16 -0
  45. data/lib/goldendocx/documents/properties.rb +12 -0
  46. data/lib/goldendocx/documents/settings.rb +23 -0
  47. data/lib/goldendocx/documents/style.rb +10 -10
  48. data/lib/goldendocx/documents/styles.rb +9 -33
  49. data/lib/goldendocx/documents.rb +1 -1
  50. data/lib/goldendocx/docx.rb +55 -12
  51. data/lib/goldendocx/element.rb +33 -140
  52. data/lib/goldendocx/has_associations.rb +54 -0
  53. data/lib/goldendocx/has_attributes.rb +67 -0
  54. data/lib/goldendocx/has_children.rb +116 -0
  55. data/lib/goldendocx/images/picture.rb +1 -1
  56. data/lib/goldendocx/images/properties.rb +1 -1
  57. data/lib/goldendocx/images.rb +1 -1
  58. data/lib/goldendocx/{documents → models}/relationship.rb +1 -1
  59. data/lib/goldendocx/models/relationships.rb +31 -0
  60. data/lib/goldendocx/models.rb +10 -0
  61. data/lib/goldendocx/parts/app.rb +46 -0
  62. data/lib/goldendocx/parts/content_types.rb +16 -30
  63. data/lib/goldendocx/parts/core.rb +46 -0
  64. data/lib/goldendocx/parts/documents.rb +27 -14
  65. data/lib/goldendocx/parts/media.rb +1 -1
  66. data/lib/goldendocx/parts/properties/created_at_property.rb +17 -0
  67. data/lib/goldendocx/parts/properties/creator_property.rb +16 -0
  68. data/lib/goldendocx/parts/properties/revision_property.rb +16 -0
  69. data/lib/goldendocx/parts/properties/updated_at_property.rb +17 -0
  70. data/lib/goldendocx/parts/properties/updater_property.rb +16 -0
  71. data/lib/goldendocx/parts/properties.rb +12 -0
  72. data/lib/goldendocx/parts.rb +1 -1
  73. data/lib/goldendocx/tables/header_cell.rb +1 -1
  74. data/lib/goldendocx/tables/properties.rb +1 -1
  75. data/lib/goldendocx/tables/row.rb +1 -1
  76. data/lib/goldendocx/tables.rb +1 -1
  77. data/lib/goldendocx/version.rb +1 -1
  78. data/lib/goldendocx/xml_serializers/nokogiri.rb +32 -24
  79. data/lib/goldendocx/xml_serializers/ox.rb +15 -25
  80. data/lib/goldendocx.rb +14 -2
  81. metadata +54 -9
  82. data/lib/goldendocx/documents/element.rb +0 -23
  83. data/lib/goldendocx/documents/relationships.rb +0 -39
  84. data/lib/goldendocx/documents/unparsed_style.rb +0 -17
@@ -1,19 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/core_ext/string/inflections'
4
-
5
- ActiveSupport::Inflector.inflections do |inflect|
6
- inflect.uncountable 'extents', 'image_data', 'data'
7
- inflect.irregular 'axis', 'axes'
8
-
9
- inflect.uncountable 'values' # TODO: Find better names
10
- end
3
+ require 'goldendocx/has_attributes'
4
+ require 'goldendocx/has_children'
11
5
 
12
6
  module Goldendocx
13
7
  module Element
14
- def self.included(base)
15
- base.extend(ClassMethods)
16
- end
8
+ extend ActiveSupport::Concern
9
+ include Goldendocx::HasAttributes
10
+ include Goldendocx::HasChildren
17
11
 
18
12
  module ClassMethods
19
13
  def tag(*args)
@@ -26,124 +20,36 @@ module Goldendocx
26
20
  @namespace
27
21
  end
28
22
 
29
- # alias_name: nil
30
- # readonly: false
31
- # default: nil
32
- # namespace: nil
33
- # setter: nil
34
- def attribute(name, **options)
35
- named = name.to_s
36
- attributes[named] = {
37
- alias_name: options[:alias_name],
38
- default: options[:default],
39
- namespace: options[:namespace],
40
- method: options[:method]
41
- }.compact
42
-
43
- readonly = options[:readonly] || false
44
- if readonly
45
- attr_reader named
46
- elsif options[:method]
47
- attr_writer named
48
- else
49
- attr_accessor named
50
- end
51
- end
52
-
53
- def attributes
54
- @attributes ||= {}
55
- end
56
-
57
- def create_children_getter(name)
58
- options = children[name]
59
- class_name = options[:class_name]
60
- multiple = options[:multiple]
61
- auto_build = options[:auto_build]
62
-
63
- define_method name do
64
- return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
65
-
66
- default_value = if multiple
67
- []
68
- else
69
- auto_build ? Kernel.const_get(class_name).new : nil
70
- end
71
- instance_variable_set("@#{name}", default_value)
72
- end
23
+ def tag_name
24
+ @tag_name ||= [namespace, tag].compact.join(':')
73
25
  end
74
26
 
75
- def create_children_setter(name)
76
- options = children[name]
77
- class_name = options[:class_name]
78
-
79
- define_method "#{name}=" do |value|
80
- value = value.to_s if value && class_name == 'String'
81
- instance_variable_set("@#{name}", value)
82
- end
27
+ def parse(xml_string)
28
+ root_node = Goldendocx.xml_serializer.parse(xml_string).root
29
+ read_from(root_node)
83
30
  end
84
31
 
85
- def create_children_builder(name)
86
- options = children[name]
87
- class_name = options[:class_name]
88
- multiple = options[:multiple]
89
-
90
- define_method "build_#{name.to_s.singularize}" do |**attributes|
91
- child = Kernel.const_get(class_name).new
92
- attributes.each { |key, value| child.send("#{key}=", value) if child.respond_to?("#{key}=") }
93
- multiple ? send(name) << child : instance_variable_set("@#{name}", child)
94
- child
95
- end
32
+ def adapt?(xml_node)
33
+ tag_name == xml_node.tag_name
96
34
  end
97
35
 
98
- def embeds_one(name, class_name:, auto_build: false)
99
- warning_naming_suggestion(name, name.to_s.singularize)
100
-
101
- children[name] = { class_name: class_name, multiple: false, auto_build: auto_build }
102
- create_children_getter(name)
103
- create_children_setter(name)
104
- create_children_builder(name)
105
- end
106
-
107
- def embeds_many(name, class_name:)
108
- warning_naming_suggestion(name, name.to_s.pluralize)
109
-
110
- children[name] = { class_name: class_name, multiple: true, auto_build: false }
111
- create_children_getter(name)
112
- create_children_builder(name)
113
- end
36
+ def read_from(xml_node)
37
+ return unless adapt?(xml_node)
114
38
 
115
- def children
116
- @children ||= {}
39
+ instance = new
40
+ instance.read_attributes(xml_node)
41
+ instance.read_children(xml_node)
42
+ instance
117
43
  end
118
44
 
119
45
  def concerning_ancestors
120
46
  ancestors.filter { |ancestor| ancestor.include?(Goldendocx::Element) }
121
47
  end
122
-
123
- private
124
-
125
- # :nocov:
126
- def warning_naming_suggestion(name, suggestion_name)
127
- return if suggestion_name == name.to_s
128
-
129
- location = caller.find { |c| c.include?('goldendocx/') && !c.include?('goldendocx/element.rb') }
130
- warn "warning: [embeds_one] `#{name}` better be singular `#{suggestion_name}` at #{location}"
131
- end
132
- # :nocov:
133
- end
134
-
135
- def attributes
136
- self.class.attributes.each_with_object({}) do |(name, options), result|
137
- value = public_send(options[:method] || name) || options[:default]
138
- next if value.nil?
139
-
140
- key = [options[:namespace], options[:alias_name] || name].compact.join(':')
141
- result[key] = value
142
- end
143
48
  end
144
49
 
145
- def assign_attributes(**attributes)
146
- attributes.each { |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
50
+ def initialize(attributes = nil)
51
+ attributes ||= {}
52
+ assign_attributes(**attributes)
147
53
  end
148
54
 
149
55
  def tag
@@ -154,39 +60,26 @@ module Goldendocx
154
60
  self.class.concerning_ancestors.find { |ancestor| ancestor.namespace.present? }&.namespace
155
61
  end
156
62
 
157
- def root_tag
158
- @root_tag ||= [namespace, tag].compact.join(':')
63
+ def tag_name
64
+ @tag_name ||= [namespace, tag].compact.join(':')
159
65
  end
160
66
 
161
- def siblings
162
- return [] unless self.class.superclass.include?(Goldendocx::Element)
163
-
164
- self.class.superclass.children.keys.flat_map { |name| send(name) }
67
+ def to_element(**context, &)
68
+ Goldendocx.xml_serializer.build_element(tag_name, **context) { |xml| build_element(xml, &) }
165
69
  end
166
70
 
167
- def children
168
- self.class.children.keys.flat_map do |name|
169
- send(name)
170
- end.concat(siblings).compact
71
+ def to_xml(&)
72
+ Goldendocx.xml_serializer.build_xml(tag_name) { |xml| build_element(xml, &) }
171
73
  end
172
74
 
173
- def to_element(**context)
174
- Goldendocx.xml_serializer.build_element(root_tag, **context) do |xml|
175
- attributes.each { |name, value| xml[name] = value }
176
- children.each { |child| xml << child }
75
+ def build_element(xml)
76
+ attributes.each { |name, value| xml[name] = value }
77
+ unparsed_attributes.each { |name, value| xml[name] = value }
177
78
 
178
- yield(xml) if block_given?
179
- end
180
- end
79
+ children.each { |child| xml << child }
80
+ unparsed_children.each { |child| xml << child }
181
81
 
182
- def to_xml
183
- Goldendocx.xml_serializer.build_xml(root_tag) do |xml|
184
- attributes.each { |name, value| xml[name] = value }
185
-
186
- yield(xml) if block_given?
187
-
188
- children.each { |child| xml << child }
189
- end
82
+ yield(xml) if block_given?
190
83
  end
191
84
  end
192
85
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module HasAssociations
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :associations, default: {}
9
+ class_attribute :relationships_xml_path
10
+
11
+ delegate :add_relationship, to: :relationships
12
+ end
13
+
14
+ Options = Data.define(:class_name, :isolate)
15
+
16
+ class_methods do
17
+ def relationships_at(xml_path)
18
+ self.relationships_xml_path = xml_path
19
+ end
20
+
21
+ def associate(name, class_name:, isolate: false)
22
+ named = name.to_s
23
+ associations[named] = Options.new(class_name:, isolate:)
24
+
25
+ define_method named do
26
+ return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
27
+
28
+ new_instance = class_name.constantize.new
29
+ instance_variable_set("@#{name}", new_instance)
30
+ end
31
+ end
32
+ end
33
+
34
+ def read_associations(docx_file)
35
+ associations.each do |association, options|
36
+ association_class = options.class_name.constantize
37
+ association_document_xml = docx_file.read(association_class::XML_PATH)
38
+ instance_variable_set("@#{association}", association_class.parse(association_document_xml))
39
+ end
40
+ end
41
+
42
+ def read_relationships(docx_file)
43
+ @relationships = Goldendocx::Models::Relationships.parse(docx_file.read(relationships_xml_path))
44
+ end
45
+
46
+ def write_relationships(zos)
47
+ relationships.write_to(zos, relationships_xml_path)
48
+ end
49
+
50
+ def relationships
51
+ @relationships ||= Goldendocx::Models::Relationships.new
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module HasAttributes
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :attributes, instance_accessor: false, default: {}
9
+
10
+ def unparsed_attributes
11
+ @unparsed_attributes ||= {}
12
+ end
13
+ end
14
+
15
+ class_methods do
16
+ # alias_name: nil
17
+ # readonly: false
18
+ # default: nil
19
+ # namespace: nil
20
+ # setter: nil
21
+ def attribute(name, **options)
22
+ named = name.to_s
23
+ attributes[named] = {
24
+ alias_name: options[:alias_name],
25
+ default: options[:default],
26
+ namespace: options[:namespace],
27
+ method: options[:method]
28
+ }.compact
29
+
30
+ readonly = options[:readonly] || false
31
+ if readonly
32
+ attr_reader named
33
+ elsif options[:method]
34
+ attr_writer named
35
+ else
36
+ attr_accessor named
37
+ end
38
+ end
39
+ end
40
+
41
+ def attributes
42
+ self.class.attributes.each_with_object({}) do |(name, options), result|
43
+ value = public_send(options[:method] || name) || options[:default]
44
+ next if value.nil?
45
+
46
+ key = [options[:namespace], options[:alias_name] || name].compact.join(':')
47
+ result[key] = value
48
+ end
49
+ end
50
+
51
+ def read_attributes(node)
52
+ node_attributes = node.attributes_hash
53
+
54
+ attributes = self.class.attributes.each_with_object({}) do |(name, options), result|
55
+ attribute_tag = [options[:namespace], (options[:alias_name] || name)].compact.join(':')
56
+ result[name] = node_attributes.delete(attribute_tag)
57
+ end
58
+ assign_attributes(**attributes)
59
+
60
+ unparsed_attributes.update(node_attributes)
61
+ end
62
+
63
+ def assign_attributes(**attributes)
64
+ attributes.each { |key, value| send("#{key}=", value) if respond_to?("#{key}=") }
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'goldendocx/has_attributes'
4
+
5
+ module Goldendocx
6
+ module HasChildren
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :children, instance_accessor: false, default: {}
11
+
12
+ def unparsed_children
13
+ @unparsed_children ||= []
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def embeds_one(name, class_name:, auto_build: false)
19
+ warning_naming_suggestion(__method__, name, name.to_s.singularize)
20
+
21
+ options = { class_name:, multiple: false, auto_build: }
22
+ self.children = children.merge(name => options)
23
+
24
+ create_children_getter(name)
25
+ create_children_setter(name)
26
+ create_children_builder(name)
27
+ end
28
+
29
+ def embeds_many(name, class_name:, uniqueness: false)
30
+ warning_naming_suggestion(__method__, name, name.to_s.pluralize)
31
+
32
+ options = { class_name:, multiple: true, uniqueness: }
33
+ self.children = children.merge(name => options)
34
+
35
+ create_children_getter(name)
36
+ create_children_setter(name)
37
+ create_children_builder(name)
38
+ end
39
+
40
+ def default_value(name)
41
+ options = children[name]
42
+ return [] if options[:multiple]
43
+
44
+ options[:class_name].constantize.new if options[:auto_build]
45
+ end
46
+
47
+ private
48
+
49
+ def create_children_getter(name)
50
+ define_method name do
51
+ return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
52
+
53
+ instance_variable_set("@#{name}", self.class.default_value(name))
54
+ end
55
+ end
56
+
57
+ def create_children_setter(name)
58
+ define_method("#{name}=") { |value| instance_variable_set("@#{name}", value) }
59
+ end
60
+
61
+ def create_children_appender(name)
62
+ options = children[name]
63
+
64
+ define_method "append_#{name.to_s.singularize}" do |child|
65
+ return instance_variable_set("@#{name}", child) unless options[:multiple]
66
+
67
+ children = send(name)
68
+ children << child unless options[:uniqueness] && children.any?(child)
69
+ child
70
+ end
71
+ end
72
+
73
+ def create_children_builder(name)
74
+ options = children[name]
75
+ create_children_appender(name)
76
+
77
+ define_method "build_#{name.to_s.singularize}" do |**attributes|
78
+ child = options[:class_name].constantize.new
79
+ attributes.each { |key, value| child.send("#{key}=", value) if child.respond_to?("#{key}=") }
80
+ send("append_#{name.to_s.singularize}", child)
81
+ end
82
+ end
83
+
84
+ # :nocov:
85
+ def warning_naming_suggestion(method, name, suggestion_name)
86
+ return if suggestion_name == name.to_s
87
+
88
+ location = caller.find { |c| c.include?('goldendocx/') && !c.include?('goldendocx/element.rb') }
89
+ warn "warning: [#{method}] `#{name}` better be `#{suggestion_name}` at #{location}"
90
+ end
91
+ # :nocov:
92
+ end
93
+
94
+ def children
95
+ self.class.children.keys.flat_map { |name| send(name) }.compact
96
+ end
97
+
98
+ def read_children(xml_node)
99
+ xml_node.children.each do |child_node|
100
+ read_child(child_node)
101
+ end
102
+ end
103
+
104
+ def read_child(child_node)
105
+ name, options = self.class.children.find do |_, opts|
106
+ opts[:class_name].constantize.adapt?(child_node)
107
+ end
108
+ if name.present?
109
+ child = options[:class_name].constantize.read_from(child_node)
110
+ send("append_#{name.to_s.singularize}", child)
111
+ else
112
+ unparsed_children << child_node
113
+ end
114
+ end
115
+ end
116
+ end
@@ -31,7 +31,7 @@ module Goldendocx
31
31
  return unless relationship_id
32
32
 
33
33
  non_visual_picture.non_visual_drawing.assign_attributes(
34
- relationship_id: relationship_id,
34
+ relationship_id:,
35
35
  name: "#{relationship_id}.png"
36
36
  )
37
37
  picture_fill.blip.relationship_id = relationship_id
@@ -7,6 +7,6 @@ module Goldendocx
7
7
  end
8
8
  end
9
9
 
10
- Dir.glob(File.join(File.dirname(__FILE__), 'properties', '*.rb').to_s).sort.each do |file|
10
+ Dir.glob(File.join(File.dirname(__FILE__), 'properties', '*.rb').to_s).each do |file|
11
11
  require file
12
12
  end
@@ -7,6 +7,6 @@ end
7
7
 
8
8
  require 'goldendocx/images/properties'
9
9
 
10
- Dir.glob(File.join(File.dirname(__FILE__), 'images', '*.rb').to_s).sort.each do |file|
10
+ Dir.glob(File.join(File.dirname(__FILE__), 'images', '*.rb').to_s).each do |file|
11
11
  require file
12
12
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Goldendocx
4
- module Documents
4
+ module Models
5
5
  class Relationship
6
6
  include Goldendocx::Element
7
7
 
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Models
5
+ class Relationships
6
+ include Goldendocx::Document
7
+
8
+ NAMESPACE = 'http://schemas.openxmlformats.org/package/2006/relationships'
9
+
10
+ tag :Relationships
11
+ attribute :xmlns, default: NAMESPACE, readonly: true
12
+
13
+ embeds_many :relationships, class_name: 'Goldendocx::Models::Relationship'
14
+
15
+ def size
16
+ relationships.size
17
+ end
18
+
19
+ def write_to(zos, xml_path)
20
+ zos.put_next_entry xml_path
21
+ zos.write to_document_xml
22
+ end
23
+
24
+ def add_relationship(type, target)
25
+ relationship_id = "rId#{relationships.size + 1}"
26
+ build_relationship(id: relationship_id, type:, target:)
27
+ relationship_id
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Models
5
+ end
6
+ end
7
+
8
+ Dir.glob(File.join(File.dirname(__FILE__), 'models', '*.rb').to_s).each do |file|
9
+ require file
10
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Parts
5
+ class App
6
+ include Goldendocx::Document
7
+
8
+ TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties'
9
+ XML_PATH = 'docProps/app.xml'
10
+ NAMESPACE = 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'
11
+ CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.extended-properties+xml'
12
+
13
+ tag :Properties
14
+ attribute :xmlns, default: NAMESPACE, readonly: true
15
+
16
+ class << self
17
+ def read_from(app_document)
18
+ new_instance = new
19
+
20
+ app_document.children.map do |node|
21
+ new_instance.properties[node.name.to_sym] = node.text
22
+ end
23
+
24
+ new_instance
25
+ end
26
+ end
27
+
28
+ def write_to(zos)
29
+ zos.put_next_entry XML_PATH
30
+ zos.write to_document_xml
31
+ end
32
+
33
+ def to_document_xml
34
+ super do |xml|
35
+ properties.each do |name, value|
36
+ xml << Goldendocx.xml_serializer.build_element(name).tap { |app| app << value }
37
+ end
38
+ end
39
+ end
40
+
41
+ def properties
42
+ @properties ||= { Application: "Goldendocx_#{Goldendocx::VERSION}" }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -11,52 +11,38 @@ module Goldendocx
11
11
  XML_PATH = '[Content_Types].xml'
12
12
  NAMESPACE = 'http://schemas.openxmlformats.org/package/2006/content-types'
13
13
 
14
- attr_reader :defaults, :overrides
14
+ REQUIRED_DEFAULTS = {
15
+ rels: 'application/vnd.openxmlformats-package.relationships+xml',
16
+ xml: 'application/xml'
17
+ }.with_indifferent_access.freeze
15
18
 
16
19
  tag :Types
17
20
  attribute :xmlns, default: NAMESPACE, readonly: true
18
21
 
19
- class << self
20
- def read_from(docx_file)
21
- content_types = Goldendocx::Parts::ContentTypes.new
22
- content_types.read_defaults(docx_file)
23
- content_types.read_overrides(docx_file)
24
- content_types
25
- end
26
- end
22
+ embeds_many :defaults, class_name: 'Goldendocx::ContentTypes::Default', uniqueness: true
23
+ embeds_many :overrides, class_name: 'Goldendocx::ContentTypes::Override', uniqueness: true
27
24
 
28
25
  def initialize
29
- @defaults = []
30
- @overrides = []
31
- end
32
-
33
- def read_defaults(docx_file)
34
- @defaults = Goldendocx.xml_serializer.parse(docx_file.read(XML_PATH), %w[Types Default]).map do |node|
35
- Goldendocx::ContentTypes::Default.new(node[:Extension], node[:ContentType])
26
+ REQUIRED_DEFAULTS.map do |extension, content_type|
27
+ build_defaults(extension:, content_type:)
36
28
  end
37
29
  end
38
30
 
39
- def read_overrides(docx_file)
40
- @overrides = Goldendocx.xml_serializer.parse(docx_file.read(XML_PATH), %w[Types Override]).map do |node|
41
- Goldendocx::ContentTypes::Override.new(node[:PartName], node[:ContentType])
42
- end
31
+ def write_to(zos)
32
+ zos.put_next_entry XML_PATH
33
+ zos.write to_document_xml
43
34
  end
44
35
 
45
36
  def add_default(extension, content_type)
46
- new_default = Goldendocx::ContentTypes::Default.new(extension, content_type)
47
- defaults << new_default if defaults.none?(new_default)
37
+ return if defaults.any? { |default| extension == default.extension && content_type == default.content_type }
38
+
39
+ build_defaults(extension:, content_type:)
48
40
  end
49
41
 
50
42
  def add_override(part_name, content_type)
51
- new_override = Goldendocx::ContentTypes::Override.new(part_name, content_type)
52
- overrides << new_override if overrides.none?(new_override)
53
- end
43
+ return if overrides.any? { |override| part_name == override.part_name && content_type == override.content_type }
54
44
 
55
- def to_document_xml
56
- super do |xml|
57
- defaults.each { |default| xml << default }
58
- overrides.each { |override| xml << override }
59
- end
45
+ build_override(part_name:, content_type:)
60
46
  end
61
47
  end
62
48
  end