Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions .github/workflows/excel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
name: Excel Integration
on:
push:
branches: ["*"]
pull_request:
branches: ["*"]

jobs:
test-excel-windows:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Cache Office installation
id: cache-office
uses: actions/cache@v4
with:
path: |
C:\Program Files\Microsoft Office
C:\Program Files (x86)\Microsoft Office
C:\ProgramData\Microsoft\Office
key: ${{ runner.os }}-office365-excel-${{ hashFiles('.github/workflows/excel-integration.yml') }}
restore-keys: |
${{ runner.os }}-office365-excel-

- name: Install Office via Chocolatey (if not cached)
if: steps.cache-office.outputs.cache-hit != 'true'
shell: powershell
run: |
Write-Host "Installing Microsoft Excel (this will take 10-20 minutes)..."
choco install office365business --params '/exclude:"Access Groove Lync OneDrive OneNote Outlook PowerPoint Publisher Teams Word"' -y --no-progress
timeout-minutes: 25

- name: Wait for Office installation to complete (if not cached)
if: steps.cache-office.outputs.cache-hit != 'true'
shell: powershell
run: |
Write-Host "Waiting for Excel COM objects to register..."
Start-Sleep -Seconds 30

- name: Re-register Excel COM objects (if cached)
if: steps.cache-office.outputs.cache-hit == 'true'
shell: powershell
run: |
Write-Host "Re-registering Excel COM objects from cache..."

# Find Office installation path
$officePath = if (Test-Path "C:\Program Files\Microsoft Office\Office16") {
"C:\Program Files\Microsoft Office\Office16"
} elseif (Test-Path "C:\Program Files (x86)\Microsoft Office\Office16") {
"C:\Program Files (x86)\Microsoft Office\Office16"
} else {
Write-Error "Office installation not found"
exit 1
}

Write-Host "Office found at: $officePath"

# Re-register Excel COM objects
$excelPath = Join-Path $officePath "EXCEL.EXE"
if (Test-Path $excelPath) {
Write-Host "Registering Excel..."
Start-Process -FilePath $excelPath -ArgumentList "/regserver" -Wait -NoNewWindow
}

Start-Sleep -Seconds 5

- name: Verify Excel COM objects are available
shell: powershell
run: |
try {
Write-Host "Testing Excel COM object..."
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
Write-Host "Excel COM object created successfully"
Write-Host "Excel Version: $($excel.Version)"
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
} catch {
Write-Error "Failed to create COM objects"
Write-Error $_.Exception.Message
exit 1
}

- name: Install ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true

- name: Run tests
run: |
bundle exec rake

test-excel-macos:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v5

- name: Cache Homebrew packages
id: cache-brew
uses: actions/cache@v4
with:
path: |
~/Library/Caches/Homebrew
/usr/local/Cellar/microsoft-excel
/Applications/Microsoft Excel.app
key: ${{ runner.os }}-brew-excel-${{ hashFiles('.github/workflows/excel-integration.yml') }}
restore-keys: |
${{ runner.os }}-brew-excel-

- name: Install Excel via Homebrew Cask (if not cached)
if: steps.cache-brew.outputs.cache-hit != 'true'
run: |
echo "Installing Microsoft Excel via Homebrew..."
brew update
brew install --cask microsoft-excel || true # Continue even if activation fails
timeout-minutes: 20
continue-on-error: true # Don't fail if license activation is required

- name: Verify Excel can launch without license
run: |
if [ -d "/Applications/Microsoft Excel.app" ]; then
echo "✓ Excel app bundle found"

# Just verify the app can launch and quit immediately
osascript -e 'try
tell application "Microsoft Excel"
activate
delay 2
quit
end tell
return "Excel launched successfully"
on error errMsg
return "Excel launch test: " & errMsg
end try' || true

echo "Basic Excel launch test completed"
else
echo "❌ Excel not found - failing build"
exit 1
fi

- name: Install ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true

- name: Run tests
run: |
bundle exec rake

- name: Test minimal Excel automation (no save)
if: success()
run: |
# Only test that we can open Excel, not create/save documents
ruby -e "
begin
puts 'Testing minimal Excel launch via AppleScript...'

script = <<~APPLESCRIPT
try
tell application \"Microsoft Excel\"
activate
delay 1
-- Don't create or save anything
-- Just verify app responds
quit
end tell
return \"Success\"
on error errMsg
return \"Error: \" & errMsg
end try
APPLESCRIPT

result = \`osascript -e '\#{script}'\`.strip
puts \"Result: \#{result}\"

if result.include?('Error')
puts '⚠️ Excel automation not available (likely no license)'
exit 0 # Don't fail the build
else
puts '✓ Excel basic launch test passed'
end if
rescue => e
puts \"⚠️ Excel test skipped: \#{e.message}\"
exit 0 # Don't fail the build
end
"
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
CHANGELOG
---------
- **Unreleased**

- **Unreleased**: 4.4.0
- [PR #469](https://github.com/caxlsx/caxlsx/pull/469) Add default theme file to Excel package.
- [PR #350](https://github.com/caxlsx/caxlsx/pull/350) Add package-level encryption and password protection

- **August.16.25**: 4.3.0
- [PR #421](https://github.com/caxlsx/caxlsx/pull/421) Add Rubyzip >= 2.4 support
Expand Down
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ group :development, :test do
gem 'kramdown'
gem 'yard'

if RUBY_ENGINE == 'ruby'
gem 'ooxml_crypt'
end

if RUBY_VERSION >= '2.7'
gem 'rubocop', '1.79.2'
gem 'rubocop-minitest', '0.38.1'
Expand All @@ -21,6 +25,8 @@ group :test do
gem 'minitest'
gem 'timecop'
gem 'webmock'
gem 'rspec-mocks'
gem 'win32ole', platforms: [:mingw, :x64_mingw, :mswin, :mswin64]
end

group :profile do
Expand Down
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,21 @@ cell level input data validation.

15. Support for page margins and print options

16. Support for password and non password based sheet protection.
16. Support for workbook-level encryption and password protection (requires [ooxml_crypt](https://github.com/teamsimplepay/ooxml_crypt) gem which only supports MRI Ruby.)

17. First stage interoperability support for GoogleDocs, LibreOffice,
and Numbers
17. Support for sheet-level password and non-password protection.

18. Support for defined names, which gives you repeated header rows for printing.
18. First stage interoperability support for GoogleDocs, LibreOffice, and Numbers.

19. Data labels for charts as well as series color customization.
19. Support for defined names, which gives you repeated header rows for printing.

20. Support for sheet headers and footers
20. Data labels for charts as well as series color customization.

21. Pivot Tables
21. Support for sheet headers and footers

22. Page Breaks
22. Pivot Tables

23. Page Breaks


## Install
Expand Down Expand Up @@ -127,6 +128,8 @@ Currently the following additional gems are available:
* Provides a `.axlsx` renderer to Rails so you can move all your spreadsheet code from your controller into view files.
- [activeadmin-caxlsx](https://github.com/caxlsx/activeadmin-caxlsx)
* An Active Admin plugin that includes DSL to create downloadable reports.
- [ooxml_crypt](https://github.com/teamsimplepay/ooxml_crypt)
* Required to enable workbook encryption and password protection.

## Security

Expand Down
28 changes: 28 additions & 0 deletions examples/encryption_example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Description

You may encrypt your package and protect it with a password.
Requires `ooxml_crypt` gem to be installed.

## Code

```ruby
require 'ooxml_crypt'
require 'axlsx'

p = Axlsx::Package.new
wb = p.workbook

wb.add_worksheet(name: 'Basic Worksheet') do |sheet|
sheet.add_row ['First', 'Second', 'Third']
sheet.add_row [1, 2, 3]
end

p.serialize('encrypted.xlsx', password: 'abc123')

# To decrypt the file
OoxmlCrypt.decrypt_file('encrypted.xlsx', 'abc123', 'decrypted.xlsx')
```

## Output

The output file will be encrypted and password-protected.
29 changes: 29 additions & 0 deletions examples/stream_with_password_example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Description

You may return a stream for a encrypted, password-protected package.
Requires `ooxml_crypt` gem to be installed.

## Code

```ruby
require 'ooxml_crypt'
require 'axlsx'

p = Axlsx::Package.new
wb = p.workbook

wb.add_worksheet(name: 'Basic Worksheet') do |sheet|
sheet.add_row ['First', 'Second', 'Third']
sheet.add_row [1, 2, 3]
end

stream = p.to_stream(password: 'abc123')
File.write('encrypted.xlsx', stream.read)

# To decrypt the file
OoxmlCrypt.decrypt_file('encrypted.xlsx', 'abc123', 'decrypted.xlsx')
```

## Output

The output is equivalent to using `Axlsx::Package#serialize` with password.
Loading