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 +5 -5
- data/CHANGELOG.md +8 -0
- data/lib/openxml-package/version.rb +1 -1
- data/lib/openxml/builder.rb +1 -0
- data/lib/openxml/builder/element.rb +1 -1
- data/lib/openxml/contains_properties.rb +29 -0
- data/lib/openxml/element.rb +3 -4
- data/lib/openxml/has_attributes.rb +76 -33
- data/lib/openxml/has_children.rb +3 -1
- data/lib/openxml/has_properties.rb +223 -0
- data/lib/openxml/package.rb +2 -2
- data/lib/openxml/parts/content_types.rb +1 -1
- data/lib/openxml/parts/rels.rb +6 -1
- data/lib/openxml/properties.rb +22 -0
- data/lib/openxml/properties/base_property.rb +80 -0
- data/lib/openxml/properties/boolean_property.rb +15 -0
- data/lib/openxml/properties/complex_property.rb +21 -0
- data/lib/openxml/properties/container_property.rb +70 -0
- data/lib/openxml/properties/integer_property.rb +15 -0
- data/lib/openxml/properties/on_off_property.rb +21 -0
- data/lib/openxml/properties/positive_integer_property.rb +15 -0
- data/lib/openxml/properties/string_property.rb +15 -0
- data/lib/openxml/properties/toggle_property.rb +11 -0
- data/lib/openxml/properties/transparent_container_property.rb +12 -0
- data/lib/openxml/properties/value_property.rb +37 -0
- data/lib/openxml/render_when_empty.rb +9 -0
- data/lib/openxml/rubyzip_fix.rb +3 -1
- data/lib/openxml/unmet_requirement.rb +3 -0
- data/openxml-package.gemspec +2 -2
- metadata +28 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7679189d1fa0249404f348c35516577f71094f29154a4d6db8d18d73a69c0890
|
4
|
+
data.tar.gz: a1a41c6458d7b4f6a90f29443b5778b61e74a44c9ce6a1e5fed2138077b6c2a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b739a383caa59890a27b389d020de3262d7017c3444ee49309b766eb211a1c67ce2c48e94fff0836c1f65b99193f5da6d13261555d5bf0a51b433d470d701b9
|
7
|
+
data.tar.gz: 2a78d1af5bce4eb2bdeb8ce20a2787070094b0590a5fa20ddf299a2b0650fb798ebf2e058ef493825b8807207b4e9d2946ea37a8930c5075912c3eeb4f12223f
|
data/CHANGELOG.md
ADDED
data/lib/openxml/builder.rb
CHANGED
@@ -8,7 +8,7 @@ module OpenXml
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def []=(attribute, value)
|
11
|
-
namespace_def = attribute.downcase.to_s.match
|
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
|
data/lib/openxml/element.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
|
10
|
-
|
11
|
-
|
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 ||
|
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 =
|
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
|
-
|
59
|
-
raise ArgumentError,
|
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
|
-
|
64
|
-
raise ArgumentError,
|
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
|
-
|
69
|
-
raise ArgumentError,
|
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
|
-
|
74
|
-
raise ArgumentError,
|
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
|
-
|
79
|
-
raise ArgumentError,
|
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
|
-
|
84
|
-
raise ArgumentError,
|
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
|
-
|
89
|
-
raise ArgumentError,
|
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
|
-
|
94
|
-
raise ArgumentError,
|
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
|
-
|
99
|
-
raise ArgumentError,
|
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
|
-
|
104
|
-
raise ArgumentError,
|
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
|
-
|
109
|
-
raise ArgumentError,
|
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,
|
156
|
+
valid_in? value, %i{ on off }
|
114
157
|
end
|
115
158
|
|
116
159
|
def valid_in?(value, list)
|
117
|
-
|
118
|
-
raise ArgumentError,
|
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
|
-
|
123
|
-
raise ArgumentError,
|
165
|
+
return if value =~ regexp
|
166
|
+
raise ArgumentError, "Value does not match #{regexp}"
|
124
167
|
end
|
125
168
|
|
126
169
|
end
|
data/lib/openxml/has_children.rb
CHANGED
@@ -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
|
data/lib/openxml/package.rb
CHANGED
@@ -18,7 +18,7 @@ module OpenXml
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def content_types(&block)
|
21
|
-
content_types_presets.instance_eval
|
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, "
|
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
|
-
|
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) }
|
data/lib/openxml/parts/rels.rb
CHANGED
@@ -23,7 +23,7 @@ module OpenXml
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
def add_relationship(type, target, id=
|
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,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,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 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,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
|
data/lib/openxml/rubyzip_fix.rb
CHANGED
@@ -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
|
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
|
data/openxml-package.gemspec
CHANGED
@@ -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"
|
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"
|
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.
|
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:
|
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:
|
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:
|
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: '
|
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: '
|
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
|
-
|
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
|