From 944ac4d79b9e2029b7dc7c7059b76c3e16311267 Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Sun, 20 Apr 2025 15:07:15 +0900 Subject: [PATCH 01/10] Bump up the version to v0.9.0 --- lib/docx/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/docx/version.rb b/lib/docx/version.rb index 314874c..598e3a4 100644 --- a/lib/docx/version.rb +++ b/lib/docx/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Docx #:nodoc: - VERSION = '0.8.0' + VERSION = '0.9.0' end From 111a83d5b08fa6765addfa167ab7cb0e06e83017 Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Sun, 4 May 2025 01:00:47 +0900 Subject: [PATCH 02/10] Accepts files whose extension is not .docx or which does not have extension Fix #162 --- lib/docx/document.rb | 1 - spec/docx/document_spec.rb | 6 ------ 2 files changed, 7 deletions(-) diff --git a/lib/docx/document.rb b/lib/docx/document.rb index 4fe0ee1..c2a3759 100755 --- a/lib/docx/document.rb +++ b/lib/docx/document.rb @@ -29,7 +29,6 @@ def initialize(path_or_io, options = {}) # if path-or_io is string && does not contain a null byte if (path_or_io.instance_of?(String) && !/\u0000/.match?(path_or_io)) - raise Errno::EIO.new('Invalid file format') if !File.extname(path_or_io).eql?('.docx') @zip = Zip::File.open(path_or_io) else @zip = Zip::File.open_buffer(path_or_io) diff --git a/spec/docx/document_spec.rb b/spec/docx/document_spec.rb index 81d57b8..92eaafc 100755 --- a/spec/docx/document_spec.rb +++ b/spec/docx/document_spec.rb @@ -20,12 +20,6 @@ end context 'When reading a un-supported file' do - it 'should throw file not supported error' do - expect do - Docx::Document.open(@fixtures_path + '/invalid_format.pdf') - end.to raise_error(Errno::EIO, 'Input/output error - Invalid file format') - end - it 'should throw file not found error' do invalid_path = @fixtures_path + '/invalid_file_path.docx' expect do From 0a536d4ac54b2da5615aebd6ec52e601368d339e Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Sun, 4 May 2025 01:05:26 +0900 Subject: [PATCH 03/10] Bump up the version --- lib/docx/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/docx/version.rb b/lib/docx/version.rb index 598e3a4..6c376a3 100644 --- a/lib/docx/version.rb +++ b/lib/docx/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Docx #:nodoc: - VERSION = '0.9.0' + VERSION = '0.9.1' end From 1a51b4eb05937fd46adbec427249ebe70f05f9a6 Mon Sep 17 00:00:00 2001 From: Jonathan Rochkind Date: Wed, 9 Apr 2025 13:51:56 -0400 Subject: [PATCH 04/10] Add substitute_with_block method to TextRun, that can take a block wtih access to MatchData with regex capture groups etc I needed to do a `substitute` where the match arg was a regex, and if it were gsub I'd be using a block to have access to capture groups in $1 $2 $3 etc. Because of the weird way variables $1 $2 $3 are handled in ruby and block scope, I couldn't provide a delegated block to give exactly the same API as ordinary gsub. My original idea was to do that, added on to existing #substitute. But instead, had to provide a new/alternate #substitute_with_block method, with a block that actually gets a MatchData object as arg, and can access whatever it needs from there, including capture groups and match string. --- README.md | 10 +++++++++- lib/docx/containers/text_run.rb | 13 +++++++++++++ spec/docx/document_spec.rb | 13 +++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31b4b39..6894ead 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,14 @@ doc.paragraphs.each do |p| end end +# Substitute text with access to captures, note block arg is a MatchData, a bit +# different than String.gsub. https://ruby-doc.org/3.3.7/MatchData.html +doc.paragraphs.each do |p| + p.each_text_run do |tr| + tr.substitute_with_block(/total: (\d+)/) { |match_data| "total: #{match_data[1].to_i * 10}" } + end +end + # Save document to specified path doc.save('example-edited.docx') ``` @@ -145,7 +153,7 @@ doc = Docx::Document.open('tables.docx') # Iterate over each table doc.tables.each do |table| last_row = table.rows.last - + # Copy last row and insert a new one before last row new_row = last_row.copy new_row.insert_before(last_row) diff --git a/lib/docx/containers/text_run.rb b/lib/docx/containers/text_run.rb index 55ed62c..18b83b1 100755 --- a/lib/docx/containers/text_run.rb +++ b/lib/docx/containers/text_run.rb @@ -57,6 +57,19 @@ def substitute(match, replacement) reset_text end + # Weird things with how $1/$2 in regex blocks are handled means we can't just delegate + # block to gsub to get block, we have to do it this way, with a block that gets a MatchData, + # from which captures and other match data can be retrieved. + # https://ruby-doc.org/3.3.7/MatchData.html + def substitute_with_block(match, &block) + @text_nodes.each do |text_node| + text_node.content = text_node.content.gsub(match) { |_unused_matched_string| + block.call(Regexp.last_match) + } + end + reset_text + end + def parse_formatting { italic: !@node.xpath('.//w:i').empty?, diff --git a/spec/docx/document_spec.rb b/spec/docx/document_spec.rb index 81d57b8..b19c2ba 100755 --- a/spec/docx/document_spec.rb +++ b/spec/docx/document_spec.rb @@ -206,6 +206,19 @@ expect(@doc.paragraphs[1].text).to eq('Multi-line paragraph line 1same paragraph line 2yet the same paragraph line3 ') end + + it "should replace placeholder in any line of paragraph using substitute_with_block" do + expect(@doc.paragraphs[0].text).to eq('Page title') + expect(@doc.paragraphs[1].text).to eq('Multi-line paragraph line 1_placeholder2_ line 2_placeholder3_ line3 ') + + @doc.paragraphs[1].each_text_run do |text_run| + text_run.substitute_with_block(/_placeholder(\d)_/) { |match_data| + "_replacement_#{match_data[1]}" + } + end + + expect(@doc.paragraphs[1].text).to eq('Multi-line paragraph line 1_replacement_2 line 2_replacement_3 line3 ') + end end describe 'read formatting' do From 48b3b32fb10daeb7ec30f7a1f9d168db259ed2ab Mon Sep 17 00:00:00 2001 From: Luke Hutscal <60324+girasquid@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:33:14 -0600 Subject: [PATCH 05/10] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31b4b39..f91d9c9 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ doc.bookmarks.each_pair do |bookmark_name, bookmark_object| end ``` -Don't have a local file but a buffer? Docx handles those to: +Don't have a local file but a buffer? Docx handles those too: ```ruby require 'docx' @@ -261,3 +261,4 @@ The following is a list of attributes and what they control within the style. * Default formatting of inserted elements to inherited values * Implement formattable elements. * Easier multi-line text insertion at a single bookmark (inserting paragraph nodes after the one containing the bookmark) + From 1f63926df983fcb8a1469cf8375ca8433b0e4765 Mon Sep 17 00:00:00 2001 From: Jonathan Rochkind Date: Thu, 4 Sep 2025 10:44:31 -0400 Subject: [PATCH 06/10] Allow rubyzip 3.x as well as 2.x in gemspec --- docx.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docx.gemspec b/docx.gemspec index 7526690..9099e77 100644 --- a/docx.gemspec +++ b/docx.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.7.0' s.add_dependency 'nokogiri', '~> 1.13', '>= 1.13.0' - s.add_dependency 'rubyzip', '~> 2.0' + s.add_dependency 'rubyzip', '>= 2.0', "< 4" s.add_development_dependency 'coveralls_reborn', '~> 0.21' s.add_development_dependency 'rake', '~> 13.0' From 6375d41bfb3a7bbe7ac8bac83215388ca3f3470a Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Fri, 12 Sep 2025 13:28:15 +0900 Subject: [PATCH 07/10] Update Ruby versions on CI --- .github/workflows/ruby.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 7e17389..056a003 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -17,7 +17,12 @@ jobs: strategy: matrix: - ruby: [2.7, "3.0", 3.1, 3.2] + ruby: + - 3.1 + - 3.2 + - 3.3 + - 3.4 + - head steps: - uses: actions/checkout@v2 From 1edbc5af88fc2ce211495cc89682873e8f6f53f2 Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Fri, 12 Sep 2025 14:08:43 +0900 Subject: [PATCH 08/10] Bump up the version to v0.10.0 --- lib/docx/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/docx/version.rb b/lib/docx/version.rb index 6c376a3..f7bd39e 100644 --- a/lib/docx/version.rb +++ b/lib/docx/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Docx #:nodoc: - VERSION = '0.9.1' + VERSION = '0.10.0' end From 27e24bd24c2af4ee4580bde36b38acb78de03ccd Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Fri, 12 Sep 2025 14:09:09 +0900 Subject: [PATCH 09/10] Add release workflow to github actions --- .github/workflows/release.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f4549f..c4b199c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,14 +8,21 @@ on: jobs: build: name: Build a package - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') # Run only when tagged like v1.0.1 + release: + name: Release + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') # Run only when tagged like v1.0.1 + steps: + - id: package_name + run: | + echo "package_name=$(basename pkg/*.gem | tail -n1)" >> $GITHUB_OUTPUT + + - uses: softprops/action-gh-release@v2 with: - files: packages/${{steps.package_name.outputs.package_name}}.zip + files: pkg/${{steps.package_name.outputs.package_name}} generate_release_notes: true + - uses: rubygems/release-gem@v1 From 6889c47b27a2fbf61dc52ba9d7d275852164ad46 Mon Sep 17 00:00:00 2001 From: Tatsuya Sato Date: Fri, 12 Sep 2025 14:29:54 +0900 Subject: [PATCH 10/10] Update release workflow --- .github/workflows/release.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4b199c..dbb3ccc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,13 +10,26 @@ jobs: name: Build a package runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4 + bundler-cache: true + - run: bundle exec rake build release: name: Release runs-on: ubuntu-latest + permissions: + contents: write + id-token: write if: startsWith(github.ref, 'refs/tags/') # Run only when tagged like v1.0.1 steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4 + bundler-cache: true - id: package_name run: | echo "package_name=$(basename pkg/*.gem | tail -n1)" >> $GITHUB_OUTPUT