goldendocx 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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