openxml-package 0.2.9 → 0.3.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c894c5ccef5327f4463ca9ddd3401bc6f3aa7e27
4
- data.tar.gz: c34eb930e46eff3af421c9ac49bf0bedc78113d6
2
+ SHA256:
3
+ metadata.gz: 7679189d1fa0249404f348c35516577f71094f29154a4d6db8d18d73a69c0890
4
+ data.tar.gz: a1a41c6458d7b4f6a90f29443b5778b61e74a44c9ce6a1e5fed2138077b6c2a7
5
5
  SHA512:
6
- metadata.gz: 2e6418b2b1dadf17009690fbfc6ada8d12f3e5b31493d4df9299bff672b8756098bb11d32c5fb9d3fe59180c7ee2838cf77ac167ca49b9ba6fe3120988cf3749
7
- data.tar.gz: f2a422bb660b761956f6a76ec856697115cc0d9ecd08b8f138c6e4093a6a74a8e83d5e2fad8325f62fda30eb6ab88f7f767b5acaa31e976e2bc231774f353cc5
6
+ metadata.gz: 8b739a383caa59890a27b389d020de3262d7017c3444ee49309b766eb211a1c67ce2c48e94fff0836c1f65b99193f5da6d13261555d5bf0a51b433d470d701b9
7
+ data.tar.gz: 2a78d1af5bce4eb2bdeb8ce20a2787070094b0590a5fa20ddf299a2b0650fb798ebf2e058ef493825b8807207b4e9d2946ea37a8930c5075912c3eeb4f12223f
@@ -0,0 +1,8 @@
1
+ ## 0.3.4 - Oct 16, 2019
2
+
3
+ - Removed rubyzip lock
4
+
5
+
6
+ ## 0.3.3 - Oct 2, 2018
7
+
8
+ - Optimized setting attributes
@@ -1,3 +1,3 @@
1
1
  module OpenXmlPackage
2
- VERSION = "0.2.9"
2
+ VERSION = "0.3.4"
3
3
  end
@@ -13,6 +13,7 @@ module OpenXml
13
13
  def initialize(options={})
14
14
  @to_s_options = { with_xml: true }
15
15
 
16
+ @ns = nil
16
17
  @document = Ox::Document.new(
17
18
  encoding: "UTF-8",
18
19
  version: "1.0",
@@ -8,7 +8,7 @@ module OpenXml
8
8
  end
9
9
 
10
10
  def []=(attribute, value)
11
- namespace_def = attribute.downcase.to_s.match /^xmlns(?:\:(?<prefix>.*))?$/
11
+ namespace_def = attribute.downcase.to_s.match(/^xmlns(?:\:(?<prefix>.*))?$/)
12
12
  namespaces << namespace_def[:prefix].to_sym if namespace_def && namespace_def[:prefix]
13
13
  super
14
14
  end
@@ -0,0 +1,29 @@
1
+ require "openxml/has_properties"
2
+
3
+ module OpenXml
4
+ module ContainsProperties
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ include HasProperties
9
+ include InstanceMethods
10
+ end
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ def property_xml(xml)
16
+ ensure_required_choices
17
+ props = active_properties
18
+ return unless render_properties? props
19
+ props.each { |prop| prop.to_xml(xml) }
20
+ end
21
+
22
+ def properties_attributes
23
+ {}
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -6,21 +6,20 @@ module OpenXml
6
6
 
7
7
  class << self
8
8
  attr_reader :property_name
9
- attr_reader :namespace
10
9
 
11
10
  def tag(*args)
12
11
  @tag = args.first if args.any?
13
- @tag
12
+ @tag ||= nil
14
13
  end
15
14
 
16
15
  def name(*args)
17
16
  @property_name = args.first if args.any?
18
- @name
17
+ @property_name ||= nil
19
18
  end
20
19
 
21
20
  def namespace(*args)
22
21
  @namespace = args.first if args.any?
23
- @namespace
22
+ @namespace ||= nil
24
23
  end
25
24
 
26
25
  end
@@ -1,3 +1,5 @@
1
+ require "openxml/unmet_requirement"
2
+
1
3
  module OpenXml
2
4
  module HasAttributes
3
5
 
@@ -6,9 +8,13 @@ module OpenXml
6
8
  end
7
9
 
8
10
  module ClassMethods
9
- def attribute(name, expects: nil, one_of: nil, in_range: nil, displays_as: nil, namespace: nil, matches: nil, deprecated: false)
10
- bad_names = %w(tag name namespace properties_tag)
11
- raise ArgumentError if bad_names.member? name
11
+
12
+ RESERVED_NAMES = %w{ tag name namespace properties_tag }.freeze
13
+
14
+ def attribute(name, expects: nil, one_of: nil, in_range: nil, displays_as: nil, namespace: nil, matches: nil, validation: nil, required: false, deprecated: false)
15
+ raise ArgumentError if RESERVED_NAMES.member? name.to_s
16
+
17
+ required_attributes.push(name) if required
12
18
 
13
19
  attr_reader name
14
20
 
@@ -17,21 +23,39 @@ module OpenXml
17
23
  send(expects, value) unless expects.nil?
18
24
  matches?(value, matches) unless matches.nil?
19
25
  in_range?(value, in_range) unless in_range.nil?
26
+ validation.call(value) if validation.respond_to? :call
20
27
  instance_variable_set "@#{name}", value
21
28
  end
22
29
 
23
30
  camelized_name = name.to_s.gsub(/_([a-z])/i) { $1.upcase }.to_sym
24
- attributes[name] = [displays_as || camelized_name, namespace || @attribute_namespace]
31
+ attributes[name] = [displays_as || camelized_name, namespace || attribute_namespace]
25
32
  end
26
33
 
27
34
  def attributes
28
- @attributes ||= {}
35
+ @attributes ||= {}.tap do |attrs|
36
+ if superclass.respond_to?(:attributes)
37
+ superclass.attributes.each do |key, value|
38
+ attrs[key] = value.dup
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def required_attributes
45
+ @required_attributes ||= [].tap do |attrs|
46
+ attrs.push(*superclass.required_attributes) if superclass.respond_to?(:required_attributes)
47
+ end
29
48
  end
30
49
 
31
50
  def with_namespace(namespace, &block)
32
51
  @attribute_namespace = namespace
33
52
  instance_eval(&block)
34
53
  end
54
+
55
+ def attribute_namespace
56
+ @attribute_namespace ||= nil
57
+ end
58
+
35
59
  end
36
60
 
37
61
  def render?
@@ -42,85 +66,104 @@ module OpenXml
42
66
  self.class.attributes
43
67
  end
44
68
 
69
+ def required_attributes
70
+ self.class.required_attributes
71
+ end
72
+
45
73
  private
46
74
 
47
75
  def xml_attributes
76
+ ensure_required_attributes_set
48
77
  attributes.each_with_object({}) do |(name, options), attrs|
49
78
  display, namespace = options
50
79
  value = send(name)
51
80
  attr_name = "#{namespace}:#{display}"
52
- attr_name = "#{display}" if namespace.nil?
81
+ attr_name = display.to_s if namespace.nil?
53
82
  attrs[attr_name] = value unless value.nil?
54
83
  end
55
84
  end
56
85
 
86
+ def ensure_required_attributes_set
87
+ unset_attributes = required_attributes.reject do |attr|
88
+ instance_variable_defined?("@#{attr}")
89
+ end
90
+ return if unset_attributes.empty?
91
+
92
+ raise OpenXml::UnmetRequirementError, "Required attribute(s) #{unset_attributes.join(", ")} have not been set"
93
+ end
94
+
57
95
  def boolean(value)
58
- message = "Invalid #{name}: frame must be true or false"
59
- raise ArgumentError, message unless [true, false].member? value
96
+ return if [true, false].member? value
97
+ raise ArgumentError, "Invalid #{name}: frame must be true or false"
60
98
  end
61
99
 
62
100
  def hex_color(value)
63
- message = "Invalid #{name}: must be :auto or a hex color, e.g. 4F1B8C"
64
- raise ArgumentError, message unless value == :auto || value =~ /^[0-9A-F]{6}$/
101
+ return if value == :auto || value =~ /^[0-9A-F]{6}$/
102
+ raise ArgumentError, "Invalid #{name}: must be :auto or a hex color, e.g. 4F1B8C"
65
103
  end
66
104
 
67
105
  def hex_digit(value)
68
- message = "Invalid #{name}: must be a two-digit hex number, e.g. BF"
69
- raise ArgumentError, message unless value =~ /^[0-9A-F]{2}$/
106
+ return if value =~ /^[0-9A-F]{2}$/
107
+ raise ArgumentError, "Invalid #{name}: must be a two-digit hex number, e.g. BF"
70
108
  end
71
109
 
72
110
  def hex_digit_4(value)
73
- message = "Invalid #{name}: must be a four-digit hex number, e.g. BF12"
74
- raise ArgumentError, message unless value =~ /^[0-9A-F]{4}$/
111
+ return if value =~ /^[0-9A-F]{4}$/
112
+ raise ArgumentError, "Invalid #{name}: must be a four-digit hex number, e.g. BF12"
75
113
  end
76
114
 
77
115
  def long_hex_number(value)
78
- message = "Invalid #{name}: must be an eight-digit hex number, e.g., FFAC0013"
79
- raise ArgumentError, message unless value =~ /^[0-9A-F]{8}$/
116
+ return if value =~ /^[0-9A-F]{8}$/
117
+ raise ArgumentError, "Invalid #{name}: must be an eight-digit hex number, e.g., FFAC0013"
80
118
  end
81
119
 
82
120
  def hex_string(value)
83
- message = "Invalid #{name}: must be a string of hexadecimal numbers, e.g. FFA23C6E"
84
- raise ArgumentError, message unless value =~ /^[0-9A-F]+$/
121
+ return if value =~ /^[0-9A-F]+$/
122
+ raise ArgumentError, "Invalid #{name}: must be a string of hexadecimal numbers, e.g. FFA23C6E"
85
123
  end
86
124
 
87
125
  def integer(value)
88
- message = "Invalid #{name}: must be an integer"
89
- raise ArgumentError, message unless value.is_a?(Integer)
126
+ return if value.is_a?(Integer)
127
+ raise ArgumentError, "Invalid #{name}: must be an integer"
90
128
  end
91
129
 
92
130
  def positive_integer(value)
93
- message = "Invalid #{name}: must be a positive integer"
94
- raise ArgumentError, message unless value.is_a?(Integer) && value >= 0
131
+ return if value.is_a?(Integer) && value >= 0
132
+ raise ArgumentError, "Invalid #{name}: must be a positive integer"
95
133
  end
96
134
 
97
135
  def string(value)
98
- message = "Invalid #{name}: must be a string"
99
- raise ArgumentError, message if !value.is_a?(String) || value.length.zero?
136
+ return if value.is_a?(String) && value.length > 0
137
+ raise ArgumentError, "Invalid #{name}: must be a string"
138
+ end
139
+
140
+ def string_or_blank(value)
141
+ return if value.is_a?(String)
142
+ raise ArgumentError, "Invalid #{name}: must be a string, even if the string is empty"
100
143
  end
101
144
 
102
145
  def in_range?(value, range)
103
- message = "Invalid #{name}: must be a number between #{range.begin} and #{range.end}"
104
- raise ArgumentError, message unless range.include?(value.to_i)
146
+ return if range.include?(value.to_i)
147
+ raise ArgumentError, "Invalid #{name}: must be a number between #{range.begin} and #{range.end}"
105
148
  end
106
149
 
107
150
  def percentage(value)
108
- message = "Invalid #{name}: must be a percentage"
109
- raise ArgumentError, message unless value.is_a?(String) && value =~ /-?[0-9]+(\.[0-9]+)?%/ # Regex supplied in sec. 22.9.2.9 of Office Open XML docs
151
+ return if value.is_a?(String) && value =~ /-?[0-9]+(\.[0-9]+)?%/ # Regex supplied in sec. 22.9.2.9 of Office Open XML docs
152
+ raise ArgumentError, "Invalid #{name}: must be a percentage"
110
153
  end
111
154
 
112
155
  def on_or_off(value)
113
- valid_in? value, [:on, :off]
156
+ valid_in? value, %i{ on off }
114
157
  end
115
158
 
116
159
  def valid_in?(value, list)
117
- message = "Invalid #{name}: must be one of #{list} (was #{value.inspect})"
118
- raise ArgumentError, message unless list.member?(value)
160
+ return if list.member?(value)
161
+ raise ArgumentError, "Invalid #{name}: must be one of #{list} (was #{value.inspect})"
119
162
  end
120
163
 
121
164
  def matches?(value, regexp)
122
- message = "Value does not match #{regexp}"
123
- raise ArgumentError, message unless value =~ regexp
165
+ return if value =~ regexp
166
+ raise ArgumentError, "Value does not match #{regexp}"
124
167
  end
125
168
 
126
169
  end
@@ -2,7 +2,8 @@ module OpenXml
2
2
  module HasChildren
3
3
  attr_reader :children
4
4
 
5
- def initialize
5
+ def initialize(*)
6
+ super
6
7
  @children = []
7
8
  end
8
9
 
@@ -26,5 +27,6 @@ module OpenXml
26
27
  def render?
27
28
  super || children.any?
28
29
  end
30
+
29
31
  end
30
32
  end
@@ -0,0 +1,223 @@
1
+ require "openxml/unmet_requirement"
2
+
3
+ module OpenXml
4
+ module HasProperties
5
+
6
+ class ChoiceGroupUniqueError < RuntimeError; end
7
+
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+
14
+ def properties_tag(*args)
15
+ @properties_tag = args.first if args.any?
16
+ @properties_tag ||= nil
17
+ end
18
+
19
+ def value_property(name, as: nil, klass: nil, required: false, default_value: nil)
20
+ attr_reader name
21
+
22
+ properties[name] = (as || name).to_s
23
+ required_properties[name] = default_value if required
24
+ classified_name = properties[name].split("_").map(&:capitalize).join
25
+ class_name = klass.to_s unless klass.nil?
26
+ class_name ||= (to_s.split("::")[0...-2] + ["Properties", classified_name]).join("::")
27
+
28
+ (choice_groups[current_group] ||= []).push(name) unless current_group.nil?
29
+
30
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
31
+ def #{name}=(value)
32
+ group_index = #{@current_group.inspect}
33
+ ensure_unique_in_group(:#{name}, group_index) unless group_index.nil?
34
+ instance_variable_set "@#{name}", #{class_name}.new(value)
35
+ end
36
+ CODE
37
+ end
38
+
39
+ def property(name, as: nil, klass: nil, required: false)
40
+ properties[name] = (as || name).to_s
41
+ required_properties[name] = true if required
42
+ classified_name = properties[name].split("_").map(&:capitalize).join
43
+ class_name = klass.to_s unless klass.nil?
44
+ class_name ||= (to_s.split("::")[0...-2] + ["Properties", classified_name]).join("::")
45
+
46
+ (choice_groups[current_group] ||= []).push(name) unless current_group.nil?
47
+
48
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
49
+ def #{name}(*args)
50
+ unless instance_variable_defined?("@#{name}")
51
+ group_index = #{@current_group.inspect}
52
+ ensure_unique_in_group(:#{name}, group_index) unless group_index.nil?
53
+ instance_variable_set "@#{name}", #{class_name}.new(*args)
54
+ end
55
+
56
+ instance_variable_get "@#{name}"
57
+ end
58
+ CODE
59
+ end
60
+
61
+ def property_choice(required: false)
62
+ @current_group = choice_groups.length
63
+ required_choices << @current_group if required
64
+ yield
65
+ @current_group = nil
66
+ end
67
+
68
+ def current_group
69
+ @current_group ||= nil
70
+ end
71
+
72
+ def properties
73
+ @properties ||= {}.tap do |props|
74
+ props.merge!(superclass.properties) if superclass.respond_to?(:properties)
75
+ end
76
+ end
77
+
78
+ def choice_groups
79
+ @choice_groups ||= [].tap do |choices|
80
+ choices.push(*superclass.choice_groups.map(&:dup)) if superclass.respond_to?(:choice_groups)
81
+ end
82
+ end
83
+
84
+ def required_properties
85
+ @required_properties ||= {}.tap do |props|
86
+ props.merge!(superclass.required_properties) if superclass.respond_to?(:required_properties)
87
+ end
88
+ end
89
+
90
+ def required_choices
91
+ @required_choices ||= [].tap do |choices|
92
+ choices.push(*superclass.required_choices) if superclass.respond_to?(:required_choices)
93
+ end
94
+ end
95
+
96
+ def properties_attribute(name, **args)
97
+ properties_element.attribute name, **args
98
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
99
+ def #{name}=(value)
100
+ properties_element.#{name} = value
101
+ end
102
+
103
+ def #{name}
104
+ properties_element.#{name}
105
+ end
106
+ RUBY
107
+ end
108
+
109
+ def properties_element
110
+ this = self
111
+ parent_klass = superclass.respond_to?(:properties_element) ? superclass.properties_element : OpenXml::Element
112
+ @properties_element ||= Class.new(parent_klass) do
113
+ tag :"#{this.properties_tag || this.default_properties_tag}"
114
+ namespace :"#{this.namespace}"
115
+ end
116
+ end
117
+
118
+ def default_properties_tag
119
+ :"#{tag}Pr"
120
+ end
121
+
122
+ end
123
+
124
+ def initialize(*_args)
125
+ super
126
+ build_required_properties
127
+ end
128
+
129
+ def properties_element
130
+ @properties_element ||= self.class.properties_element.new
131
+ end
132
+
133
+ def properties_attributes
134
+ properties_element.attributes
135
+ end
136
+
137
+ def render?
138
+ return true unless defined?(super)
139
+ render_properties? || super
140
+ end
141
+
142
+ def to_xml(xml)
143
+ super(xml) do
144
+ property_xml(xml)
145
+ yield xml if block_given?
146
+ end
147
+ end
148
+
149
+ def property_xml(xml)
150
+ ensure_required_choices
151
+ props = active_properties
152
+ return unless render_properties? props
153
+
154
+ properties_element.to_xml(xml) do
155
+ props.each { |prop| prop.to_xml(xml) }
156
+ end
157
+ end
158
+
159
+ def build_required_properties
160
+ required_properties.each do |prop, default_value|
161
+ public_send(:"#{prop}=", default_value) if respond_to? :"#{prop}="
162
+ public_send(:"#{prop}")
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def properties
169
+ self.class.properties
170
+ end
171
+
172
+ def active_properties
173
+ properties.keys.map { |property| instance_variable_get("@#{property}") }.compact
174
+ end
175
+
176
+ def render_properties?(properties=active_properties)
177
+ properties.any?(&:render?) || properties_attributes.keys.any? do |key|
178
+ properties_element.instance_variable_defined?("@#{key}")
179
+ end
180
+ end
181
+
182
+ def properties_tag
183
+ self.class.properties_tag || default_properties_tag
184
+ end
185
+
186
+ def default_properties_tag
187
+ :"#{tag}Pr"
188
+ end
189
+
190
+ def choice_groups
191
+ self.class.choice_groups
192
+ end
193
+
194
+ def required_properties
195
+ self.class.required_properties
196
+ end
197
+
198
+ def required_choices
199
+ self.class.required_choices
200
+ end
201
+
202
+ def ensure_unique_in_group(name, group_index)
203
+ other_names = (choice_groups[group_index] - [name])
204
+ return if other_names.none? { |other_name| instance_variable_defined?("@#{other_name}") }
205
+ raise ChoiceGroupUniqueError, "Property #{name} cannot also be set with #{other_names.join(", ")}."
206
+ end
207
+
208
+ def unmet_choices
209
+ required_choices.reject do |choice_index|
210
+ choice_groups[choice_index].one? do |prop_name|
211
+ instance_variable_defined?("@#{prop_name}")
212
+ end
213
+ end
214
+ end
215
+
216
+ def ensure_required_choices
217
+ unmet_choice_groups = unmet_choices.map { |index| choice_groups[index].join(", ") }
218
+ return if unmet_choice_groups.empty?
219
+ raise OpenXml::UnmetRequirementError, "Required choice from among group(s) (#{unmet_choice_groups.join("), (")}) not made"
220
+ end
221
+
222
+ end
223
+ end
@@ -18,7 +18,7 @@ module OpenXml
18
18
  end
19
19
 
20
20
  def content_types(&block)
21
- content_types_presets.instance_eval &block
21
+ content_types_presets.instance_eval(&block)
22
22
  end
23
23
 
24
24
  def open(path)
@@ -80,7 +80,7 @@ module OpenXml
80
80
  end
81
81
 
82
82
  def write_to(path)
83
- File.open(path, "w") do |file|
83
+ File.open(path, "wb") do |file|
84
84
  file.write to_stream.string
85
85
  end
86
86
  end
@@ -40,7 +40,7 @@ module OpenXml
40
40
  end
41
41
 
42
42
  def to_xml
43
- build_xml do |xml|
43
+ build_standalone_xml do |xml|
44
44
  xml.Types(xmlns: "http://schemas.openxmlformats.org/package/2006/content-types") {
45
45
  defaults.each { |extension, content_type| xml.Default("Extension" => extension, "ContentType" => content_type) }
46
46
  overrides.each { |part_name, content_type| xml.Override("PartName" => part_name, "ContentType" => content_type) }
@@ -23,7 +23,7 @@ module OpenXml
23
23
  end
24
24
  end
25
25
 
26
- def add_relationship(type, target, id=nil, target_mode=nil)
26
+ def add_relationship(type, target, id=next_id, target_mode=nil)
27
27
  OpenXml::Elements::Relationship.new(type, target, id, target_mode).tap do |relationship|
28
28
  relationships.push relationship
29
29
  end
@@ -56,6 +56,11 @@ module OpenXml
56
56
  private
57
57
  attr_reader :relationships
58
58
 
59
+ def next_id
60
+ @current_id = (@current_id || 0) + 1
61
+ "rId#{@current_id}"
62
+ end
63
+
59
64
  end
60
65
  end
61
66
  end
@@ -0,0 +1,22 @@
1
+ module OpenXml
2
+ module Properties
3
+ end
4
+ end
5
+
6
+ require "openxml/properties/base_property"
7
+ require "openxml/properties/complex_property"
8
+ require "openxml/properties/value_property"
9
+
10
+ require "openxml/properties/boolean_property"
11
+ require "openxml/properties/integer_property"
12
+ require "openxml/properties/positive_integer_property"
13
+ require "openxml/properties/string_property"
14
+ require "openxml/properties/on_off_property"
15
+ require "openxml/properties/toggle_property"
16
+
17
+ require "openxml/properties/container_property"
18
+ require "openxml/properties/transparent_container_property"
19
+
20
+ Dir.glob(File.join(File.dirname(__FILE__), "properties", "*.rb").to_s).each do |file|
21
+ require file
22
+ end
@@ -0,0 +1,80 @@
1
+ module OpenXml
2
+ module Properties
3
+ class BaseProperty
4
+ attr_reader :value
5
+
6
+ class << self
7
+ attr_reader :property_name
8
+ attr_reader :allowed_tags
9
+
10
+ def tag_is_one_of(tags)
11
+ attr_accessor :tag
12
+ @allowed_tags = tags
13
+ end
14
+
15
+ def tag(*args)
16
+ @tag = args.first if args.any?
17
+ @tag ||= nil
18
+ end
19
+
20
+ def name(*args)
21
+ @property_name = args.first if args.any?
22
+ @property_name ||= nil
23
+ end
24
+
25
+ def namespace(*args)
26
+ @namespace = args.first if args.any?
27
+ @namespace ||= nil
28
+ end
29
+
30
+ end
31
+
32
+ def initialize(tag=nil, *_args)
33
+ return unless self.class.allowed_tags
34
+ validate_tag tag
35
+ @tag = tag
36
+ end
37
+
38
+ def validate_tag(tag)
39
+ return if self.class.allowed_tags.include?(tag)
40
+ allowed = self.class.allowed_tags.join(", ")
41
+ raise ArgumentError, "Invalid tag name for #{name}: #{tag.inspect}. It should be one of #{allowed}."
42
+ end
43
+
44
+ def render?
45
+ !value.nil?
46
+ end
47
+
48
+ def name
49
+ self.class.property_name || default_name
50
+ end
51
+
52
+ def default_name
53
+ class_name.gsub(/(.)([A-Z])/, '\1_\2').downcase
54
+ end
55
+
56
+ def tag
57
+ self.class.tag || default_tag
58
+ end
59
+
60
+ def default_tag
61
+ (class_name[0, 1].downcase + class_name[1..-1]).to_sym
62
+ end
63
+
64
+ def namespace
65
+ self.class.namespace
66
+ end
67
+
68
+ private
69
+
70
+ def apply_namespace(xml)
71
+ namespace.nil? ? xml : xml[namespace]
72
+ end
73
+
74
+ def class_name
75
+ self.class.to_s.split(/::/).last
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,15 @@
1
+ module OpenXml
2
+ module Properties
3
+ class BooleanProperty < ValueProperty
4
+
5
+ def ok_values
6
+ [nil, true, false]
7
+ end
8
+
9
+ def to_xml(xml)
10
+ super if value
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ require "openxml/has_attributes"
2
+
3
+ module OpenXml
4
+ module Properties
5
+ class ComplexProperty < BaseProperty
6
+ include HasAttributes
7
+
8
+ def to_xml(xml)
9
+ return unless render?
10
+ apply_namespace(xml).public_send(tag, xml_attributes) do
11
+ yield xml if block_given?
12
+ end
13
+ end
14
+
15
+ def render?
16
+ !xml_attributes.empty?
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,70 @@
1
+ require "openxml/has_attributes"
2
+
3
+ module OpenXml
4
+ module Properties
5
+ class ContainerProperty < BaseProperty
6
+ include Enumerable
7
+ include HasAttributes
8
+
9
+ class << self
10
+
11
+ def child_class(*args)
12
+ unless args.empty?
13
+ @child_classes = args.map { |arg|
14
+ prop_name = arg.to_s.split(/_/).map(&:capitalize).join # LazyCamelCase
15
+ const_name = (self.to_s.split(/::/)[0...-1] + [prop_name]).join("::")
16
+ Object.const_get const_name
17
+ }
18
+ end
19
+
20
+ @child_classes
21
+ end
22
+ alias child_classes child_class
23
+
24
+ end
25
+
26
+ def initialize
27
+ @children = []
28
+ end
29
+
30
+ def <<(child)
31
+ raise ArgumentError, invalid_child_message unless valid_child?(child)
32
+ children << child
33
+ end
34
+
35
+ def each(*args, &block)
36
+ children.each(*args, &block)
37
+ end
38
+
39
+ def render?
40
+ !children.length.zero?
41
+ end
42
+
43
+ def to_xml(xml)
44
+ return unless render?
45
+
46
+ apply_namespace(xml).public_send(tag, xml_attributes) {
47
+ each { |child| child.to_xml(xml) }
48
+ }
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :children
54
+
55
+ def invalid_child_message
56
+ class_name = self.class.to_s.split(/::/).last
57
+ "#{class_name} must be instances of one of the following: #{child_classes}"
58
+ end
59
+
60
+ def valid_child?(child)
61
+ child_classes.any? { |child_class| child.is_a?(child_class) }
62
+ end
63
+
64
+ def child_classes
65
+ self.class.child_classes
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,15 @@
1
+ module OpenXml
2
+ module Properties
3
+ class IntegerProperty < ValueProperty
4
+
5
+ def valid?
6
+ value.is_a? Integer
7
+ end
8
+
9
+ def invalid_message
10
+ "Invalid #{name}: must be an integer"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module OpenXml
2
+ module Properties
3
+ class OnOffProperty < ValueProperty
4
+
5
+ def ok_values
6
+ [true, false, :on, :off] # :on and :off are from the Transitional Spec
7
+ end
8
+
9
+ def to_xml(xml)
10
+ if value == true
11
+ apply_namespace(xml).public_send(tag) do
12
+ yield xml if block_given?
13
+ end
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module OpenXml
2
+ module Properties
3
+ class PositiveIntegerProperty < IntegerProperty
4
+
5
+ def valid?
6
+ super && value >= 0
7
+ end
8
+
9
+ def invalid_message
10
+ "Invalid #{name}: must be a positive integer"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module OpenXml
2
+ module Properties
3
+ class StringProperty < ValueProperty
4
+
5
+ def valid?
6
+ value.is_a?(String) && !value.length.zero?
7
+ end
8
+
9
+ def invalid_message
10
+ "Invalid value for #{name}; string expected (provided: #{value.inspect})"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module OpenXml
2
+ module Properties
3
+ class ToggleProperty < OnOffProperty
4
+ # Toggle properties are no different in representation than on/off properties;
5
+ # rather, the difference is in how they compose with one another (cf.
6
+ # Section 17.7.3). It's helpful, then, to retain the concept, but entirely
7
+ # unnecessary to duplicate implementation.
8
+ # cf. Section A.6.9 of the spec, and Section A.7.9 of the transitional spec.
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module OpenXml
2
+ module Properties
3
+ class TransparentContainerProperty < ContainerProperty
4
+
5
+ def to_xml(xml)
6
+ return unless render?
7
+ each { |child| child.to_xml(xml) }
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ module OpenXml
2
+ module Properties
3
+ class ValueProperty < BaseProperty
4
+ attr_reader :value
5
+
6
+ def initialize(value)
7
+ @value = value
8
+ raise ArgumentError, invalid_message unless valid?
9
+ end
10
+
11
+ def valid?
12
+ ok_values.member? value
13
+ end
14
+
15
+ def invalid_message
16
+ "#{value.inspect} is an invalid value for #{name}; acceptable: #{ok_values.join(", ")}"
17
+ end
18
+
19
+ def render?
20
+ !value.nil?
21
+ end
22
+
23
+ def to_xml(xml)
24
+ apply_namespace(xml).public_send(tag, :"#{value_attribute}" => value) do
25
+ yield xml if block_given?
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def value_attribute
32
+ namespace.nil? ? "val" : "#{namespace}:val"
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module OpenXml
2
+ module RenderWhenEmpty
3
+
4
+ def render?
5
+ true
6
+ end
7
+
8
+ end
9
+ end
@@ -4,10 +4,12 @@ module Zip
4
4
  class InputStream
5
5
  protected
6
6
 
7
+ alias _old_get_io get_io
8
+
7
9
  # The problem in RubyZip 1.1.0 is that we only call `seek`
8
10
  # when `io` is a File. We need to move the cursor to the
9
11
  # right position when `io` is a StringIO as well.
10
- def get_io(io, offset = 0)
12
+ def get_io(io, offset=0)
11
13
  io = ::File.open(io, "rb") unless io.is_a?(IO) || io.is_a?(StringIO)
12
14
  io.seek(offset, ::IO::SEEK_SET)
13
15
  io
@@ -0,0 +1,3 @@
1
+ module OpenXml
2
+ class UnmetRequirementError < RuntimeError; end
3
+ end
@@ -19,11 +19,11 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_dependency "rubyzip", "~> 1.2.1"
22
+ spec.add_dependency "rubyzip"
23
23
  spec.add_dependency "nokogiri"
24
24
  spec.add_dependency "ox"
25
25
 
26
- spec.add_development_dependency "bundler", "~> 1.6"
26
+ spec.add_development_dependency "bundler"
27
27
  spec.add_development_dependency "rake"
28
28
  spec.add_development_dependency "minitest"
29
29
  spec.add_development_dependency "minitest-reporters"
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openxml-package
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Lail
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-13 00:00:00.000000000 Z
11
+ date: 2021-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.2.1
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.2.1
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '1.6'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '1.6'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -187,6 +187,7 @@ extra_rdoc_files: []
187
187
  files:
188
188
  - ".gitignore"
189
189
  - ".travis.yml"
190
+ - CHANGELOG.md
190
191
  - Gemfile
191
192
  - LICENSE.txt
192
193
  - README.md
@@ -195,20 +196,36 @@ files:
195
196
  - lib/openxml-package/version.rb
196
197
  - lib/openxml/builder.rb
197
198
  - lib/openxml/builder/element.rb
199
+ - lib/openxml/contains_properties.rb
198
200
  - lib/openxml/content_types_presets.rb
199
201
  - lib/openxml/element.rb
200
202
  - lib/openxml/errors.rb
201
203
  - lib/openxml/has_attributes.rb
202
204
  - lib/openxml/has_children.rb
205
+ - lib/openxml/has_properties.rb
203
206
  - lib/openxml/package.rb
204
207
  - lib/openxml/part.rb
205
208
  - lib/openxml/parts.rb
206
209
  - lib/openxml/parts/content_types.rb
207
210
  - lib/openxml/parts/rels.rb
208
211
  - lib/openxml/parts/unparsed_part.rb
212
+ - lib/openxml/properties.rb
213
+ - lib/openxml/properties/base_property.rb
214
+ - lib/openxml/properties/boolean_property.rb
215
+ - lib/openxml/properties/complex_property.rb
216
+ - lib/openxml/properties/container_property.rb
217
+ - lib/openxml/properties/integer_property.rb
218
+ - lib/openxml/properties/on_off_property.rb
219
+ - lib/openxml/properties/positive_integer_property.rb
220
+ - lib/openxml/properties/string_property.rb
221
+ - lib/openxml/properties/toggle_property.rb
222
+ - lib/openxml/properties/transparent_container_property.rb
223
+ - lib/openxml/properties/value_property.rb
209
224
  - lib/openxml/relationship.rb
225
+ - lib/openxml/render_when_empty.rb
210
226
  - lib/openxml/rubyzip_fix.rb
211
227
  - lib/openxml/types.rb
228
+ - lib/openxml/unmet_requirement.rb
212
229
  - openxml-package.gemspec
213
230
  - tmp/.keep
214
231
  homepage: https://github.com/openxml/openxml-package
@@ -230,8 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
247
  - !ruby/object:Gem::Version
231
248
  version: '0'
232
249
  requirements: []
233
- rubyforge_project:
234
- rubygems_version: 2.6.11
250
+ rubygems_version: 3.1.4
235
251
  signing_key:
236
252
  specification_version: 4
237
253
  summary: A Ruby implementation of OpenXmlPackage from Microsoft's Open XML SDK