├── .github
└── workflows
│ └── python-package.yml
├── README.md
├── excelAddinGenerator
├── __init__.py
└── main.py
├── setup.py
├── src
└── data
│ ├── [Content_Types].xml
│ ├── _rels
│ └── .rels
│ ├── docProps
│ ├── app.xml
│ └── core.xml
│ └── xl
│ ├── _rels
│ └── workbook.xml.rels
│ ├── styles.xml
│ ├── theme
│ └── theme1.xml
│ ├── workbook.xml
│ └── worksheets
│ └── sheet1.xml
└── tests
├── __init__.py
├── blank.bin
├── test.xlam
├── test_excelAddinGenerator.py
└── vbaProject.bin
/.github/workflows/python-package.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3 |
4 | name: Python package
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | python-version: ["3.8", "3.9", "3.10"]
20 |
21 | steps:
22 | - uses: actions/checkout@v3
23 | - name: Set up Python ${{ matrix.python-version }}
24 | uses: actions/setup-python@v3
25 | with:
26 | python-version: ${{ matrix.python-version }}
27 | - name: Install dependencies
28 | run: |
29 | python -m pip install --upgrade pip
30 | python -m pip install flake8 pytest
31 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
32 | if [ -f requirements_dev.txt ]; then pip install -r requirements_dev.txt; fi
33 | - name: Lint with flake8
34 | run: |
35 | # stop the build if there are Python syntax errors or undefined names
36 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
37 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
38 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
39 | - name: Test with pytest
40 | env:
41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42 | run: |
43 | pytest --cov=excelAddinGenerator
44 | coveralls --service=github
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Excel-Addin-Generator
2 | Tools to create an Excel Addin from VBA code
3 |
4 | [](https://coveralls.io/github/Beakerboy/Excel-Addin-Generator?branch=master)
5 | 
6 |
7 |
8 | Preparation
9 | ------------
10 |
11 | An Excel Addin (.xlam) is a zip archive of XML and binary files. Some of these XML files contain information specific to the computer that was used to create the xlam file. This script can be used to create a standard xlam file from the VBA code.
12 |
13 | The script can be given a source xlam file, from which the binary file is extracted and repackaged, or the binary file can be provided alone.
14 |
15 | **Usage**
16 |
17 | `python excelAddinGenerator/main.py path/to/vbaProject.bin output/to/myAddin.xlam`
18 |
19 | or
20 |
21 | `python excelAddinGenerator/main.py path/to/source.xlam output/to/myAddin.xlam`
22 |
--------------------------------------------------------------------------------
/excelAddinGenerator/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/excelAddinGenerator/main.py:
--------------------------------------------------------------------------------
1 | import shutil, sys, os, zipfile
2 |
3 | def main(args):
4 | if len(args) > 2:
5 | # check the extension on sys.argv[1] to determine which function to call
6 | input_file = args[1]
7 | output_file_name = args[2]
8 | if input_file.endswith('.xlam'):
9 | createFromZip(input_file, args[0] + '/../src/data', output_file_name)
10 | elif input_file.endswith('.bin'):
11 | createFromBin(input_file, args[0] + '/../src/data', output_file_name)
12 | else:
13 | raise Exception(input_file, " is not a valid file format.")
14 |
15 | def createFromBin(input_file, wrapper_dir, output_file_name):
16 | """Create a zip file containing the provided bin"""
17 | # file must start with 'd0 cf 11 e0 a1 b1 1a e1'
18 | fileSig = open(input_file, "rb").read(8).hex()
19 | if fileSig != 'd0cf11e0a1b11ae1':
20 | raise Exception('File signature {} is not as expected.', format(fileSig))
21 | shutil.copy(input_file, wrapper_dir + "/xl/vbaProject.bin")
22 | shutil.make_archive(output_file_name, 'zip', wrapper_dir)
23 | shutil.move(output_file_name + ".zip", output_file_name)
24 |
25 | def createFromZip(input_file, wrapper_dir, output_file_name):
26 | """Create a zip file containing the bin file within the provided zip file"""
27 | extractBinFromZip(input_file)
28 | createFromBin('xl/vbaProject.bin', wrapper_dir, output_file_name)
29 |
30 | def extractBinFromZip(input_file):
31 | # check that input is a zip file
32 | if zipfile.is_zipfile(input_file):
33 | # check that the zip archive contains /xl/vbaProject.bin
34 | with zipfile.ZipFile(input_file, 'r') as zip:
35 | zip.extract('xl/vbaProject.bin')
36 | else:
37 | raise Exception(input_file, " is not a valid file format.")
38 |
39 | if __name__ == "__main__":
40 | args = []
41 | args[0] = os.path.dirname(sys.argv[0])
42 | args[1] = sys.argv[1]
43 | args[2] = sys.argv[2]
44 | main(args)
45 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name="excelAddinGenerator",
5 | packages=['excelAddinGenerator'],
6 | tests_require=['pytest'],
7 | )
8 |
--------------------------------------------------------------------------------
/src/data/[Content_Types].xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/data/_rels/.rels:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/data/docProps/app.xml:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Excel0falseWorksheets1Sheet1Beakerboy Softwarefalsefalsefalse16.0300
--------------------------------------------------------------------------------
/src/data/docProps/core.xml:
--------------------------------------------------------------------------------
1 |
2 | Excel Addin GeneratorExcel Addin Generator2019-04-24T20:22:02Z2019-04-24T20:22:28Z
--------------------------------------------------------------------------------
/src/data/xl/_rels/workbook.xml.rels:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/data/xl/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/data/xl/theme/theme1.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/data/xl/workbook.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/data/xl/worksheets/sheet1.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/blank.bin:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/test.xlam:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Beakerboy/Excel-Addin-Generator/2d43b06e17531eb14fc58c9d0a677ebb2214e3c8/tests/test.xlam
--------------------------------------------------------------------------------
/tests/test_excelAddinGenerator.py:
--------------------------------------------------------------------------------
1 | # test_excelAddinGenerator.py
2 |
3 | import pytest
4 | from excelAddinGenerator.main import *
5 | from os.path import exists
6 | from filehash import FileHash
7 |
8 | def test_success_from_bin():
9 | """Test that xlam is successfully generated from a OLE file"""
10 | createFromBin("tests/vbaProject.bin", "src/data", "success_bin.xlam")
11 | # Assert that xlam file is created
12 | assert exists("success_bin.xlam")
13 | #assert that bin file within success_bin.xlam matches tests/vbaProject.bin
14 | extractBinFromZip("success_bin.xlam")
15 | md5hasher = FileHash('md5')
16 | assert md5hasher.hash_file("tests/vbaProject.bin") == md5hasher.hash_file("xl/vbaProject.bin")
17 |
18 | createFromZip("success_bin.xlam", "src/data", "success_xlam.xlam")
19 | assert exists("success_xlam.xlam")
20 | #assert that bin file within success_xlam.xlam matches bin file within success_bin.xlam
21 | extractBinFromZip("success_xlam.xlam")
22 | assert md5hasher.hash_file("tests/vbaProject.bin") == md5hasher.hash_file("xl/vbaProject.bin")
23 |
24 | def test_not_bin_exception():
25 | """ Test that an exception is thrown if the bin file is not an OLE file"""
26 | with pytest.raises(Exception) as e_info:
27 | createFromBin("tests/blank.bin", "src/data", "./fail.xlam")
28 |
29 | def test_xlam_not_zip():
30 | """ Test that an exception is thrown if the zip is not a zip archive"""
31 | with pytest.raises(Exception) as e_info:
32 | createFromZip("tests/blank.bin", "src/data", "./fail.xlam")
33 |
34 | def test_main():
35 | main(["./excelAddinGenerator", "./tests/vbaProject.bin", "success_bin.xlam"])
36 | main(["./excelAddinGenerator", "success_bin.xlam", "success_xlam.xlam"])
37 |
38 | def test_main_incorrect_type():
39 | """ Test that an exception is thrown if the zip is not a zip archive"""
40 | with pytest.raises(Exception) as e_info:
41 | main(["./excelAddinGenerator", "./src/data/xl/styles.xml", "fail.xlam"])
42 |
--------------------------------------------------------------------------------
/tests/vbaProject.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Beakerboy/Excel-Addin-Generator/2d43b06e17531eb14fc58c9d0a677ebb2214e3c8/tests/vbaProject.bin
--------------------------------------------------------------------------------