docx 0.8.0 → 0.9.1

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: f170295a0362d6fcbbe6313ddd9f3ccf50379eb220ab52b26caee581786471a4
4
+ data.tar.gz: f6050fc0a2959392184da37012e0a2efcb5290f8bb1d015510e9dd52abf71ee7
5
5
  SHA512:
6
- metadata.gz: 8dd7ac5d3396372c3c5e21c1a8f73e6090854397874b5f69fe26f8e289c7417dd963fd1767434c8b6af920de96a1ea7a14d165e4ea9cb027f087f2b5c5248e02
7
- data.tar.gz: '084b2be2ebf7072a472802ca449806063930723d244cda22ff6e73bcee24d9a6689d0daaf531bb2429372f639714ed8f8d18f0925c8800cbd8ff1bb313391610'
6
+ metadata.gz: de8c52dced6b5d95b7fa3d34ab8c124ce5e1539fc5874fac1b5a71d31c3f7c46af7b27cc53bd89960f122a01a5a36372825f91acd8c1d0cae715f7380a2b7625
7
+ data.tar.gz: bb39bab159ac4f5ccec77ddd3c64bd9a1a8863f80a948c7d963cfde2e6904ecc112f2c4c79e080dbfc37b2580ecc37ab4249c0d303f945a02ccae432a33671fc
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 = {})
@@ -38,7 +42,7 @@ module Docx
38
42
  load_styles
39
43
  yield(self) if block_given?
40
44
  ensure
41
- @zip.close
45
+ @zip.close unless @zip.nil?
42
46
  end
43
47
 
44
48
  # This stores the current global document properties, for now
@@ -81,10 +85,11 @@ module Docx
81
85
  # Some documents have this set, others don't.
82
86
  # Values are returned as half-points, so to get points, that's why it's divided by 2.
83
87
  def font_size
84
- return nil unless @styles
88
+ size_value = @styles&.at_xpath('//w:docDefaults//w:rPrDefault//w:rPr//w:sz/@w:val')&.value
89
+
90
+ return nil unless size_value
85
91
 
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
92
+ size_value.to_i / 2
88
93
  end
89
94
 
90
95
  # Hyperlink targets are extracted from the document.xml.rels file
@@ -129,13 +134,11 @@ module Docx
129
134
  next unless entry.file?
130
135
 
131
136
  out.put_next_entry(entry.name)
137
+ value = @replace[entry.name] || zip.read(entry.name)
132
138
 
133
- if @replace[entry.name]
134
- out.write(@replace[entry.name])
135
- else
136
- out.write(zip.read(entry.name))
137
- end
139
+ out.write(value)
138
140
  end
141
+
139
142
  end
140
143
  zip.close
141
144
  end
@@ -167,6 +170,18 @@ module Docx
167
170
  @replace[entry_path] = file_contents
168
171
  end
169
172
 
173
+ def default_paragraph_style
174
+ @styles.at_xpath("w:styles/w:style[@w:type='paragraph' and @w:default='1']/w:name/@w:val").value
175
+ end
176
+
177
+ def style_name_of(style_id)
178
+ styles_configuration.style_of(style_id).name
179
+ end
180
+
181
+ def styles_configuration
182
+ @styles_configuration ||= Elements::Containers::StylesConfiguration.new(@styles.dup)
183
+ end
184
+
170
185
  private
171
186
 
172
187
  def load_styles
@@ -193,11 +208,12 @@ module Docx
193
208
  #++
194
209
  def update
195
210
  replace_entry 'word/document.xml', doc.serialize(save_with: 0)
211
+ replace_entry 'word/styles.xml', styles_configuration.serialize(save_with: 0)
196
212
  end
197
213
 
198
214
  # generate Elements::Containers::Paragraph from paragraph XML node
199
215
  def parse_paragraph_from(p_node)
200
- Elements::Containers::Paragraph.new(p_node, document_properties)
216
+ Elements::Containers::Paragraph.new(p_node, document_properties, self)
201
217
  end
202
218
 
203
219
  # 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.1'
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.1
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-05-03 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: []