docx 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b777dc986b688750a284502115839f0c1fa58b50d562f1c098155e4386afe85b
4
- data.tar.gz: 6de4259ddd408787823b60557535906aacc3357d54794941df3ef7e58105c713
3
+ metadata.gz: 38d8b4c53502b022e87a0b00ac8b15c99c06d79c2a51e664364ddd52dcca52a7
4
+ data.tar.gz: e7a747b4ef58b53e279fdd0cfc516b06ab5c14820e622938bea4ec73858f637c
5
5
  SHA512:
6
- metadata.gz: 8dd7ac5d3396372c3c5e21c1a8f73e6090854397874b5f69fe26f8e289c7417dd963fd1767434c8b6af920de96a1ea7a14d165e4ea9cb027f087f2b5c5248e02
7
- data.tar.gz: '084b2be2ebf7072a472802ca449806063930723d244cda22ff6e73bcee24d9a6689d0daaf531bb2429372f639714ed8f8d18f0925c8800cbd8ff1bb313391610'
6
+ metadata.gz: f4db62333498540c5ff8ea488828d17c05629c0477fd917bd9106a061d987f368e02a16d4fe566b93c456a5b1920fe1aa5dfe3c39aa3d68682a99e83087c4f25
7
+ data.tar.gz: 12864372c581d4fb4cd7f4330b49b4aa115061dfef5b8bc9ff603adaea6a0766b6e1475ae402eef473ce7636352e8b68657be59bc18cd048d85e2314d523f416
data/README.md CHANGED
@@ -181,6 +181,78 @@ p_children = p_element.xpath("//child::*") # selects all children
181
181
  p_child = p_element.at_xpath("//child::*") # selects first child
182
182
  ```
183
183
 
184
+ ### Writing and Manipulating Styles
185
+ ``` ruby
186
+ require 'docx'
187
+
188
+ d = Docx::Document.open('example.docx')
189
+ existing_style = d.styles_configuration.style_of("Heading 1")
190
+ existing_style.font_color = "000000"
191
+
192
+ # see attributes below
193
+ new_style = d.styles_configuration.add_style("Red", name: "Red", font_color: "FF0000", font_size: 20)
194
+ new_style.bold = true
195
+
196
+ d.paragraphs.each do |p|
197
+ p.style = "Red"
198
+ end
199
+
200
+ d.paragraphs.each do |p|
201
+ p.style = "Heading 1"
202
+ end
203
+
204
+ d.styles_configuration.remove_style("Red")
205
+ ```
206
+
207
+ #### Style Attributes
208
+
209
+ The following is a list of attributes and what they control within the style.
210
+
211
+ - **id**: The unique identifier of the style. (required)
212
+ - **name**: The human-readable name of the style. (required)
213
+ - **type**: Indicates the type of the style (e.g., paragraph, character).
214
+ - **keep_next**: Boolean value controlling whether to keep a paragraph and the next one on the same page. Valid values: `true`/`false`.
215
+ - **keep_lines**: Boolean value specifying whether to keep all lines of a paragraph together on one page. Valid values: `true`/`false`.
216
+ - **page_break_before**: Boolean value indicating whether to insert a page break before the paragraph. Valid values: `true`/`false`.
217
+ - **widow_control**: Boolean value controlling widow and orphan lines in a paragraph. Valid values: `true`/`false`.
218
+ - **shading_style**: Defines the shading pattern style.
219
+ - **shading_color**: Specifies the color of the shading pattern. Valid values: Hex color codes.
220
+ - **shading_fill**: Indicates the background fill color of shading.
221
+ - **suppress_auto_hyphens**: Boolean value controlling automatic hyphenation. Valid values: `true`/`false`.
222
+ - **bidirectional_text**: Boolean value indicating if the paragraph contains bidirectional text. Valid values: `true`/`false`.
223
+ - **spacing_before**: Defines the spacing before a paragraph.
224
+ - **spacing_after**: Specifies the spacing after a paragraph.
225
+ - **line_spacing**: Indicates the line spacing of a paragraph.
226
+ - **line_rule**: Defines how line spacing is calculated.
227
+ - **indent_left**: Sets the left indentation of a paragraph.
228
+ - **indent_right**: Specifies the right indentation of a paragraph.
229
+ - **indent_first_line**: Indicates the first line indentation of a paragraph.
230
+ - **align**: Controls the text alignment within a paragraph.
231
+ - **font**: Sets the font for different scripts (ASCII, complex script, East Asian, etc.).
232
+ - **font_ascii**: Specifies the font for ASCII characters.
233
+ - **font_cs**: Indicates the font for complex script characters.
234
+ - **font_hAnsi**: Sets the font for high ANSI characters.
235
+ - **font_eastAsia**: Specifies the font for East Asian characters.
236
+ - **bold**: Boolean value controlling bold formatting. Valid values: `true`/`false`.
237
+ - **italic**: Boolean value indicating italic formatting. Valid values: `true`/`false`.
238
+ - **caps**: Boolean value controlling capitalization. Valid values: `true`/`false`.
239
+ - **small_caps**: Boolean value specifying small capital letters. Valid values: `true`/`false`.
240
+ - **strike**: Boolean value indicating strikethrough formatting. Valid values: `true`/`false`.
241
+ - **double_strike**: Boolean value defining double strikethrough formatting. Valid values: `true`/`false`.
242
+ - **outline**: Boolean value specifying outline effects. Valid values: `true`/`false`.
243
+ - **outline_level**: Indicates the outline level in a document's hierarchy.
244
+ - **font_color**: Sets the text color. Valid values: Hex color codes.
245
+ - **font_size**: Controls the font size.
246
+ - **font_size_cs**: Specifies the font size for complex script characters.
247
+ - **underline_style**: Indicates the style of underlining.
248
+ - **underline_color**: Specifies the color of the underline. Valid values: Hex color codes.
249
+ - **spacing**: Controls character spacing.
250
+ - **kerning**: Sets the space between characters.
251
+ - **position**: Controls the position of characters (superscript/subscript).
252
+ - **text_fill_color**: Sets the fill color of text. Valid values: Hex color codes.
253
+ - **vertical_alignment**: Controls the vertical alignment of text within a line.
254
+ - **lang**: Specifies the language tag for the text.
255
+
184
256
  ## Development
185
257
 
186
258
  ### todo
@@ -188,5 +260,4 @@ p_child = p_element.at_xpath("//child::*") # selects first child
188
260
  * Calculate element formatting based on values present in element properties as well as properties inherited from parents
189
261
  * Default formatting of inserted elements to inherited values
190
262
  * Implement formattable elements.
191
- * Implement styles.
192
263
  * Easier multi-line text insertion at a single bookmark (inserting paragraph nodes after the one containing the bookmark)
@@ -7,12 +7,12 @@ module Docx
7
7
  # Relation methods
8
8
  # TODO: Create a properties object, include Element
9
9
  def properties
10
- @node.at_xpath("./#{@properties_tag}")
10
+ @node.at_xpath("./w:#{@properties_tag}")
11
11
  end
12
12
 
13
13
  # Erase text within an element
14
14
  def blank!
15
- @node.xpath(".//w:t").each {|t| t.content = '' }
15
+ @node.xpath('.//w:t').each { |t| t.content = '' }
16
16
  end
17
17
 
18
18
  def remove!
@@ -15,11 +15,12 @@ module Docx
15
15
 
16
16
  # Child elements: pPr, r, fldSimple, hlink, subDoc
17
17
  # http://msdn.microsoft.com/en-us/library/office/ee364458(v=office.11).aspx
18
- def initialize(node, document_properties = {})
18
+ def initialize(node, document_properties = {}, doc = nil)
19
19
  @node = node
20
20
  @properties_tag = 'pPr'
21
21
  @document_properties = document_properties
22
22
  @font_size = @document_properties[:font_size]
23
+ @document = doc
23
24
  end
24
25
 
25
26
  # Set text of paragraph
@@ -48,6 +49,7 @@ module Docx
48
49
  html << text_run.to_html
49
50
  end
50
51
  styles = { 'font-size' => "#{font_size}pt" }
52
+ styles['color'] = "##{font_color}" if font_color
51
53
  styles['text-align'] = alignment if alignment
52
54
  html_tag(:p, content: html, styles: styles)
53
55
  end
@@ -76,20 +78,48 @@ module Docx
76
78
  end
77
79
 
78
80
  def font_size
79
- size_tag = @node.xpath('w:pPr//w:sz').first
80
- size_tag ? size_tag.attributes['val'].value.to_i / 2 : @font_size
81
+ size_attribute = @node.at_xpath('w:pPr//w:sz//@w:val')
82
+
83
+ return @font_size unless size_attribute
84
+
85
+ size_attribute.value.to_i / 2
86
+ end
87
+
88
+ def font_color
89
+ color_tag = @node.xpath('w:r//w:rPr//w:color').first
90
+ color_tag ? color_tag.attributes['val'].value : nil
91
+ end
92
+
93
+ def style
94
+ return nil unless @document
95
+
96
+ @document.style_name_of(style_id) ||
97
+ @document.default_paragraph_style
98
+ end
99
+
100
+ def style_id
101
+ style_property.get_attribute('w:val')
102
+ end
103
+
104
+ def style=(identifier)
105
+ id = @document.styles_configuration.style_of(identifier).id
106
+
107
+ style_property.set_attribute('w:val', id)
81
108
  end
82
-
109
+
110
+ alias_method :style_id=, :style=
83
111
  alias_method :text, :to_s
84
112
 
85
113
  private
86
114
 
115
+ def style_property
116
+ properties&.at_xpath('w:pStyle') || properties&.add_child('<w:pStyle/>').first
117
+ end
118
+
87
119
  # Returns the alignment if any, or nil if left
88
120
  def alignment
89
- alignment_tag = @node.xpath('.//w:jc').first
90
- alignment_tag ? alignment_tag.attributes['val'].value : nil
121
+ @node.at_xpath('.//w:jc/@w:val')&.value
91
122
  end
92
-
93
123
  end
94
124
  end
95
125
  end
@@ -0,0 +1,52 @@
1
+ require 'docx/containers/container'
2
+ require 'docx/elements/style'
3
+
4
+ module Docx
5
+ module Elements
6
+ module Containers
7
+ StyleNotFound = Class.new(StandardError)
8
+
9
+ class StylesConfiguration
10
+ def initialize(raw_styles)
11
+ @raw_styles = raw_styles
12
+ @styles_parent_node = raw_styles.root
13
+ end
14
+
15
+ attr_reader :styles, :styles_parent_node
16
+
17
+ def styles
18
+ styles_parent_node
19
+ .children
20
+ .filter_map do |style|
21
+ next unless style.get_attribute("w:styleId")
22
+
23
+ Elements::Style.new(self, style)
24
+ end
25
+ end
26
+
27
+ def style_of(id_or_name)
28
+ styles.find { |style| style.id == id_or_name || style.name == id_or_name } || raise(Errors::StyleNotFound, "Style name or id '#{id_or_name}' not found")
29
+ end
30
+
31
+ def size
32
+ styles.size
33
+ end
34
+
35
+ def add_style(id, attributes = {})
36
+ Elements::Style.create(self, {id: id, name: id}.merge(attributes))
37
+ end
38
+
39
+ def remove_style(id)
40
+ style = styles.find { |style| style.id == id }
41
+
42
+ style.node.remove
43
+ styles.delete(style)
44
+ end
45
+
46
+ def serialize(**options)
47
+ @raw_styles.serialize(**options)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -10,7 +10,8 @@ module Docx
10
10
  DEFAULT_FORMATTING = {
11
11
  italic: false,
12
12
  bold: false,
13
- underline: false
13
+ underline: false,
14
+ strike: false
14
15
  }
15
16
 
16
17
  def self.tag
@@ -60,7 +61,8 @@ module Docx
60
61
  {
61
62
  italic: !@node.xpath('.//w:i').empty?,
62
63
  bold: !@node.xpath('.//w:b').empty?,
63
- underline: !@node.xpath('.//w:u').empty?
64
+ underline: !@node.xpath('.//w:u').empty?,
65
+ strike: !@node.xpath('.//w:strike').empty?
64
66
  }
65
67
  end
66
68
 
@@ -73,6 +75,7 @@ module Docx
73
75
  html = @text
74
76
  html = html_tag(:em, content: html) if italicized?
75
77
  html = html_tag(:strong, content: html) if bolded?
78
+ html = html_tag(:s, content: html) if striked?
76
79
  styles = {}
77
80
  styles['text-decoration'] = 'underline' if underlined?
78
81
  # No need to be granular with font size down to the span level if it doesn't vary.
@@ -90,12 +93,20 @@ module Docx
90
93
  @formatting[:bold]
91
94
  end
92
95
 
96
+ def striked?
97
+ @formatting[:strike]
98
+ end
99
+
93
100
  def underlined?
94
101
  @formatting[:underline]
95
102
  end
96
103
 
97
104
  def hyperlink?
98
- @node.name == 'hyperlink'
105
+ @node.name == 'hyperlink' && external_link?
106
+ end
107
+
108
+ def external_link?
109
+ !@node.attributes['id'].nil?
99
110
  end
100
111
 
101
112
  def href
@@ -107,8 +118,11 @@ module Docx
107
118
  end
108
119
 
109
120
  def font_size
110
- size_tag = @node.xpath('w:rPr//w:sz').first
111
- size_tag ? size_tag.attributes['val'].value.to_i / 2 : @font_size
121
+ size_attribute = @node.at_xpath('w:rPr//w:sz//@w:val')
122
+
123
+ return @font_size unless size_attribute
124
+
125
+ size_attribute.value.to_i / 2
112
126
  end
113
127
 
114
128
  private
@@ -2,3 +2,4 @@ require 'docx/containers/container'
2
2
  require 'docx/containers/text_run'
3
3
  require 'docx/containers/paragraph'
4
4
  require 'docx/containers/table'
5
+ require 'docx/containers/styles_configuration'
data/lib/docx/document.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'docx/containers'
2
2
  require 'docx/elements'
3
+ require 'docx/errors'
4
+ require 'docx/helpers'
3
5
  require 'nokogiri'
4
6
  require 'zip'
5
7
 
@@ -18,6 +20,8 @@ module Docx
18
20
  # puts d.text
19
21
  # end
20
22
  class Document
23
+ include Docx::SimpleInspect
24
+
21
25
  attr_reader :xml, :doc, :zip, :styles
22
26
 
23
27
  def initialize(path_or_io, options = {})
@@ -25,6 +29,7 @@ module Docx
25
29
 
26
30
  # if path-or_io is string && does not contain a null byte
27
31
  if (path_or_io.instance_of?(String) && !/\u0000/.match?(path_or_io))
32
+ raise Errno::EIO.new('Invalid file format') if !File.extname(path_or_io).eql?('.docx')
28
33
  @zip = Zip::File.open(path_or_io)
29
34
  else
30
35
  @zip = Zip::File.open_buffer(path_or_io)
@@ -38,7 +43,7 @@ module Docx
38
43
  load_styles
39
44
  yield(self) if block_given?
40
45
  ensure
41
- @zip.close
46
+ @zip.close unless @zip.nil?
42
47
  end
43
48
 
44
49
  # This stores the current global document properties, for now
@@ -81,10 +86,11 @@ module Docx
81
86
  # Some documents have this set, others don't.
82
87
  # Values are returned as half-points, so to get points, that's why it's divided by 2.
83
88
  def font_size
84
- return nil unless @styles
89
+ size_value = @styles&.at_xpath('//w:docDefaults//w:rPrDefault//w:rPr//w:sz/@w:val')&.value
90
+
91
+ return nil unless size_value
85
92
 
86
- size_tag = @styles.xpath('//w:docDefaults//w:rPrDefault//w:rPr//w:sz').first
87
- size_tag ? size_tag.attributes['val'].value.to_i / 2 : nil
93
+ size_value.to_i / 2
88
94
  end
89
95
 
90
96
  # Hyperlink targets are extracted from the document.xml.rels file
@@ -129,13 +135,11 @@ module Docx
129
135
  next unless entry.file?
130
136
 
131
137
  out.put_next_entry(entry.name)
138
+ value = @replace[entry.name] || zip.read(entry.name)
132
139
 
133
- if @replace[entry.name]
134
- out.write(@replace[entry.name])
135
- else
136
- out.write(zip.read(entry.name))
137
- end
140
+ out.write(value)
138
141
  end
142
+
139
143
  end
140
144
  zip.close
141
145
  end
@@ -167,6 +171,18 @@ module Docx
167
171
  @replace[entry_path] = file_contents
168
172
  end
169
173
 
174
+ def default_paragraph_style
175
+ @styles.at_xpath("w:styles/w:style[@w:type='paragraph' and @w:default='1']/w:name/@w:val").value
176
+ end
177
+
178
+ def style_name_of(style_id)
179
+ styles_configuration.style_of(style_id).name
180
+ end
181
+
182
+ def styles_configuration
183
+ @styles_configuration ||= Elements::Containers::StylesConfiguration.new(@styles.dup)
184
+ end
185
+
170
186
  private
171
187
 
172
188
  def load_styles
@@ -193,11 +209,12 @@ module Docx
193
209
  #++
194
210
  def update
195
211
  replace_entry 'word/document.xml', doc.serialize(save_with: 0)
212
+ replace_entry 'word/styles.xml', styles_configuration.serialize(save_with: 0)
196
213
  end
197
214
 
198
215
  # generate Elements::Containers::Paragraph from paragraph XML node
199
216
  def parse_paragraph_from(p_node)
200
- Elements::Containers::Paragraph.new(p_node, document_properties)
217
+ Elements::Containers::Paragraph.new(p_node, document_properties, self)
201
218
  end
202
219
 
203
220
  # generate Elements::Bookmark from bookmark XML node
@@ -0,0 +1,37 @@
1
+ module Docx
2
+ module Elements
3
+ class Style
4
+ module Converters
5
+ class DefaultValueConverter
6
+ def self.encode(value)
7
+ value
8
+ end
9
+
10
+ def self.decode(value)
11
+ value
12
+ end
13
+ end
14
+
15
+ class FontSizeConverter
16
+ def self.encode(value)
17
+ value.to_i * 2
18
+ end
19
+
20
+ def self.decode(value)
21
+ value.to_i / 2
22
+ end
23
+ end
24
+
25
+ class BooleanConverter
26
+ def self.encode(value)
27
+ value ? "1" : "0"
28
+ end
29
+
30
+ def self.decode(value)
31
+ value == "1"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ module Docx
2
+ module Elements
3
+ class Style
4
+ module Validators
5
+ class DefaultValidator
6
+ def self.validate(value)
7
+ true
8
+ end
9
+ end
10
+
11
+ class ColorValidator
12
+ COLOR_REGEX = /^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
13
+
14
+ def self.validate(value)
15
+ value =~ COLOR_REGEX
16
+ end
17
+ end
18
+
19
+ class ValueValidator
20
+ def initialize(*values)
21
+ @values = values
22
+ end
23
+
24
+ def validate(value)
25
+ @values.include?(value)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,179 @@
1
+ require 'docx/helpers'
2
+ require 'docx/elements'
3
+ require 'docx/elements/style/converters'
4
+ require 'docx/elements/style/validators'
5
+
6
+ module Docx
7
+ module Elements
8
+ class Style
9
+ include Docx::SimpleInspect
10
+
11
+ class Attribute
12
+ attr_reader :name, :selectors, :required, :converter, :validator
13
+
14
+ def initialize(name, selectors, required: false, converter:, validator:)
15
+ @name = name
16
+ @selectors = selectors
17
+ @required = required
18
+ @converter = converter || Converters::DefaultValueConverter
19
+ @validator = validator || Validators::DefaultValidator
20
+ end
21
+
22
+ def required?
23
+ required
24
+ end
25
+
26
+ def retrieve_from(style)
27
+ selectors
28
+ .lazy
29
+ .filter_map { |node_xpath| style.node.at_xpath(node_xpath)&.value }
30
+ .map { |value| converter.decode(value) }
31
+ .first
32
+ end
33
+
34
+ def assign_to(style, value)
35
+ (required && value.nil?) &&
36
+ raise(Errors::StyleRequiredPropertyValue, "Required value #{name}")
37
+
38
+ validator.validate(value) ||
39
+ raise(Errors::StyleInvalidPropertyValue, "Invalid value for #{name}: '#{value.nil? ? "nil" : value}'")
40
+
41
+ encoded_value = converter.encode(value)
42
+
43
+ selectors.map do |attribute_xpath|
44
+ if (existing_attribute = style.node.at_xpath(attribute_xpath))
45
+ if encoded_value.nil?
46
+ existing_attribute.remove
47
+ else
48
+ existing_attribute.value = encoded_value.to_s
49
+ end
50
+
51
+ next encoded_value
52
+ end
53
+
54
+ next encoded_value if encoded_value.nil?
55
+
56
+ node_xpath, attribute = attribute_xpath.split("/@")
57
+
58
+ created_node =
59
+ node_xpath
60
+ .split("/")
61
+ .reduce(style.node) do |parent_node, child_xpath|
62
+ # find the child node
63
+ parent_node.at_xpath(child_xpath) ||
64
+ # or create the child node
65
+ Nokogiri::XML::Node.new(child_xpath, parent_node)
66
+ .tap { |created_child_node| parent_node << created_child_node }
67
+ end
68
+
69
+ created_node.set_attribute(attribute, encoded_value)
70
+ end
71
+ .first
72
+ end
73
+ end
74
+
75
+ @attributes = []
76
+
77
+ class << self
78
+ attr_accessor :attributes
79
+
80
+ def required_attributes
81
+ attributes.select(&:required?)
82
+ end
83
+
84
+ def attribute(name, *selectors, required: false, converter: nil, validator: nil)
85
+ new_attribute = Attribute.new(name, selectors, required: required, converter: converter, validator: validator)
86
+ attributes << new_attribute
87
+
88
+ define_method(name) do
89
+ new_attribute.retrieve_from(self)
90
+ end
91
+
92
+ define_method("#{name}=") do |value|
93
+ new_attribute.assign_to(self, value)
94
+ end
95
+ end
96
+
97
+ def create(configuration, attributes = {})
98
+ node = Nokogiri::XML::Node.new("w:style", configuration.styles_parent_node)
99
+ configuration.styles_parent_node.add_child(node)
100
+
101
+ Elements::Style.new(configuration, node, **attributes)
102
+ end
103
+ end
104
+
105
+ def initialize(configuration, node, **attributes)
106
+ @configuration = configuration
107
+ @node = node
108
+
109
+ attributes.each do |name, value|
110
+ self.send("#{name}=", value)
111
+ end
112
+ end
113
+
114
+ attr_accessor :node
115
+
116
+ attribute :id, "./@w:styleId", required: true
117
+ attribute :name, "./w:name/@w:val", "./w:next/@w:val", required: true
118
+ attribute :type, ".//@w:type", required: true, validator: Validators::ValueValidator.new("paragraph", "character", "table", "numbering")
119
+ attribute :keep_next, "./w:pPr/w:keepNext/@w:val", converter: Converters::BooleanConverter
120
+ attribute :keep_lines, "./w:pPr/w:keepLines/@w:val", converter: Converters::BooleanConverter
121
+ attribute :page_break_before, "./w:pPr/w:pageBreakBefore/@w:val", converter: Converters::BooleanConverter
122
+ attribute :widow_control, "./w:pPr/w:widowControl/@w:val", converter: Converters::BooleanConverter
123
+ attribute :shading_style, "./w:pPr/w:shd/@w:val", "./w:rPr/w:shd/@w:val"
124
+ attribute :shading_color, "./w:pPr/w:shd/@w:color", "./w:rPr/w:shd/@w:color", validator: Validators::ColorValidator
125
+ attribute :shading_fill, "./w:pPr/w:shd/@w:fill", "./w:rPr/w:shd/@w:fill"
126
+ attribute :suppress_auto_hyphens, "./w:pPr/w:suppressAutoHyphens/@w:val", converter: Converters::BooleanConverter
127
+ attribute :bidirectional_text, "./w:pPr/w:bidi/@w:val", converter: Converters::BooleanConverter
128
+ attribute :spacing_before, "./w:pPr/w:spacing/@w:before"
129
+ attribute :spacing_after, "./w:pPr/w:spacing/@w:after"
130
+ attribute :line_spacing, "./w:pPr/w:spacing/@w:line"
131
+ attribute :line_rule, "./w:pPr/w:spacing/@w:lineRule"
132
+ attribute :indent_left, "./w:pPr/w:ind/@w:left"
133
+ attribute :indent_right, "./w:pPr/w:ind/@w:right"
134
+ attribute :indent_first_line, "./w:pPr/w:ind/@w:firstLine"
135
+ attribute :align, "./w:pPr/w:jc/@w:val"
136
+ attribute :font, "./w:rPr/w:rFonts/@w:ascii", "./w:rPr/w:rFonts/@w:cs", "./w:rPr/w:rFonts/@w:hAnsi", "./w:rPr/w:rFonts/@w:eastAsia" # setting :font, will set all other fonts
137
+ attribute :font_ascii, "./w:rPr/w:rFonts/@w:ascii"
138
+ attribute :font_cs, "./w:rPr/w:rFonts/@w:cs"
139
+ attribute :font_hAnsi, "./w:rPr/w:rFonts/@w:hAnsi"
140
+ attribute :font_eastAsia, "./w:rPr/w:rFonts/@w:eastAsia"
141
+ attribute :bold, "./w:rPr/w:b/@w:val", "./w:rPr/w:bCs/@w:val", converter: Converters::BooleanConverter
142
+ attribute :italic, "./w:rPr/w:i/@w:val", "./w:rPr/w:iCs/@w:val", converter: Converters::BooleanConverter
143
+ attribute :caps, "./w:rPr/w:caps/@w:val", converter: Converters::BooleanConverter
144
+ attribute :small_caps, "./w:rPr/w:smallCaps/@w:val", converter: Converters::BooleanConverter
145
+ attribute :strike, "./w:rPr/w:strike/@w:val", converter: Converters::BooleanConverter
146
+ attribute :double_strike, "./w:rPr/w:dstrike/@w:val", converter: Converters::BooleanConverter
147
+ attribute :outline, "./w:rPr/w:outline/@w:val", converter: Converters::BooleanConverter
148
+ attribute :outline_level, "./w:pPr/w:outlineLvl/@w:val"
149
+ attribute :font_color, "./w:rPr/w:color/@w:val", validator: Validators::ColorValidator
150
+ attribute :font_size, "./w:rPr/w:sz/@w:val", "./w:rPr/w:szCs/@w:val", converter: Converters::FontSizeConverter
151
+ attribute :font_size_cs, "./w:rPr/w:szCs/@w:val", converter: Converters::FontSizeConverter
152
+ attribute :underline_style, "./w:rPr/w:u/@w:val"
153
+ attribute :underline_color, "./w:rPr/w:u/@w:color", validator: Validators::ColorValidator
154
+ attribute :spacing, "./w:rPr/w:spacing/@w:val"
155
+ attribute :kerning, "./w:rPr/w:kern/@w:val"
156
+ attribute :position, "./w:rPr/w:position/@w:val"
157
+ attribute :text_fill_color, "./w:rPr/w14:textFill/w14:solidFill/w14:srgbClr/@w14:val", validator: Validators::ColorValidator
158
+ attribute :vertical_alignment, "./w:rPr/w:vertAlign/@w:val"
159
+ attribute :lang, "./w:rPr/w:lang/@w:val"
160
+
161
+ def valid?
162
+ self.class.required_attributes.all? do |a|
163
+ attribute_value = a.retrieve_from(self)
164
+
165
+ a.validator&.validate(attribute_value)
166
+ end
167
+ end
168
+
169
+ def to_xml
170
+ node.to_xml
171
+ end
172
+
173
+ def remove
174
+ node.remove
175
+ @configuration.styles.delete(self)
176
+ end
177
+ end
178
+ end
179
+ end
data/lib/docx/elements.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require 'docx/elements/bookmark'
2
2
  require 'docx/elements/element'
3
- require 'docx/elements/text'
3
+ require 'docx/elements/text'
4
+ require 'docx/elements/style'
@@ -0,0 +1,7 @@
1
+ module Docx
2
+ module Errors
3
+ StyleNotFound = Class.new(StandardError)
4
+ StyleInvalidPropertyValue = Class.new(StandardError)
5
+ StyleRequiredPropertyValue = Class.new(StandardError)
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module Docx
2
+ module SimpleInspect
3
+ # Returns a string representation of the document that is far more readable and understandable
4
+ # than the default inspect method. But you can still get the default inspect method by passing
5
+ # true as the first argument.
6
+ def inspect(full = false)
7
+ return(super) if full
8
+
9
+ variable_values =
10
+ instance_variables.map do |var|
11
+ value = v = instance_variable_get(var).inspect
12
+
13
+ [
14
+ var,
15
+ value.length > 100 ? "#{value[0..100]}..." : value
16
+ ].join('=')
17
+ end
18
+
19
+ "#<#{self.class}:0x#{(object_id << 1).to_s(16)} #{variable_values.join(' ')}>"
20
+ end
21
+ end
22
+ end
data/lib/docx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Docx #:nodoc:
4
- VERSION = '0.8.0'
4
+ VERSION = '0.9.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Hunt
@@ -9,10 +9,9 @@ authors:
9
9
  - Higgins Dragon
10
10
  - Toms Mikoss
11
11
  - Sebastian Wittenkamp
12
- autorequire:
13
12
  bindir: bin
14
13
  cert_chain: []
15
- date: 2023-05-20 00:00:00.000000000 Z
14
+ date: 2025-04-20 00:00:00.000000000 Z
16
15
  dependencies:
17
16
  - !ruby/object:Gem::Dependency
18
17
  name: nokogiri
@@ -104,6 +103,7 @@ files:
104
103
  - lib/docx/containers.rb
105
104
  - lib/docx/containers/container.rb
106
105
  - lib/docx/containers/paragraph.rb
106
+ - lib/docx/containers/styles_configuration.rb
107
107
  - lib/docx/containers/table.rb
108
108
  - lib/docx/containers/table_cell.rb
109
109
  - lib/docx/containers/table_column.rb
@@ -113,13 +113,17 @@ files:
113
113
  - lib/docx/elements.rb
114
114
  - lib/docx/elements/bookmark.rb
115
115
  - lib/docx/elements/element.rb
116
+ - lib/docx/elements/style.rb
117
+ - lib/docx/elements/style/converters.rb
118
+ - lib/docx/elements/style/validators.rb
116
119
  - lib/docx/elements/text.rb
120
+ - lib/docx/errors.rb
121
+ - lib/docx/helpers.rb
117
122
  - lib/docx/version.rb
118
123
  homepage: https://github.com/chrahunt/docx
119
124
  licenses:
120
125
  - MIT
121
126
  metadata: {}
122
- post_install_message:
123
127
  rdoc_options: []
124
128
  require_paths:
125
129
  - lib
@@ -127,15 +131,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
131
  requirements:
128
132
  - - ">="
129
133
  - !ruby/object:Gem::Version
130
- version: 2.6.0
134
+ version: 2.7.0
131
135
  required_rubygems_version: !ruby/object:Gem::Requirement
132
136
  requirements:
133
137
  - - ">="
134
138
  - !ruby/object:Gem::Version
135
139
  version: '0'
136
140
  requirements: []
137
- rubygems_version: 3.4.1
138
- signing_key:
141
+ rubygems_version: 3.6.2
139
142
  specification_version: 4
140
143
  summary: a ruby library/gem for interacting with .docx files
141
144
  test_files: []