goldendocx 0.2.2 → 0.2.3

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/CHANGELOG.md +8 -2
  4. data/Gemfile.lock +3 -1
  5. data/README.md +10 -2
  6. data/Rakefile +14 -3
  7. data/demo/templates/generate_codes.rb +10 -0
  8. data/demo/templates/xml_to_class.rb +91 -0
  9. data/demo/texts/append_plain_text.rb +1 -1
  10. data/demo/texts/append_styled_text.rb +1 -2
  11. data/demo/texts/create_text.rb +14 -0
  12. data/lib/extensions/active_support_extensions.rb +16 -0
  13. data/lib/extensions/nokogiri_extensions.rb +41 -0
  14. data/lib/extensions/ox_extensions.rb +35 -0
  15. data/lib/extensions/xml_serialize_extensions.rb +45 -0
  16. data/lib/goldendocx/components/properties/font_property.rb +19 -0
  17. data/lib/goldendocx/components/properties/language_property.rb +18 -0
  18. data/lib/goldendocx/components/properties/run_property.rb +2 -0
  19. data/lib/goldendocx/content_types/default.rb +2 -6
  20. data/lib/goldendocx/content_types/override.rb +2 -6
  21. data/lib/goldendocx/document.rb +9 -28
  22. data/lib/goldendocx/documents/body.rb +20 -11
  23. data/lib/goldendocx/documents/document.rb +2 -4
  24. data/lib/goldendocx/documents/latent_styles.rb +12 -0
  25. data/lib/goldendocx/documents/properties/default_style_property.rb +17 -0
  26. data/lib/goldendocx/documents/properties/page_margin_property.rb +37 -0
  27. data/lib/goldendocx/documents/properties/page_size_property.rb +17 -0
  28. data/lib/goldendocx/documents/properties/paragraph_default_style_property.rb +16 -0
  29. data/lib/goldendocx/documents/properties/run_default_style_property.rb +22 -0
  30. data/lib/goldendocx/documents/properties/section_property.rb +17 -0
  31. data/lib/goldendocx/documents/properties/style_name_property.rb +16 -0
  32. data/lib/goldendocx/documents/properties.rb +12 -0
  33. data/lib/goldendocx/documents/settings.rb +23 -0
  34. data/lib/goldendocx/documents/style.rb +10 -10
  35. data/lib/goldendocx/documents/styles.rb +9 -33
  36. data/lib/goldendocx/docx.rb +55 -12
  37. data/lib/goldendocx/element.rb +33 -140
  38. data/lib/goldendocx/has_associations.rb +52 -0
  39. data/lib/goldendocx/has_attributes.rb +67 -0
  40. data/lib/goldendocx/has_children.rb +116 -0
  41. data/lib/goldendocx/{documents → models}/relationship.rb +1 -1
  42. data/lib/goldendocx/{documents → models}/relationships.rb +4 -12
  43. data/lib/goldendocx/models.rb +10 -0
  44. data/lib/goldendocx/parts/app.rb +46 -0
  45. data/lib/goldendocx/parts/content_types.rb +16 -30
  46. data/lib/goldendocx/parts/core.rb +46 -0
  47. data/lib/goldendocx/parts/documents.rb +26 -13
  48. data/lib/goldendocx/parts/properties/created_at_property.rb +17 -0
  49. data/lib/goldendocx/parts/properties/creator_property.rb +16 -0
  50. data/lib/goldendocx/parts/properties/revision_property.rb +16 -0
  51. data/lib/goldendocx/parts/properties/updated_at_property.rb +17 -0
  52. data/lib/goldendocx/parts/properties/updater_property.rb +16 -0
  53. data/lib/goldendocx/parts/properties.rb +12 -0
  54. data/lib/goldendocx/tables/row.rb +1 -1
  55. data/lib/goldendocx/version.rb +1 -1
  56. data/lib/goldendocx/xml_serializers/nokogiri.rb +30 -22
  57. data/lib/goldendocx/xml_serializers/ox.rb +10 -20
  58. data/lib/goldendocx.rb +14 -2
  59. metadata +51 -7
  60. data/lib/goldendocx/documents/element.rb +0 -23
  61. data/lib/goldendocx/documents/unparsed_style.rb +0 -17
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Documents
5
+ module Properties
6
+ class PageMarginProperty
7
+ include Goldendocx::Element
8
+
9
+ namespace :w
10
+ tag :pgMar
11
+
12
+ attribute :top, namespace: :w, default: 1440
13
+ attribute :bottom, namespace: :w, default: 1440
14
+ attribute :left, namespace: :w, default: 1800
15
+ attribute :right, namespace: :w, default: 1800
16
+ attribute :header, namespace: :w, default: 851
17
+ attribute :footer, namespace: :w, default: 992
18
+ attribute :gutter, namespace: :w, default: 0
19
+
20
+ # Follow html margin style setting
21
+ def margin=(*args)
22
+ args = Array(*args)
23
+ raise StandardError("wrong number of arguments (given #{args.length}, expected 1..4)") unless (1..4).cover?(args.length)
24
+
25
+ margins =
26
+ case args.length
27
+ when 1 then [args.first] * 4
28
+ when 2 then [args.first, args.last, args.first, args.last]
29
+ when 3 then [args.first, args.second, args.last, args.second]
30
+ else args
31
+ end
32
+ assign_attributes(**%i[top right bottom left].zip(margins).to_h)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Documents
5
+ module Properties
6
+ class PageSizeProperty
7
+ include Goldendocx::Element
8
+
9
+ namespace :w
10
+ tag :pgSz
11
+
12
+ attribute :width, alias_name: :w, namespace: :w, default: 11906
13
+ attribute :height, alias_name: :h, namespace: :w, default: 16838
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Documents
5
+ module Properties
6
+ class ParagraphDefaultStyleProperty
7
+ include Goldendocx::Element
8
+
9
+ namespace :w
10
+ tag :pPrDefault
11
+
12
+ embeds_one :property, class_name: 'Goldendocx::Components::Properties::Property'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Documents
5
+ module Properties
6
+ class RunDefaultStyleProperty
7
+ include Goldendocx::Element
8
+
9
+ namespace :w
10
+ tag :rPrDefault
11
+
12
+ embeds_one :property, class_name: 'Goldendocx::Components::Properties::RunProperty'
13
+
14
+ def initialize
15
+ build_property
16
+ property.build_language
17
+ property.build_font
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Documents
5
+ module Properties
6
+ class SectionProperty
7
+ include Goldendocx::Element
8
+
9
+ namespace :w
10
+ tag :sectPr
11
+
12
+ embeds_one :size, class_name: 'Goldendocx::Documents::Properties::PageSizeProperty', auto_build: true
13
+ embeds_one :margin, class_name: 'Goldendocx::Documents::Properties::PageMarginProperty', auto_build: true
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Documents
5
+ module Properties
6
+ class StyleNameProperty
7
+ include Goldendocx::Element
8
+
9
+ namespace :w
10
+ tag :name
11
+
12
+ attribute :name, alias_name: :val, namespace: :w
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Documents
5
+ module Properties
6
+ end
7
+ end
8
+ end
9
+
10
+ Dir.glob(File.join(File.dirname(__FILE__), 'properties', '*.rb').to_s).sort.each do |file|
11
+ require file
12
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Goldendocx
4
+ module Documents
5
+ class Settings
6
+ include Goldendocx::Document
7
+
8
+ XML_PATH = 'word/settings.xml'
9
+ TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings'
10
+ CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml'
11
+
12
+ namespace :w
13
+ tag :settings
14
+ concern_namespaces :mc, :o, :r, :m, :v, :w10, :w, :w14, :w15, :w16cid, :w16se, :sl
15
+ ignore_namespaces :w14, :w15, :w16se, :w16cid
16
+
17
+ def write_to(zos)
18
+ zos.put_next_entry XML_PATH
19
+ zos.write to_document_xml
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,18 +3,18 @@
3
3
  module Goldendocx
4
4
  module Documents
5
5
  class Style
6
- attr_reader :node, :id, :type, :name
6
+ include Goldendocx::Element
7
7
 
8
- def initialize(node)
9
- @node = node
10
- @id = node['w:styleId']
11
- @type = node['w:type']
12
- @name = node.public_send(:'w:name')['w:val']
13
- @default = node['w:default']
14
- end
8
+ namespace :w
9
+ tag :style
10
+
11
+ attribute :id, alias_name: :styleId, namespace: :w
12
+ attribute :type, namespace: :w
13
+
14
+ embeds_one :style_name, class_name: 'Goldendocx::Documents::Properties::StyleNameProperty', auto_build: true
15
15
 
16
- def to_element(**_context)
17
- @node
16
+ def name
17
+ style_name.name
18
18
  end
19
19
  end
20
20
  end
@@ -6,42 +6,22 @@ module Goldendocx
6
6
  include Goldendocx::Document
7
7
 
8
8
  XML_PATH = 'word/styles.xml'
9
+ TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles'
10
+ CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml'
9
11
 
10
12
  tag :styles
11
13
  namespace :w
12
14
  concern_namespaces :mc, :r, :w, :w14, :w15
13
15
  ignore_namespaces :w14, :w15
14
16
 
15
- attr_reader :styles, :doc_defaults, :latent_styles
16
-
17
- def initialize
18
- @styles = []
19
- end
17
+ embeds_one :defaults, class_name: 'Goldendocx::Documents::Properties::DefaultStyleProperty', auto_build: true
18
+ embeds_one :latentStyles, class_name: 'Goldendocx::Documents::LatentStyles'
19
+ embeds_many :styles, class_name: 'Goldendocx::Documents::Style'
20
20
 
21
21
  def size
22
22
  styles.size
23
23
  end
24
24
 
25
- def read_from(docx_file)
26
- @styles = Goldendocx.xml_serializer.parse(docx_file.read(XML_PATH), %w[w:styles w:style]).map do |node|
27
- Goldendocx::Documents::Style.new(node)
28
- end
29
- @doc_defaults = Goldendocx.xml_serializer.parse(docx_file.read(XML_PATH), %w[w:styles w:docDefaults]).map do |node|
30
- Goldendocx::Documents::UnparsedStyle.new(node)
31
- end
32
- @latent_styles = Goldendocx.xml_serializer.parse(docx_file.read(XML_PATH), %w[w:styles w:latentStyles]).map do |node|
33
- Goldendocx::Documents::UnparsedStyle.new(node)
34
- end
35
- end
36
-
37
- def to_document_xml
38
- super do |xml|
39
- @doc_defaults&.each { |element| xml << element }
40
- @latent_styles&.each { |element| xml << element }
41
- styles.each { |style| xml << style }
42
- end
43
- end
44
-
45
25
  def write_to(zos)
46
26
  zos.put_next_entry XML_PATH
47
27
  zos.write to_document_xml
@@ -60,14 +40,10 @@ module Goldendocx
60
40
  end
61
41
 
62
42
  def add_style(fragment)
63
- # FIXME: Not a good implementation for Nokogiri compatibility
64
- raise NotImplementedError unless Goldendocx.config.xml_serializer == :ox
65
-
66
- style_id = (@styles.size + 1).to_s
67
- style = Goldendocx.xml_serializer.parse(fragment)
68
- style['w:styleId'] = style_id if style['w:styleId'].nil? || style['w:styleId'].empty?
69
- @styles << Goldendocx::Documents::Style.new(style)
70
- style_id
43
+ style = Goldendocx::Documents::Style.parse(fragment)
44
+ style.id = (styles.size + 1).to_s # Rearrange id to prevent duplicates
45
+ styles << style
46
+ style.id
71
47
  end
72
48
  end
73
49
  end
@@ -1,32 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'goldendocx/units'
4
- require 'goldendocx/element'
5
- require 'goldendocx/document'
6
-
3
+ require 'goldendocx/models'
7
4
  require 'goldendocx/components'
8
5
  require 'goldendocx/documents'
9
6
  require 'goldendocx/parts'
10
7
 
11
8
  module Goldendocx
12
9
  class Docx
13
- attr_reader :unstructured_entries, :documents, :content_types
10
+ include Goldendocx::HasAssociations
11
+
12
+ attr_reader :unstructured_entries, :documents
13
+
14
+ RELATIONSHIPS_XML_PATH = '_rels/.rels'
14
15
 
15
16
  STRUCTURED_ENTRIES = [
17
+ RELATIONSHIPS_XML_PATH,
18
+
16
19
  Goldendocx::Parts::ContentTypes::XML_PATH,
20
+ Goldendocx::Parts::App::XML_PATH,
21
+ Goldendocx::Parts::Core::XML_PATH,
22
+
23
+ Goldendocx::Parts::Documents::RELATIONSHIPS_XML_PATH,
17
24
 
18
- Goldendocx::Documents::Body::XML_PATH,
19
- Goldendocx::Documents::Relationships::XML_PATH,
25
+ Goldendocx::Documents::Document::XML_PATH,
26
+ Goldendocx::Documents::Settings::XML_PATH,
20
27
  Goldendocx::Documents::Styles::XML_PATH
21
28
  ].freeze
22
29
 
23
- def initialize(file_path)
30
+ relationships_at RELATIONSHIPS_XML_PATH
31
+ associate :app, class_name: 'Goldendocx::Parts::App'
32
+ associate :core, class_name: 'Goldendocx::Parts::Core'
33
+ associate :content_types, class_name: 'Goldendocx::Parts::ContentTypes', isolate: true
34
+
35
+ def initialize(file_path = nil)
36
+ file_path.present? ? read_from(file_path) : build_default
37
+ end
38
+
39
+ def read_from(file_path)
24
40
  docx_file = Zip::File.new(file_path)
41
+
42
+ read_relationships(docx_file)
43
+ read_associations(docx_file)
44
+
25
45
  @documents = Goldendocx::Parts::Documents.read_from(docx_file)
26
- @content_types = Goldendocx::Parts::ContentTypes.read_from(docx_file)
46
+
27
47
  @unstructured_entries = docx_file.entries.filter_map do |entry|
28
48
  UnStructuredEntry.new(entry) unless STRUCTURED_ENTRIES.include?(entry.name)
29
49
  end
50
+
51
+ self
30
52
  end
31
53
 
32
54
  def write_to(new_path)
@@ -65,6 +87,25 @@ module Goldendocx
65
87
 
66
88
  private
67
89
 
90
+ def build_default
91
+ associations.each do |association, options|
92
+ association_class = options[:class_name].constantize
93
+ instance_variable_set("@#{association}", association_class.new)
94
+ add_relationship association_class::TYPE, association_class::XML_PATH unless options[:isolate]
95
+ end
96
+
97
+ content_types.add_override "/#{Goldendocx::Parts::App::XML_PATH}", Goldendocx::Parts::App::CONTENT_TYPE
98
+ content_types.add_override "/#{Goldendocx::Parts::Core::XML_PATH}", Goldendocx::Parts::Core::CONTENT_TYPE
99
+ content_types.add_override "/#{Goldendocx::Documents::Styles::XML_PATH}", Goldendocx::Documents::Styles::CONTENT_TYPE
100
+ content_types.add_override "/#{Goldendocx::Documents::Settings::XML_PATH}", Goldendocx::Documents::Settings::CONTENT_TYPE
101
+
102
+ @documents = Goldendocx::Parts::Documents.new
103
+ add_relationship Goldendocx::Documents::Document::TYPE, Goldendocx::Documents::Document::XML_PATH
104
+ content_types.add_override "/#{Goldendocx::Documents::Document::XML_PATH}", Goldendocx::Documents::Document::CONTENT_TYPE
105
+
106
+ @unstructured_entries = []
107
+ end
108
+
68
109
  def ensure_image_content_type!(_image_data)
69
110
  extension = 'png'
70
111
  content_type = 'image/png'
@@ -79,10 +120,12 @@ module Goldendocx
79
120
 
80
121
  def to_stream
81
122
  Zip::OutputStream.write_buffer do |zos|
82
- zos.put_next_entry Goldendocx::Parts::ContentTypes::XML_PATH
83
- zos.write content_types.to_document_xml
123
+ write_relationships(zos)
124
+
125
+ associations.each_key do |association|
126
+ send(association).write_to(zos)
127
+ end
84
128
 
85
- zos.put_next_entry Goldendocx::Parts::Documents::XML_PATH
86
129
  documents.write_stream(zos)
87
130
 
88
131
  @unstructured_entries.each { |unstructured_entry| unstructured_entry.write_to(zos) }
@@ -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, &block)
68
+ Goldendocx.xml_serializer.build_element(tag_name, **context) { |xml| build_element(xml, &block) }
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(&block)
72
+ Goldendocx.xml_serializer.build_xml(tag_name) { |xml| build_element(xml, &block) }
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,52 @@
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
+ class_methods do
15
+ def relationships_at(xml_path)
16
+ self.relationships_xml_path = xml_path
17
+ end
18
+
19
+ def associate(name, class_name:, isolate: false)
20
+ named = name.to_s
21
+ associations[named] = { class_name: class_name, isolate: isolate }
22
+
23
+ define_method named do
24
+ return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
25
+
26
+ new_instance = class_name.constantize.new
27
+ instance_variable_set("@#{name}", new_instance)
28
+ end
29
+ end
30
+ end
31
+
32
+ def read_associations(docx_file)
33
+ associations.each do |association, options|
34
+ association_class = options[:class_name].constantize
35
+ association_document_xml = docx_file.read(association_class::XML_PATH)
36
+ instance_variable_set("@#{association}", association_class.parse(association_document_xml))
37
+ end
38
+ end
39
+
40
+ def read_relationships(docx_file)
41
+ @relationships = Goldendocx::Models::Relationships.parse(docx_file.read(relationships_xml_path))
42
+ end
43
+
44
+ def write_relationships(zos)
45
+ relationships.write_to(zos, relationships_xml_path)
46
+ end
47
+
48
+ def relationships
49
+ @relationships ||= Goldendocx::Models::Relationships.new
50
+ end
51
+ end
52
+ end