diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f4549f..dbb3ccc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,14 +8,34 @@ on: jobs: build: name: Build a package - runs-on: ubuntu-latest + steps: + - 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@v3 + - 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 - - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') # Run only when tagged like v1.0.1 + - 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 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 diff --git a/README.md b/README.md index 31b4b39..c81c7f7 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' @@ -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) @@ -261,3 +269,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) + 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' 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/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/lib/docx/version.rb b/lib/docx/version.rb index 314874c..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.8.0' + VERSION = '0.10.0' end diff --git a/spec/docx/document_spec.rb b/spec/docx/document_spec.rb index 81d57b8..edd404c 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 @@ -206,6 +200,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