├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yml ├── .gitignore ├── .python-version ├── ExcelRobot ├── __init__.py ├── base.py ├── reader.py ├── utils.py └── writer.py ├── LICENSE ├── README.md ├── docs ├── antora.yml └── modules │ └── ROOT │ ├── attachments │ └── keywords │ │ └── .gitkeep │ ├── nav.adoc │ └── pages │ ├── index.adoc │ ├── pg-changelog.adoc │ └── pg-copyright-and-license.adoc ├── poetry.lock ├── pyproject.toml ├── sonar-project.properties └── tests ├── __init__.py ├── acceptance └── ExcelRobotTest.robot ├── data ├── ExcelRobotTest.xls ├── ExcelRobotTest.xlsx └── a.txt └── unit ├── __init__.py ├── test_data_format.py ├── test_reader.py └── test_writer.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **Environment** 11 | - Library Version 12 | - Python Version 13 | - OS 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 18 | **Exception Log** 19 | Any log of exception via console, file, etc 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci-pipeline 2 | 3 | on: 4 | create: 5 | branches: [ release/** ] 6 | push: 7 | branches: 8 | - main 9 | - hotfix/** 10 | tags: [ 'v*' ] 11 | paths-ignore: 12 | - '.github/ISSUE_TEMPLATE/**' 13 | - '.github/*.yml' 14 | - '*.md' 15 | - 'LICENSE' 16 | pull_request: 17 | types: [ opened, synchronize, reopened, closed ] 18 | branches: 19 | - main 20 | - hotfix/** 21 | paths-ignore: 22 | - '.github/ISSUE_TEMPLATE/**' 23 | - '.github/*.yml' 24 | - '*.md' 25 | - 'LICENSE' 26 | 27 | env: 28 | POETRY_VERSION: 1.8.4 29 | PROJECT_NAME: robotframework-excel 30 | PROJECT_DOCS_REF: docs/main 31 | PROJECT_DOCS_PATH: ./robotframework-excel-docs 32 | DOC_PROFILE_PATH: antora/robotframework-excel 33 | ANTORA_DOC_PATH: ./docs 34 | 35 | jobs: 36 | context: 37 | uses: zero88/shared-ghactions/.github/workflows/project-context.yml@main 38 | with: 39 | profile: robotframework-excel 40 | versionFile: pyproject.toml 41 | secrets: 42 | githubToken: ${{ secrets.OSS_GITHUB_TOKEN }} 43 | gpgKey: ${{ secrets.CI_GPG_PRIVATE_KEY }} 44 | gpgPassphrase: ${{ secrets.CI_GPG_PASSPHARSE }} 45 | gpgFingerprint: ${{ secrets.CI_GPG_SUBKEY_FINGERPRINT }} 46 | 47 | build: 48 | needs: context 49 | if: needs.context.outputs.shouldBuild == 'true' 50 | strategy: 51 | matrix: 52 | # python: [ '3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12' ] 53 | python: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] 54 | poetry: [ '1.8.4' ] 55 | os: [ 'ubuntu-latest', 'windows-latest' ] 56 | fail-fast: false 57 | name: With Python-${{ matrix.python }} on ${{ matrix.os }} 58 | runs-on: ${{ matrix.os }} 59 | steps: 60 | - uses: actions/checkout@v4 61 | 62 | - uses: actions/setup-python@v5 63 | with: 64 | python-version: ${{ matrix.python }} 65 | 66 | - name: Install python-poetry 67 | uses: abatilo/actions-poetry@v3 68 | with: 69 | poetry-version: ${{ matrix.poetry }} 70 | 71 | - name: Setup poetry 72 | run: | 73 | poetry about 74 | poetry config virtualenvs.create true --local 75 | poetry config virtualenvs.in-project true --local 76 | 77 | - name: Cache virtualenv 78 | uses: actions/cache@v4 79 | with: 80 | path: ./.venv 81 | key: venv-${{ matrix.python }}-${{ hashFiles('poetry.lock') }} 82 | 83 | - name: Install dependencies 84 | run: poetry install --with test 85 | 86 | - name: Unit test 87 | run: poetry run pytest -v 88 | 89 | - name: UAT test 90 | run: | 91 | poetry run robot -L DEBUG -d out/uat -v type:xls ./tests/acceptance 92 | poetry run robot -L DEBUG -d out/uat -v type:xlsx ./tests/acceptance 93 | 94 | docs: 95 | name: Generate robot docs 96 | runs-on: ubuntu-latest 97 | needs: context 98 | if: needs.context.outputs.shouldBuild == 'true' 99 | steps: 100 | - uses: actions/checkout@v4 101 | with: 102 | ref: ${{ needs.context.outputs.sha }} 103 | 104 | - name: Checkout ${{ env.PROJECT_NAME }} ${{ env.PROJECT_DOCS_REF }} 105 | uses: actions/checkout@v4 106 | with: 107 | ref: ${{ env.PROJECT_DOCS_REF }} 108 | path: ${{ env.PROJECT_DOCS_PATH }} 109 | token: ${{ secrets.OSS_GITHUB_TOKEN }} 110 | 111 | - uses: actions/setup-python@v5 112 | with: 113 | python-version-file: '.python-version' 114 | 115 | - name: Install python-poetry 116 | uses: abatilo/actions-poetry@v3 117 | with: 118 | poetry-version: ${{ env.POETRY_VERSION }} 119 | 120 | - name: Setup poetry 121 | run: | 122 | poetry about 123 | poetry config virtualenvs.create true --local 124 | poetry config virtualenvs.in-project true --local 125 | 126 | - name: Cache virtualenv 127 | uses: actions/cache@v4 128 | with: 129 | path: ./.venv 130 | key: venv-python-version-file-${{ hashFiles('poetry.lock') }} 131 | 132 | - name: Install dependencies 133 | run: poetry install 134 | 135 | - name: Generate robot doc 136 | run: | 137 | poetry run python -m robot.libdoc -f html -v $(poetry version -s) ExcelRobot ${{ env.ANTORA_DOC_PATH }}/modules/ROOT/attachments/keywords/ExcelRobot.html 138 | 139 | - name: Import GPG key 140 | uses: crazy-max/ghaction-import-gpg@v6 141 | if: needs.context.outputs.shouldPublish == 'true' 142 | with: 143 | git_user_signingkey: true 144 | git_commit_gpgsign: false # TODO: need to sign commit 145 | git_tag_gpgsign: true 146 | gpg_private_key: ${{ secrets.CI_GPG_PRIVATE_KEY }} 147 | passphrase: ${{ secrets.CI_GPG_PASSPHARSE }} 148 | fingerprint: ${{ secrets.CI_GPG_SUBKEY_FINGERPRINT }} 149 | workdir: ${{ env.PROJECT_DOCS_PATH }} 150 | 151 | - name: Sync doc output to Git branch [${{ needs.context.outputs.docBranch }}] 152 | if: needs.context.outputs.shouldPublish == 'true' 153 | shell: bash 154 | run: | 155 | fqn_doc_path="${{ env.PROJECT_DOCS_PATH }}/${{ env.DOC_PROFILE_PATH }}" 156 | rm -rf $fqn_doc_path \ 157 | && mkdir $fqn_doc_path \ 158 | && touch $fqn_doc_path/.gitkeep \ 159 | && cp -rf ${{ env.ANTORA_DOC_PATH }}/* $fqn_doc_path 160 | cd ${{ env.PROJECT_DOCS_PATH }} 161 | git add . 162 | git diff-index --quiet HEAD || git commit -am "Update ${{ needs.context.outputs.docCommitMsg }}" 163 | if [[ '${{ needs.context.outputs.isRelease }}' == 'true' ]]; then 164 | git tag -sf -am "Release ${{ needs.context.outputs.docCommitMsg }}" ${{ needs.context.outputs.docBranch }} 165 | fi 166 | git push -u origin ${{ needs.context.outputs.docBranch }} 167 | 168 | webdocs: 169 | uses: zero88/shared-ghactions/.github/workflows/webdocs-communal-publish.yml@main 170 | needs: [ context, docs ] 171 | if: needs.context.outputs.shouldPublish == 'true' 172 | with: 173 | webdocsRepo: 'zero88/webdocs' 174 | webdocsRef: 'main' 175 | webdocsWorkflow: 'webdocs.yml' 176 | docCommitMsg: ${{ needs.context.outputs.docCommitMsg }} 177 | secrets: 178 | githubToken: ${{ secrets.OSS_GITHUB_TOKEN }} 179 | 180 | publish: 181 | name: Publish to PyPI 182 | runs-on: ubuntu-latest 183 | needs: [ context, build, docs ] 184 | if: needs.context.outputs.isRelease == 'true' 185 | environment: 186 | name: pypi 187 | url: https://pypi.org/p/${{ env.PROJECT_NAME }} 188 | permissions: 189 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 190 | steps: 191 | - uses: actions/checkout@v4 192 | with: 193 | ref: ${{ needs.context.outputs.sha }} 194 | 195 | - uses: actions/setup-python@v5 196 | with: 197 | python-version-file: '.python-version' 198 | 199 | - name: Install python-poetry 200 | uses: abatilo/actions-poetry@v3 201 | with: 202 | poetry-version: ${{ env.POETRY_VERSION }} 203 | 204 | - name: Setup poetry 205 | run: | 206 | poetry about 207 | poetry config virtualenvs.create true --local 208 | poetry config virtualenvs.in-project true --local 209 | 210 | - name: Cache virtualenv 211 | uses: actions/cache@v4 212 | with: 213 | path: ./.venv 214 | key: venv-python-version-file-${{ hashFiles('poetry.lock') }} 215 | 216 | - name: Install dependencies 217 | run: poetry install 218 | 219 | - name: Build project 220 | run: poetry build 221 | 222 | - name: Publish package distributions to PyPI 223 | uses: pypa/gh-action-pypi-publish@release/v1 224 | 225 | release: 226 | runs-on: ubuntu-latest 227 | needs: [ build, docs, publish ] 228 | if: needs.context.outputs.isRelease == 'true' 229 | steps: 230 | - name: Create GitHub Release 231 | uses: softprops/action-gh-release@v2 232 | env: 233 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 234 | with: 235 | name: Release '${{ env.PROJECT_NAME }}' ${{ needs.context.outputs.version }} 236 | tag_name: ${{ needs.context.outputs.branch }} 237 | generate_release_notes: true 238 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /dist 3 | /build 4 | *egg-info/ 5 | /*out 6 | .scannerwork 7 | .coverage 8 | 9 | build.md -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11.9 2 | -------------------------------------------------------------------------------- /ExcelRobot/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import logging 4 | import six 5 | from ExcelRobot.base import ExcelLibrary 6 | from ExcelRobot.utils import DateFormat, NumberFormat, BoolFormat 7 | 8 | 9 | class ExcelRobot(ExcelLibrary): 10 | """ 11 | This test library provides some keywords to allow opening, reading, writing, and saving Excel files from Robot Framework. 12 | 13 | *Before running tests* 14 | 15 | Prior to running tests, ExcelRobot must first be imported into your Robot test suite. 16 | 17 | Example: 18 | | Library | ExcelRobot | 19 | 20 | To setup some Excel configurations related to date format and number format, use these arguments 21 | 22 | *Excel Date Time format* 23 | | Date Format | Default: `yyyy-mm-dd` | 24 | | Time Format | Default: `HH:MM:SS AM/PM` | 25 | | Date Time Format | Default: `yyyy-mm-dd HH:MM` | 26 | For more information, check this article 27 | https://support.office.com/en-us/article/format-numbers-as-dates-or-times-418bd3fe-0577-47c8-8caa-b4d30c528309 28 | 29 | *Excel Number format* 30 | | Decimal Separator | Default: `.` | 31 | | Thousand Separator | Default: `,` | 32 | | Precision | Default: `2` | 33 | 34 | *Excel Boolean format* 35 | | Boolean Format | Default: `Yes/No` | 36 | 37 | Example: 38 | | Library | ExcelRobot | date_format='dd/mm/yyyy' 39 | """ 40 | ROBOT_LIBRARY_SCOPE = 'GLOBAL' 41 | 42 | def __init__(self, 43 | date_format='yyyy-mm-dd', time_format='HH:MM:SS AM/PM', datetime_format='yyyy-mm-dd HH:MM', 44 | decimal_sep='.', thousand_sep=',', precision='2', bool_format='Yes/No'): 45 | logging.basicConfig() 46 | logging.getLogger().setLevel(logging.INFO) 47 | logger = logging.getLogger(__name__) 48 | logger.info('ExcelRobot::Robotframework Excel Library') 49 | super(ExcelRobot, self).__init__( 50 | DateFormat(date_format, time_format, datetime_format), 51 | NumberFormat(decimal_sep, thousand_sep, precision), 52 | BoolFormat(bool_format)) 53 | -------------------------------------------------------------------------------- /ExcelRobot/base.py: -------------------------------------------------------------------------------- 1 | from ExcelRobot.reader import ExcelReader 2 | from ExcelRobot.utils import BoolFormat, DateFormat, NumberFormat 3 | from ExcelRobot.writer import ExcelWriter 4 | 5 | 6 | class ExcelLibrary(object): 7 | 8 | def __init__(self, date_format=DateFormat(), number_format=NumberFormat(), bool_format=BoolFormat()): 9 | """ 10 | Init Excel Keyword with some default configuration. 11 | 12 | Excel Date Time format 13 | https://support.office.com/en-us/article/format-numbers-as-dates-or-times-418bd3fe-0577-47c8-8caa-b4d30c528309 14 | """ 15 | self.date_format = date_format 16 | self.number_format = number_format 17 | self.bool_format = bool_format 18 | self.reader = None 19 | self.writer = None 20 | 21 | def open_excel(self, file_path): 22 | """ 23 | Opens the Excel file to read from the path provided in the file path parameter. 24 | 25 | Arguments: 26 | | File Path (string) | The Excel file name or path will be opened. If file name then openning file in current directory. | 27 | Example: 28 | 29 | | *Keywords* | *Parameters* | 30 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 31 | 32 | """ 33 | self.reader = ExcelReader(file_path, self.date_format, self.number_format, self.bool_format) 34 | 35 | def open_excel_to_write(self, file_path, new_path=None, override=False): 36 | """ 37 | Opens the Excel file to write from the path provided in the file name parameter. 38 | In case `New Path` is given, new file will be created based on content of current file. 39 | 40 | Arguments: 41 | | File Path (string) | The Excel file name or path will be opened. If file name then openning file in current directory. | 42 | | New Path | New path will be saved. | 43 | | Override (Default: `False`) | If `True`, new file will be overriden if it exists. | 44 | Example: 45 | 46 | | *Keywords* | *Parameters* | 47 | | Open Excel To Write | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 48 | 49 | """ 50 | self.writer = ExcelWriter(file_path, new_path, override, self.date_format, self.number_format, self.bool_format) 51 | 52 | def get_sheet_names(self): 53 | """ 54 | Returns the names of all the worksheets in the current workbook. 55 | 56 | Example: 57 | 58 | | *Keywords* | *Parameters* | 59 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 60 | | Get Sheets Names | | 61 | 62 | """ 63 | return self.reader.get_sheet_names() 64 | 65 | def get_number_of_sheets(self): 66 | """ 67 | Returns the number of worksheets in the current workbook. 68 | 69 | Example: 70 | 71 | | *Keywords* | *Parameters* | 72 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 73 | | Get Number of Sheets | | 74 | 75 | """ 76 | return self.reader.get_number_of_sheets() 77 | 78 | def get_column_count(self, sheet_name): 79 | """ 80 | Returns the specific number of columns of the sheet name specified. 81 | 82 | Arguments: 83 | | Sheet Name (string) | The selected sheet that the column count will be returned from. | 84 | Example: 85 | 86 | | *Keywords* | *Parameters* | 87 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 88 | | Get Column Count | TestSheet1 | 89 | 90 | """ 91 | return self.reader.get_column_count(sheet_name) 92 | 93 | def get_row_count(self, sheet_name): 94 | """ 95 | Returns the specific number of rows of the sheet name specified. 96 | 97 | Arguments: 98 | | Sheet Name (string) | The selected sheet that the row count will be returned from. | 99 | Example: 100 | 101 | | *Keywords* | *Parameters* | 102 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 103 | | Get Row Count | TestSheet1 | 104 | 105 | """ 106 | return self.reader.get_row_count(sheet_name) 107 | 108 | def get_column_values(self, sheet_name, column, include_empty_cells=True): 109 | """ 110 | Returns the specific column values of the sheet name specified. 111 | 112 | Arguments: 113 | | Sheet Name (string) | The selected sheet that the column values will be returned from. | 114 | | Column (int) | The column integer value is indicated to get values. | 115 | | Include Empty Cells (Default: `True`) | If `False` then only return cells with values. | 116 | Example: 117 | 118 | | *Keywords* | *Parameters* | 119 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | 120 | | Get Column Values | TestSheet1 | 0 | 121 | 122 | """ 123 | return self.reader.get_column_values(sheet_name, column, include_empty_cells) 124 | 125 | def get_row_values(self, sheet_name, row, include_empty_cells=True): 126 | """ 127 | Returns the specific row values of the sheet name specified. 128 | 129 | Arguments: 130 | | Sheet Name (string) | The selected sheet that the row values will be returned from. | 131 | | Row (int) | The row integer value value is indicated to get values. | 132 | | Include Empty Cells (Default: `True`) | If `False` then only return cells with values. | 133 | Example: 134 | 135 | | *Keywords* | *Parameters* | 136 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | 137 | | Get Row Values | TestSheet1 | 0 | 138 | 139 | """ 140 | return self.reader.get_row_values(sheet_name, row, include_empty_cells) 141 | 142 | def get_sheet_values(self, sheet_name, include_empty_cells=True): 143 | """ 144 | Returns the values from the sheet name specified. 145 | 146 | Arguments: 147 | | Sheet Name (string | The selected sheet that the cell values will be returned from. | 148 | | Include Empty Cells (Default: `True`) | If `False` then only return cells with values. | 149 | Example: 150 | 151 | | *Keywords* | *Parameters* | 152 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 153 | | Get Sheet Values | TestSheet1 | 154 | 155 | """ 156 | return self.reader.get_sheet_values(sheet_name, include_empty_cells) 157 | 158 | def get_workbook_values(self, include_empty_cells=True): 159 | """ 160 | Returns the values from each sheet of the current workbook. 161 | 162 | Arguments: 163 | | Include Empty Cells (Default: `True`) | If `False` then only return cells with values. | 164 | Example: 165 | 166 | | *Keywords* | *Parameters* | 167 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 168 | | Get Workbook Values | | 169 | 170 | """ 171 | return self.reader.get_workbook_values(include_empty_cells) 172 | 173 | def read_cell_data_by_name(self, sheet_name, cell_name, data_type=None, use_format=True): 174 | """ 175 | Uses the cell name to return the data from that cell. 176 | 177 | - `Data Type` indicates explicit data type to convert cell value to correct data type. 178 | - `Use Format` is False, then cell value will be raw data with correct data type. 179 | 180 | Arguments: 181 | | Sheet Name (string) | The selected sheet that the cell value will be returned from. | 182 | | Cell Name (string) | The selected cell name that the value will be returned from. | 183 | | Data Type (string) | Available options: `TEXT`, DATE`, `TIME`, `DATETIME`, `NUMBER`, `BOOL` | 184 | | Use Format (boolean) (Default: `True`) | Use format to convert data to string. | 185 | Example: 186 | 187 | | *Keywords* | *Parameters* | 188 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | 189 | | Read Cell Data By Name | TestSheet1 | A2 | 190 | 191 | """ 192 | return self.reader.read_cell_data_by_name(sheet_name, cell_name, data_type, use_format) 193 | 194 | def read_cell_data(self, sheet_name, column, row, data_type=None, use_format=True): 195 | """ 196 | Uses the column and row to return the data from that cell. 197 | 198 | - `Data Type` indicates explicit data type to convert cell value to correct data type. 199 | - `Use Format` is False, then cell value will be raw data with correct data type. 200 | 201 | Arguments: 202 | | Sheet Name (string) | The selected sheet that the cell value will be returned from. | 203 | | Column (int) | The column integer value that the cell value will be returned from. | 204 | | Row (int) | The row integer value that the cell value will be returned from. | 205 | | Data Type (string) | Available options: `TEXT`, DATE`, `TIME`, `DATETIME`, `NUMBER`, `BOOL` | 206 | | Use Format (boolean) (Default: `True`) | Use format to convert data to string. | 207 | Example: 208 | 209 | | *Keywords* | *Parameters* | 210 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | | 211 | | Read Cell Data | TestSheet1 | 0 | 0 | 212 | 213 | """ 214 | return self.reader.read_cell_data(sheet_name, column, row, data_type, use_format) 215 | 216 | def check_cell_type(self, sheet_name, column, row, data_type): 217 | """ 218 | Checks the type of value that is within the cell of the sheet name selected. 219 | 220 | Arguments: 221 | | Sheet Name (string) | The selected sheet that the cell type will be checked from. | 222 | | Column (int) | The column integer value that will be used to check the cell type. | 223 | | Row (int) | The row integer value that will be used to check the cell type. | 224 | | Data Type (string) | Available options: `DATE`, `TIME`, `DATE_TIME`, `TEXT`, `NUMBER`, `BOOL`, `EMPTY`, `ERROR` | 225 | Example: 226 | 227 | | *Keywords* | *Parameters* | | 228 | | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | | | 229 | | Check Cell Type | TestSheet1 | 0 | 0 | DATE | 230 | 231 | """ 232 | return self.reader.check_cell_type(sheet_name, column, row, data_type) 233 | 234 | def write_to_cell_by_name(self, sheet_name, cell_name, value, data_type=None): 235 | """ 236 | Write data to cell by using the given sheet name and the given cell that defines by name. 237 | 238 | If `Data Type` is not provided, `ExcelRobot` will introspect data type from given `value` to define cell type 239 | 240 | Arguments: 241 | | Sheet Name (string) | The selected sheet that the cell will be modified from. | 242 | | Cell Name (string) | The selected cell name that the value will be returned from. | 243 | | Value (string|number|datetime|boolean) | Raw value or string value then using DataType to decide data type to write | 244 | | Data Type (string) | Available options: `DATE`, `TIME`, `DATE_TIME`, `TEXT`, `NUMBER`, `BOOL` | 245 | Example: 246 | 247 | | *Keywords* | *Parameters* | 248 | | Open Excel To Write | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | | 249 | | Write To Cell By Name | TestSheet1 | A1 | 34 | | 250 | | Write To Cell By Name | TestSheet1 | A2 | 2018-03-29 | DATE | 251 | | Write To Cell By Name | TestSheet1 | A3 | YES | BOOL | 252 | 253 | """ 254 | self.writer.write_to_cell_by_name(sheet_name, cell_name, value, data_type) 255 | 256 | def write_to_cell(self, sheet_name, column, row, value, data_type=None): 257 | """ 258 | Write data to cell by using the given sheet name and the given cell that defines by column and row. 259 | 260 | If `Data Type` is not provided, `ExcelRobot` will introspect data type from given `value` to define cell type 261 | 262 | Arguments: 263 | | Sheet Name (string) | The selected sheet that the cell will be modified from. | 264 | | Column (int) | The column integer value that will be used to modify the cell. | 265 | | Row (int) | The row integer value that will be used to modify the cell. | 266 | | Value (string|number|datetime|boolean) | Raw value or string value then using DataType to decide data type to write | 267 | | Data Type (string) | Available options: `DATE`, `TIME`, `DATE_TIME`, `TEXT`, `NUMBER`, `BOOL` | 268 | Example: 269 | 270 | | *Keywords* | *Parameters* | 271 | | Open Excel To Write | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | | | | 272 | | Write To Cell | TestSheet1 | 0 | 0 | 34 | | 273 | | Write To Cell | TestSheet1 | 1 | 1 | 2018-03-29 | DATE | 274 | | Write To Cell | TestSheet1 | 2 | 2 | YES | BOOL | 275 | 276 | """ 277 | self.writer.write_to_cell(sheet_name, column, row, value, data_type) 278 | 279 | # def modify_cell_with(self, sheet_name, column, row, op, val): 280 | # """ 281 | # Using the sheet name a cell is modified with the given operation and value. 282 | 283 | # Arguments: 284 | # | Sheet Name (string) | The selected sheet that the cell will be modified from. | 285 | # | Column (int) | The column integer value that will be used to modify the cell. | 286 | # | Row (int) | The row integer value that will be used to modify the cell. | 287 | # | Operation (operator) | The operation that will be performed on the value within the cell located by the column and row values. | 288 | # | Value (int) | The integer value that will be used in conjuction with the operation parameter. | 289 | # Example: 290 | 291 | # | *Keywords* | *Parameters* | 292 | # | Open Excel | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | | | | 293 | # | Modify Cell With | TestSheet1 | 0 | 0 | * | 56 | 294 | 295 | # """ 296 | # self.writer.modify_cell_with(sheet_name, column, row, op, val) 297 | 298 | def save_excel(self): 299 | """ 300 | Saves the Excel file that was opened to write before. 301 | 302 | Example: 303 | 304 | | *Keywords* | *Parameters* | 305 | | Open Excel To Write | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | | 306 | | Write To Cell | TestSheet1 | 0 | 0 | 34 | 307 | | Save Excel | | | 308 | 309 | """ 310 | self.writer.save_excel() 311 | 312 | def create_sheet(self, sheet_name): 313 | """ 314 | Creates and appends new Excel worksheet using the new sheet name to the current workbook. 315 | 316 | Arguments: 317 | | New Sheet name (string) | The name of the new sheet added to the workbook. | 318 | Example: 319 | 320 | | *Keywords* | *Parameters* | 321 | | Open Excel To Write | C:\\Python27\\ExcelRobotTest\\ExcelRobotTest.xls | 322 | | Create Sheet | NewSheet | 323 | 324 | """ 325 | self.writer.create_sheet(sheet_name) 326 | -------------------------------------------------------------------------------- /ExcelRobot/reader.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from operator import itemgetter 3 | 4 | import natsort 5 | from ExcelRobot.utils import (BoolFormat, DataType, DateFormat, NumberFormat, 6 | excel_name2coord, get_file_path, is_file) 7 | from six import PY2 8 | from xlrd import cellname, open_workbook, xldate 9 | 10 | LOGGER = logging.getLogger(__name__) 11 | 12 | 13 | class ExcelReader(object): 14 | 15 | def __init__(self, file_path, date_format=DateFormat(), number_format=NumberFormat(), bool_format=BoolFormat()): 16 | self.file_path = get_file_path(file_path) 17 | LOGGER.info('Opening file at %s', self.file_path) 18 | if not self.file_path or not is_file(self.file_path): 19 | self._workbook = None 20 | raise IOError('Excel file is not found') if PY2 else FileNotFoundError('Excel file is not found') 21 | self._workbook = open_workbook(self.file_path, formatting_info=self.is_xls, on_demand=True) 22 | self.date_format = date_format 23 | self.number_format = number_format 24 | self.bool_format = bool_format 25 | 26 | @property 27 | def is_xls(self): 28 | return not self.file_path.endswith('.xlsx') 29 | 30 | @property 31 | def extension(self): 32 | return 'xls' if self.is_xls else 'xlsx' 33 | 34 | @property 35 | def workbook(self): 36 | if not self._workbook: 37 | self._workbook = open_workbook(self.file_path, formatting_info=self.is_xls, on_demand=True) 38 | return self._workbook 39 | 40 | def _get_sheet(self, sheet_name): 41 | return self.workbook.sheet_by_name(sheet_name) 42 | 43 | def _get_cell_type(self, sheet_name, column, row): 44 | return self._get_sheet(sheet_name).cell_type(int(row), int(column)) 45 | 46 | def get_sheet_names(self): 47 | """ 48 | Returns the names of all the worksheets in the current workbook. 49 | """ 50 | return self.workbook.sheet_names() 51 | 52 | def get_number_of_sheets(self): 53 | """ 54 | Returns the number of worksheets in the current workbook. 55 | """ 56 | return self.workbook.nsheets 57 | 58 | def get_column_count(self, sheet_name): 59 | """ 60 | Returns the specific number of columns of the sheet name specified. 61 | """ 62 | return self._get_sheet(sheet_name).ncols 63 | 64 | def get_row_count(self, sheet_name): 65 | """ 66 | Returns the specific number of rows of the sheet name specified. 67 | """ 68 | return self._get_sheet(sheet_name).nrows 69 | 70 | def get_column_values(self, sheet_name, column, include_empty_cells=True): 71 | """ 72 | Returns the specific column values of the sheet name specified. 73 | """ 74 | sheet = self._get_sheet(sheet_name) 75 | data = {} 76 | for row_index in range(sheet.nrows): 77 | cell = cellname(row_index, int(column)) 78 | value = sheet.cell(row_index, int(column)).value 79 | data[cell] = value 80 | if not include_empty_cells: 81 | data = dict([(k, v) for (k, v) in data.items() if v]) 82 | return natsort.natsorted(data.items(), key=itemgetter(0)) 83 | 84 | def get_row_values(self, sheet_name, row, include_empty_cells=True): 85 | """ 86 | Returns the specific row values of the sheet name specified. 87 | """ 88 | sheet = self._get_sheet(sheet_name) 89 | data = {} 90 | for col_index in range(sheet.ncols): 91 | cell = cellname(int(row), col_index) 92 | value = sheet.cell(int(row), col_index).value 93 | data[cell] = value 94 | if not include_empty_cells: 95 | data = dict([(k, v) for (k, v) in data.items() if v]) 96 | return natsort.natsorted(data.items(), key=itemgetter(0)) 97 | 98 | def get_sheet_values(self, sheet_name, include_empty_cells=True): 99 | """ 100 | Returns the values from the sheet name specified. 101 | """ 102 | sheet = self._get_sheet(sheet_name) 103 | data = {} 104 | for row_index in range(sheet.nrows): 105 | for col_index in range(sheet.ncols): 106 | cell = cellname(row_index, col_index) 107 | value = sheet.cell(row_index, col_index).value 108 | data[cell] = value 109 | if not include_empty_cells: 110 | data = dict([(k, v) for (k, v) in data.items() if v]) 111 | return natsort.natsorted(data.items(), key=itemgetter(0)) 112 | 113 | def get_workbook_values(self, include_empty_cells=True): 114 | """ 115 | Returns the values from each sheet of the current workbook. 116 | """ 117 | sheet_data = [] 118 | workbook_data = [] 119 | for sheet_name in self.workbook.sheet_names(): 120 | sheet_data = self.get_sheet_values(sheet_name, include_empty_cells) 121 | sheet_data.insert(0, sheet_name) 122 | workbook_data.append(sheet_data) 123 | return workbook_data 124 | 125 | def read_cell_data_by_name(self, sheet_name, cell_name, data_type=None, use_format=True): 126 | """ 127 | Uses the cell name to return the data from that cell. 128 | """ 129 | col, row = excel_name2coord(cell_name) 130 | return self.read_cell_data(sheet_name, col, row, data_type, use_format) 131 | 132 | def read_cell_data(self, sheet_name, column, row, data_type=None, use_format=True): 133 | """ 134 | Uses the column and row to return the data from that cell. 135 | 136 | :Args: 137 | data_type: Indicate explicit data type to convert 138 | use_format: Use format to convert data to string 139 | """ 140 | sheet = self._get_sheet(sheet_name) 141 | cell = sheet.cell(int(row), int(column)) 142 | ctype = DataType.parse_type_by_value(cell.ctype) 143 | value = cell.value 144 | gtype = DataType.parse_type(data_type) 145 | LOGGER.debug('Given Type: %s', gtype) 146 | LOGGER.debug('Cell Type: %s', ctype) 147 | LOGGER.debug('Cell Value: %s', value) 148 | if DataType.is_date(ctype): 149 | if gtype and not DataType.is_date(gtype): 150 | raise ValueError('Cell type does not match with given data type') 151 | date_value = xldate.xldate_as_datetime(value, self.date_format.datemode) 152 | if use_format: 153 | return self.date_format.format(gtype, date_value) 154 | elif DataType.DATE == gtype: 155 | return date_value.date() 156 | elif DataType.TIME == gtype: 157 | return date_value.time() 158 | return date_value 159 | if DataType.is_number(ctype): 160 | if gtype and not DataType.is_number(gtype): 161 | raise ValueError('Cell type does not match with given data type') 162 | return self.number_format.format(gtype, value) if use_format else value 163 | if DataType.is_bool(ctype): 164 | if gtype and not DataType.is_bool(gtype): 165 | raise ValueError('Cell type does not match with given data type') 166 | return self.bool_format.format(value) if use_format else value 167 | return value 168 | 169 | def check_cell_type(self, sheet_name, column, row, data_type): 170 | """ 171 | Checks the type of value that is within the cell of the sheet name selected. 172 | """ 173 | ctype = DataType.parse_type_by_value(self._get_cell_type(sheet_name, column, row)) 174 | gtype = DataType.parse_type(data_type) 175 | LOGGER.debug('Given Type: %s', gtype) 176 | LOGGER.debug('Cell Type: %s', ctype) 177 | return ctype == gtype 178 | -------------------------------------------------------------------------------- /ExcelRobot/utils.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import logging 3 | import numbers 4 | import os 5 | import os.path as path 6 | import re 7 | import shutil 8 | import string 9 | import tempfile 10 | from datetime import date, datetime, time 11 | from enum import Enum 12 | from random import choice 13 | 14 | from xlrd import (XL_CELL_BLANK, XL_CELL_BOOLEAN, XL_CELL_DATE, XL_CELL_EMPTY, 15 | XL_CELL_ERROR, XL_CELL_NUMBER, XL_CELL_TEXT) 16 | 17 | LOGGER = logging.getLogger(__name__) 18 | 19 | 20 | def random_temp_file(ext='txt'): 21 | return path.join(tempfile.gettempdir(), random_name() + '.' + ext) 22 | 23 | 24 | def random_name(): 25 | return ''.join(choice(string.ascii_letters) for x in range(8)) 26 | 27 | 28 | def is_file(file_path): 29 | return path.isfile(file_path) 30 | 31 | 32 | def del_file(file_path): 33 | os.remove(file_path) 34 | 35 | 36 | def copy_file(src, dest, force=False): 37 | if src == dest: 38 | raise ValueError('Same file error') 39 | if force and is_file(dest): 40 | del_file(dest) 41 | shutil.copy(src, dest) 42 | 43 | 44 | def get_file_path(file_name): 45 | """ 46 | Return None if file_name is None 47 | """ 48 | if not file_name: 49 | return None 50 | if not file_name.endswith('.xlsx') and not file_name.endswith('.xls'): 51 | raise ValueError('Only support file with extenstion: xls and xlsx') 52 | file_path = path.normpath(file_name) 53 | if len(path.splitdrive(file_path)) > 1: 54 | return path.join(os.getcwd(), file_name) 55 | return file_name 56 | 57 | 58 | def excel_name2coord(cell_name): 59 | matrix = list(filter(lambda x: x.strip(), re.split(r'(\d+)', cell_name.upper()))) 60 | LOGGER.debug('Matrix: %s', matrix) 61 | if len(matrix) != 2 or not re.match(r'[A-Z]+', matrix[0]) or not re.match(r'\d+', matrix[1]): 62 | raise ValueError('Cell name is invalid') 63 | col = int(functools.reduce(lambda s, a: s * 26 + ord(a) - ord('A') + 1, matrix[0], 0)) - 1 64 | row = int(matrix[1]) - 1 65 | LOGGER.debug('Col, Row: %d, %d', col, row) 66 | return col, row 67 | 68 | 69 | 70 | class DataType(Enum): 71 | 72 | DATE = XL_CELL_DATE 73 | TIME = XL_CELL_DATE * 11 74 | DATE_TIME = XL_CELL_DATE * 13 75 | TEXT = XL_CELL_TEXT 76 | NUMBER = XL_CELL_NUMBER 77 | CURRENCY = XL_CELL_NUMBER * 11 78 | PERCENTAGE = XL_CELL_NUMBER * 13 79 | BLANK = XL_CELL_BLANK 80 | EMPTY = XL_CELL_EMPTY 81 | ERROR = XL_CELL_ERROR 82 | BOOL = XL_CELL_BOOLEAN 83 | 84 | @staticmethod 85 | def is_date(dtype, value=None): 86 | if value and not dtype: 87 | return isinstance(value, (date, time)) 88 | return DataType.DATE == dtype or DataType.TIME == dtype or DataType.DATE_TIME == dtype 89 | 90 | @staticmethod 91 | def is_number(ntype, value=None): 92 | if value and not ntype: 93 | return isinstance(value, numbers.Number) 94 | return DataType.NUMBER == ntype or DataType.CURRENCY == ntype or DataType.PERCENTAGE == ntype 95 | 96 | @staticmethod 97 | def is_bool(btype, value=None): 98 | if value and not btype: 99 | return isinstance(value, bool) 100 | return DataType.BOOL == btype 101 | 102 | @staticmethod 103 | def parse_type_by_value(type_value): 104 | return None if type_value is None else DataType(type_value) 105 | 106 | @staticmethod 107 | def parse_type(type_name): 108 | return None if type_name is None else DataType[type_name] 109 | 110 | 111 | class DateFormat: 112 | 113 | @staticmethod 114 | def excel2python_format(dt_format): 115 | """ 116 | https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior 117 | """ 118 | if not dt_format: 119 | return '' 120 | _format = dt_format 121 | _format = re.sub(r'(?:(?=2.7" 9 | files = [ 10 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 11 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 12 | ] 13 | 14 | [[package]] 15 | name = "coverage" 16 | version = "4.5.1" 17 | description = "Code coverage measurement for Python" 18 | optional = false 19 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" 20 | files = [ 21 | {file = "coverage-4.5.1-cp26-cp26m-macosx_10_10_x86_64.whl", hash = "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc"}, 22 | {file = "coverage-4.5.1-cp27-cp27m-macosx_10_12_intel.whl", hash = "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694"}, 23 | {file = "coverage-4.5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80"}, 24 | {file = "coverage-4.5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed"}, 25 | {file = "coverage-4.5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249"}, 26 | {file = "coverage-4.5.1-cp27-cp27m-win32.whl", hash = "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5"}, 27 | {file = "coverage-4.5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508"}, 28 | {file = "coverage-4.5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1"}, 29 | {file = "coverage-4.5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9"}, 30 | {file = "coverage-4.5.1-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f"}, 31 | {file = "coverage-4.5.1-cp33-cp33m-manylinux1_i686.whl", hash = "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba"}, 32 | {file = "coverage-4.5.1-cp33-cp33m-manylinux1_x86_64.whl", hash = "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e"}, 33 | {file = "coverage-4.5.1-cp34-cp34m-macosx_10_12_x86_64.whl", hash = "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd"}, 34 | {file = "coverage-4.5.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba"}, 35 | {file = "coverage-4.5.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162"}, 36 | {file = "coverage-4.5.1-cp34-cp34m-win32.whl", hash = "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d"}, 37 | {file = "coverage-4.5.1-cp34-cp34m-win_amd64.whl", hash = "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558"}, 38 | {file = "coverage-4.5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c"}, 39 | {file = "coverage-4.5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062"}, 40 | {file = "coverage-4.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640"}, 41 | {file = "coverage-4.5.1-cp35-cp35m-win32.whl", hash = "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99"}, 42 | {file = "coverage-4.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287"}, 43 | {file = "coverage-4.5.1-cp36-cp36m-macosx_10_12_x86_64.whl", hash = "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000"}, 44 | {file = "coverage-4.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6"}, 45 | {file = "coverage-4.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc"}, 46 | {file = "coverage-4.5.1-cp36-cp36m-win32.whl", hash = "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653"}, 47 | {file = "coverage-4.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a"}, 48 | {file = "coverage-4.5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd"}, 49 | {file = "coverage-4.5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95"}, 50 | {file = "coverage-4.5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1"}, 51 | {file = "coverage-4.5.1-cp37-cp37m-win32.whl", hash = "sha256:0bf8cbbd71adfff0ef1f3a1531e6402d13b7b01ac50a79c97ca15f030dba6306"}, 52 | {file = "coverage-4.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f05a636b4564104120111800021a92e43397bc12a5c72fed7036be8556e0029e"}, 53 | {file = "coverage-4.5.1.tar.gz", hash = "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1"}, 54 | ] 55 | 56 | [[package]] 57 | name = "enum34" 58 | version = "1.1.6" 59 | description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" 60 | optional = false 61 | python-versions = "*" 62 | files = [ 63 | {file = "enum34-1.1.6-py2-none-any.whl", hash = "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79"}, 64 | {file = "enum34-1.1.6-py3-none-any.whl", hash = "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a"}, 65 | {file = "enum34-1.1.6.tar.gz", hash = "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"}, 66 | {file = "enum34-1.1.6.zip", hash = "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850"}, 67 | ] 68 | 69 | [[package]] 70 | name = "et-xmlfile" 71 | version = "2.0.0" 72 | description = "An implementation of lxml.xmlfile for the standard library" 73 | optional = false 74 | python-versions = ">=3.8" 75 | files = [ 76 | {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, 77 | {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, 78 | ] 79 | 80 | [[package]] 81 | name = "exceptiongroup" 82 | version = "1.2.2" 83 | description = "Backport of PEP 654 (exception groups)" 84 | optional = false 85 | python-versions = ">=3.7" 86 | files = [ 87 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 88 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 89 | ] 90 | 91 | [package.extras] 92 | test = ["pytest (>=6)"] 93 | 94 | [[package]] 95 | name = "iniconfig" 96 | version = "2.0.0" 97 | description = "brain-dead simple config-ini parsing" 98 | optional = false 99 | python-versions = ">=3.7" 100 | files = [ 101 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 102 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 103 | ] 104 | 105 | [[package]] 106 | name = "jdcal" 107 | version = "1.4.1" 108 | description = "Julian dates from proleptic Gregorian and Julian calendars." 109 | optional = false 110 | python-versions = "*" 111 | files = [ 112 | {file = "jdcal-1.4.1-py2.py3-none-any.whl", hash = "sha256:1abf1305fce18b4e8aa248cf8fe0c56ce2032392bc64bbd61b5dff2a19ec8bba"}, 113 | {file = "jdcal-1.4.1.tar.gz", hash = "sha256:472872e096eb8df219c23f2689fc336668bdb43d194094b5cc1707e1640acfc8"}, 114 | ] 115 | 116 | [[package]] 117 | name = "natsort" 118 | version = "5.2.0" 119 | description = "Simple yet flexible natural sorting in Python." 120 | optional = false 121 | python-versions = "*" 122 | files = [ 123 | {file = "natsort-5.2.0-py2.py3-none-any.whl", hash = "sha256:a9156f61336a8d743f67a8c9f336b1287529f67a07fe6001c7d4b5673a42308e"}, 124 | {file = "natsort-5.2.0.tar.gz", hash = "sha256:c960082d2145b04723041c4b85092546560538e29664dd197a1344d5b090bc91"}, 125 | ] 126 | 127 | [[package]] 128 | name = "openpyxl" 129 | version = "2.6.2" 130 | description = "A Python library to read/write Excel 2010 xlsx/xlsm files" 131 | optional = false 132 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 133 | files = [ 134 | {file = "openpyxl-2.6.2.tar.gz", hash = "sha256:1d2af392cef8c8227bd2ac3ebe3a28b25aba74fd4fa473ce106065f0b73bfe2e"}, 135 | ] 136 | 137 | [package.dependencies] 138 | et_xmlfile = "*" 139 | jdcal = "*" 140 | 141 | [[package]] 142 | name = "packaging" 143 | version = "24.2" 144 | description = "Core utilities for Python packages" 145 | optional = false 146 | python-versions = ">=3.8" 147 | files = [ 148 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 149 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 150 | ] 151 | 152 | [[package]] 153 | name = "pluggy" 154 | version = "1.5.0" 155 | description = "plugin and hook calling mechanisms for python" 156 | optional = false 157 | python-versions = ">=3.8" 158 | files = [ 159 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 160 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 161 | ] 162 | 163 | [package.extras] 164 | dev = ["pre-commit", "tox"] 165 | testing = ["pytest", "pytest-benchmark"] 166 | 167 | [[package]] 168 | name = "pytest" 169 | version = "8.3.3" 170 | description = "pytest: simple powerful testing with Python" 171 | optional = false 172 | python-versions = ">=3.8" 173 | files = [ 174 | {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, 175 | {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, 176 | ] 177 | 178 | [package.dependencies] 179 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 180 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 181 | iniconfig = "*" 182 | packaging = "*" 183 | pluggy = ">=1.5,<2" 184 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 185 | 186 | [package.extras] 187 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 188 | 189 | [[package]] 190 | name = "robotframework" 191 | version = "7.1.1" 192 | description = "Generic automation framework for acceptance testing and robotic process automation (RPA)" 193 | optional = false 194 | python-versions = ">=3.8" 195 | files = [ 196 | {file = "robotframework-7.1.1-py3-none-any.whl", hash = "sha256:0461360be00dfb8ce1ab3f42370fa6eea3779e41c0b8d79a1f8ddcd2ec8e3679"}, 197 | {file = "robotframework-7.1.1.zip", hash = "sha256:f85919c68c4d0837006e5f09dde1ef689f082eba2e7e64d5758753f9ee8bfea9"}, 198 | ] 199 | 200 | [[package]] 201 | name = "ruff" 202 | version = "0.7.4" 203 | description = "An extremely fast Python linter and code formatter, written in Rust." 204 | optional = false 205 | python-versions = ">=3.7" 206 | files = [ 207 | {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"}, 208 | {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"}, 209 | {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"}, 210 | {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"}, 211 | {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"}, 212 | {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"}, 213 | {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"}, 214 | {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"}, 215 | {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"}, 216 | {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"}, 217 | {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"}, 218 | {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"}, 219 | {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"}, 220 | {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"}, 221 | {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"}, 222 | {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"}, 223 | {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"}, 224 | {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"}, 225 | ] 226 | 227 | [[package]] 228 | name = "six" 229 | version = "1.11.0" 230 | description = "Python 2 and 3 compatibility utilities" 231 | optional = false 232 | python-versions = "*" 233 | files = [ 234 | {file = "six-1.11.0-py2.py3-none-any.whl", hash = "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"}, 235 | {file = "six-1.11.0.tar.gz", hash = "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"}, 236 | ] 237 | 238 | [[package]] 239 | name = "tomli" 240 | version = "2.1.0" 241 | description = "A lil' TOML parser" 242 | optional = false 243 | python-versions = ">=3.8" 244 | files = [ 245 | {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, 246 | {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, 247 | ] 248 | 249 | [[package]] 250 | name = "xlrd" 251 | version = "1.2.0" 252 | description = "Library for developers to extract data from Microsoft Excel (tm) spreadsheet files" 253 | optional = false 254 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 255 | files = [ 256 | {file = "xlrd-1.2.0-py2.py3-none-any.whl", hash = "sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde"}, 257 | {file = "xlrd-1.2.0.tar.gz", hash = "sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2"}, 258 | ] 259 | 260 | [[package]] 261 | name = "xlutils" 262 | version = "2.0.0" 263 | description = "Utilities for working with Excel files that require both xlrd and xlwt" 264 | optional = false 265 | python-versions = "*" 266 | files = [ 267 | {file = "xlutils-2.0.0-py2.py3-none-any.whl", hash = "sha256:b56640862c030e9d53104e7f1d750135fdfabf6bf55e425e5ac40fdee9dbaeb9"}, 268 | {file = "xlutils-2.0.0.tar.gz", hash = "sha256:7e0e2c233bd185fecf5e2bd3f4e9469ca4a3bd87da64c82cfe5b2af27e7f9e54"}, 269 | ] 270 | 271 | [package.dependencies] 272 | xlrd = ">=0.7.2" 273 | xlwt = ">=0.7.4" 274 | 275 | [package.extras] 276 | build = ["pkginfo", "setuptools-git", "sphinx", "twine", "wheel"] 277 | test = ["coveralls", "errorhandler", "manuel", "mock", "nose", "nose-cov", "nose-fixes", "testfixtures", "tox"] 278 | 279 | [[package]] 280 | name = "xlwt" 281 | version = "1.3.0" 282 | description = "Library to create spreadsheet files compatible with MS Excel 97/2000/XP/2003 XLS files, on any platform, with Python 2.6, 2.7, 3.3+" 283 | optional = false 284 | python-versions = "*" 285 | files = [ 286 | {file = "xlwt-1.3.0-py2.py3-none-any.whl", hash = "sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e"}, 287 | {file = "xlwt-1.3.0.tar.gz", hash = "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"}, 288 | ] 289 | 290 | [metadata] 291 | lock-version = "2.0" 292 | python-versions = "^3.8" 293 | content-hash = "90a26cdd33593d6714b0f15f209c94103bee1bc2b1ec0d6357ddbf4b0b848652" 294 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | package-mode = true 3 | name = "robotframework-excel" 4 | version = "2.0.0" 5 | description = "This test library provides some keywords to allow opening, reading, writing, and saving Excel files from Robot Framework." 6 | authors = ["zero88 <10863525+zero88@users.noreply.github.com>"] 7 | license = "Apache-2.0" 8 | readme = "README.md" 9 | homepage = "https://github.com/zero88/robotframework-excel" 10 | repository = "https://github.com/zero88/robotframework-excel" 11 | documentation = "https://zero88.github.io/webdocs/robotframework-excel" 12 | keywords = ["robotframework", "excel"] 13 | classifiers =[ 14 | "Development Status :: 5 - Production/Stable", 15 | "Framework :: Robot Framework :: Library", 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: Apache Software License", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3.8", 20 | "Topic :: Software Development :: Testing", 21 | "Topic :: Software Development :: Quality Assurance", 22 | ] 23 | packages = [ 24 | { include = "ExcelRobot" } 25 | ] 26 | 27 | [tool.poetry.dependencies] 28 | python = "^3.8" 29 | six = "1.11.0" 30 | natsort = "5.2.0" 31 | xlutils = "2.0.0" 32 | xlrd = "1.2.0" 33 | xlwt = "1.3.0" 34 | openpyxl = "2.6.2" 35 | robotframework = ">=3.0" 36 | enum34 = "1.1.6" 37 | 38 | [tool.poetry.group.test.dependencies] 39 | coverage = "4.5.1" 40 | pytest = "^8.3.3" 41 | 42 | [tool.poetry.group.dev.dependencies] 43 | ruff = "^0.7.3" 44 | 45 | [tool.poetry.scripts] 46 | #unittest = "nosetests tests.unit -v --with-xunit --xunit-file=out/unit/nosetests.xml -s --debug=ExcelRobot" 47 | #uat-xls = "robot -L DEBUG -d out/uat -v type:xls ./tests/acceptance" 48 | #uat-xlsx = "robot -L DEBUG -d out/uat -v type:xlsx ./tests/acceptance" 49 | 50 | [tool.poetry.urls] 51 | "Bug Tracker" = "https://github.com/zero88/robotframework-excel/issues" 52 | 53 | [tool.ruff] 54 | line-length = 120 55 | 56 | [tool.ruff.format] 57 | quote-style = "single" 58 | indent-style = "space" 59 | docstring-code-format = true 60 | 61 | [build-system] 62 | requires = ["poetry-core"] 63 | build-backend = "poetry.core.masonry.api" 64 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # must be unique in a given SonarQube instance 2 | sonar.projectKey=robotframework-excel 3 | sonar.organization=zero-88-github 4 | # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. 5 | 6 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 7 | # This property is optional if sonar.modules is set. 8 | sonar.sources=ExcelRobot 9 | sonar.tests=tests 10 | 11 | # Encoding of the source code. Default is default system encoding 12 | sonar.sourceEncoding=UTF-8 13 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | -------------------------------------------------------------------------------- /tests/acceptance/ExcelRobotTest.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library ExcelRobot 3 | Library OperatingSystem 4 | Suite Setup Init Test 5 | 6 | *** Variables *** 7 | ${Names} 8 | ${Num} 9 | ${Excel_File} ExcelRobotTest 10 | ${xlsx} xlsx 11 | ${xls} xls 12 | ${type} ${xlsx} 13 | ${Test_Data_Path} ${CURDIR}${/}..${/}data${/} 14 | ${Out_Data_Path} ${TEMPDIR}${/}excelrobot${/} 15 | ${SheetName} Graph Data 16 | ${NewSheetName} NewSheet 17 | ${NewSheetName} NewSheet 18 | 19 | *** Test Cases *** 20 | Read Excel 21 | Get Values excel_type=${type} 22 | 23 | Write Excel 1 24 | Create New Excel excel_type=${type} 25 | 26 | Write Excel 2 27 | Create Excel From Existing File excel_type=${type} 28 | 29 | Write Excel 3 30 | Create New Sheet excel_type=${type} 31 | 32 | Write Excel 4 33 | Write New Value excel_type=${type} 34 | 35 | *** Keywords *** 36 | Init Test 37 | Wait Until Keyword Succeeds 3x 2sec Remove Directory ${Out_Data_Path} True 38 | Create Directory ${Out_Data_Path} 39 | Copy Files ${Test_Data_Path}${/}* ${Out_Data_Path} 40 | 41 | Get Values 42 | [Arguments] ${excel_type} 43 | Open Excel ${Out_Data_Path}${Excel_File}.${excel_type} 44 | ${Names}= Get Sheet Names 45 | Set Suite Variable ${Names} 46 | ${Num}= Get Number of Sheets 47 | Set Suite Variable ${Num} 48 | ${Col}= Get Column Count TestSheet1 49 | ${Row}= Get Row Count TestSheet1 50 | ${ColVal}= Get Column Values TestSheet2 1 51 | ${RowVal}= Get Row Values TestSheet2 1 52 | ${Sheet}= Get Sheet Values DataSheet 53 | Log ${Sheet} 54 | ${Workbook}= Get Workbook Values False 55 | Log ${Workbook} 56 | ${ByName}= Read Cell Data By Name GraphSheet B2 57 | ${ByCoords}= Read Cell Data GraphSheet 1 1 58 | Check Cell Type TestSheet1 0 1 TEXT 59 | 60 | 61 | Create Excel From Existing File 62 | [Arguments] ${excel_type} 63 | Open Excel To Write ${Out_Data_Path}${Excel_File}.${excel_type} ${Out_Data_Path}Clone_${Excel_File}.${excel_type} True 64 | Save Excel 65 | 66 | Create New Excel 67 | [Arguments] ${excel_type} 68 | Open Excel To Write ${Out_Data_Path}NewExcelSheet.${excel_type} 69 | Save Excel 70 | 71 | Create New Sheet 72 | [Arguments] ${excel_type} 73 | Open Excel To Write ${Out_Data_Path}NewExcelSheet.${excel_type} 74 | Create Sheet ${NewSheetName} 75 | Save Excel 76 | 77 | Write New Value 78 | [Arguments] ${excel_type} 79 | Open Excel To Write ${Out_Data_Path}WriteExcelSheet.${excel_type} 80 | Create Sheet ${NewSheetName} 81 | Write To Cell By Name ${NewSheetName} A1 abc 82 | Write To Cell By Name ${NewSheetName} A2 34 83 | Write To Cell By Name ${NewSheetName} A3 True 84 | Write To Cell ${NewSheetName} 0 4 xx TEXT 85 | Save Excel 86 | -------------------------------------------------------------------------------- /tests/data/ExcelRobotTest.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero88/robotframework-excel/93d25783103d15513455a2ba315f759c32effad9/tests/data/ExcelRobotTest.xls -------------------------------------------------------------------------------- /tests/data/ExcelRobotTest.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero88/robotframework-excel/93d25783103d15513455a2ba315f759c32effad9/tests/data/ExcelRobotTest.xlsx -------------------------------------------------------------------------------- /tests/data/a.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero88/robotframework-excel/93d25783103d15513455a2ba315f759c32effad9/tests/data/a.txt -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | -------------------------------------------------------------------------------- /tests/unit/test_data_format.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import pytest 4 | 5 | from ExcelRobot.utils import DateFormat 6 | 7 | 8 | @pytest.mark.parametrize( 9 | 'pattern,expected', 10 | [ 11 | ('yyyy-mm-dd', '%Y-%m-%d'), 12 | ('yyyymmdd', '%Y%m%d'), 13 | ('dd/mm/yyyy', '%d/%m/%Y'), 14 | ('mmm, dd yyyy', '%b, %d %Y'), 15 | ('m/d/yy', '%-m/%-d/%y'), 16 | ('HH:MM:SS', '%H:%M:%S'), 17 | ('HH.MM.SS', '%H.%M.%S'), 18 | ('HH:MM:SS AM/PM', '%I:%M:%S %p'), 19 | ('HHMMSS-A/P', '%I%M%S-%p'), 20 | ('yyyy mm dd HH:MM:SS', '%Y %m %d %H:%M:%S'), 21 | ], 22 | ) 23 | def test_date_format(pattern, expected): 24 | assert DateFormat.excel2python_format(pattern) == expected 25 | -------------------------------------------------------------------------------- /tests/unit/test_reader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os.path as path 3 | from datetime import datetime 4 | 5 | import pytest 6 | from six import PY2 7 | 8 | from ExcelRobot.reader import ExcelReader 9 | from ExcelRobot.utils import DataType 10 | 11 | CURRENT_DIR = path.dirname(path.abspath(__file__)) 12 | DATA_DIR = path.join(CURRENT_DIR, '../data') 13 | 14 | 15 | def test_open_not_valid(): 16 | with pytest.raises(ValueError): 17 | ExcelReader(path.join(DATA_DIR, 'a.txt')) 18 | 19 | 20 | def test_open_not_found(): 21 | with pytest.raises(IOError if PY2 else FileNotFoundError): 22 | ExcelReader(path.join(DATA_DIR, 'a.xls')) 23 | 24 | 25 | @pytest.mark.parametrize('input_file,expected', [('ExcelRobotTest.xls', 5), ('ExcelRobotTest.xlsx', 5)]) 26 | def test_open_success(input_file, expected): 27 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 28 | assert reader.get_number_of_sheets() == expected 29 | 30 | 31 | @pytest.mark.parametrize( 32 | 'input_file,expected', 33 | [('ExcelRobotTest.xls', 'TestSheet1'), ('ExcelRobotTest.xlsx', 'TestSheet1')], 34 | ) 35 | def test_sheet_name(input_file, expected): 36 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 37 | assert expected in reader.get_sheet_names() 38 | 39 | 40 | @pytest.mark.parametrize( 41 | 'input_file, sheet_name, col_count, row_count', 42 | [ 43 | ('ExcelRobotTest.xls', 'TestSheet1', 2, 3), 44 | ('ExcelRobotTest.xlsx', 'TestSheet1', 2, 3), 45 | ], 46 | ) 47 | def test_sheet_size(input_file, sheet_name, col_count, row_count): 48 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 49 | assert reader.get_column_count(sheet_name) == col_count 50 | assert reader.get_row_count(sheet_name) == row_count 51 | 52 | 53 | @pytest.mark.parametrize( 54 | 'input_file, sheet_name, column, expected', 55 | [ 56 | ('ExcelRobotTest.xls', 'TestSheet1', 0, [('A1', 'This is a test sheet'), ('A2', 'User1'), ('A3', 'User2')]), 57 | ('ExcelRobotTest.xls', 'TestSheet1', 1, [('B1', 'Points'), ('B2', 57), ('B3', 5178)]), 58 | ('ExcelRobotTest.xlsx', 'TestSheet1', 0, [('A1', 'This is a test sheet'), ('A2', 'User1'), ('A3', 'User2')]), 59 | ('ExcelRobotTest.xlsx', 'TestSheet1', 1, [('B1', 'Points'), ('B2', 57), ('B3', 5178)]), 60 | ], 61 | ) 62 | def test_get_col_values(input_file, sheet_name, column, expected): 63 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 64 | assert reader.get_column_values(sheet_name, column) == expected 65 | 66 | 67 | @pytest.mark.parametrize( 68 | 'input_file, sheet_name, column, expected', 69 | [ 70 | ('ExcelRobotTest.xls', 'TestSheet2', 0, [('A1', 'This is a test sheet'), ('B1', 'Date of Birth')]), 71 | ('ExcelRobotTest.xls', 'TestSheet2', 1, [('A2', 'User3'), ('B2', '23.8.1982')]), 72 | ('ExcelRobotTest.xlsx', 'TestSheet2', 0, [('A1', 'This is a test sheet'), ('B1', 'Date of Birth')]), 73 | ('ExcelRobotTest.xlsx', 'TestSheet2', 1, [('A2', 'User3'), ('B2', '23.8.1982')]), 74 | ], 75 | ) 76 | def test_get_row_values(input_file, sheet_name, column, expected): 77 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 78 | assert reader.get_row_values(sheet_name, column) == expected 79 | 80 | 81 | @pytest.mark.parametrize( 82 | 'input_file, sheet_name, cell_name, expected', 83 | [ 84 | ('ExcelRobotTest.xls', 'TestSheet1', 'a2', 'User1'), 85 | ('ExcelRobotTest.xls', 'TestSheet1', 'B2', '57.00'), 86 | ('ExcelRobotTest.xls', 'TestSheet2', 'B2', '23.8.1982'), 87 | ('ExcelRobotTest.xlsx', 'TestSheet1', 'A2', 'User1'), 88 | ('ExcelRobotTest.xlsx', 'TestSheet1', 'B2', '57.00'), 89 | ('ExcelRobotTest.xlsx', 'TestSheet2', 'B2', '23.8.1982'), 90 | ], 91 | ) 92 | def test_get_cell_value_by_name(input_file, sheet_name, cell_name, expected): 93 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 94 | assert reader.read_cell_data_by_name(sheet_name, cell_name) == expected 95 | 96 | 97 | @pytest.mark.parametrize( 98 | 'input_file, sheet_name, col, row, data_type, expected', 99 | [ 100 | ('ExcelRobotTest.xls', 'TestSheet1', 0, 1, None, 'User1'), 101 | ('ExcelRobotTest.xls', 'TestSheet1', 1, 1, DataType.NUMBER.name, '57.00'), 102 | ('ExcelRobotTest.xls', 'TestSheet2', 1, 1, DataType.TEXT.name, '23.8.1982'), 103 | ('ExcelRobotTest.xls', 'TestSheet3', 2, 1, DataType.DATE.name, '1982-05-14'), 104 | ('ExcelRobotTest.xls', 'TestSheet3', 3, 1, DataType.BOOL.name, 'Yes'), 105 | ('ExcelRobotTest.xls', 'TestSheet3', 6, 1, DataType.NUMBER.name, '7.50'), 106 | ('ExcelRobotTest.xls', 'TestSheet3', 7, 1, DataType.TIME.name, '08:00:00 AM'), 107 | ('ExcelRobotTest.xls', 'TestSheet3', 8, 1, DataType.DATE_TIME.name, '2018-01-02 22:00'), 108 | ('ExcelRobotTest.xlsx', 'TestSheet1', 0, 1, None, 'User1'), 109 | ('ExcelRobotTest.xlsx', 'TestSheet1', 1, 1, DataType.NUMBER.name, '57.00'), 110 | ('ExcelRobotTest.xlsx', 'TestSheet2', 1, 1, DataType.TEXT.name, '23.8.1982'), 111 | ('ExcelRobotTest.xlsx', 'TestSheet3', 2, 1, DataType.DATE.name, '1982-05-14'), 112 | ('ExcelRobotTest.xlsx', 'TestSheet3', 3, 1, DataType.BOOL.name, 'Yes'), 113 | ('ExcelRobotTest.xlsx', 'TestSheet3', 6, 1, DataType.NUMBER.name, '7.50'), 114 | ('ExcelRobotTest.xlsx', 'TestSheet3', 7, 1, DataType.TIME.name, '08:00:00 AM'), 115 | ('ExcelRobotTest.xlsx', 'TestSheet3', 8, 1, DataType.DATE_TIME.name, '2018-01-02 22:00'), 116 | ], 117 | ) 118 | def test_get_cell_value_by_coord(input_file, sheet_name, col, row, data_type, expected): 119 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 120 | assert reader.read_cell_data(sheet_name, col, row, data_type=data_type) == expected 121 | 122 | 123 | @pytest.mark.parametrize( 124 | 'input_file, sheet_name, col, row, expected', 125 | [ 126 | ('ExcelRobotTest.xls', 'TestSheet1', 0, 1, 'User1'), 127 | ('ExcelRobotTest.xls', 'TestSheet1', 1, 1, 57), 128 | ('ExcelRobotTest.xls', 'TestSheet2', 1, 1, '23.8.1982'), 129 | ('ExcelRobotTest.xls', 'TestSheet3', 2, 1, datetime(1982, 5, 14)), 130 | ('ExcelRobotTest.xls', 'TestSheet3', 3, 1, True), 131 | ('ExcelRobotTest.xls', 'TestSheet3', 6, 1, 7.5), 132 | ('ExcelRobotTest.xlsx', 'TestSheet1', 0, 1, 'User1'), 133 | ('ExcelRobotTest.xlsx', 'TestSheet1', 1, 1, 57), 134 | ('ExcelRobotTest.xlsx', 'TestSheet2', 1, 1, '23.8.1982'), 135 | ('ExcelRobotTest.xlsx', 'TestSheet3', 2, 1, datetime(1982, 5, 14)), 136 | ('ExcelRobotTest.xlsx', 'TestSheet3', 3, 1, True), 137 | ('ExcelRobotTest.xlsx', 'TestSheet3', 6, 1, 7.5), 138 | ], 139 | ) 140 | def test_get_cell_raw_value_by_coord(input_file, sheet_name, col, row, expected): 141 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 142 | assert reader.read_cell_data(sheet_name, col, row, use_format=False) == expected 143 | 144 | 145 | @pytest.mark.parametrize( 146 | 'input_file, sheet_name, col, row, data_type, expected', 147 | [ 148 | ('ExcelRobotTest.xls', 'TestSheet3', 0, 1, DataType.NUMBER.name, True), 149 | ('ExcelRobotTest.xls', 'TestSheet3', 1, 1, DataType.TEXT.name, True), 150 | ('ExcelRobotTest.xls', 'TestSheet3', 2, 1, DataType.DATE.name, True), 151 | ('ExcelRobotTest.xls', 'TestSheet3', 3, 1, DataType.BOOL.name, True), 152 | ('ExcelRobotTest.xls', 'TestSheet3', 5, 1, DataType.EMPTY.name, True), 153 | ('ExcelRobotTest.xlsx', 'TestSheet3', 0, 1, DataType.NUMBER.name, True), 154 | ('ExcelRobotTest.xlsx', 'TestSheet3', 1, 1, DataType.TEXT.name, True), 155 | ('ExcelRobotTest.xlsx', 'TestSheet3', 2, 1, DataType.DATE.name, True), 156 | ('ExcelRobotTest.xlsx', 'TestSheet3', 3, 1, DataType.BOOL.name, True), 157 | ('ExcelRobotTest.xlsx', 'TestSheet3', 5, 1, DataType.EMPTY.name, True), 158 | ], 159 | ) 160 | def test_check_cell_type(input_file, sheet_name, col, row, data_type, expected): 161 | reader = ExcelReader(path.join(DATA_DIR, input_file)) 162 | assert reader.check_cell_type(sheet_name, col, row, data_type) == expected 163 | -------------------------------------------------------------------------------- /tests/unit/test_writer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import os.path as path 5 | import shutil 6 | import tempfile 7 | from datetime import date, datetime, time 8 | 9 | import pytest 10 | from six import PY2 11 | 12 | from ExcelRobot.reader import ExcelReader 13 | from ExcelRobot.utils import DataType, copy_file, random_name 14 | from ExcelRobot.writer import ExcelWriter 15 | 16 | CURRENT_DIR = path.dirname(path.abspath(__file__)) 17 | DATA_DIR = path.join(CURRENT_DIR, '../data') 18 | TEMP_DIR = path.join(tempfile.gettempdir(), 'ExcelRobot') 19 | 20 | 21 | @pytest.fixture(autouse=True, scope='session') 22 | def setup_module(): 23 | try: 24 | os.makedirs(TEMP_DIR) 25 | except OSError as _: 26 | shutil.rmtree(TEMP_DIR) 27 | os.makedirs(TEMP_DIR) 28 | 29 | yield 30 | shutil.rmtree(TEMP_DIR) 31 | 32 | 33 | @pytest.fixture 34 | def create_tmp_file(): 35 | open(path.join(TEMP_DIR, 'temp.xls'), mode='w+').close() 36 | yield 37 | os.remove(path.join(TEMP_DIR, 'temp.xls')) 38 | 39 | 40 | def test_open_without_override(create_tmp_file): 41 | with pytest.raises(IOError if PY2 else FileExistsError): 42 | ExcelWriter(path.join(DATA_DIR, 'ExcelRobotTest.xls'), new_path=path.join(TEMP_DIR, 'temp.xls'), override=False) 43 | 44 | 45 | @pytest.mark.parametrize('input_file, nb_of_sheets, sheet_name', [('1.xls', 1, 'Sheet'), ('2.xlsx', 1, 'Sheet')]) 46 | def test_open_new_file(input_file, nb_of_sheets, sheet_name): 47 | writer = ExcelWriter(path.join(TEMP_DIR, input_file)) 48 | writer.save_excel() 49 | assert writer.get_number_of_sheets() == nb_of_sheets 50 | assert sheet_name in writer.get_sheet_names() 51 | 52 | 53 | @pytest.mark.parametrize( 54 | 'input_file, nb_of_sheets, sheet_name', 55 | [('ExcelRobotTest.xls', 5, 'TestSheet3'), ('ExcelRobotTest.xlsx', 5, 'TestSheet3')], 56 | ) 57 | def test_open_existed_file(input_file, nb_of_sheets, sheet_name): 58 | new_file = path.join(TEMP_DIR, random_name() + '_' + input_file) 59 | writer = ExcelWriter(path.join(DATA_DIR, input_file), new_file) 60 | writer.save_excel() 61 | assert writer.get_number_of_sheets() == nb_of_sheets 62 | assert sheet_name in writer.get_sheet_names() 63 | 64 | 65 | @pytest.mark.parametrize( 66 | 'input_file, sheet_name', [('ExcelRobotTest.xls', 'TestSheet10'), ('ExcelRobotTest.xlsx', 'TestSheet10')] 67 | ) 68 | def test_create_sheet_in_new_file(input_file, sheet_name): 69 | new_file = path.join(TEMP_DIR, random_name() + '_' + input_file) 70 | writer = ExcelWriter(path.join(DATA_DIR, input_file), new_file) 71 | writer.create_sheet(sheet_name) 72 | writer.save_excel() 73 | assert sheet_name in ExcelReader(new_file).get_sheet_names() 74 | 75 | 76 | @pytest.mark.parametrize( 77 | 'input_file, sheet_name', [('ExcelRobotTest.xls', 'TestSheet11'), ('ExcelRobotTest.xlsx', 'TestSheet11')] 78 | ) 79 | def test_create_sheet_in_same_file(input_file, sheet_name): 80 | # TODO: Prepare data in tmp 81 | test_file = path.join(TEMP_DIR, random_name() + '_' + input_file) 82 | copy_file(path.join(DATA_DIR, input_file), test_file, True) 83 | writer = ExcelWriter(path.join(DATA_DIR, test_file)) 84 | writer.create_sheet(sheet_name) 85 | writer.save_excel() 86 | assert sheet_name in ExcelReader(test_file).get_sheet_names() 87 | 88 | 89 | def list_data(): 90 | d1 = {'row': 0, 'column': 0, 'raw': 'Name', 'value': 'Name', 'type': DataType.TEXT.name} 91 | d2 = {'row': 0, 'column': 1, 'raw': 25.5, 'value': '25.50', 'type': DataType.NUMBER.name} 92 | d3 = {'row': 0, 'column': 2, 'raw': date(2018, 1, 1), 'value': '2018-01-01', 'type': DataType.DATE.name} 93 | d4 = {'row': 0, 'column': 3, 'raw': time(8, 0, 0), 'value': '08:00:00 AM', 'type': DataType.TIME.name} 94 | d5 = { 95 | 'row': 0, 96 | 'column': 4, 97 | 'raw': datetime(2018, 1, 2, 22, 00), 98 | 'value': '2018-01-02 22:00', 99 | 'type': DataType.DATE_TIME.name, 100 | } 101 | d6 = {'row': 0, 'column': 5, 'raw': True, 'value': 'Yes', 'type': DataType.BOOL.name} 102 | return [d1, d2, d3, d4, d5, d6] 103 | 104 | 105 | @pytest.mark.parametrize( 106 | 'input_file, sheet_name, data', 107 | list(map(lambda x: ('ExcelRobotTest.xls', 'TestSheet20', x), list_data())) 108 | + list(map(lambda x: ('ExcelRobotTest.xlsx', 'TestSheet20', x), list_data())), 109 | ) 110 | def test_write_raw_value_in_new_sheet(input_file, sheet_name, data): 111 | new_file = path.join(TEMP_DIR, random_name() + '_' + input_file) 112 | writer = ExcelWriter(path.join(DATA_DIR, input_file), new_file) 113 | writer.create_sheet(sheet_name) 114 | writer.write_to_cell(sheet_name, data['column'], data['row'], data['raw']) 115 | writer.save_excel() 116 | reader = ExcelReader(new_file) 117 | assert ( 118 | reader.read_cell_data(sheet_name, data['column'], data['row'], data_type=data['type'], use_format=False) 119 | == data['raw'] 120 | ) 121 | assert ( 122 | reader.read_cell_data(sheet_name, data['column'], data['row'], data_type=data['type'], use_format=True) 123 | == data['value'] 124 | ) 125 | 126 | 127 | @pytest.mark.parametrize( 128 | 'input_file, sheet_name, data', 129 | list(map(lambda x: ('ExcelRobotTest.xls', 'TestSheet30', x), list_data())) 130 | + list(map(lambda x: ('ExcelRobotTest.xlsx', 'TestSheet30', x), list_data())), 131 | ) 132 | def test_write_format_value_in_new_sheet(input_file, sheet_name, data): 133 | new_file = path.join(TEMP_DIR, random_name() + '_' + input_file) 134 | writer = ExcelWriter(path.join(DATA_DIR, input_file), new_file) 135 | writer.create_sheet(sheet_name) 136 | writer.write_to_cell(sheet_name, data['column'], data['row'], data['value'], data_type=data['type']) 137 | writer.save_excel() 138 | reader = ExcelReader(new_file) 139 | assert ( 140 | reader.read_cell_data(sheet_name, data['column'], data['row'], data_type=data['type'], use_format=False) 141 | == data['raw'] 142 | ) 143 | assert ( 144 | reader.read_cell_data(sheet_name, data['column'], data['row'], data_type=data['type'], use_format=True) 145 | == data['value'] 146 | ) 147 | --------------------------------------------------------------------------------