├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── lint.yml │ ├── moban-update.yml │ ├── pythonpublish.yml │ └── tests.yml ├── .gitignore ├── .moban.d ├── README.rst ├── docs │ └── source │ │ ├── conf.py │ │ └── index.rst ├── handsontable │ ├── handsontable.rst.jj2 │ ├── rtd_style.css.jj2 │ └── style.css.jj2 ├── requirements.txt ├── setup.py ├── test.bat ├── test.sh └── tests │ ├── fixtures │ ├── book.handsontable.html │ └── sheet_rendering.html │ └── requirements.txt ├── .moban.yml ├── .readthedocs.yml ├── .travis.yml ├── CHANGELOG.rst ├── CONTRIBUTORS.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── changelog.yml ├── demo ├── Makefile ├── README.rst ├── census.xls ├── demo.handsontable.html ├── demo.py ├── handsontable.full.min.css ├── handsontable.full.min.js ├── jupyter-demo.png ├── requirements.txt ├── screenshot.png └── working-jupyter-notebook.ipynb ├── docs └── source │ ├── conf.py │ └── index.rst ├── format.sh ├── lint.sh ├── pyexcel-handsontable.yml ├── pyexcel_handsontable ├── __init__.py ├── handsontable.py └── templates │ ├── components.html │ ├── css.html │ ├── embed.html │ ├── full.html │ ├── notebook.html │ ├── pyexcel-handsontable │ ├── ZeroClipboard.js │ ├── ZeroClipboard.swf │ ├── handsontable.min.css │ ├── handsontable.min.js │ ├── main.js │ ├── moment-2.18.1.min.js │ ├── numbro-1.11.0.min.js │ └── pikaday-1.5.1.min.js │ ├── registry.json │ └── sheet.html ├── requirements.txt ├── rnd_requirements.txt ├── setup.py ├── styles ├── rtd_style.css ├── rtd_style.scss ├── style.css └── style.scss ├── test.bat ├── test.sh └── tests ├── fixtures ├── book.handsontable.html ├── book.jupyter_notebook ├── sheet.jupyter_notebook ├── sheet_rendering.html ├── sheet_rendering_custom_urls.html └── sheet_rendering_embed.html ├── mytestwrapper.py ├── requirements.txt ├── test_book.py └── test_sheet.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: chfw 4 | patreon: chfw 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | With your PR, here is a check list: 2 | 3 | - [ ] Has test cases written? 4 | - [ ] Has all code lines tested? 5 | - [ ] Has `make format` been run? 6 | - [ ] Please update CHANGELOG.yml(not CHANGELOG.rst) 7 | - [ ] Has fair amount of documentation if your change is complex 8 | - [ ] Agree on NEW BSD License for your contribution 9 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | name: lint code 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Set up Python 12 | uses: actions/setup-python@v1 13 | with: 14 | python-version: 3.8 15 | - name: lint 16 | run: | 17 | pip --use-deprecated=legacy-resolver install flake8 18 | pip --use-deprecated=legacy-resolver install -r tests/requirements.txt 19 | flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long . 20 | python setup.py checkdocs 21 | -------------------------------------------------------------------------------- /.github/workflows/moban-update.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | jobs: 4 | run_moban: 5 | runs-on: ubuntu-latest 6 | name: synchronize templates via moban 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | ref: ${{ github.head_ref }} 11 | token: ${{ secrets.PAT }} 12 | - name: Set up Python 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: '3.7' 16 | - name: check changes 17 | run: | 18 | pip install moban gitfs2 pypifs moban-jinja2-github moban-ansible 19 | moban 20 | git status 21 | git diff --exit-code 22 | - name: Auto-commit 23 | if: failure() 24 | uses: stefanzweifel/git-auto-commit-action@v4 25 | with: 26 | commit_message: >- 27 | This is an auto-commit, updating project meta data, 28 | such as changelog.rst, contributors.rst 29 | -------------------------------------------------------------------------------- /.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 23 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 24 | run: | 25 | python setup.py sdist bdist_wheel 26 | twine upload dist/* 27 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run unit tests on Windows, Ubuntu and Mac 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | python-version: [3.7, 3.8, 3.9] 11 | os: [macOs-latest, ubuntu-latest, windows-latest] 12 | exclude: 13 | - os: macOs-latest 14 | python-version: 3.7 15 | 16 | runs-on: ${{ matrix.os }} 17 | name: run tests 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Python 21 | uses: actions/setup-python@v1 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: install 25 | run: | 26 | pip --use-deprecated=legacy-resolver install -r requirements.txt 27 | pip --use-deprecated=legacy-resolver install -r tests/requirements.txt 28 | - name: test 29 | run: | 30 | pip freeze 31 | nosetests --verbosity=3 --with-coverage --cover-package pyexcel_handsontable --cover-package tests tests docs/source pyexcel_handsontable 32 | - name: Upload coverage 33 | uses: codecov/codecov-action@v1 34 | with: 35 | name: ${{ matrix.os }} Python ${{ matrix.python-version }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # moban hashes 2 | .moban.hashes 3 | 4 | # Extra rules from https://github.com/github/gitignore/ 5 | # Python rules 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 103 | __pypackages__/ 104 | 105 | # Celery stuff 106 | celerybeat-schedule 107 | celerybeat.pid 108 | 109 | # SageMath parsed files 110 | *.sage.py 111 | 112 | # Environments 113 | .env 114 | .venv 115 | env/ 116 | venv/ 117 | ENV/ 118 | env.bak/ 119 | venv.bak/ 120 | 121 | # Spyder project settings 122 | .spyderproject 123 | .spyproject 124 | 125 | # Rope project settings 126 | .ropeproject 127 | 128 | # mkdocs documentation 129 | /site 130 | 131 | # mypy 132 | .mypy_cache/ 133 | .dmypy.json 134 | dmypy.json 135 | 136 | # Pyre type checker 137 | .pyre/ 138 | 139 | # pytype static type analyzer 140 | .pytype/ 141 | 142 | # Cython debug symbols 143 | cython_debug/ 144 | 145 | # VirtualEnv rules 146 | # Virtualenv 147 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 148 | .Python 149 | [Bb]in 150 | [Ii]nclude 151 | [Ll]ib 152 | [Ll]ib64 153 | [Ll]ocal 154 | [Ss]cripts 155 | pyvenv.cfg 156 | .venv 157 | pip-selfcheck.json 158 | 159 | # Linux rules 160 | *~ 161 | 162 | # temporary files which can be created if a process still has a handle open of a deleted file 163 | .fuse_hidden* 164 | 165 | # KDE directory preferences 166 | .directory 167 | 168 | # Linux trash folder which might appear on any partition or disk 169 | .Trash-* 170 | 171 | # .nfs files are created when an open file is removed but is still being accessed 172 | .nfs* 173 | 174 | # Windows rules 175 | # Windows thumbnail cache files 176 | Thumbs.db 177 | Thumbs.db:encryptable 178 | ehthumbs.db 179 | ehthumbs_vista.db 180 | 181 | # Dump file 182 | *.stackdump 183 | 184 | # Folder config file 185 | [Dd]esktop.ini 186 | 187 | # Recycle Bin used on file shares 188 | $RECYCLE.BIN/ 189 | 190 | # Windows Installer files 191 | *.cab 192 | *.msi 193 | *.msix 194 | *.msm 195 | *.msp 196 | 197 | # Windows shortcuts 198 | *.lnk 199 | 200 | # macOS rules 201 | # General 202 | .DS_Store 203 | .AppleDouble 204 | .LSOverride 205 | 206 | # Icon must end with two \r 207 | Icon 208 | 209 | 210 | # Thumbnails 211 | ._* 212 | 213 | # Files that might appear in the root of a volume 214 | .DocumentRevisions-V100 215 | .fseventsd 216 | .Spotlight-V100 217 | .TemporaryItems 218 | .Trashes 219 | .VolumeIcon.icns 220 | .com.apple.timemachine.donotpresent 221 | 222 | # Directories potentially created on remote AFP share 223 | .AppleDB 224 | .AppleDesktop 225 | Network Trash Folder 226 | Temporary Items 227 | .apdisk 228 | 229 | # Emacs rules 230 | # -*- mode: gitignore; -*- 231 | *~ 232 | \#*\# 233 | /.emacs.desktop 234 | /.emacs.desktop.lock 235 | *.elc 236 | auto-save-list 237 | tramp 238 | .\#* 239 | 240 | # Org-mode 241 | .org-id-locations 242 | *_archive 243 | 244 | # flymake-mode 245 | *_flymake.* 246 | 247 | # eshell files 248 | /eshell/history 249 | /eshell/lastdir 250 | 251 | # elpa packages 252 | /elpa/ 253 | 254 | # reftex files 255 | *.rel 256 | 257 | # AUCTeX auto folder 258 | /auto/ 259 | 260 | # cask packages 261 | .cask/ 262 | dist/ 263 | 264 | # Flycheck 265 | flycheck_*.el 266 | 267 | # server auth directory 268 | /server/ 269 | 270 | # projectiles files 271 | .projectile 272 | 273 | # directory configuration 274 | .dir-locals.el 275 | 276 | # network security 277 | /network-security.data 278 | 279 | 280 | # Vim rules 281 | # Swap 282 | [._]*.s[a-v][a-z] 283 | !*.svg # comment out if you don't need vector files 284 | [._]*.sw[a-p] 285 | [._]s[a-rt-v][a-z] 286 | [._]ss[a-gi-z] 287 | [._]sw[a-p] 288 | 289 | # Session 290 | Session.vim 291 | Sessionx.vim 292 | 293 | # Temporary 294 | .netrwhist 295 | *~ 296 | # Auto-generated tag files 297 | tags 298 | # Persistent undo 299 | [._]*.un~ 300 | 301 | # JetBrains rules 302 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 303 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 304 | 305 | # User-specific stuff 306 | .idea/**/workspace.xml 307 | .idea/**/tasks.xml 308 | .idea/**/usage.statistics.xml 309 | .idea/**/dictionaries 310 | .idea/**/shelf 311 | 312 | # Generated files 313 | .idea/**/contentModel.xml 314 | 315 | # Sensitive or high-churn files 316 | .idea/**/dataSources/ 317 | .idea/**/dataSources.ids 318 | .idea/**/dataSources.local.xml 319 | .idea/**/sqlDataSources.xml 320 | .idea/**/dynamic.xml 321 | .idea/**/uiDesigner.xml 322 | .idea/**/dbnavigator.xml 323 | 324 | # Gradle 325 | .idea/**/gradle.xml 326 | .idea/**/libraries 327 | 328 | # Gradle and Maven with auto-import 329 | # When using Gradle or Maven with auto-import, you should exclude module files, 330 | # since they will be recreated, and may cause churn. Uncomment if using 331 | # auto-import. 332 | # .idea/artifacts 333 | # .idea/compiler.xml 334 | # .idea/jarRepositories.xml 335 | # .idea/modules.xml 336 | # .idea/*.iml 337 | # .idea/modules 338 | # *.iml 339 | # *.ipr 340 | 341 | # CMake 342 | cmake-build-*/ 343 | 344 | # Mongo Explorer plugin 345 | .idea/**/mongoSettings.xml 346 | 347 | # File-based project format 348 | *.iws 349 | 350 | # IntelliJ 351 | out/ 352 | 353 | # mpeltonen/sbt-idea plugin 354 | .idea_modules/ 355 | 356 | # JIRA plugin 357 | atlassian-ide-plugin.xml 358 | 359 | # Cursive Clojure plugin 360 | .idea/replstate.xml 361 | 362 | # Crashlytics plugin (for Android Studio and IntelliJ) 363 | com_crashlytics_export_strings.xml 364 | crashlytics.properties 365 | crashlytics-build.properties 366 | fabric.properties 367 | 368 | # Editor-based Rest Client 369 | .idea/httpRequests 370 | 371 | # Android studio 3.1+ serialized cache file 372 | .idea/caches/build_file_checksums.ser 373 | 374 | # SublimeText rules 375 | # Cache files for Sublime Text 376 | *.tmlanguage.cache 377 | *.tmPreferences.cache 378 | *.stTheme.cache 379 | 380 | # Workspace files are user-specific 381 | *.sublime-workspace 382 | 383 | # Project files should be checked into the repository, unless a significant 384 | # proportion of contributors will probably not be using Sublime Text 385 | # *.sublime-project 386 | 387 | # SFTP configuration file 388 | sftp-config.json 389 | sftp-config-alt*.json 390 | 391 | # Package control specific files 392 | Package Control.last-run 393 | Package Control.ca-list 394 | Package Control.ca-bundle 395 | Package Control.system-ca-bundle 396 | Package Control.cache/ 397 | Package Control.ca-certs/ 398 | Package Control.merged-ca-bundle 399 | Package Control.user-ca-bundle 400 | oscrypto-ca-bundle.crt 401 | bh_unicode_properties.cache 402 | 403 | # Sublime-github package stores a github token in this file 404 | # https://packagecontrol.io/packages/sublime-github 405 | GitHub.sublime-settings 406 | 407 | # KDevelop4 rules 408 | *.kdev4 409 | .kdev4/ 410 | 411 | # Kate rules 412 | # Swap Files # 413 | .*.kate-swp 414 | .swp.* 415 | 416 | # TextMate rules 417 | *.tmproj 418 | *.tmproject 419 | tmtags 420 | 421 | # VisualStudioCode rules 422 | .vscode/* 423 | !.vscode/settings.json 424 | !.vscode/tasks.json 425 | !.vscode/launch.json 426 | !.vscode/extensions.json 427 | *.code-workspace 428 | 429 | # Local History for Visual Studio Code 430 | .history/ 431 | 432 | # Xcode rules 433 | # Xcode 434 | # 435 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 436 | 437 | ## User settings 438 | xcuserdata/ 439 | 440 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 441 | *.xcscmblueprint 442 | *.xccheckout 443 | 444 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 445 | build/ 446 | DerivedData/ 447 | *.moved-aside 448 | *.pbxuser 449 | !default.pbxuser 450 | *.mode1v3 451 | !default.mode1v3 452 | *.mode2v3 453 | !default.mode2v3 454 | *.perspectivev3 455 | !default.perspectivev3 456 | 457 | ## Gcc Patch 458 | /*.gcno 459 | 460 | # Eclipse rules 461 | .metadata 462 | bin/ 463 | tmp/ 464 | *.tmp 465 | *.bak 466 | *.swp 467 | *~.nib 468 | local.properties 469 | .settings/ 470 | .loadpath 471 | .recommenders 472 | 473 | # External tool builders 474 | .externalToolBuilders/ 475 | 476 | # Locally stored "Eclipse launch configurations" 477 | *.launch 478 | 479 | # PyDev specific (Python IDE for Eclipse) 480 | *.pydevproject 481 | 482 | # CDT-specific (C/C++ Development Tooling) 483 | .cproject 484 | 485 | # CDT- autotools 486 | .autotools 487 | 488 | # Java annotation processor (APT) 489 | .factorypath 490 | 491 | # PDT-specific (PHP Development Tools) 492 | .buildpath 493 | 494 | # sbteclipse plugin 495 | .target 496 | 497 | # Tern plugin 498 | .tern-project 499 | 500 | # TeXlipse plugin 501 | .texlipse 502 | 503 | # STS (Spring Tool Suite) 504 | .springBeans 505 | 506 | # Code Recommenders 507 | .recommenders/ 508 | 509 | # Annotation Processing 510 | .apt_generated/ 511 | .apt_generated_test/ 512 | 513 | # Scala IDE specific (Scala & Java development for Eclipse) 514 | .cache-main 515 | .scala_dependencies 516 | .worksheet 517 | 518 | # Uncomment this line if you wish to ignore the project description file. 519 | # Typically, this file would be tracked if it contains build/dependency configurations: 520 | #.project 521 | 522 | # TortoiseGit rules 523 | # Project-level settings 524 | /.tgitconfig 525 | 526 | # Tags rules 527 | # Ignore tags created by etags, ctags, gtags (GNU global) and cscope 528 | TAGS 529 | .TAGS 530 | !TAGS/ 531 | tags 532 | .tags 533 | !tags/ 534 | gtags.files 535 | GTAGS 536 | GRTAGS 537 | GPATH 538 | GSYMS 539 | cscope.files 540 | cscope.out 541 | cscope.in.out 542 | cscope.po.out 543 | 544 | 545 | # remove moban hash dictionary 546 | .moban.hashes 547 | -------------------------------------------------------------------------------- /.moban.d/README.rst: -------------------------------------------------------------------------------- 1 | {% extends "BASIC-README.rst.jj2" %} 2 | 3 | 4 | {%block features %} 5 | {%include "handsontable/handsontable.rst.jj2" %} 6 | {%endblock%} 7 | 8 | 9 | {%block custom_guide %} 10 | 11 | Update styles 12 | -------------------- 13 | 14 | `styles/style.scss` control the look and feel of the frame. In order to view the changes 15 | in that file, you will need to compile, moban and install it. Here is the sequence 16 | of commands:: 17 | 18 | $ make css 19 | $ moban 20 | $ python setup.py install 21 | $ make -C demo 22 | 23 | Then please open handsontable.html from demo directory. 24 | 25 | {%endblock%} 26 | -------------------------------------------------------------------------------- /.moban.d/docs/source/conf.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.moban.d/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | {%include "header.rst.jj2" %} 2 | 3 | Introduction 4 | -------------------------------------------------------------------------------- 5 | 6 | {%include "handsontable/handsontable.rst.jj2"%} 7 | 8 | {%include "constraints.rst.jj2"%} 9 | 10 | Installation 11 | -------------------------------------------------------------------------------- 12 | 13 | {%include "installation.rst.jj2" %} 14 | 15 | 16 | Rendering Options 17 | -------------------------------------------------------------------------------- 18 | 19 | You can pass the following options to :meth:`pyexcel.Sheet.save_as` and 20 | :meth:`pyexcel.Book.save_as`. The same options are applicable to 21 | pyexcel's signature functions, but please remember to add 'dest_' prefix. 22 | 23 | **js_url** The default url for handsontable javascript file points to cdnjs 24 | version 0.31.0. You can replace it with your custom url 25 | 26 | **css_url** The default url for handsontable style sheet points to cdnjs 27 | version 0.31.0. You can replace it with your custom url 28 | 29 | **embed** If it is set true, the resulting html will only contain a portion 30 | of HTML without the HTML header. And it is expected that you, as the 31 | developer to provide the necessary HTML header in your web page. 32 | 33 | What's more, you could apply 34 | `all handsontable's options `_ 35 | to the rendering too. for example, 'readOnly' 36 | was set to `True` as default in this library. In the demo, 'readOnly' was 37 | overridden as `False`. 38 | 39 | 40 | Embed Setup 41 | -------------------------------------------------------------------------------- 42 | 43 | 44 | Please copy the hightlighted lines into the head section of each of your web pages: 45 | 46 | .. code-block:: html 47 | :linenos: 48 | :emphasize-lines: 3-7 49 | 50 | 51 | ... 52 | 53 | 54 | 57 | ... 58 | 59 | ... 60 | 61 | 62 | Then pass on `embed=True` to pyexcel signature functions. It is as simple as that. 63 | 64 | CSS for readthedocs website 65 | ================================================================================ 66 | 67 | .. code-block:: html 68 | :linenos: 69 | :emphasize-lines: 3-7 70 | 71 | 72 | ... 73 | 74 | 75 | 78 | ... 79 | 80 | ... 81 | 82 | 83 | .. note:: 84 | For latest handsontable releases, please visit `cdnjs `_ 85 | 86 | {%include "license.rst.jj2" %} 87 | -------------------------------------------------------------------------------- /.moban.d/handsontable/handsontable.rst.jj2: -------------------------------------------------------------------------------- 1 | **{{name}}** is a rendering plugin to 2 | `pyexcel `_ and renders 3 | `pyexcel.Sheet` and `pyexcel.Book` into a 4 | `handsontable `_ in your web page. As long as you 5 | have a browser, you could view the data. However, please note 6 | that this library does not aim to replace any current excel softwares, such 7 | as Micorsoft Office. But it aims to extends the capability of a 8 | Python user/developer in viewing plain data. 9 | 10 | 11 | Main features: 12 | 13 | #. transform your excel sheets into excel alike html file. 14 | #. embed your excel sheets into your web page. 15 | #. show your data like excel in jupyter notebook (added in 0.0.2). 16 | 17 | Here is one liner to use it with pyexcel: 18 | 19 | .. code-block:: python 20 | 21 | import pyexcel as p 22 | 23 | p.save_as(file_name='your.xls', dest_file_name='your.handsontable.html') 24 | 25 | Alternatively, you can use this library with pyexcel cli module:: 26 | 27 | 28 | $ pip install pyexcel-cli 29 | $ pyexcel transcode your.xls your.handsontable.html 30 | 31 | 32 | Please remember to give this file suffix always: **handsontable.html**. It is because `handsontable.html` triggers this plugin in pyexcel. 33 | 34 | 35 | Screenshots 36 | -------------- 37 | 38 | View as html 39 | ***************** 40 | 41 | .. image:: https://github.com/pyexcel/pyexcel-handsontable/raw/master/demo/screenshot.png 42 | 43 | 44 | View in jupyter notebook 45 | ************************** 46 | 47 | .. image:: https://github.com/pyexcel/pyexcel-handsontable/raw/master/demo/jupyter-demo.png 48 | 49 | 50 | -------------------------------------------------------------------------------- /.moban.d/handsontable/rtd_style.css.jj2: -------------------------------------------------------------------------------- 1 | body{font-family:Helvetica,sans-serif;margin:2 0 0 0}.tab{margin-bottom:0 !important;text-align:center;list-style:none;padding:0 0 0 10px;line-height:24px;height:26px;overflow:hidden;font-size:12px;font-family:verdana;position:relative;margin:0}.tab li{margin-left:0 !important;margin-top:2px !important;float:left;height:24px;border:1px solid #aaa;background:#d1d1d1;background:linear-gradient(top, #ececec 50%, #d1d1d1);display:inline-block;position:relative;z-index:0;border-top-left-radius:6px;border-top-right-radius:6px;box-shadow:0 3px 3px rgba(0,0,0,0.4),inset 0 1px 0 #fff;text-shadow:0 1px #fff;margin:0 -5px;padding:0 20px}.tab li.active{background:#fff;color:#333;z-index:2}.tab li:before{left:-6px;border-width:0 1px 1px 0;box-shadow:2px 2px 0 #d1d1d1}.tab li:after{right:-6px;border-width:0 0 1px 1px;box-shadow:-2px 2px 0 #d1d1d1}.tab a{color:#555;text-decoration:none}.tab:before{position:absolute;content:" ";width:100%;bottom:0;left:0;border-bottom:1px solid #aaa;z-index:1}.tabcontent{margin-top:-1px} 2 | -------------------------------------------------------------------------------- /.moban.d/handsontable/style.css.jj2: -------------------------------------------------------------------------------- 1 | body{font-family:Helvetica,sans-serif;margin:2 0 0 0}.tab{margin-bottom:0 !important;text-align:center;list-style:none;padding:0 0 0 10px;line-height:24px;height:26px;overflow:hidden;font-size:12px;font-family:verdana;position:relative;margin:0}.tab li{margin-left:0 !important;float:left;height:24px;border:1px solid #aaa;background:#d1d1d1;background:linear-gradient(top, #ececec 50%, #d1d1d1);display:inline-block;position:relative;z-index:0;border-top-left-radius:6px;border-top-right-radius:6px;box-shadow:0 3px 3px rgba(0,0,0,0.4),inset 0 1px 0 #fff;text-shadow:0 1px #fff;margin:0 -5px;padding:0 20px}.tab li.active{background:#fff;color:#333;z-index:2}.tab li:before{left:-6px;border-width:0 1px 1px 0;box-shadow:2px 2px 0 #d1d1d1}.tab li:after{right:-6px;border-width:0 0 1px 1px;box-shadow:-2px 2px 0 #d1d1d1}.tab a{color:#555;text-decoration:none}.tab:before{position:absolute;content:" ";width:100%;bottom:0;left:0;border-bottom:1px solid #aaa;z-index:1}.tabcontent{margin-top:-1px} 2 | -------------------------------------------------------------------------------- /.moban.d/requirements.txt: -------------------------------------------------------------------------------- 1 | {% for dependency in dependencies: %} 2 | {{dependency}} 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /.moban.d/setup.py: -------------------------------------------------------------------------------- 1 | {%extends "pyexcel-setup.py.jj2"%} 2 | 3 | {%block platform_block%} 4 | try: 5 | from pyecharts_jupyter_installer import install_cmd_for 6 | except ImportError: 7 | import subprocess 8 | import importlib 9 | 10 | subprocess.check_call([sys.executable, '-m', 11 | 'pip', 'install', 'pyecharts-jupyter-installer']) 12 | install_cmd_for = importlib.import_module( 13 | 'pyecharts_jupyter_installer').install_cmd_for 14 | {%endblock%} 15 | 16 | {%block additional_setup_commands %} 17 | SETUP_COMMANDS = install_cmd_for( 18 | 'pyexcel-handsontable', 19 | 'pyexcel_handsontable/templates/pyexcel-handsontable' 20 | ) 21 | {%endblock%} 22 | -------------------------------------------------------------------------------- /.moban.d/test.bat: -------------------------------------------------------------------------------- 1 | {% extends "test.sh.jj2" %} 2 | 3 | {%block pretest %} 4 | cd tests\test_plugin 5 | python setup.py install 6 | cd ..\..\ 7 | {%endblock %} 8 | 9 | {%block flake8_options%} 10 | --builtins=unicode,xrange,long 11 | {%endblock%} 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.moban.d/test.sh: -------------------------------------------------------------------------------- 1 | {% extends "test.sh.jj2" %} 2 | 3 | {%block pretest %} 4 | #/bin/bash 5 | 6 | cd tests/test_plugin 7 | python setup.py install 8 | cd ../../ 9 | {%endblock %} 10 | 11 | {%block flake8_options%} 12 | --builtins=unicode,xrange,long 13 | {%endblock%} 14 | -------------------------------------------------------------------------------- /.moban.d/tests/fixtures/book.handsontable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 |
35 |
36 | 37 | 38 |
39 |
40 |
41 | 42 | 43 | 44 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /.moban.d/tests/fixtures/sheet_rendering.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.moban.d/tests/requirements.txt: -------------------------------------------------------------------------------- 1 | {% extends 'tests/requirements.txt.jj2' %} 2 | {%block extras %} 3 | pyexcel 4 | pyexcel-xls 5 | pyscss 6 | {%endblock%} 7 | -------------------------------------------------------------------------------- /.moban.yml: -------------------------------------------------------------------------------- 1 | overrides: "git://github.com/pyexcel/pyexcel-mobans!/mobanfile.yaml" 2 | configuration: 3 | configuration: pyexcel-handsontable.yml 4 | targets: 5 | - README.rst: README.rst 6 | - .gitignore: gitignore.jj2 7 | - "pyexcel_handsontable/templates/css.html": "handsontable/style.css.jj2" 8 | - "docs/source/index.rst": "docs/source/index.rst" 9 | - "docs/source/conf.py": "docs/source/conf.py.jj2" 10 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/source/conf.py 11 | 12 | # Optionally build your docs in additional formats such as PDF 13 | formats: 14 | - pdf 15 | 16 | # Optionally set the version of Python and requirements required to build your docs 17 | python: 18 | version: 3.7 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | notifications: 4 | email: false 5 | python: 6 | - pypy-5.3.1 7 | - 3.7-dev 8 | - 3.6 9 | - 3.5 10 | - 3.4 11 | - 2.7 12 | before_install: 13 | - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install flake8==2.6.2; fi 14 | - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then 15 | mv min_requirements.txt requirements.txt ; 16 | fi 17 | - test ! -f rnd_requirements.txt || pip install --no-deps -r rnd_requirements.txt 18 | - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ; 19 | - pip install -r tests/requirements.txt 20 | script: 21 | - make test 22 | after_success: 23 | codecov 24 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Change log 2 | ================================================================================ 3 | 4 | 0.0.3 - 20-02-2022 5 | -------------------------------------------------------------------------------- 6 | 7 | **Updated** 8 | 9 | #. `#7 `_: 10 | upgrade handsontable to 6.2.2 11 | 12 | 0.0.2 - 19-08-2018 13 | -------------------------------------------------------------------------------- 14 | 15 | **Updated** 16 | 17 | #. `#1 `_: 18 | keep the access to the rendered handsontables 19 | #. `#3 `_: 20 | pass on custom handsontable configurations 21 | 22 | **Added** 23 | 24 | #. support jupyter-notebook 25 | 26 | 0.0.1 - 19-06-2017 27 | -------------------------------------------------------------------------------- 28 | 29 | **Added** 30 | 31 | #. initial release 32 | -------------------------------------------------------------------------------- /CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | No contributors yet 4 | ======================= 5 | 6 | * Your github link will be listed here after your PR is merged 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2022 by Onni Software Ltd. and its contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms of the software as well 5 | as documentation, with or without modification, are permitted provided 6 | that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of 'pyexcel-handsontable' nor the names of the contributors 16 | may not be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 20 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 21 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 23 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 30 | DAMAGE. 31 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include CHANGELOG.rst 3 | include pyexcel_handsontable/templates/*.html 4 | include pyexcel_handsontable/templates/registry.json 5 | include pyexcel_handsontable/templates/pyexcel-handsontable/*.js 6 | include pyexcel_handsontable/templates/pyexcel-handsontable/*.css 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | test: lint 4 | bash test.sh 5 | 6 | install_test: 7 | pip install -r tests/requirements.txt 8 | 9 | lint: 10 | bash lint.sh 11 | 12 | format: 13 | bash format.sh 14 | 15 | git-diff-check: 16 | git diff --exit-code 17 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | pyexcel-handsontable - Let you focus on data, instead of file formats 3 | ================================================================================ 4 | 5 | .. image:: https://raw.githubusercontent.com/pyexcel/pyexcel.github.io/master/images/patreon.png 6 | :target: https://www.patreon.com/pyexcel 7 | 8 | .. image:: https://api.bountysource.com/badge/team?team_id=288537 9 | :target: https://salt.bountysource.com/teams/chfw-pyexcel 10 | 11 | .. image:: https://travis-ci.org/pyexcel-renderers/pyexcel-handsontable.svg?branch=master 12 | :target: http://travis-ci.org/pyexcel-renderers/pyexcel-handsontable 13 | 14 | .. image:: https://codecov.io/gh/pyexcel-renderers/pyexcel-handsontable/branch/master/graph/badge.svg 15 | :target: https://codecov.io/gh/pyexcel-renderers/pyexcel-handsontable 16 | 17 | .. image:: https://img.shields.io/gitter/room/gitterHQ/gitter.svg 18 | :target: https://gitter.im/pyexcel/Lobby 19 | 20 | .. image:: https://readthedocs.org/projects/pyexcel-handsontable/badge/?version=latest 21 | :target: http://pyexcel-handsontable.readthedocs.org/en/latest/ 22 | 23 | Support the project 24 | ================================================================================ 25 | 26 | If your company has embedded pyexcel and its components into a revenue generating 27 | product, please support me on `patreon `_ 28 | or `bounty source `_ to maintain 29 | the project and develop it further. 30 | 31 | If you are an individual, you are welcome to support me too and for however long 32 | you feel like. As my backer, you will receive 33 | `early access to pyexcel related contents `_. 34 | 35 | And your issues will get prioritized if you would like to become my patreon as `pyexcel pro user`. 36 | 37 | With your financial support, I will be able to invest 38 | a little bit more time in coding, documentation and writing interesting posts. 39 | 40 | 41 | Known constraints 42 | ================== 43 | 44 | Fonts, colors and charts are not supported. 45 | 46 | Introduction 47 | ================================================================================ 48 | **pyexcel-handsontable** is a rendering plugin to 49 | `pyexcel `_ and renders 50 | `pyexcel.Sheet` and `pyexcel.Book` into a 51 | `handsontable `_ in your web page. As long as you 52 | have a browser, you could view the data. However, please note 53 | that this library does not aim to replace any current excel softwares, such 54 | as Micorsoft Office. But it aims to extends the capability of a 55 | Python user/developer in viewing plain data. 56 | 57 | 58 | Main features: 59 | 60 | #. transform your excel sheets into excel alike html file. 61 | #. embed your excel sheets into your web page. 62 | #. show your data like excel in jupyter notebook (added in 0.0.2). 63 | 64 | Here is one liner to use it with pyexcel: 65 | 66 | .. code-block:: python 67 | 68 | import pyexcel as p 69 | 70 | p.save_as(file_name='your.xls', dest_file_name='your.handsontable.html') 71 | 72 | Alternatively, you can use this library with pyexcel cli module:: 73 | 74 | 75 | $ pip install pyexcel-cli 76 | $ pyexcel transcode your.xls your.handsontable.html 77 | 78 | 79 | Please remember to give this file suffix always: **handsontable.html**. It is because `handsontable.html` triggers this plugin in pyexcel. 80 | 81 | 82 | Screenshots 83 | -------------- 84 | 85 | View as html 86 | ***************** 87 | 88 | .. image:: https://github.com/pyexcel/pyexcel-handsontable/raw/master/demo/screenshot.png 89 | 90 | 91 | View in jupyter notebook 92 | ************************** 93 | 94 | .. image:: https://github.com/pyexcel/pyexcel-handsontable/raw/master/demo/jupyter-demo.png 95 | 96 | 97 | 98 | 99 | 100 | Installation 101 | ================================================================================ 102 | 103 | You can install pyexcel-handsontable via pip: 104 | 105 | .. code-block:: bash 106 | 107 | $ pip install pyexcel-handsontable 108 | 109 | 110 | or clone it and install it: 111 | 112 | .. code-block:: bash 113 | 114 | $ git clone https://github.com/pyexcel-renderers/pyexcel-handsontable.git 115 | $ cd pyexcel-handsontable 116 | $ python setup.py install 117 | 118 | 119 | 120 | Development guide 121 | ================================================================================ 122 | 123 | Development steps for code changes 124 | 125 | #. git clone https://github.com/pyexcel/pyexcel-handsontable.git 126 | #. cd pyexcel-handsontable 127 | 128 | Upgrade your setup tools and pip. They are needed for development and testing only: 129 | 130 | #. pip install --upgrade setuptools pip 131 | 132 | Then install relevant development requirements: 133 | 134 | #. pip install -r rnd_requirements.txt # if such a file exists 135 | #. pip install -r requirements.txt 136 | #. pip install -r tests/requirements.txt 137 | 138 | Once you have finished your changes, please provide test case(s), relevant documentation 139 | and update CHANGELOG.rst. 140 | 141 | .. note:: 142 | 143 | As to rnd_requirements.txt, usually, it is created when a dependent 144 | library is not released. Once the dependecy is installed 145 | (will be released), the future 146 | version of the dependency in the requirements.txt will be valid. 147 | 148 | 149 | How to test your contribution 150 | ------------------------------ 151 | 152 | Although `nose` and `doctest` are both used in code testing, it is adviable that unit tests are put in tests. `doctest` is incorporated only to make sure the code examples in documentation remain valid across different development releases. 153 | 154 | On Linux/Unix systems, please launch your tests like this:: 155 | 156 | $ make 157 | 158 | On Windows systems, please issue this command:: 159 | 160 | > test.bat 161 | 162 | How to update test environment and update documentation 163 | --------------------------------------------------------- 164 | 165 | Additional steps are required: 166 | 167 | #. pip install moban 168 | #. git clone https://github.com/moremoban/setupmobans.git # generic setup 169 | #. git clone https://github.com/pyexcel/pyexcel-commons.git commons 170 | #. make your changes in `.moban.d` directory, then issue command `moban` 171 | 172 | What is pyexcel-commons 173 | --------------------------------- 174 | 175 | Many information that are shared across pyexcel projects, such as: this developer guide, license info, etc. are stored in `pyexcel-commons` project. 176 | 177 | What is .moban.d 178 | --------------------------------- 179 | 180 | `.moban.d` stores the specific meta data for the library. 181 | 182 | Acceptance criteria 183 | ------------------- 184 | 185 | #. Has Test cases written 186 | #. Has all code lines tested 187 | #. Passes all Travis CI builds 188 | #. Has fair amount of documentation if your change is complex 189 | #. Please update CHANGELOG.rst 190 | #. Please add yourself to CONTRIBUTORS.rst 191 | #. Agree on NEW BSD License for your contribution 192 | 193 | 194 | Update styles 195 | -------------------- 196 | 197 | `styles/style.scss` control the look and feel of the frame. In order to view the changes 198 | in that file, you will need to compile, moban and install it. Here is the sequence 199 | of commands:: 200 | 201 | $ make css 202 | $ moban 203 | $ python setup.py install 204 | $ make -C demo 205 | 206 | Then please open handsontable.html from demo directory. 207 | 208 | 209 | 210 | License 211 | ================================================================================ 212 | 213 | New BSD License 214 | -------------------------------------------------------------------------------- /changelog.yml: -------------------------------------------------------------------------------- 1 | name: pyexcel-handsontable 2 | organisation: pyexcel-renderers 3 | releases: 4 | - changes: 5 | - action: Updated 6 | details: 7 | - "`#7`: upgrade handsontable to 6.2.2" 8 | date: 20-02-2022 9 | version: 0.0.3 10 | - changes: 11 | - action: Updated 12 | details: 13 | - "`#1`: keep the access to the rendered handsontables" 14 | - "`#3`: pass on custom handsontable configurations" 15 | - action: Added 16 | details: 17 | - "support jupyter-notebook" 18 | date: 19-08-2018 19 | version: 0.0.2 20 | - changes: 21 | - action: Added 22 | details: 23 | - initial release 24 | date: 19-06-2017 25 | version: 0.0.1 26 | -------------------------------------------------------------------------------- /demo/Makefile: -------------------------------------------------------------------------------- 1 | all: demo 2 | 3 | demo: 4 | python demo.py 5 | -------------------------------------------------------------------------------- /demo/README.rst: -------------------------------------------------------------------------------- 1 | Data source 2 | =============== 3 | 4 | `2011 Census: population and household estimates for England and Wales - unrounded figures for the data published 16 July 2012 `_ 5 | 6 | 7 | Usage 8 | =============== 9 | 10 | Please install dependencies:: 11 | 12 | $ pip install -r requirements.txt 13 | 14 | And then issue:: 15 | 16 | $ python demo.py 17 | 18 | Please use your browser and open handsontable.html 19 | 20 | 21 | License of the data 22 | ===================== 23 | 24 | `Open Government License 3.0 `_ 25 | 26 | License of the handsontable libraries 27 | ======================================== 28 | 29 | MIT License 30 | 31 | Copyright (c) 2012-2014 Marcin Warpechowski 32 | Copyright (c) 2015 Handsoncode sp. z o.o. 33 | 34 | -------------------------------------------------------------------------------- /demo/census.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyexcel-renderers/pyexcel-handsontable/0ed2bf06acb56dae02a9793c457524f9bd5e8b7a/demo/census.xls -------------------------------------------------------------------------------- /demo/demo.py: -------------------------------------------------------------------------------- 1 | import pyexcel 2 | # The census file has three hidden sheets 3 | book = pyexcel.get_book(file_name="census.xls", skip_hidden_sheets=False) 4 | # Please note that the file name has to be *.handsontable.html 5 | book.save_as('demo.handsontable.html', 6 | readOnly=False, 7 | js_url='handsontable.full.min.js', 8 | css_url='handsontable.full.min.css') 9 | -------------------------------------------------------------------------------- /demo/handsontable.full.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";/*! 2 | (The MIT License) 3 | 4 | Copyright (c) 2012-2014 Marcin Warpechowski 5 | Copyright (c) 2015 Handsoncode sp. z o.o. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | */.handsontable{position:relative}.handsontable .hide{display:none}.handsontable .relative{position:relative}.handsontable.htAutoSize{visibility:hidden;left:-99000px;position:absolute;top:-99000px}.handsontable .wtHider{width:0}.handsontable .wtSpreader{position:relative;width:0;height:auto}.handsontable table,.handsontable tbody,.handsontable thead,.handsontable td,.handsontable th,.handsontable input,.handsontable textarea,.handsontable div{box-sizing:content-box;-webkit-box-sizing:content-box;-moz-box-sizing:content-box}.handsontable input,.handsontable textarea{min-height:initial}.handsontable table.htCore{border-collapse:separate;border-spacing:0;margin:0;border-width:0;table-layout:fixed;width:0;outline-width:0;max-width:none;max-height:none}.handsontable col{width:50px}.handsontable col.rowHeader{width:50px}.handsontable th,.handsontable td{border-top-width:0;border-left-width:0;border-right:1px solid #CCC;border-bottom:1px solid #CCC;height:22px;empty-cells:show;line-height:21px;padding:0 4px 0 4px;background-color:#FFF;vertical-align:top;overflow:hidden;outline-width:0;white-space:pre-line;background-clip:padding-box}.handsontable td.htInvalid{background-color:#ff4c42!important}.handsontable td.htNoWrap{white-space:nowrap}.handsontable th:last-child{border-right:1px solid #CCC;border-bottom:1px solid #CCC}.handsontable tr:first-child th.htNoFrame,.handsontable th:first-child.htNoFrame,.handsontable th.htNoFrame{border-left-width:0;background-color:white;border-color:#FFF}.handsontable th:first-child,.handsontable th:nth-child(2),.handsontable td:first-of-type,.handsontable .htNoFrame+th,.handsontable .htNoFrame+td{border-left:1px solid #CCC}.handsontable.htRowHeaders thead tr th:nth-child(2){border-left:1px solid #CCC}.handsontable tr:first-child th,.handsontable tr:first-child td{border-top:1px solid #CCC}.ht_master:not(.innerBorderLeft):not(.emptyColumns) ~ .handsontable tbody tr th,.ht_master:not(.innerBorderLeft):not(.emptyColumns) ~ .handsontable:not(.ht_clone_top) thead tr th:first-child{border-right-width:0}.ht_master:not(.innerBorderTop) thead tr:last-child th,.ht_master:not(.innerBorderTop) ~ .handsontable thead tr:last-child th,.ht_master:not(.innerBorderTop) thead tr.lastChild th,.ht_master:not(.innerBorderTop) ~ .handsontable thead tr.lastChild th{border-bottom-width:0}.handsontable th{background-color:#f3f3f3;color:#222;text-align:center;font-weight:normal;white-space:nowrap}.handsontable thead th{padding:0}.handsontable th.active{background-color:#CCC}.handsontable thead th .relative{padding:2px 4px}.handsontable tbody th.ht__highlight,.handsontable thead th.ht__highlight{background-color:#dcdcdc}.handsontable.ht__selection--columns thead th.ht__highlight,.handsontable.ht__selection--rows tbody th.ht__highlight{background-color:#8eb0e7;color:#000}.handsontable .manualColumnResizer{position:fixed;top:0;cursor:col-resize;z-index:110;width:5px;height:25px}.handsontable .manualRowResizer{position:fixed;left:0;cursor:row-resize;z-index:110;height:5px;width:50px}.handsontable .manualColumnResizer:hover,.handsontable .manualColumnResizer.active,.handsontable .manualRowResizer:hover,.handsontable .manualRowResizer.active{background-color:#AAB}.handsontable .manualColumnResizerGuide{position:fixed;right:0;top:0;background-color:#AAB;display:none;width:0;border-right:1px dashed #777;margin-left:5px}.handsontable .manualRowResizerGuide{position:fixed;left:0;bottom:0;background-color:#AAB;display:none;height:0;border-bottom:1px dashed #777;margin-top:5px}.handsontable .manualColumnResizerGuide.active,.handsontable .manualRowResizerGuide.active{display:block;z-index:199}.handsontable .columnSorting{position:relative}.handsontable .columnSorting:hover{text-decoration:underline;cursor:pointer}.handsontable .columnSorting.ascending::after{content:'\25B2';color:#5f5f5f;position:absolute;right:-15px}.handsontable .columnSorting.descending::after{content:'\25BC';color:#5f5f5f;position:absolute;right:-15px}.handsontable .wtBorder{position:absolute;font-size:0}.handsontable .wtBorder.hidden{display:none!important}.handsontable td.area{background:-moz-linear-gradient(top,rgba(181,209,255,0.34) 0,rgba(181,209,255,0.34) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,rgba(181,209,255,0.34)),color-stop(100%,rgba(181,209,255,0.34)));background:-webkit-linear-gradient(top,rgba(181,209,255,0.34) 0,rgba(181,209,255,0.34) 100%);background:-o-linear-gradient(top,rgba(181,209,255,0.34) 0,rgba(181,209,255,0.34) 100%);background:-ms-linear-gradient(top,rgba(181,209,255,0.34) 0,rgba(181,209,255,0.34) 100%);background:linear-gradient(to bottom,rgba(181,209,255,0.34) 0,rgba(181,209,255,0.34) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#57b5d1ff',endColorstr='#57b5d1ff',GradientType=0);background-color:#fff}.handsontable .wtBorder.corner{font-size:0;cursor:crosshair}.handsontable .htBorder.htFillBorder{background:red;width:1px;height:1px}.handsontableInput{border:0;outline-width:0;margin:0;padding:1px 5px 0 5px;font-family:inherit;line-height:21px;font-size:inherit;box-shadow:0 0 0 2px #5292f7 inset;resize:none;display:inline-block;color:#000;border-radius:0;background-color:#FFF}.handsontableInputHolder{position:absolute;top:0;left:0;z-index:100}.htSelectEditor{-webkit-appearance:menulist-button!important;position:absolute;width:auto}.handsontable .htDimmed{color:#777}.handsontable .htSubmenu{position:relative}.handsontable .htSubmenu :after{content:'▶';color:#777;position:absolute;right:5px}.handsontable .htLeft{text-align:left}.handsontable .htCenter{text-align:center}.handsontable .htRight{text-align:right}.handsontable .htJustify{text-align:justify}.handsontable .htTop{vertical-align:top}.handsontable .htMiddle{vertical-align:middle}.handsontable .htBottom{vertical-align:bottom}.handsontable .htPlaceholder{color:#999}.handsontable .htAutocompleteArrow{float:right;font-size:10px;color:#EEE;cursor:default;width:16px;text-align:center}.handsontable td .htAutocompleteArrow:hover{color:#777}.handsontable td.area .htAutocompleteArrow{color:#d3d3d3}.handsontable .htCheckboxRendererInput{display:inline-block;vertical-align:middle}.handsontable .htCheckboxRendererInput.noValue{opacity:.5}.handsontable .htCheckboxRendererLabel{cursor:pointer;display:inline-block;width:100%}@-webkit-keyframes opacity-hide{from{opacity:1}to{opacity:0}}@keyframes opacity-hide{from{opacity:1}to{opacity:0}}@-webkit-keyframes opacity-show{from{opacity:0}to{opacity:1}}@keyframes opacity-show{from{opacity:0}to{opacity:1}}.handsontable .handsontable.ht_clone_top .wtHider{padding:0 0 5px 0}.handsontable .autocompleteEditor.handsontable{padding-right:17px}.handsontable .autocompleteEditor.handsontable.htMacScroll{padding-right:15px}.handsontable.listbox{margin:0}.handsontable.listbox .ht_master table{border:1px solid #ccc;border-collapse:separate;background:white}.handsontable.listbox th,.handsontable.listbox tr:first-child th,.handsontable.listbox tr:last-child th,.handsontable.listbox tr:first-child td,.handsontable.listbox td{border-color:transparent}.handsontable.listbox th,.handsontable.listbox td{white-space:nowrap;text-overflow:ellipsis}.handsontable.listbox td.htDimmed{cursor:default;color:inherit;font-style:inherit}.handsontable.listbox .wtBorder{visibility:hidden}.handsontable.listbox tr td.current,.handsontable.listbox tr:hover td{background:#eee}.ht_clone_top{z-index:101}.ht_clone_left{z-index:102}.ht_clone_top_left_corner,.ht_clone_bottom_left_corner{z-index:103}.ht_clone_debug{z-index:103}.handsontable td.htSearchResult{background:#fcedd9;color:#583707}.htBordered{border-width:1px}.htBordered.htTopBorderSolid{border-top-style:solid;border-top-color:#000}.htBordered.htRightBorderSolid{border-right-style:solid;border-right-color:#000}.htBordered.htBottomBorderSolid{border-bottom-style:solid;border-bottom-color:#000}.htBordered.htLeftBorderSolid{border-left-style:solid;border-left-color:#000}.handsontable tbody tr th:nth-last-child(2){border-right:1px solid #CCC}.handsontable thead tr:nth-last-child(2) th.htGroupIndicatorContainer{border-bottom:1px solid #CCC;padding-bottom:5px}.ht_clone_top_left_corner thead tr th:nth-last-child(2){border-right:1px solid #CCC}.htCollapseButton{width:10px;height:10px;line-height:10px;text-align:center;border-radius:5px;border:1px solid #f3f3f3;-webkit-box-shadow:1px 1px 3px rgba(0,0,0,0.4);box-shadow:1px 1px 3px rgba(0,0,0,0.4);cursor:pointer;margin-bottom:3px;position:relative}.htCollapseButton:after{content:"";height:300%;width:1px;display:block;background:#ccc;margin-left:4px;position:absolute;bottom:10px}thead .htCollapseButton{right:5px;position:absolute;top:5px;background:#fff}thead .htCollapseButton:after{height:1px;width:700%;right:10px;top:4px}.handsontable tr th .htExpandButton{position:absolute;width:10px;height:10px;line-height:10px;text-align:center;border-radius:5px;border:1px solid #f3f3f3;-webkit-box-shadow:1px 1px 3px rgba(0,0,0,0.4);box-shadow:1px 1px 3px rgba(0,0,0,0.4);cursor:pointer;top:0;display:none}.handsontable thead tr th .htExpandButton{top:5px}.handsontable tr th .htExpandButton.clickable{display:block}.collapsibleIndicator{position:absolute;top:50%;transform:translate(0%,-50%);right:5px;border:1px solid #a6a6a6;line-height:10px;color:#222;border-radius:10px;font-size:10px;width:10px;height:10px;cursor:pointer;-webkit-box-shadow:0 0 0 6px rgba(238,238,238,1);-moz-box-shadow:0 0 0 6px rgba(238,238,238,1);box-shadow:0 0 0 6px rgba(238,238,238,1);background:#eee}.handsontable col.hidden{width:0!important}.handsontable table tr th.lightRightBorder{border-right:1px solid #e6e6e6}.handsontable tr.hidden,.handsontable tr.hidden td,.handsontable tr.hidden th{display:none}.ht_master,.ht_clone_left,.ht_clone_top,.ht_clone_bottom{overflow:hidden}.ht_master .wtHolder{overflow:auto}.ht_clone_left .wtHolder{overflow-x:hidden;overflow-y:auto}.ht_clone_top .wtHolder,.ht_clone_bottom .wtHolder{overflow-x:auto;overflow-y:hidden}.wtDebugHidden{display:none}.wtDebugVisible{display:block;-webkit-animation-duration:.5s;-webkit-animation-name:wtFadeInFromNone;animation-duration:.5s;animation-name:wtFadeInFromNone}@keyframes wtFadeInFromNone{0%{display:none;opacity:0}1%{display:block;opacity:0}100%{display:block;opacity:1}}@-webkit-keyframes wtFadeInFromNone{0%{display:none;opacity:0}1%{display:block;opacity:0}100%{display:block;opacity:1}}.handsontable.mobile,.handsontable.mobile .wtHolder{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-overflow-scrolling:touch}.htMobileEditorContainer{display:none;position:absolute;top:0;width:70%;height:54pt;background:#f8f8f8;border-radius:20px;border:1px solid #ebebeb;z-index:999;box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-text-size-adjust:none}.topLeftSelectionHandle:not(.ht_master .topLeftSelectionHandle),.topLeftSelectionHandle-HitArea:not(.ht_master .topLeftSelectionHandle-HitArea){z-index:9999}.topLeftSelectionHandle,.topLeftSelectionHandle-HitArea,.bottomRightSelectionHandle,.bottomRightSelectionHandle-HitArea{left:-10000px;top:-10000px}.htMobileEditorContainer.active{display:block}.htMobileEditorContainer .inputs{position:absolute;right:210pt;bottom:10pt;top:10pt;left:14px;height:34pt}.htMobileEditorContainer .inputs textarea{font-size:13pt;border:1px solid #a1a1a1;-webkit-appearance:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;position:absolute;left:14px;right:14px;top:0;bottom:0;padding:7pt}.htMobileEditorContainer .cellPointer{position:absolute;top:-13pt;height:0;width:0;left:30px;border-left:13pt solid transparent;border-right:13pt solid transparent;border-bottom:13pt solid #ebebeb}.htMobileEditorContainer .cellPointer.hidden{display:none}.htMobileEditorContainer .cellPointer:before{content:'';display:block;position:absolute;top:2px;height:0;width:0;left:-13pt;border-left:13pt solid transparent;border-right:13pt solid transparent;border-bottom:13pt solid #f8f8f8}.htMobileEditorContainer .moveHandle{position:absolute;top:10pt;left:5px;width:30px;bottom:0;cursor:move;z-index:9999}.htMobileEditorContainer .moveHandle:after{content:"..\a..\a..\a..";white-space:pre;line-height:10px;font-size:20pt;display:inline-block;margin-top:-8px;color:#ebebeb}.htMobileEditorContainer .positionControls{width:205pt;position:absolute;right:5pt;top:0;bottom:0}.htMobileEditorContainer .positionControls>div{width:50pt;height:100%;float:left}.htMobileEditorContainer .positionControls>div:after{content:" ";display:block;width:15pt;height:15pt;text-align:center;line-height:50pt}.htMobileEditorContainer .leftButton:after,.htMobileEditorContainer .rightButton:after,.htMobileEditorContainer .upButton:after,.htMobileEditorContainer .downButton:after{transform-origin:5pt 5pt;-webkit-transform-origin:5pt 5pt;margin:21pt 0 0 21pt}.htMobileEditorContainer .leftButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(-45deg)}.htMobileEditorContainer .leftButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .rightButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(135deg)}.htMobileEditorContainer .rightButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .upButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(45deg)}.htMobileEditorContainer .upButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .downButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(225deg)}.htMobileEditorContainer .downButton:active:after{border-color:#cfcfcf}.handsontable.hide-tween{-webkit-animation:opacity-hide .3s;animation:opacity-hide .3s;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards}.handsontable.show-tween{-webkit-animation:opacity-show .3s;animation:opacity-show .3s;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards}.htCommentCell{position:relative}.htCommentCell:after{content:'';position:absolute;top:0;right:0;border-left:6px solid transparent;border-top:6px solid black}.htComments{display:none;z-index:1059;position:absolute}.htCommentTextArea{box-shadow:rgba(0,0,0,0.117647) 0 1px 3px,rgba(0,0,0,0.239216) 0 1px 2px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border:0;border-left:3px solid #ccc;background-color:#fff;width:215px;height:90px;font-size:12px;padding:5px;outline:0!important;-webkit-appearance:none}.htCommentTextArea:focus{box-shadow:rgba(0,0,0,0.117647) 0 1px 3px,rgba(0,0,0,0.239216) 0 1px 2px,inset 0 0 0 1px #5292f7;border-left:3px solid #5292f7}/*! 27 | * Handsontable ContextMenu 28 | */.htContextMenu{display:none;position:absolute;z-index:1060}.htContextMenu .ht_clone_top,.htContextMenu .ht_clone_left,.htContextMenu .ht_clone_corner,.htContextMenu .ht_clone_debug{display:none}.htContextMenu table.htCore{border:1px solid #ccc;border-bottom-width:2px;border-right-width:2px}.htContextMenu .wtBorder{visibility:hidden}.htContextMenu table tbody tr td{background:white;border-width:0;padding:4px 6px 0 6px;cursor:pointer;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.htContextMenu table tbody tr td:first-child{border:0}.htContextMenu table tbody tr td.htDimmed{font-style:normal;color:#323232}.htContextMenu table tbody tr td.current,.htContextMenu table tbody tr td.zeroclipboard-is-hover{background:#f3f3f3}.htContextMenu table tbody tr td.htSeparator{border-top:1px solid #bbb;height:0;padding:0;cursor:default}.htContextMenu table tbody tr td.htDisabled{color:#999;cursor:default}.htContextMenu table tbody tr td.htDisabled:hover{background:#fff;color:#999;cursor:default}.htContextMenu table tbody tr.htHidden{display:none}.htContextMenu table tbody tr td .htItemWrapper{margin-left:10px;margin-right:6px}.htContextMenu table tbody tr td div span.selected{margin-top:-2px;position:absolute;left:4px}.htContextMenu .ht_master .wtHolder{overflow:hidden}.htRowHeaders .ht_master.innerBorderLeft ~ .ht_clone_top_left_corner th:nth-child(2),.htRowHeaders .ht_master.innerBorderLeft ~ .ht_clone_left td:first-of-type{border-left:0 none}.handsontable .wtHider{position:relative}.handsontable.ht__manualColumnMove.after-selection--columns thead th.ht__highlight{cursor:move;cursor:-moz-grab;cursor:-webkit-grab;cursor:grab}.handsontable.ht__manualColumnMove.on-moving--columns,.handsontable.ht__manualColumnMove.on-moving--columns thead th.ht__highlight{cursor:move;cursor:-moz-grabbing;cursor:-webkit-grabbing;cursor:grabbing}.handsontable.ht__manualColumnMove.on-moving--columns .manualColumnResizer{display:none}.handsontable .ht__manualColumnMove--guideline,.handsontable .ht__manualColumnMove--backlight{position:absolute;height:100%;display:none}.handsontable .ht__manualColumnMove--guideline{background:#757575;width:2px;top:0;margin-left:-1px;z-index:105}.handsontable .ht__manualColumnMove--backlight{background:#343434;background:rgba(52,52,52,0.25);display:none;z-index:105;pointer-events:none}.handsontable.on-moving--columns.show-ui .ht__manualColumnMove--guideline,.handsontable.on-moving--columns .ht__manualColumnMove--backlight{display:block}.handsontable .wtHider{position:relative}.handsontable.ht__manualRowMove.after-selection--rows tbody th.ht__highlight{cursor:move;cursor:-moz-grab;cursor:-webkit-grab;cursor:grab}.handsontable.ht__manualRowMove.on-moving--rows,.handsontable.ht__manualRowMove.on-moving--rows tbody th.ht__highlight{cursor:move;cursor:-moz-grabbing;cursor:-webkit-grabbing;cursor:grabbing}.handsontable.ht__manualRowMove.on-moving--rows .manualRowResizer{display:none}.handsontable .ht__manualRowMove--guideline,.handsontable .ht__manualRowMove--backlight{position:absolute;width:100%;display:none}.handsontable .ht__manualRowMove--guideline{background:#757575;height:2px;left:0;margin-top:-1px;z-index:105}.handsontable .ht__manualRowMove--backlight{background:#343434;background:rgba(52,52,52,0.25);display:none;z-index:105;pointer-events:none}.handsontable.on-moving--rows.show-ui .ht__manualRowMove--guideline,.handsontable.on-moving--rows .ht__manualRowMove--backlight{display:block}/*! 29 | * Pikaday 30 | * Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/ 31 | */.pika-single{z-index:9999;display:block;position:relative;color:#333;background:#fff;border:1px solid #ccc;border-bottom-color:#bbb;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.pika-single:before,.pika-single:after{content:" ";display:table}.pika-single:after{clear:both}.pika-single{*zoom:1}.pika-single.is-hidden{display:none}.pika-single.is-bound{position:absolute;box-shadow:0 5px 15px -5px rgba(0,0,0,.5)}.pika-lendar{float:left;width:240px;margin:8px}.pika-title{position:relative;text-align:center}.pika-label{display:inline-block;*display:inline;position:relative;z-index:9999;overflow:hidden;margin:0;padding:5px 3px;font-size:14px;line-height:20px;font-weight:bold;background-color:#fff}.pika-title select{cursor:pointer;position:absolute;z-index:9998;margin:0;left:0;top:5px;filter:alpha(opacity=0);opacity:0}.pika-prev,.pika-next{display:block;cursor:pointer;position:relative;outline:0;border:0;padding:0;width:20px;height:30px;text-indent:20px;white-space:nowrap;overflow:hidden;background-color:transparent;background-position:center center;background-repeat:no-repeat;background-size:75% 75%;opacity:.5;*position:absolute;*top:0}.pika-prev:hover,.pika-next:hover{opacity:1}.pika-prev,.is-rtl .pika-next{float:left;background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg==');*left:0}.pika-next,.is-rtl .pika-prev{float:right;background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII=');*right:0}.pika-prev.is-disabled,.pika-next.is-disabled{cursor:default;opacity:.2}.pika-select{display:inline-block;*display:inline}.pika-table{width:100%;border-collapse:collapse;border-spacing:0;border:0}.pika-table th,.pika-table td{width:14.285714285714286%;padding:0}.pika-table th{color:#999;font-size:12px;line-height:25px;font-weight:bold;text-align:center}.pika-button{cursor:pointer;display:block;box-sizing:border-box;-moz-box-sizing:border-box;outline:0;border:0;margin:0;width:100%;padding:5px;color:#666;font-size:12px;line-height:15px;text-align:right;background:#f5f5f5}.pika-week{font-size:11px;color:#999}.is-today .pika-button{color:#3af;font-weight:bold}.is-selected .pika-button{color:#fff;font-weight:bold;background:#3af;box-shadow:inset 0 1px 3px #178fe5;border-radius:3px}.is-inrange .pika-button{background:#d5e9f7}.is-startrange .pika-button{color:#fff;background:#6cb31d;box-shadow:none;border-radius:3px}.is-endrange .pika-button{color:#fff;background:#3af;box-shadow:none;border-radius:3px}.is-disabled .pika-button,.is-outside-current-month .pika-button{pointer-events:none;cursor:default;color:#999;opacity:.3}.pika-button:hover{color:#fff;background:#ff8000;box-shadow:none;border-radius:3px}.pika-table abbr{border-bottom:0;cursor:help} -------------------------------------------------------------------------------- /demo/jupyter-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyexcel-renderers/pyexcel-handsontable/0ed2bf06acb56dae02a9793c457524f9bd5e8b7a/demo/jupyter-demo.png -------------------------------------------------------------------------------- /demo/requirements.txt: -------------------------------------------------------------------------------- 1 | pyexcel>=0.5.0 2 | pyexcel-xls>=0.3.0 3 | pyexcel-handsontable 4 | -------------------------------------------------------------------------------- /demo/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyexcel-renderers/pyexcel-handsontable/0ed2bf06acb56dae02a9793c457524f9bd5e8b7a/demo/screenshot.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DESCRIPTION = ( 3 | 'A pyexcel plugin to render data as handsontable in html pages' + 4 | '' 5 | ) 6 | # Configuration file for the Sphinx documentation builder. 7 | # 8 | # This file only contains a selection of the most common options. For a full 9 | # list see the documentation: 10 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 11 | 12 | # -- Path setup -------------------------------------------------------------- 13 | 14 | # If extensions (or modules to document with autodoc) are in another directory, 15 | # add these directories to sys.path here. If the directory is relative to the 16 | # documentation root, use os.path.abspath to make it absolute, like shown here. 17 | # 18 | # import os 19 | # import sys 20 | # sys.path.insert(0, os.path.abspath('.')) 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = 'pyexcel-handsontable' 25 | copyright = '2015-2022 Onni Software Ltd.' 26 | author = 'C.W.' 27 | # The short X.Y version 28 | version = '0.0.2' 29 | # The full version, including alpha/beta/rc tags 30 | release = '0.0.2' 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be 35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 36 | # ones. 37 | extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode',] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The language for content autogenerated by Sphinx. Refer to documentation 43 | # for a list of supported languages. 44 | # 45 | # This is also used if you do content translation via gettext catalogs. 46 | # Usually you set "language" from the command line for these cases. 47 | language = 'en' 48 | 49 | # List of patterns, relative to source directory, that match files and 50 | # directories to ignore when looking for source files. 51 | # This pattern also affects html_static_path and html_extra_path. 52 | exclude_patterns = [] 53 | 54 | 55 | # -- Options for HTML output ------------------------------------------------- 56 | 57 | # The theme to use for HTML and HTML Help pages. See the documentation for 58 | # a list of builtin themes. 59 | # 60 | html_theme = 'alabaster' 61 | 62 | # Add any paths that contain custom static files (such as style sheets) here, 63 | # relative to this directory. They are copied after the builtin static files, 64 | # so a file named "default.css" will overwrite the builtin "default.css". 65 | html_static_path = ['_static'] 66 | 67 | # -- Extension configuration ------------------------------------------------- 68 | # -- Options for intersphinx extension --------------------------------------- 69 | 70 | # Example configuration for intersphinx: refer to the Python standard library. 71 | intersphinx_mapping = {'https://docs.python.org/3/': None} 72 | # TODO: html_theme not configurable upstream 73 | html_theme = 'default' 74 | 75 | # TODO: DESCRIPTION not configurable upstream 76 | texinfo_documents = [ 77 | ('index', 'pyexcel-handsontable', 78 | 'pyexcel-handsontable Documentation', 79 | 'Onni Software Ltd.', 'pyexcel-handsontable', 80 | DESCRIPTION, 81 | 'Miscellaneous'), 82 | ] 83 | intersphinx_mapping.update({ 84 | 'pyexcel': ('http://pyexcel.readthedocs.io/en/latest/', None), 85 | }) 86 | master_doc = "index" 87 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | `pyexcel-handsontable` - Let you focus on data, instead of file formats 2 | ================================================================================ 3 | 4 | :Author: C.W. 5 | :Source code: http://github.com/pyexcel-renderers/pyexcel-handsontable.git 6 | :Issues: http://github.com/pyexcel-renderers/pyexcel-handsontable/issues 7 | :License: New BSD License 8 | :Released: |version| 9 | :Generated: |today| 10 | 11 | Introduction 12 | -------------------------------------------------------------------------------- 13 | 14 | **pyexcel-handsontable** is a rendering plugin to 15 | `pyexcel `_ and renders 16 | `pyexcel.Sheet` and `pyexcel.Book` into a 17 | `handsontable `_ in your web page. As long as you 18 | have a browser, you could view the data. However, please note 19 | that this library does not aim to replace any current excel softwares, such 20 | as Micorsoft Office. But it aims to extends the capability of a 21 | Python user/developer in viewing plain data. 22 | 23 | 24 | Main features: 25 | 26 | #. transform your excel sheets into excel alike html file. 27 | #. embed your excel sheets into your web page. 28 | #. show your data like excel in jupyter notebook (added in 0.0.2). 29 | 30 | Here is one liner to use it with pyexcel: 31 | 32 | .. code-block:: python 33 | 34 | import pyexcel as p 35 | 36 | p.save_as(file_name='your.xls', dest_file_name='your.handsontable.html') 37 | 38 | Alternatively, you can use this library with pyexcel cli module:: 39 | 40 | 41 | $ pip install pyexcel-cli 42 | $ pyexcel transcode your.xls your.handsontable.html 43 | 44 | 45 | Please remember to give this file suffix always: **handsontable.html**. It is because `handsontable.html` triggers this plugin in pyexcel. 46 | 47 | 48 | Screenshots 49 | -------------- 50 | 51 | View as html 52 | ***************** 53 | 54 | .. image:: https://github.com/pyexcel/pyexcel-handsontable/raw/master/demo/screenshot.png 55 | 56 | 57 | View in jupyter notebook 58 | ************************** 59 | 60 | .. image:: https://github.com/pyexcel/pyexcel-handsontable/raw/master/demo/jupyter-demo.png 61 | 62 | 63 | 64 | Known constraints 65 | ================== 66 | 67 | Fonts, colors and charts are not supported. 68 | 69 | Installation 70 | -------------------------------------------------------------------------------- 71 | 72 | 73 | You can install pyexcel-handsontable via pip: 74 | 75 | .. code-block:: bash 76 | 77 | $ pip install pyexcel-handsontable 78 | 79 | 80 | or clone it and install it: 81 | 82 | .. code-block:: bash 83 | 84 | $ git clone https://github.com/pyexcel-renderers/pyexcel-handsontable.git 85 | $ cd pyexcel-handsontable 86 | $ python setup.py install 87 | 88 | 89 | Rendering Options 90 | -------------------------------------------------------------------------------- 91 | 92 | You can pass the following options to :meth:`pyexcel.Sheet.save_as` and 93 | :meth:`pyexcel.Book.save_as`. The same options are applicable to 94 | pyexcel's signature functions, but please remember to add 'dest_' prefix. 95 | 96 | **js_url** The default url for handsontable javascript file points to cdnjs 97 | version 0.31.0. You can replace it with your custom url 98 | 99 | **css_url** The default url for handsontable style sheet points to cdnjs 100 | version 0.31.0. You can replace it with your custom url 101 | 102 | **embed** If it is set true, the resulting html will only contain a portion 103 | of HTML without the HTML header. And it is expected that you, as the 104 | developer to provide the necessary HTML header in your web page. 105 | 106 | What's more, you could apply 107 | `all handsontable's options `_ 108 | to the rendering too. for example, 'readOnly' 109 | was set to `True` as default in this library. In the demo, 'readOnly' was 110 | overridden as `False`. 111 | 112 | 113 | Embed Setup 114 | -------------------------------------------------------------------------------- 115 | 116 | 117 | Please copy the hightlighted lines into the head section of each of your web pages: 118 | 119 | .. code-block:: html 120 | :linenos: 121 | :emphasize-lines: 3-7 122 | 123 | 124 | ... 125 | 126 | 127 | 130 | ... 131 | 132 | ... 133 | 134 | 135 | Then pass on `embed=True` to pyexcel signature functions. It is as simple as that. 136 | 137 | CSS for readthedocs website 138 | ================================================================================ 139 | 140 | .. code-block:: html 141 | :linenos: 142 | :emphasize-lines: 3-7 143 | 144 | 145 | ... 146 | 147 | 148 | 151 | ... 152 | 153 | ... 154 | 155 | 156 | .. note:: 157 | For latest handsontable releases, please visit `cdnjs `_ 158 | 159 | License 160 | ================================================================================ 161 | 162 | New BSD License 163 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | isort $(find pyexcel_handsontable -name "*.py"|xargs echo) $(find tests -name "*.py"|xargs echo) 2 | black -l 79 pyexcel_handsontable 3 | black -l 79 tests 4 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | pip install flake8 2 | flake8 --exclude=.moban.d,docs,setup.py --builtins=unicode,xrange,long . && python setup.py checkdocs -------------------------------------------------------------------------------- /pyexcel-handsontable.yml: -------------------------------------------------------------------------------- 1 | overrides: "pyexcel.yaml" 2 | name: "pyexcel-handsontable" 3 | organisation: pyexcel-renderers 4 | nick_name: handsontable 5 | version: 0.0.3 6 | current_version: 0.0.3 7 | release: 0.0.3 8 | include_doctest: false 9 | dependencies: 10 | - pyexcel>=0.5.0 11 | - jinja2 12 | test_dependencies: 13 | - pyscss 14 | exclude_doctest: true 15 | description: A pyexcel plugin to render data as handsontable in html pages -------------------------------------------------------------------------------- /pyexcel_handsontable/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | pyexcel_handsontable 3 | ~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | chart drawing plugin for pyexcel 6 | 7 | :copyright: (c) 2016-2017 by Onni Software Ltd. 8 | :license: New BSD License, see LICENSE for further details 9 | """ 10 | from lml.plugin import PluginInfo, PluginInfoChain 11 | 12 | 13 | class MyPluginInfo(PluginInfo): 14 | def tags(self): 15 | file_types = self.file_types 16 | for file_type in file_types: 17 | yield file_type 18 | 19 | 20 | PluginInfoChain(__name__).add_a_plugin_instance( 21 | MyPluginInfo( 22 | "renderer", 23 | "%s.handsontable.HandsonTable" % __name__, 24 | file_types=["handsontable.html"], 25 | stream_type="string", 26 | ) 27 | ).add_a_plugin_instance( 28 | MyPluginInfo( 29 | "renderer", 30 | "%s.handsontable.HandsonTableInJupyter" % __name__, 31 | file_types=["handsontable"], 32 | stream_type="string", 33 | ) 34 | ) 35 | -------------------------------------------------------------------------------- /pyexcel_handsontable/handsontable.py: -------------------------------------------------------------------------------- 1 | """ 2 | pyexcel_handsontable.handsontable 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Transform pyexcel sheet into a handsontable html 6 | 7 | :copyright: (c) 2016-2022 by Onni Software Ltd. 8 | :license: New BSD License, see LICENSE for further details 9 | """ 10 | import codecs 11 | import json 12 | import os 13 | import uuid 14 | from datetime import date, datetime 15 | from functools import partial 16 | 17 | from jinja2 import Environment, FileSystemLoader 18 | from pyexcel.renderer import Renderer 19 | 20 | HOST = "https://cdnjs.cloudflare.com/ajax/libs/handsontable/6.2.2/" 21 | JS_URL = HOST + "handsontable.full.min.js" 22 | CSS_URL = HOST + "handsontable.full.min.css" 23 | DEFAULTS = dict( 24 | colHeaders=True, 25 | rowHeaders=True, 26 | preventOverflow="hornizontal", 27 | readOnly=True, 28 | columnSorting=True, 29 | ) 30 | 31 | 32 | class HandsonTable(Renderer): 33 | def __init__(self, file_type): 34 | Renderer.__init__(self, file_type) 35 | loader = FileSystemLoader(_get_resource_dir("templates")) 36 | self._env = Environment( 37 | loader=loader, 38 | keep_trailing_newline=True, 39 | trim_blocks=True, 40 | lstrip_blocks=True, 41 | ) 42 | 43 | def render_book( 44 | self, book, embed=False, css_url=CSS_URL, js_url=JS_URL, **keywords 45 | ): 46 | """ 47 | Render the book data in handsontable 48 | """ 49 | book_data = self._parse_book(book, **keywords) 50 | book_data["css_url"] = css_url 51 | book_data["js_url"] = js_url 52 | if embed: 53 | template = self._env.get_template("embed.html") 54 | else: 55 | template = self._env.get_template("full.html") 56 | self._stream.write(template.render(**book_data)) 57 | 58 | def render_sheet(self, sheet, embed=False, **keywords): 59 | book = [sheet] 60 | self.render_book(book, embed=embed, **keywords) 61 | 62 | def _parse_book(self, book, styles=None, **keywords): 63 | if styles is None: 64 | styles = {} 65 | config = {} 66 | config.update(DEFAULTS) 67 | config.update(keywords) 68 | book_uuid = _generate_uuid() + "-book" 69 | book_data = { 70 | "width": keywords.get("width", None), 71 | "height": keywords.get("height", None), 72 | "sheets": [], 73 | "uid": book_uuid, 74 | "config": _dump_dict(config), 75 | } 76 | uids = [] 77 | for sheet in book: 78 | sheet_uid = _generate_uuid() 79 | handson_sheet = { 80 | "uid": sheet_uid, 81 | "name": sheet.name, 82 | "content": _dumps(sheet.array), 83 | "style": {"notused": "value"}, 84 | } 85 | if sheet.name in styles: 86 | sheet_style = styles.get(sheet.name) 87 | handson_sheet["style"] = _dump_dict(sheet_style) 88 | book_data["sheets"].append(handson_sheet) 89 | uids.append(sheet_uid) 90 | book_data["active"] = uids[0] 91 | return book_data 92 | 93 | 94 | class HandsonTableInJupyter(HandsonTable): 95 | def get_io(self): 96 | io = HandsonTable.get_io(self) 97 | setattr(io, "_repr_html_", partial(lambda s: s.getvalue(), io)) 98 | return io 99 | 100 | def render_book_to_stream( 101 | self, 102 | file_stream, 103 | book, 104 | write_title=True, 105 | caption="", 106 | display_length=None, 107 | **keywords 108 | ): 109 | self.set_write_title(write_title) 110 | self.set_output_stream(file_stream) 111 | book_data = self._parse_book(book, **keywords) 112 | __css_file__ = os.path.join( 113 | _get_resource_dir("templates"), 114 | "pyexcel-handsontable", 115 | "handsontable.min.css", 116 | ) 117 | with codecs.open(__css_file__, "r", "utf-8") as f: 118 | book_data["handsontable_css"] = f.read() 119 | template = self._env.get_template("notebook.html") 120 | self._stream.write(template.render(**book_data)) 121 | 122 | def render_sheet_to_stream(self, file_stream, sheet, **keywords): 123 | self.render_book_to_stream(file_stream, [sheet], **keywords) 124 | 125 | def render_book(self, book, **keywords): 126 | """ 127 | Render the book data in handsontable 128 | """ 129 | raise NotImplementedError() 130 | 131 | def render_sheet(self, sheet, **keywords): 132 | raise NotImplementedError() 133 | 134 | 135 | def _generate_uuid(): 136 | return "pyexcel-" + uuid.uuid4().hex 137 | 138 | 139 | def _get_resource_dir(folder): 140 | current_path = os.path.dirname(__file__) 141 | resource_path = os.path.join(current_path, folder) 142 | return resource_path 143 | 144 | 145 | class DateTimeEncoder(json.JSONEncoder): 146 | def default(self, obj): 147 | if isinstance(obj, date): 148 | date_string = obj.strftime("%Y-%m-%d") 149 | return date_string 150 | if isinstance(obj, datetime): 151 | datetime_string = obj.strftime("%Y-%m-%d %H:%M:%S") 152 | return datetime_string 153 | return json.JSONEncoder.default(self, obj) 154 | 155 | 156 | def _dumps(data): 157 | return json.dumps(data, cls=DateTimeEncoder) 158 | 159 | 160 | def _dump_dict(aconfig): 161 | """ 162 | This function is made for testing purpose 163 | """ 164 | return _dumps(aconfig) 165 | -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/components.html: -------------------------------------------------------------------------------- 1 | {%macro sheet_tab(name, sheet_uid, book_uid)%} 2 | 3 |
  • 4 | {{name}} 5 |
  • 6 | {%endmacro%} 7 | 8 | {%macro sheet_div(book_uid, sheet_uid)%} 9 | 10 |
    11 |
    12 |
    13 | {%endmacro%} 14 | 15 | {%macro sheet_script(sheet_uid, content, style)%} 16 | // BOOK_SHEET 17 | var sheetHandle = {}; 18 | sheetHandle.element = document.querySelector("#{{sheet_uid}}"); 19 | 20 | var mydata = {{content}}; 21 | var customStyle = {{ style }}; 22 | sheetHandle.config = Object.assign({}, config, customStyle); 23 | sheetHandle.config.data = mydata; 24 | sheetHandle.handsontable = new Handsontable(sheetHandle.element, sheetHandle.config); 25 | sheetHandle.sheetId = "{{sheet_uid}}"; 26 | if(tableHandles){ 27 | tableHandles.push(sheetHandle); 28 | } 29 | {%endmacro%} 30 | -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/css.html: -------------------------------------------------------------------------------- 1 | body{font-family:Helvetica,sans-serif;margin:2 0 0 0}.tab{margin-bottom:0 !important;text-align:center;list-style:none;padding:0 0 0 10px;line-height:24px;height:26px;overflow:hidden;font-size:12px;font-family:verdana;position:relative;margin:0}.tab li{margin-left:0 !important;float:left;height:24px;border:1px solid #aaa;background:#d1d1d1;background:linear-gradient(top, #ececec 50%, #d1d1d1);display:inline-block;position:relative;z-index:0;border-top-left-radius:6px;border-top-right-radius:6px;box-shadow:0 3px 3px rgba(0,0,0,0.4),inset 0 1px 0 #fff;text-shadow:0 1px #fff;margin:0 -5px;padding:0 20px}.tab li.active{background:#fff;color:#333;z-index:2}.tab li:before{left:-6px;border-width:0 1px 1px 0;box-shadow:2px 2px 0 #d1d1d1}.tab li:after{right:-6px;border-width:0 0 1px 1px;box-shadow:-2px 2px 0 #d1d1d1}.tab a{color:#555;text-decoration:none}.tab:before{position:absolute;content:" ";width:100%;bottom:0;left:0;border-bottom:1px solid #aaa;z-index:1}.tabcontent{margin-top:-1px} 2 | -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/embed.html: -------------------------------------------------------------------------------- 1 | {% from "components.html" import sheet_tab, sheet_div, sheet_script %} 2 | 3 |
      4 | {% for sheet in sheets %} 5 | {{ sheet_tab(sheet.name, sheet.uid, uid) }} 6 | {% endfor %} 7 |
    8 | {% for sheet in sheets %} 9 | {{ sheet_div(uid, sheet.uid) }} 10 | {% endfor %} 11 | 12 | 13 | 43 | 44 | -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/full.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | {%include "embed.html" %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/notebook.html: -------------------------------------------------------------------------------- 1 | {% from "components.html" import sheet_tab, sheet_div, sheet_script %} 2 | 3 | 7 | 24 |
      25 | {% for sheet in sheets %} 26 | {{ sheet_tab(sheet.name, sheet.uid, uid) }} 27 | {% endfor %} 28 |
    29 | {% for sheet in sheets %} 30 | {{ sheet_div(uid, sheet.uid) }} 31 | {% endfor %} 32 | 63 | 64 | -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/pyexcel-handsontable/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyexcel-renderers/pyexcel-handsontable/0ed2bf06acb56dae02a9793c457524f9bd5e8b7a/pyexcel_handsontable/templates/pyexcel-handsontable/ZeroClipboard.swf -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/pyexcel-handsontable/handsontable.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * (The MIT License) 3 | * 4 | * Copyright (c) 2012-2014 Marcin Warpechowski 5 | * Copyright (c) 2015 Handsoncode sp. z o.o. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining 8 | * a copy of this software and associated documentation files (the 9 | * 'Software'), to deal in the Software without restriction, including 10 | * without limitation the rights to use, copy, modify, merge, publish, 11 | * distribute, sublicense, and/or sell copies of the Software, and to 12 | * permit persons to whom the Software is furnished to do so, subject to 13 | * the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | * 26 | * Version: 6.2.2 27 | * Release date: 19/12/2018 (built at 18/12/2018 14:43:44) 28 | */.handsontable .table td,.handsontable .table th{border-top:none}.handsontable tr{background:#fff}.handsontable td{background-color:inherit}.handsontable .table caption+thead tr:first-child td,.handsontable .table caption+thead tr:first-child th,.handsontable .table colgroup+thead tr:first-child td,.handsontable .table colgroup+thead tr:first-child th,.handsontable .table thead:first-child tr:first-child td,.handsontable .table thead:first-child tr:first-child th{border-top:1px solid #ccc}.handsontable .table-bordered{border:0;border-collapse:separate}.handsontable .table-bordered td,.handsontable .table-bordered th{border-left:none}.handsontable .table-bordered td:first-child,.handsontable .table-bordered th:first-child{border-left:1px solid #ccc}.handsontable .table>tbody>tr>td,.handsontable .table>tbody>tr>th,.handsontable .table>tfoot>tr>td,.handsontable .table>tfoot>tr>th,.handsontable .table>thead>tr>td,.handsontable .table>thead>tr>th{line-height:21px;padding:0 4px}.col-lg-1.handsontable,.col-lg-2.handsontable,.col-lg-3.handsontable,.col-lg-4.handsontable,.col-lg-5.handsontable,.col-lg-6.handsontable,.col-lg-7.handsontable,.col-lg-8.handsontable,.col-lg-9.handsontable,.col-lg-10.handsontable,.col-lg-11.handsontable,.col-lg-12.handsontable,.col-md-1.handsontable,.col-md-2.handsontable,.col-md-3.handsontable,.col-md-4.handsontable,.col-md-5.handsontable,.col-md-6.handsontable,.col-md-7.handsontable,.col-md-8.handsontable,.col-md-9.handsontable .col-sm-1.handsontable,.col-md-10.handsontable,.col-md-11.handsontable,.col-md-12.handsontable,.col-sm-2.handsontable,.col-sm-3.handsontable,.col-sm-4.handsontable,.col-sm-5.handsontable,.col-sm-6.handsontable,.col-sm-7.handsontable,.col-sm-8.handsontable,.col-sm-9.handsontable .col-xs-1.handsontable,.col-sm-10.handsontable,.col-sm-11.handsontable,.col-sm-12.handsontable,.col-xs-2.handsontable,.col-xs-3.handsontable,.col-xs-4.handsontable,.col-xs-5.handsontable,.col-xs-6.handsontable,.col-xs-7.handsontable,.col-xs-8.handsontable,.col-xs-9.handsontable,.col-xs-10.handsontable,.col-xs-11.handsontable,.col-xs-12.handsontable{padding-left:0;padding-right:0}.handsontable .table-striped>tbody>tr:nth-of-type(2n){background-color:#fff}.handsontable{position:relative}.handsontable .hide{display:none}.handsontable .relative{position:relative}.handsontable.htAutoSize{visibility:hidden;left:-99000px;position:absolute;top:-99000px}.handsontable .wtHider{width:0}.handsontable .wtSpreader{position:relative;width:0;height:auto}.handsontable div,.handsontable input,.handsontable table,.handsontable tbody,.handsontable td,.handsontable textarea,.handsontable th,.handsontable thead{box-sizing:content-box;-webkit-box-sizing:content-box;-moz-box-sizing:content-box}.handsontable input,.handsontable textarea{min-height:0}.handsontable table.htCore{border-collapse:separate;border-spacing:0;margin:0;border-width:0;table-layout:fixed;width:0;outline-width:0;cursor:default;max-width:none;max-height:none}.handsontable col,.handsontable col.rowHeader{width:50px}.handsontable td,.handsontable th{border-top-width:0;border-left-width:0;border-right:1px solid #ccc;border-bottom:1px solid #ccc;height:22px;empty-cells:show;line-height:21px;padding:0 4px;background-color:#fff;vertical-align:top;overflow:hidden;outline-width:0;white-space:pre-line;background-clip:padding-box}.handsontable td.htInvalid{background-color:#ff4c42!important}.handsontable td.htNoWrap{white-space:nowrap}.handsontable th:last-child{border-right:1px solid #ccc;border-bottom:1px solid #ccc}.handsontable th.htNoFrame,.handsontable th:first-child.htNoFrame,.handsontable tr:first-child th.htNoFrame{border-left-width:0;background-color:#fff;border-color:#fff}.handsontable .htNoFrame+td,.handsontable .htNoFrame+th,.handsontable.htRowHeaders thead tr th:nth-child(2),.handsontable td:first-of-type,.handsontable th:first-child,.handsontable th:nth-child(2){border-left:1px solid #ccc}.handsontable tr:first-child td,.handsontable tr:first-child th{border-top:1px solid #ccc}.ht_master:not(.innerBorderLeft):not(.emptyColumns)~.handsontable:not(.ht_clone_top) thead tr th:first-child,.ht_master:not(.innerBorderLeft):not(.emptyColumns)~.handsontable tbody tr th{border-right-width:0}.ht_master:not(.innerBorderTop) thead tr.lastChild th,.ht_master:not(.innerBorderTop) thead tr:last-child th,.ht_master:not(.innerBorderTop)~.handsontable thead tr.lastChild th,.ht_master:not(.innerBorderTop)~.handsontable thead tr:last-child th{border-bottom-width:0}.handsontable th{background-color:#f0f0f0;color:#222;text-align:center;font-weight:400;white-space:nowrap}.handsontable thead th{padding:0}.handsontable th.active{background-color:#ccc}.handsontable thead th .relative{padding:2px 4px}#hot-display-license-info{font-size:10px;color:#323232;padding:5px 0 3px;font-family:Helvetica,Arial,sans-serif;text-align:left}.handsontable .manualColumnResizer{position:fixed;top:0;cursor:col-resize;z-index:110;width:5px;height:25px}.handsontable .manualRowResizer{position:fixed;left:0;cursor:row-resize;z-index:110;height:5px;width:50px}.handsontable .manualColumnResizer.active,.handsontable .manualColumnResizer:hover,.handsontable .manualRowResizer.active,.handsontable .manualRowResizer:hover{background-color:#34a9db}.handsontable .manualColumnResizerGuide{position:fixed;right:0;top:0;background-color:#34a9db;display:none;width:0;border-right:1px dashed #777;margin-left:5px}.handsontable .manualRowResizerGuide{position:fixed;left:0;bottom:0;background-color:#34a9db;display:none;height:0;border-bottom:1px dashed #777;margin-top:5px}.handsontable .manualColumnResizerGuide.active,.handsontable .manualRowResizerGuide.active{display:block;z-index:199}.handsontable .columnSorting{position:relative}.handsontable .columnSorting.sortAction:hover{text-decoration:underline;cursor:pointer}.handsontable span.colHeader{display:inline-block;line-height:1.1}.handsontable span.colHeader.columnSorting:before{top:50%;margin-top:-6px;padding-left:8px;position:absolute;right:-9px;content:"";height:10px;width:5px;background-size:contain;background-repeat:no-repeat;background-position-x:right}.handsontable span.colHeader.columnSorting.ascending:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAMAAADJ7yrpAAAAKlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKE86IAAAADXRSTlMABBEmRGprlJW72e77tTkTKwAAAFNJREFUeAHtzjkSgCAUBNHPgsoy97+ulGXRqJE5L+xkxoYt2UdsLb5bqFINz+aLuuLn5rIu2RkO3fZpWENimNgiw6iBYRTPMLJjGFxQZ1hxxb/xBI1qC8k39CdKAAAAAElFTkSuQmCC")}.handsontable span.colHeader.columnSorting.descending:before{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAMAAADJ7yrpAAAAKlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKE86IAAAADXRSTlMABBEmRGprlJW72e77tTkTKwAAAFJJREFUeAHtzjkSgCAQRNFmQYUZ7n9dKUvru0TmvPAn3br0QfgdZ5xx6x+rQn23GqTYnq1FDcnuzZIO2WmedVqIRVxgGKEyjNgYRjKGkZ1hFIZ3I70LyM0VtU8AAAAASUVORK5CYII=")}.htGhostTable .htCore span.colHeader.columnSorting:not(.indicatorDisabled):after{content:"*";display:inline-block;position:relative;padding-right:20px}.handsontable .wtBorder{position:absolute;font-size:0}.handsontable .wtBorder.hidden{display:none!important}.handsontable .wtBorder.current{z-index:10}.handsontable .wtBorder.area{z-index:8}.handsontable .wtBorder.fill{z-index:6}.handsontable td.area,.handsontable td.area-1,.handsontable td.area-2,.handsontable td.area-3,.handsontable td.area-4,.handsontable td.area-5,.handsontable td.area-6,.handsontable td.area-7{position:relative}.handsontable td.area-1:before,.handsontable td.area-2:before,.handsontable td.area-3:before,.handsontable td.area-4:before,.handsontable td.area-5:before,.handsontable td.area-6:before,.handsontable td.area-7:before,.handsontable td.area:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;bottom:-100%\9;background:#005eff}@media (-ms-high-contrast:none),screen and (-ms-high-contrast:active){.handsontable td.area-1:before,.handsontable td.area-2:before,.handsontable td.area-3:before,.handsontable td.area-4:before,.handsontable td.area-5:before,.handsontable td.area-6:before,.handsontable td.area-7:before,.handsontable td.area:before{bottom:-100%}}.handsontable td.area:before{opacity:.1}.handsontable td.area-1:before{opacity:.2}.handsontable td.area-2:before{opacity:.27}.handsontable td.area-3:before{opacity:.35}.handsontable td.area-4:before{opacity:.41}.handsontable td.area-5:before{opacity:.47}.handsontable td.area-6:before{opacity:.54}.handsontable td.area-7:before{opacity:.58}.handsontable tbody th.ht__highlight,.handsontable thead th.ht__highlight{background-color:#dcdcdc}.handsontable tbody th.ht__active_highlight,.handsontable thead th.ht__active_highlight{background-color:#8eb0e7;color:#000}.handsontable .wtBorder.corner{font-size:0;cursor:crosshair}.handsontable .htBorder.htFillBorder{background:red;width:1px;height:1px}.handsontableInput{border:none;outline-width:0;margin:0;padding:1px 5px 0;font-family:inherit;line-height:21px;font-size:inherit;box-shadow:inset 0 0 0 2px #5292f7;resize:none;display:block;color:#000;border-radius:0;background-color:#fff}.handsontableInputHolder{position:absolute;top:0;left:0;z-index:104}.htSelectEditor{-webkit-appearance:menulist-button!important;position:absolute;width:auto}.handsontable .htDimmed{color:#777}.handsontable .htSubmenu{position:relative}.handsontable .htSubmenu :after{content:"\25B6";color:#777;position:absolute;right:5px;font-size:9px}.handsontable .htLeft{text-align:left}.handsontable .htCenter{text-align:center}.handsontable .htRight{text-align:right}.handsontable .htJustify{text-align:justify}.handsontable .htTop{vertical-align:top}.handsontable .htMiddle{vertical-align:middle}.handsontable .htBottom{vertical-align:bottom}.handsontable .htPlaceholder{color:#999}.handsontable .htAutocompleteArrow{float:right;font-size:10px;color:#eee;cursor:default;width:16px;text-align:center}.handsontable td .htAutocompleteArrow:hover{color:#777}.handsontable td.area .htAutocompleteArrow{color:#d3d3d3}.handsontable .htCheckboxRendererInput{display:inline-block;vertical-align:middle}.handsontable .htCheckboxRendererInput.noValue{opacity:.5}.handsontable .htCheckboxRendererLabel{cursor:pointer;display:inline-block;width:100%}.handsontable .handsontable.ht_clone_top .wtHider{padding:0 0 5px}.handsontable .autocompleteEditor.handsontable{padding-right:17px}.handsontable .autocompleteEditor.handsontable.htMacScroll{padding-right:15px}.handsontable.listbox{margin:0}.handsontable.listbox .ht_master table{border:1px solid #ccc;border-collapse:separate;background:#fff}.handsontable.listbox td,.handsontable.listbox th,.handsontable.listbox tr:first-child td,.handsontable.listbox tr:first-child th,.handsontable.listbox tr:last-child th{border-color:transparent}.handsontable.listbox td,.handsontable.listbox th{white-space:nowrap;text-overflow:ellipsis}.handsontable.listbox td.htDimmed{cursor:default;color:inherit;font-style:inherit}.handsontable.listbox .wtBorder{visibility:hidden}.handsontable.listbox tr:hover td,.handsontable.listbox tr td.current{background:#eee}.ht_clone_top{z-index:101}.ht_clone_left{z-index:102}.ht_clone_bottom_left_corner,.ht_clone_debug,.ht_clone_top_left_corner{z-index:103}.handsontable td.htSearchResult{background:#fcedd9;color:#583707}.htBordered{border-width:1px}.htBordered.htTopBorderSolid{border-top-style:solid;border-top-color:#000}.htBordered.htRightBorderSolid{border-right-style:solid;border-right-color:#000}.htBordered.htBottomBorderSolid{border-bottom-style:solid;border-bottom-color:#000}.htBordered.htLeftBorderSolid{border-left-style:solid;border-left-color:#000}.handsontable tbody tr th:nth-last-child(2){border-right:1px solid #ccc}.handsontable thead tr:nth-last-child(2) th.htGroupIndicatorContainer{border-bottom:1px solid #ccc;padding-bottom:5px}.ht_clone_top_left_corner thead tr th:nth-last-child(2){border-right:1px solid #ccc}.htCollapseButton{width:10px;height:10px;line-height:10px;text-align:center;border-radius:5px;border:1px solid #f3f3f3;box-shadow:1px 1px 3px rgba(0,0,0,.4);cursor:pointer;margin-bottom:3px;position:relative}.htCollapseButton:after{content:"";height:300%;width:1px;display:block;background:#ccc;margin-left:4px;position:absolute;bottom:10px}thead .htCollapseButton{right:5px;position:absolute;top:5px;background:#fff}thead .htCollapseButton:after{height:1px;width:700%;right:10px;top:4px}.handsontable tr th .htExpandButton{position:absolute;width:10px;height:10px;line-height:10px;text-align:center;border-radius:5px;border:1px solid #f3f3f3;box-shadow:1px 1px 3px rgba(0,0,0,.4);cursor:pointer;top:0;display:none}.handsontable thead tr th .htExpandButton{top:5px}.handsontable tr th .htExpandButton.clickable{display:block}.collapsibleIndicator{position:absolute;top:50%;transform:translateY(-50%);right:5px;border:1px solid #a6a6a6;line-height:10px;color:#222;border-radius:10px;font-size:10px;width:10px;height:10px;cursor:pointer;box-shadow:0 0 0 6px #eee;background:#eee}.handsontable col.hidden{width:0!important}.handsontable table tr th.lightRightBorder{border-right:1px solid #e6e6e6}.handsontable tr.hidden,.handsontable tr.hidden td,.handsontable tr.hidden th{display:none}.ht_clone_bottom,.ht_clone_left,.ht_clone_top,.ht_master{overflow:hidden}.ht_master .wtHolder{overflow:auto}.handsontable .ht_clone_left thead,.handsontable .ht_master thead,.handsontable .ht_master tr th{visibility:hidden}.ht_clone_bottom .wtHolder,.ht_clone_left .wtHolder,.ht_clone_top .wtHolder{overflow:hidden}.handsontable.mobile,.handsontable.mobile .wtHolder{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-overflow-scrolling:touch}.htMobileEditorContainer{display:none;position:absolute;top:0;width:70%;height:54pt;background:#f8f8f8;border-radius:20px;border:1px solid #ebebeb;z-index:999;box-sizing:border-box;-webkit-box-sizing:border-box;-webkit-text-size-adjust:none}.topLeftSelectionHandle-HitArea:not(.ht_master .topLeftSelectionHandle-HitArea),.topLeftSelectionHandle:not(.ht_master .topLeftSelectionHandle){z-index:9999}.bottomRightSelectionHandle,.bottomRightSelectionHandle-HitArea,.topLeftSelectionHandle,.topLeftSelectionHandle-HitArea{left:-10000px;top:-10000px}.htMobileEditorContainer.active{display:block}.htMobileEditorContainer .inputs{position:absolute;right:210pt;bottom:10pt;top:10pt;left:14px;height:34pt}.htMobileEditorContainer .inputs textarea{font-size:13pt;border:1px solid #a1a1a1;-webkit-appearance:none;box-shadow:none;position:absolute;left:14px;right:14px;top:0;bottom:0;padding:7pt}.htMobileEditorContainer .cellPointer{position:absolute;top:-13pt;height:0;width:0;left:30px;border-left:13pt solid transparent;border-right:13pt solid transparent;border-bottom:13pt solid #ebebeb}.htMobileEditorContainer .cellPointer.hidden{display:none}.htMobileEditorContainer .cellPointer:before{content:"";display:block;position:absolute;top:2px;height:0;width:0;left:-13pt;border-left:13pt solid transparent;border-right:13pt solid transparent;border-bottom:13pt solid #f8f8f8}.htMobileEditorContainer .moveHandle{position:absolute;top:10pt;left:5px;width:30px;bottom:0;cursor:move;z-index:9999}.htMobileEditorContainer .moveHandle:after{content:"..\A..\A..\A..";white-space:pre;line-height:10px;font-size:20pt;display:inline-block;margin-top:-8px;color:#ebebeb}.htMobileEditorContainer .positionControls{width:205pt;position:absolute;right:5pt;top:0;bottom:0}.htMobileEditorContainer .positionControls>div{width:50pt;height:100%;float:left}.htMobileEditorContainer .positionControls>div:after{content:" ";display:block;width:15pt;height:15pt;text-align:center;line-height:50pt}.htMobileEditorContainer .downButton:after,.htMobileEditorContainer .leftButton:after,.htMobileEditorContainer .rightButton:after,.htMobileEditorContainer .upButton:after{transform-origin:5pt 5pt;-webkit-transform-origin:5pt 5pt;margin:21pt 0 0 21pt}.htMobileEditorContainer .leftButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(-45deg)}.htMobileEditorContainer .leftButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .rightButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(135deg)}.htMobileEditorContainer .rightButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .upButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(45deg)}.htMobileEditorContainer .upButton:active:after{border-color:#cfcfcf}.htMobileEditorContainer .downButton:after{border-top:2px solid #288ffe;border-left:2px solid #288ffe;-webkit-transform:rotate(225deg)}.htMobileEditorContainer .downButton:active:after{border-color:#cfcfcf}.handsontable.hide-tween{animation:opacity-hide .3s;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards}.handsontable.show-tween{animation:opacity-show .3s;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards}.htCommentCell{position:relative}.htCommentCell:after{content:"";position:absolute;top:0;right:0;border-left:6px solid transparent;border-top:6px solid #000}.htComments{display:none;z-index:1059;position:absolute}.htCommentTextArea{box-shadow:0 1px 3px rgba(0,0,0,.117647),0 1px 2px rgba(0,0,0,.239216);box-sizing:border-box;border:none;border-left:3px solid #ccc;background-color:#fff;width:215px;height:90px;font-size:12px;padding:5px;outline:0!important;-webkit-appearance:none}.htCommentTextArea:focus{box-shadow:0 1px 3px rgba(0,0,0,.117647),0 1px 2px rgba(0,0,0,.239216),inset 0 0 0 1px #5292f7;border-left:3px solid #5292f7} 29 | /*! 30 | * Handsontable ContextMenu 31 | */.htContextMenu:not(.htGhostTable){display:none;position:absolute;z-index:1060}.htContextMenu .ht_clone_corner,.htContextMenu .ht_clone_debug,.htContextMenu .ht_clone_left,.htContextMenu .ht_clone_top{display:none}.htContextMenu table.htCore{border:1px solid #ccc;border-bottom-width:2px;border-right-width:2px}.htContextMenu .wtBorder{visibility:hidden}.htContextMenu table tbody tr td{background:#fff;border-width:0;padding:4px 6px 0;cursor:pointer;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.htContextMenu table tbody tr td:first-child{border:0}.htContextMenu table tbody tr td.htDimmed{font-style:normal;color:#323232}.htContextMenu table tbody tr td.current,.htContextMenu table tbody tr td.zeroclipboard-is-hover{background:#f3f3f3}.htContextMenu table tbody tr td.htSeparator{border-top:1px solid #e6e6e6;height:0;padding:0;cursor:default}.htContextMenu table tbody tr td.htDisabled{color:#999;cursor:default}.htContextMenu table tbody tr td.htDisabled:hover{background:#fff;color:#999;cursor:default}.htContextMenu table tbody tr.htHidden{display:none}.htContextMenu table tbody tr td .htItemWrapper{margin-left:10px;margin-right:6px}.htContextMenu table tbody tr td div span.selected{margin-top:-2px;position:absolute;left:4px}.htContextMenu .ht_master .wtHolder{overflow:hidden}textarea#HandsontableCopyPaste{position:fixed!important;top:0!important;right:100%!important;overflow:hidden;opacity:0;outline:0 none!important}.htRowHeaders .ht_master.innerBorderLeft~.ht_clone_left td:first-of-type,.htRowHeaders .ht_master.innerBorderLeft~.ht_clone_top_left_corner th:nth-child(2){border-left:0 none}.handsontable.ht__manualColumnMove.after-selection--columns thead th.ht__highlight{cursor:move;cursor:grab}.handsontable.ht__manualColumnMove.on-moving--columns,.handsontable.ht__manualColumnMove.on-moving--columns thead th.ht__highlight{cursor:move;cursor:grabbing}.handsontable.ht__manualColumnMove.on-moving--columns .manualColumnResizer{display:none}.handsontable .ht__manualColumnMove--backlight,.handsontable .ht__manualColumnMove--guideline{position:absolute;height:100%;display:none}.handsontable .ht__manualColumnMove--guideline{background:#757575;width:2px;top:0;margin-left:-1px;z-index:105}.handsontable .ht__manualColumnMove--backlight{background:#343434;background:rgba(52,52,52,.25);display:none;z-index:105;pointer-events:none}.handsontable.on-moving--columns .ht__manualColumnMove--backlight,.handsontable.on-moving--columns.show-ui .ht__manualColumnMove--guideline{display:block}.handsontable .wtHider{position:relative}.handsontable.ht__manualRowMove.after-selection--rows tbody th.ht__highlight{cursor:move;cursor:grab}.handsontable.ht__manualRowMove.on-moving--rows,.handsontable.ht__manualRowMove.on-moving--rows tbody th.ht__highlight{cursor:move;cursor:grabbing}.handsontable.ht__manualRowMove.on-moving--rows .manualRowResizer{display:none}.handsontable .ht__manualRowMove--backlight,.handsontable .ht__manualRowMove--guideline{position:absolute;width:100%;display:none}.handsontable .ht__manualRowMove--guideline{background:#757575;height:2px;left:0;margin-top:-1px;z-index:105}.handsontable .ht__manualRowMove--backlight{background:#343434;background:rgba(52,52,52,.25);display:none;z-index:105;pointer-events:none}.handsontable.on-moving--rows .ht__manualRowMove--backlight,.handsontable.on-moving--rows.show-ui .ht__manualRowMove--guideline{display:block}.handsontable tbody td[rowspan][class*=area][class*=highlight]:not([class*=fullySelectedMergedCell]):before{opacity:0}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-0]:before,.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-multiple]:before{opacity:.1}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-1]:before{opacity:.2}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-2]:before{opacity:.27}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-3]:before{opacity:.35}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-4]:before{opacity:.41}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-5]:before{opacity:.47}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-6]:before{opacity:.54}.handsontable tbody td[rowspan][class*=area][class*=highlight][class*=fullySelectedMergedCell-7]:before{opacity:.58} -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/pyexcel-handsontable/main.js: -------------------------------------------------------------------------------- 1 | define(["require", "exports"], function (require, exports) { 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var version = '0.0.4'; 5 | function load_ipython_extension() { 6 | console.log("pyexcel-handsontable " + version + " has been loaded"); 7 | } 8 | exports.load_ipython_extension = load_ipython_extension; 9 | }); 10 | -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/pyexcel-handsontable/numbro-1.11.0.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * numbro.js 3 | * version : 1.11.0 4 | * author : Företagsplatsen AB 5 | * license : MIT 6 | * http://www.foretagsplatsen.se 7 | */ 8 | (function(){"use strict";/************************************ 9 | Constructors 10 | ************************************/ 11 | // Numbro prototype object 12 | function a(a){this._value=a}function b(a){return 0===a?1:Math.floor(Math.log(Math.abs(a))/Math.LN10)+1}function c(a){var b,c="";for(b=0;a>b;b++)c+="0";return c}/** 13 | * Implementation of toFixed() for numbers with exponents 14 | * This function may return negative representations for zero values e.g. "-0.0" 15 | */ 16 | function d(a,b){var d,e,f,g,h,i,j,k; 17 | // exponent is positive - add zeros after the numbers 18 | // exponent is negative 19 | // tack on the decimal point if needed 20 | // substring off the end to satisfy the precision 21 | // only add percision 0's if the exponent is positive 22 | return k=a.toString(),d=k.split("e")[0],g=k.split("e")[1],e=d.split(".")[0],f=d.split(".")[1]||"",+g>0?k=e+f+c(g-f.length):(h=0>+e?"-0":"0",b>0&&(h+="."),j=c(-1*g-1),i=(j+Math.abs(e)+f).substr(0,b),k=h+i),+g>0&&b>0&&(k+="."+c(b)),k}/** 23 | * Implementation of toFixed() that treats floats more like decimals 24 | * 25 | * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present 26 | * problems for accounting- and finance-related software. 27 | * 28 | * Also removes negative signs for zero-formatted numbers. e.g. -0.01 w/ precision 1 -> 0.0 29 | */ 30 | function e(a,b,c,e){var f,g,h=Math.pow(10,b); 31 | // toFixed returns scientific notation for numbers above 1e21 and below 1e-7 32 | // remove the leading negative sign if it exists and should not be present (e.g. -0.00) 33 | // Multiply up by precision, round accurately, then divide and use native toFixed(): 34 | return a.toString().indexOf("e")>-1?(g=d(a,b),"-"===g.charAt(0)&&+g>=0&&(g=g.substr(1))):g=(c(a+"e+"+b)/h).toFixed(b),e&&(f=new RegExp("0{1,"+e+"}$"),g=g.replace(f,"")),g}/************************************ 35 | Formatting 36 | ************************************/ 37 | // determine what type of formatting we need to do 38 | function f(a,b,c){var d,e=b.replace(/\{[^\{\}]*\}/g,""); 39 | // return string 40 | // figure out what kind of format we are dealing with 41 | // currency!!!!! 42 | return d=e.indexOf("$")>-1?h(a,z[B].currency.symbol,b,c):e.indexOf("%")>-1?j(a,b,c):e.indexOf(":")>-1?k(a,b):n(a._value,b,c)} 43 | // revert to number 44 | function g(a,b){var c,d,e,f,g,h=b,i=!1;if(b.indexOf(":")>-1)a._value=l(b);else if(b===C)a._value=0;else{ 45 | // see if bytes are there so that we can multiply to the correct number 46 | for("."!==z[B].delimiters.decimal&&(b=b.replace(/\./g,"").replace(z[B].delimiters.decimal,".")),c=new RegExp("[^a-zA-Z]"+z[B].abbreviations.thousand+"(?:\\)|(\\"+z[B].currency.symbol+")?(?:\\))?)?$"),d=new RegExp("[^a-zA-Z]"+z[B].abbreviations.million+"(?:\\)|(\\"+z[B].currency.symbol+")?(?:\\))?)?$"),e=new RegExp("[^a-zA-Z]"+z[B].abbreviations.billion+"(?:\\)|(\\"+z[B].currency.symbol+")?(?:\\))?)?$"),f=new RegExp("[^a-zA-Z]"+z[B].abbreviations.trillion+"(?:\\)|(\\"+z[B].currency.symbol+")?(?:\\))?)?$"),g=1;g-1?i=Math.pow(1024,g):b.indexOf(w[g])>-1&&(i=Math.pow(1e3,g));var j=b.replace(/[^0-9\.]+/g,"");""===j? 47 | // An empty string is not a number. 48 | a._value=NaN:( 49 | // do some math to create our number 50 | a._value=(i?i:1)*(h.match(c)?Math.pow(10,3):1)*(h.match(d)?Math.pow(10,6):1)*(h.match(e)?Math.pow(10,9):1)*(h.match(f)?Math.pow(10,12):1)*(b.indexOf("%")>-1?.01:1)*((b.split("-").length+Math.min(b.split("(").length-1,b.split(")").length-1))%2?1:-1)*Number(j), 51 | // round if we are talking about bytes 52 | a._value=i?Math.ceil(a._value):a._value)}return a._value}function h(a,b,c,d){var e,f,g=c,h=g.indexOf("$"),i=g.indexOf("("),j=g.indexOf("+"),k=g.indexOf("-"),l="",m="";if(-1===g.indexOf("$")? 53 | // Use defaults instead of the format provided 54 | "infix"===z[B].currency.position?(m=b,z[B].currency.spaceSeparated&&(m=" "+m+" ")):z[B].currency.spaceSeparated&&(l=" "):g.indexOf(" $")>-1?(l=" ",g=g.replace(" $","")):g.indexOf("$ ")>-1?(l=" ",g=g.replace("$ ","")):g=g.replace("$",""),f=n(a._value,g,d,m),-1===c.indexOf("$")) 55 | // Use defaults instead of the format provided 56 | switch(z[B].currency.position){case"postfix":f.indexOf(")")>-1?(f=f.split(""),f.splice(-1,0,l+b),f=f.join("")):f=f+l+b;break;case"infix":break;case"prefix":f.indexOf("(")>-1||f.indexOf("-")>-1?(f=f.split(""),e=Math.max(i,k)+1,f.splice(e,0,b+l),f=f.join("")):f=b+l+f;break;default:throw Error('Currency position should be among ["prefix", "infix", "postfix"]')}else 57 | // position the symbol 58 | 1>=h?f.indexOf("(")>-1||f.indexOf("+")>-1||f.indexOf("-")>-1?(f=f.split(""),e=1,(i>h||j>h||k>h)&&(e=0),f.splice(e,0,b+l),f=f.join("")):f=b+l+f:f.indexOf(")")>-1?(f=f.split(""),f.splice(-1,0,l+b),f=f.join("")):f=f+l+b;return f}function i(a,b,c,d){return h(a,b,c,d)}function j(a,b,c){var d,e="",f=100*a._value; 59 | // check for space before % 60 | return b.indexOf(" %")>-1?(e=" ",b=b.replace(" %","")):b=b.replace("%",""),d=n(f,b,c),d.indexOf(")")>-1?(d=d.split(""),d.splice(-1,0,e+"%"),d=d.join("")):d=d+e+"%",d}function k(a){var b=Math.floor(a._value/60/60),c=Math.floor((a._value-60*b*60)/60),d=Math.round(a._value-60*b*60-60*c);return b+":"+(10>c?"0"+c:c)+":"+(10>d?"0"+d:d)}function l(a){var b=a.split(":"),c=0; 61 | // turn hours and minutes into seconds and add them all up 62 | // hours 63 | // minutes 64 | // seconds 65 | // minutes 66 | // seconds 67 | return 3===b.length?(c+=60*Number(b[0])*60,c+=60*Number(b[1]),c+=Number(b[2])):2===b.length&&(c+=60*Number(b[0]),c+=Number(b[1])),Number(c)}function m(a,b,c){var d,e,f,g=b[0],h=Math.abs(a);if(h>=c){for(d=1;d=e&&f>h){g=b[d],a/=e;break} 68 | // values greater than or equal to [scale] YB never set the suffix 69 | g===b[0]&&(a/=Math.pow(c,b.length-1),g=b[b.length-1])}return{value:a,suffix:g}}function n(a,d,f,g){var h,i,j,k,l,n,o,p,q,r,s,t,u,v,w,x,A=!1,D=!1,E=!1,F="",G=!1,// force abbreviation to thousands 70 | H=!1,// force abbreviation to millions 71 | I=!1,// force abbreviation to billions 72 | J=!1,// force abbreviation to trillions 73 | K=!1,// force abbreviation 74 | L="",M="",N=Math.abs(a),O="",P=!1,Q=!1,R=""; 75 | // check if number is zero and a custom zero format has been set 76 | if(0===a&&null!==C)return C;if(!isFinite(a))return""+a;if(0===d.indexOf("{")){var S=d.indexOf("}");if(-1===S)throw Error('Format should also contain a "}"');r=d.slice(1,S),d=d.slice(S+1)}else r="";if(d.indexOf("}")===d.length-1&&d.length){var T=d.indexOf("{");if(-1===T)throw Error('Format should also contain a "{"');s=d.slice(T+1,-1),d=d.slice(0,T+1)}else s=""; 77 | // check for min length 78 | var U; 79 | // see if we are formatting 80 | // binary-decimal bytes (1024 MB), binary bytes (1024 MiB), or decimal bytes (1000 MB) 81 | for(U=-1===d.indexOf(".")?d.match(/([0-9]+).*/):d.match(/([0-9]+)\..*/),w=null===U?-1:U[1].length,-1!==d.indexOf("-")&&(P=!0),d.indexOf("(")>-1?(A=!0,d=d.slice(1,-1)):d.indexOf("+")>-1&&(D=!0,d=d.replace(/\+/g,"")),d.indexOf("a")>-1&&(p=d.split(".")[0].match(/[0-9]+/g)||["0"],p=parseInt(p[0],10),G=d.indexOf("aK")>=0,H=d.indexOf("aM")>=0,I=d.indexOf("aB")>=0,J=d.indexOf("aT")>=0,K=G||H||I||J,d.indexOf(" a")>-1?(F=" ",d=d.replace(" a","")):d=d.replace("a",""),j=b(a),l=j%3,l=0===l?3:l,p&&0!==N&&(n=3*~~((Math.min(p,j)-l)/3),N/=Math.pow(10,n)),j!==p&&(N>=Math.pow(10,12)&&!K||J?(F+=z[B].abbreviations.trillion,a/=Math.pow(10,12)):N=Math.pow(10,9)&&!K||I?(F+=z[B].abbreviations.billion,a/=Math.pow(10,9)):N=Math.pow(10,6)&&!K||H?(F+=z[B].abbreviations.million,a/=Math.pow(10,6)):(N=Math.pow(10,3)&&!K||G)&&(F+=z[B].abbreviations.thousand,a/=Math.pow(10,3))),k=b(a),p&&p>k&&-1===d.indexOf(".")&&(d+="[.]",d+=c(p-k))),x=0;x-1){ 82 | // check for space before 83 | d.indexOf(" "+h.marker)>-1&&(L=" "), 84 | // remove the marker (with the space if it had one) 85 | d=d.replace(L+h.marker,""),i=m(a,h.suffixes,h.scale),a=i.value,L+=i.suffix;break}if( 86 | // see if ordinal is wanted 87 | d.indexOf("o")>-1&&( 88 | // check for space before 89 | d.indexOf(" o")>-1?(M=" ",d=d.replace(" o","")):d=d.replace("o",""),z[B].ordinal&&(M+=z[B].ordinal(a))),d.indexOf("[.]")>-1&&(E=!0,d=d.replace("[.]",".")),q=d.split(".")[1],t=d.indexOf(","),q){var V=[];if(-1!==q.indexOf("*")?(O=a.toString(),V=O.split("."),V.length>1&&(O=e(a,V[1].length,f))):q.indexOf("[")>-1?(q=q.replace("]",""),q=q.split("["),O=e(a,q[0].length+q[1].length,f,q[1].length)):O=e(a,q.length,f),V=O.split("."),o=V[0],V.length>1&&V[1].length){var W=g?F+g:z[B].delimiters.decimal;O=W+V[1]}else O="";E&&0===Number(O.slice(1))&&(O="")}else o=e(a,0,f); 90 | // format number 91 | return o.indexOf("-")>-1&&(o=o.slice(1),Q=!0),o.length-1&&(o=o.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1"+z[B].delimiters.thousands)),0===d.indexOf(".")&&(o=""),u=d.indexOf("("),v=d.indexOf("-"),R=v>u?(A&&Q?"(":"")+(P&&Q||!A&&Q?"-":""):(P&&Q||!A&&Q?"-":"")+(A&&Q?"(":""),r+R+(!Q&&D&&0!==a?"+":"")+o+O+(M?M:"")+(F&&!g?F:"")+(L?L:"")+(A&&Q?")":"")+s}/************************************ 92 | Helpers 93 | ************************************/ 94 | function o(a,b){z[a]=b}function p(a){B=a;var b=z[a].defaults;b&&b.format&&t.defaultFormat(b.format),b&&b.currencyFormat&&t.defaultCurrencyFormat(b.currencyFormat)}function q(){return"undefined"!=typeof process&&void 0===process.browser&&process.title&&(-1!==process.title.indexOf("node")||process.title.indexOf("meteor-tool")>0||"grunt"===process.title||"gulp"===process.title)&&"undefined"!=typeof require}/** 95 | * Computes the multiplier necessary to make x >= 1, 96 | * effectively eliminating miscalculations caused by 97 | * finite precision. 98 | */ 99 | function r(a){var b=a.toString().split(".");return b.length<2?1:Math.pow(10,b[1].length)}/** 100 | * Given a variable number of arguments, returns the maximum 101 | * multiplier that must be used to normalize an operation involving 102 | * all of them. 103 | */ 104 | function s(){var a=Array.prototype.slice.call(arguments);return a.reduce(function(a,b){var c=r(a),d=r(b);return c>d?c:d},-(1/0))}/************************************ 105 | Constants 106 | ************************************/ 107 | var t,u="1.11.0",v=["B","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],w=["B","KB","MB","GB","TB","PB","EB","ZB","YB"],x={general:{scale:1024,suffixes:w,marker:"bd"},binary:{scale:1024,suffixes:v,marker:"b"},decimal:{scale:1e3,suffixes:w,marker:"d"}}, 108 | // general must be before the others because it reuses their characters! 109 | y=[x.general,x.binary,x.decimal], 110 | // internal storage for culture config files 111 | z={}, 112 | // Todo: Remove in 2.0.0 113 | A=z,B="en-US",C=null,D="0,0",E="0$", 114 | // check for nodeJS 115 | F="undefined"!=typeof module&&module.exports, 116 | // default culture 117 | G={delimiters:{thousands:",",decimal:"."},abbreviations:{thousand:"k",million:"m",billion:"b",trillion:"t"},ordinal:function(a){var b=a%10;return 1===~~(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th"},currency:{symbol:"$",position:"prefix"},defaults:{currencyFormat:",0000 a"},formats:{fourDigits:"0000 a",fullWithTwoDecimals:"$ ,0.00",fullWithTwoDecimalsNoCurrency:",0.00"}};t=function(b){return b=t.isNumbro(b)?b.value():"string"==typeof b||"number"==typeof b?t.fn.unformat(b):NaN,new a(Number(b))},t.version=u,t.isNumbro=function(b){return b instanceof a},t.setLanguage=function(a,b){console.warn("`setLanguage` is deprecated since version 1.6.0. Use `setCulture` instead");var c=a,d=a.split("-")[0],e=null;A[c]||(Object.keys(A).forEach(function(a){e||a.split("-")[0]!==d||(e=a)}),c=e||b||"en-US"),p(c)},t.setCulture=function(a,b){var c=a,d=a.split("-")[1],e=null;z[c]||(d&&Object.keys(z).forEach(function(a){e||a.split("-")[1]!==d||(e=a)}),c=e||b||"en-US"),p(c)},t.language=function(a,b){if(console.warn("`language` is deprecated since version 1.6.0. Use `culture` instead"),!a)return B;if(a&&!b){if(!A[a])throw new Error("Unknown language : "+a);p(a)}return!b&&A[a]||o(a,b),t},t.culture=function(a,b){if(!a)return B;if(a&&!b){if(!z[a])throw new Error("Unknown culture : "+a);p(a)}return!b&&z[a]||o(a,b),t},t.languageData=function(a){if(console.warn("`languageData` is deprecated since version 1.6.0. Use `cultureData` instead"),!a)return A[B];if(!A[a])throw new Error("Unknown language : "+a);return A[a]},t.cultureData=function(a){if(!a)return z[B];if(!z[a])throw new Error("Unknown culture : "+a);return z[a]},t.culture("en-US",G),t.languages=function(){return console.warn("`languages` is deprecated since version 1.6.0. Use `cultures` instead"),A},t.cultures=function(){return z},t.zeroFormat=function(a){C="string"==typeof a?a:null},t.defaultFormat=function(a){D="string"==typeof a?a:"0.0"},t.defaultCurrencyFormat=function(a){E="string"==typeof a?a:"0$"},t.validate=function(a,b){var c,d,e,f,g,h,i,j;if("string"!=typeof a&&(a+="",console.warn&&console.warn("Numbro.js: Value is not string. It has been co-erced to: ",a)),a=a.trim(),a=a.replace(/^[+-]?/,""),a.match(/^\d+$/))return!0;if(""===a)return!1;try{i=t.cultureData(b)}catch(k){i=t.cultureData(t.culture())}return e=i.currency.symbol,g=i.abbreviations,c=i.delimiters.decimal,d="."===i.delimiters.thousands?"\\.":i.delimiters.thousands,j=a.match(/^[^\d\.\,]+/),null!==j&&(a=a.substr(1),j[0]!==e)?!1:(j=a.match(/[^\d]+$/),null!==j&&(a=a.slice(0,-1),j[0]!==g.thousand&&j[0]!==g.million&&j[0]!==g.billion&&j[0]!==g.trillion)?!1:(h=new RegExp(d+"{2}"),a.match(/[^\d.,]/g)?!1:(f=a.split(c),f.length>2?!1:f.length<2?!!f[0].match(/^\d+.*\d$/)&&!f[0].match(h):""===f[0]?!f[0].match(h)&&!!f[1].match(/^\d+$/):1===f[0].length?!!f[0].match(/^\d+$/)&&!f[0].match(h)&&!!f[1].match(/^\d+$/):!!f[0].match(/^\d+.*\d$/)&&!f[0].match(h)&&!!f[1].match(/^\d+$/))))},t.loadLanguagesInNode=function(){console.warn("`loadLanguagesInNode` is deprecated since version 1.6.0. Use `loadCulturesInNode` instead"),t.loadCulturesInNode()},t.loadCulturesInNode=function(){var a=require("./languages");for(var b in a)b&&t.culture(b,a[b])},"function"!=typeof Array.prototype.reduce&&(Array.prototype.reduce=function(a,b){if(null===this||"undefined"==typeof this)throw new TypeError("Array.prototype.reduce called on null or undefined");if("function"!=typeof a)throw new TypeError(a+" is not a function");var c,d,e=this.length>>>0,f=!1;for(1c;++c)this.hasOwnProperty(c)&&(f?d=a(d,this[c],c,this):(d=this[c],f=!0));if(!f)throw new TypeError("Reduce of empty array with no initial value");return d}),t.fn=a.prototype={clone:function(){return t(this)},format:function(a,b){return f(this,a?a:D,void 0!==b?b:Math.round)},formatCurrency:function(a,b){return h(this,z[B].currency.symbol,a?a:E,void 0!==b?b:Math.round)},formatForeignCurrency:function(a,b,c){return i(this,a,b?b:E,void 0!==c?c:Math.round)},unformat:function(a){if("number"==typeof a)return a;if("string"==typeof a){var b=g(this,a);return isNaN(b)?void 0:b}},binaryByteUnits:function(){return m(this._value,x.binary.suffixes,x.binary.scale).suffix},byteUnits:function(){return m(this._value,x.general.suffixes,x.general.scale).suffix},decimalByteUnits:function(){return m(this._value,x.decimal.suffixes,x.decimal.scale).suffix},value:function(){return this._value},valueOf:function(){return this._value},set:function(a){return this._value=Number(a),this},add:function(a){function b(a,b){return a+c*b}var c=s.call(null,this._value,a);return this._value=[this._value,a].reduce(b,0)/c,this},subtract:function(a){function b(a,b){return a-c*b}var c=s.call(null,this._value,a);return this._value=[a].reduce(b,this._value*c)/c,this},multiply:function(a){function b(a,b){var c=s(a,b),d=a*c;return d*=b*c,d/=c*c}return this._value=[this._value,a].reduce(b,1),this},divide:function(a){function b(a,b){var c=s(a,b);return a*c/(b*c)}return this._value=[this._value,a].reduce(b),this},difference:function(a){return Math.abs(t(this._value).subtract(a).value())}},q()&&t.loadCulturesInNode(),F?module.exports=t:("undefined"==typeof ender&&(this.numbro=t),"function"==typeof define&&define.amd&&define([],function(){return t}))}).call("undefined"==typeof window?this:window); -------------------------------------------------------------------------------- /pyexcel_handsontable/templates/pyexcel-handsontable/pikaday-1.5.1.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"use strict";var n;if("object"==typeof exports){try{n=require("moment")}catch(e){}module.exports=t(n)}else"function"==typeof define&&define.amd?define(function(e){var a="moment";try{n=e(a)}catch(e){}return t(n)}):e.Pikaday=t(e.moment)}(this,function(e){"use strict";var t="function"==typeof e,n=!!window.addEventListener,a=window.document,i=window.setTimeout,s=function(e,t,a,i){n?e.addEventListener(t,a,!!i):e.attachEvent("on"+t,a)},o=function(e,t,a,i){n?e.removeEventListener(t,a,!!i):e.detachEvent("on"+t,a)},r=function(e,t,n){var i;a.createEvent?(i=a.createEvent("HTMLEvents"),i.initEvent(t,!0,!1),i=_(i,n),e.dispatchEvent(i)):a.createEventObject&&(i=a.createEventObject(),i=_(i,n),e.fireEvent("on"+t,i))},h=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")},l=function(e,t){return(" "+e.className+" ").indexOf(" "+t+" ")!==-1},d=function(e,t){l(e,t)||(e.className=""===e.className?t:e.className+" "+t)},u=function(e,t){e.className=h((" "+e.className+" ").replace(" "+t+" "," "))},c=function(e){return/Array/.test(Object.prototype.toString.call(e))},f=function(e){return/Date/.test(Object.prototype.toString.call(e))&&!isNaN(e.getTime())},m=function(e){var t=e.getDay();return 0===t||6===t},g=function(e){return e%4===0&&e%100!==0||e%400===0},p=function(e,t){return[31,g(e)?29:28,31,30,31,30,31,31,30,31,30,31][t]},y=function(e){f(e)&&e.setHours(0,0,0,0)},D=function(e,t){return e.getTime()===t.getTime()},_=function(e,t,n){var a,i;for(a in t)i=void 0!==e[a],i&&"object"==typeof t[a]&&null!==t[a]&&void 0===t[a].nodeName?f(t[a])?n&&(e[a]=new Date(t[a].getTime())):c(t[a])?n&&(e[a]=t[a].slice(0)):e[a]=_({},t[a],n):!n&&i||(e[a]=t[a]);return e},v=function(e){return e.month<0&&(e.year-=Math.ceil(Math.abs(e.month)/12),e.month+=12),e.month>11&&(e.year+=Math.floor(Math.abs(e.month)/12),e.month-=12),e},b={field:null,bound:void 0,position:"bottom left",reposition:!0,format:"YYYY-MM-DD",defaultDate:null,setDefaultDate:!1,firstDay:0,formatStrict:!1,minDate:null,maxDate:null,yearRange:10,showWeekNumber:!1,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,yearSuffix:"",showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,numberOfMonths:1,mainCalendar:"left",container:void 0,i18n:{previousMonth:"Previous Month",nextMonth:"Next Month",months:["January","February","March","April","May","June","July","August","September","October","November","December"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},theme:null,onSelect:null,onOpen:null,onClose:null,onDraw:null},w=function(e,t,n){for(t+=e.firstDay;t>=7;)t-=7;return n?e.i18n.weekdaysShort[t]:e.i18n.weekdays[t]},M=function(e){var t=[],n="false";if(e.isEmpty){if(!e.showDaysInNextAndPreviousMonths)return'';t.push("is-outside-current-month")}return e.isDisabled&&t.push("is-disabled"),e.isToday&&t.push("is-today"),e.isSelected&&(t.push("is-selected"),n="true"),e.isInRange&&t.push("is-inrange"),e.isStartRange&&t.push("is-startrange"),e.isEndRange&&t.push("is-endrange"),'"},k=function(e,t,n){var a=new Date(n,0,1),i=Math.ceil(((new Date(n,t,e)-a)/864e5+a.getDay()+1)/7);return''+i+""},x=function(e,t){return""+(t?e.reverse():e).join("")+""},R=function(e){return""+e.join("")+""},N=function(e){var t,n=[];for(e.showWeekNumber&&n.push(""),t=0;t<7;t++)n.push(''+w(e,t,!0)+"");return""+(e.isRTL?n.reverse():n).join("")+""},C=function(e,t,n,a,i,s){var o,r,h,l,d,u=e._o,f=n===u.minYear,m=n===u.maxYear,g='
    ',p=!0,y=!0;for(h=[],o=0;o<12;o++)h.push('");for(l='
    '+u.i18n.months[a]+'
    ",c(u.yearRange)?(o=u.yearRange[0],r=u.yearRange[1]+1):(o=n-u.yearRange,r=1+n+u.yearRange),h=[];o=u.minYear&&h.push('");return d='
    '+n+u.yearSuffix+'
    ",g+=u.showMonthAfterYear?d+l:l+d,f&&(0===a||u.minMonth>=a)&&(p=!1),m&&(11===a||u.maxMonth<=a)&&(y=!1),0===t&&(g+='"),t===e._o.numberOfMonths-1&&(g+='"),g+="
    "},T=function(e,t,n){return''+N(e)+R(t)+"
    "},Y=function(o){var r=this,h=r.config(o);r._onMouseDown=function(e){if(r._v){e=e||window.event;var t=e.target||e.srcElement;if(t)if(l(t,"is-disabled")||(!l(t,"pika-button")||l(t,"is-empty")||l(t.parentNode,"is-disabled")?l(t,"pika-prev")?r.prevMonth():l(t,"pika-next")&&r.nextMonth():(r.setDate(new Date(t.getAttribute("data-pika-year"),t.getAttribute("data-pika-month"),t.getAttribute("data-pika-day"))),h.bound&&i(function(){r.hide(),h.field&&h.field.blur()},100))),l(t,"pika-select"))r._c=!0;else{if(!e.preventDefault)return e.returnValue=!1,!1;e.preventDefault()}}},r._onChange=function(e){e=e||window.event;var t=e.target||e.srcElement;t&&(l(t,"pika-select-month")?r.gotoMonth(t.value):l(t,"pika-select-year")&&r.gotoYear(t.value))},r._onKeyChange=function(e){if(e=e||window.event,r.isVisible())switch(e.keyCode){case 13:case 27:h.field.blur();break;case 37:e.preventDefault(),r.adjustDate("subtract",1);break;case 38:r.adjustDate("subtract",7);break;case 39:r.adjustDate("add",1);break;case 40:r.adjustDate("add",7)}},r._onInputChange=function(n){var a;n.firedBy!==r&&(t?(a=e(h.field.value,h.format,h.formatStrict),a=a&&a.isValid()?a.toDate():null):a=new Date(Date.parse(h.field.value)),f(a)&&r.setDate(a),r._v||r.show())},r._onInputFocus=function(){r.show()},r._onInputClick=function(){r.show()},r._onInputBlur=function(){var e=a.activeElement;do if(l(e,"pika-single"))return;while(e=e.parentNode);r._c||(r._b=i(function(){r.hide()},50)),r._c=!1},r._onClick=function(e){e=e||window.event;var t=e.target||e.srcElement,a=t;if(t){!n&&l(t,"pika-select")&&(t.onchange||(t.setAttribute("onchange","return;"),s(t,"change",r._onChange)));do if(l(a,"pika-single")||a===h.trigger)return;while(a=a.parentNode);r._v&&t!==h.trigger&&a!==h.trigger&&r.hide()}},r.el=a.createElement("div"),r.el.className="pika-single"+(h.isRTL?" is-rtl":"")+(h.theme?" "+h.theme:""),s(r.el,"mousedown",r._onMouseDown,!0),s(r.el,"touchend",r._onMouseDown,!0),s(r.el,"change",r._onChange),s(a,"keydown",r._onKeyChange),h.field&&(h.container?h.container.appendChild(r.el):h.bound?a.body.appendChild(r.el):h.field.parentNode.insertBefore(r.el,h.field.nextSibling),s(h.field,"change",r._onInputChange),h.defaultDate||(t&&h.field.value?h.defaultDate=e(h.field.value,h.format).toDate():h.defaultDate=new Date(Date.parse(h.field.value)),h.setDefaultDate=!0));var d=h.defaultDate;f(d)?h.setDefaultDate?r.setDate(d,!0):r.gotoDate(d):r.gotoDate(new Date),h.bound?(this.hide(),r.el.className+=" is-bound",s(h.trigger,"click",r._onInputClick),s(h.trigger,"focus",r._onInputFocus),s(h.trigger,"blur",r._onInputBlur)):this.show()};return Y.prototype={config:function(e){this._o||(this._o=_({},b,!0));var t=_(this._o,e,!0);t.isRTL=!!t.isRTL,t.field=t.field&&t.field.nodeName?t.field:null,t.theme="string"==typeof t.theme&&t.theme?t.theme:null,t.bound=!!(void 0!==t.bound?t.field&&t.bound:t.field),t.trigger=t.trigger&&t.trigger.nodeName?t.trigger:t.field,t.disableWeekends=!!t.disableWeekends,t.disableDayFn="function"==typeof t.disableDayFn?t.disableDayFn:null;var n=parseInt(t.numberOfMonths,10)||1;if(t.numberOfMonths=n>4?4:n,f(t.minDate)||(t.minDate=!1),f(t.maxDate)||(t.maxDate=!1),t.minDate&&t.maxDate&&t.maxDate100&&(t.yearRange=100);return t},toString:function(n){return f(this._d)?t?e(this._d).format(n||this._o.format):this._d.toDateString():""},getMoment:function(){return t?e(this._d):null},setMoment:function(n,a){t&&e.isMoment(n)&&this.setDate(n.toDate(),a)},getDate:function(){return f(this._d)?new Date(this._d.getTime()):new Date},setDate:function(e,t){if(!e)return this._d=null,this._o.field&&(this._o.field.value="",r(this._o.field,"change",{firedBy:this})),this.draw();if("string"==typeof e&&(e=new Date(Date.parse(e))),f(e)){var n=this._o.minDate,a=this._o.maxDate;f(n)&&ea&&(e=a),this._d=new Date(e.getTime()),y(this._d),this.gotoDate(this._d),this._o.field&&(this._o.field.value=this.toString(),r(this._o.field,"change",{firedBy:this})),t||"function"!=typeof this._o.onSelect||this._o.onSelect.call(this,this.getDate())}},gotoDate:function(e){var t=!0;if(f(e)){if(this.calendars){var n=new Date(this.calendars[0].year,this.calendars[0].month,1),a=new Date(this.calendars[this.calendars.length-1].year,this.calendars[this.calendars.length-1].month,1),i=e.getTime();a.setMonth(a.getMonth()+1),a.setDate(a.getDate()-1),t=i=s&&(this._y=s,!isNaN(r)&&this._m>r&&(this._m=r)),t="pika-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l'+C(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,t)+this.render(this.calendars[l].year,this.calendars[l].month,t)+"";this.el.innerHTML=h,n.bound&&"hidden"!==n.field.type&&i(function(){n.trigger.focus()},1),"function"==typeof this._o.onDraw&&this._o.onDraw(this),n.bound&&n.field.setAttribute("aria-label","Use the arrow keys to pick a date")}},adjustPosition:function(){var e,t,n,i,s,o,r,h,l,d;if(!this._o.container){if(this.el.style.position="absolute",e=this._o.trigger,t=e,n=this.el.offsetWidth,i=this.el.offsetHeight,s=window.innerWidth||a.documentElement.clientWidth,o=window.innerHeight||a.documentElement.clientHeight,r=window.pageYOffset||a.body.scrollTop||a.documentElement.scrollTop,"function"==typeof e.getBoundingClientRect)d=e.getBoundingClientRect(),h=d.left+window.pageXOffset,l=d.bottom+window.pageYOffset;else for(h=t.offsetLeft,l=t.offsetTop+t.offsetHeight;t=t.offsetParent;)h+=t.offsetLeft,l+=t.offsetTop;(this._o.reposition&&h+n>s||this._o.position.indexOf("right")>-1&&h-n+e.offsetWidth>0)&&(h=h-n+e.offsetWidth),(this._o.reposition&&l+i>o+r||this._o.position.indexOf("top")>-1&&l-i-e.offsetHeight>0)&&(l=l-i-e.offsetHeight),this.el.style.left=h+"px",this.el.style.top=l+"px"}},render:function(e,t,n){var a=this._o,i=new Date,s=p(e,t),o=new Date(e,t,1).getDay(),r=[],h=[];y(i),a.firstDay>0&&(o-=a.firstDay,o<0&&(o+=7));for(var l=0===t?11:t-1,d=11===t?0:t+1,u=0===t?e-1:e,c=11===t?e+1:e,g=p(u,l),_=s+o,v=_;v>7;)v-=7;_+=7-v;for(var b=0,w=0;b<_;b++){var R=new Date(e,t,1+(b-o)),N=!!f(this._d)&&D(R,this._d),C=D(R,i),Y=b=s+o,E=1+(b-o),S=t,j=e,I=a.startRange&&D(a.startRange,R),O=a.endRange&&D(a.endRange,R),F=a.startRange&&a.endRange&&a.startRangea.maxDate||a.disableWeekends&&m(R)||a.disableDayFn&&a.disableDayFn(R);Y&&(b=0.5.0 2 | jinja2 3 | -------------------------------------------------------------------------------- /rnd_requirements.txt: -------------------------------------------------------------------------------- 1 | https://github.com/chfw/lml/archive/master.zip 2 | https://github.com/pyexcel/pyexcel-io/archive/v0.4.x.zip 3 | https://github.com/pyexcel/pyexcel/archive/master.zip 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Template by pypi-mobans 4 | import os 5 | import sys 6 | import codecs 7 | from shutil import rmtree 8 | from setuptools import setup, find_packages, Command 9 | try: 10 | from pyecharts_jupyter_installer import install_cmd_for 11 | except ImportError: 12 | import subprocess 13 | import importlib 14 | 15 | subprocess.check_call([sys.executable, '-m', 16 | 'pip', 'install', 'pyecharts-jupyter-installer']) 17 | install_cmd_for = importlib.import_module( 18 | 'pyecharts_jupyter_installer').install_cmd_for 19 | PY2 = sys.version_info[0] == 2 20 | PY26 = PY2 and sys.version_info[1] < 7 21 | 22 | NAME = 'pyexcel-handsontable' 23 | AUTHOR = 'C.W.' 24 | VERSION = '0.0.2' 25 | EMAIL = 'wangc_2011@hotmail.com' 26 | LICENSE = 'New BSD' 27 | DESCRIPTION = ( 28 | 'A pyexcel plugin to render data as handsontable in html pages' 29 | ) 30 | URL = 'https://github.com/pyexcel-renderers/pyexcel-handsontable' 31 | DOWNLOAD_URL = '%s/archive/0.0.2.tar.gz' % URL 32 | FILES = ['README.rst', 'CHANGELOG.rst'] 33 | KEYWORDS = [ 34 | 'python', 35 | ] 36 | 37 | CLASSIFIERS = [ 38 | 'Topic :: Software Development :: Libraries', 39 | 'Programming Language :: Python', 40 | 'Intended Audience :: Developers', 41 | 'Programming Language :: Python :: 2.6', 42 | 'Programming Language :: Python :: 2.7', 43 | 'Programming Language :: Python :: 3.3', 44 | 'Programming Language :: Python :: 3.4', 45 | 'Programming Language :: Python :: 3.5', 46 | 'Programming Language :: Python :: 3.6', 47 | 'License :: OSI Approved :: BSD License', 48 | ] 49 | 50 | INSTALL_REQUIRES = [ 51 | 'pyexcel>=0.5.0', 52 | 'jinja2', 53 | ] 54 | SETUP_COMMANDS = install_cmd_for( 55 | 'pyexcel-handsontable', 56 | 'pyexcel_handsontable/templates/pyexcel-handsontable' 57 | ) 58 | 59 | 60 | PACKAGES = find_packages(exclude=['ez_setup', 'examples', 'tests']) 61 | EXTRAS_REQUIRE = { 62 | } 63 | # You do not need to read beyond this line 64 | PUBLISH_COMMAND = '{0} setup.py sdist bdist_wheel upload -r pypi'.format( 65 | sys.executable) 66 | GS_COMMAND = ('gs pyexcel-handsontable v0.0.2 ' + 67 | "Find 0.0.2 in changelog for more details") 68 | NO_GS_MESSAGE = ('Automatic github release is disabled. ' + 69 | 'Please install gease to enable it.') 70 | UPLOAD_FAILED_MSG = ( 71 | 'Upload failed. please run "%s" yourself.' % PUBLISH_COMMAND) 72 | HERE = os.path.abspath(os.path.dirname(__file__)) 73 | 74 | 75 | class PublishCommand(Command): 76 | """Support setup.py upload.""" 77 | 78 | description = 'Build and publish the package on github and pypi' 79 | user_options = [] 80 | 81 | @staticmethod 82 | def status(s): 83 | """Prints things in bold.""" 84 | print('\033[1m{0}\033[0m'.format(s)) 85 | 86 | def initialize_options(self): 87 | pass 88 | 89 | def finalize_options(self): 90 | pass 91 | 92 | def run(self): 93 | try: 94 | self.status('Removing previous builds...') 95 | rmtree(os.path.join(HERE, 'dist')) 96 | rmtree(os.path.join(HERE, 'build')) 97 | rmtree(os.path.join(HERE, 'pyexcel_handsontable.egg-info')) 98 | except OSError: 99 | pass 100 | 101 | self.status('Building Source and Wheel (universal) distribution...') 102 | run_status = True 103 | if has_gease(): 104 | run_status = os.system(GS_COMMAND) == 0 105 | else: 106 | self.status(NO_GS_MESSAGE) 107 | if run_status: 108 | if os.system(PUBLISH_COMMAND) != 0: 109 | self.status(UPLOAD_FAILED_MSG % PUBLISH_COMMAND) 110 | 111 | sys.exit() 112 | 113 | 114 | SETUP_COMMANDS.update({ 115 | 'publish': PublishCommand 116 | }) 117 | 118 | 119 | def has_gease(): 120 | """ 121 | test if github release command is installed 122 | 123 | visit http://github.com/moremoban/gease for more info 124 | """ 125 | try: 126 | import gease # noqa 127 | return True 128 | except ImportError: 129 | return False 130 | 131 | 132 | def read_files(*files): 133 | """Read files into setup""" 134 | text = "" 135 | for single_file in files: 136 | content = read(single_file) 137 | text = text + content + "\n" 138 | return text 139 | 140 | 141 | def read(afile): 142 | """Read a file into setup""" 143 | the_relative_file = os.path.join(HERE, afile) 144 | with codecs.open(the_relative_file, 'r', 'utf-8') as opened_file: 145 | content = filter_out_test_code(opened_file) 146 | content = "".join(list(content)) 147 | return content 148 | 149 | 150 | def filter_out_test_code(file_handle): 151 | found_test_code = False 152 | for line in file_handle.readlines(): 153 | if line.startswith('.. testcode:'): 154 | found_test_code = True 155 | continue 156 | if found_test_code is True: 157 | if line.startswith(' '): 158 | continue 159 | else: 160 | empty_line = line.strip() 161 | if len(empty_line) == 0: 162 | continue 163 | else: 164 | found_test_code = False 165 | yield line 166 | else: 167 | for keyword in ['|version|', '|today|']: 168 | if keyword in line: 169 | break 170 | else: 171 | yield line 172 | 173 | 174 | if __name__ == '__main__': 175 | setup( 176 | name=NAME, 177 | author=AUTHOR, 178 | version=VERSION, 179 | author_email=EMAIL, 180 | description=DESCRIPTION, 181 | url=URL, 182 | download_url=DOWNLOAD_URL, 183 | long_description=read_files(*FILES), 184 | license=LICENSE, 185 | keywords=KEYWORDS, 186 | extras_require=EXTRAS_REQUIRE, 187 | tests_require=['nose'], 188 | install_requires=INSTALL_REQUIRES, 189 | packages=PACKAGES, 190 | include_package_data=True, 191 | zip_safe=False, 192 | classifiers=CLASSIFIERS, 193 | cmdclass=SETUP_COMMANDS 194 | ) 195 | -------------------------------------------------------------------------------- /styles/rtd_style.css: -------------------------------------------------------------------------------- 1 | body{font-family:Helvetica,sans-serif;margin:2 0 0 0}.tab{margin-bottom:0 !important;text-align:center;list-style:none;padding:0 0 0 10px;line-height:24px;height:26px;overflow:hidden;font-size:12px;font-family:verdana;position:relative;margin:0}.tab li{margin-left:0 !important;margin-top:2px !important;float:left;height:24px;border:1px solid #aaa;background:#d1d1d1;background:linear-gradient(top, #ececec 50%, #d1d1d1);display:inline-block;position:relative;z-index:0;border-top-left-radius:6px;border-top-right-radius:6px;box-shadow:0 3px 3px rgba(0,0,0,0.4),inset 0 1px 0 #fff;text-shadow:0 1px #fff;margin:0 -5px;padding:0 20px}.tab li.active{background:#fff;color:#333;z-index:2}.tab li:before{left:-6px;border-width:0 1px 1px 0;box-shadow:2px 2px 0 #d1d1d1}.tab li:after{right:-6px;border-width:0 0 1px 1px;box-shadow:-2px 2px 0 #d1d1d1}.tab a{color:#555;text-decoration:none}.tab:before{position:absolute;content:" ";width:100%;bottom:0;left:0;border-bottom:1px solid #aaa;z-index:1}.tabcontent{margin-top:-1px} 2 | -------------------------------------------------------------------------------- /styles/rtd_style.scss: -------------------------------------------------------------------------------- 1 | //colors 2 | $color_silver_chalice_approx: #aaa; 3 | $color_celeste_approx: #d1d1d1; 4 | $color_cararra_approx: #ececec; 5 | $white: #fff; 6 | $black_40: rgba(0, 0, 0, 0.4); 7 | $color_fuscous_gray_approx: #555; 8 | $color_mine_shaft_approx: #333; 9 | 10 | //fonts 11 | $font_0: Helvetica; 12 | $font_1: sans-serif; 13 | $font_2: verdana; 14 | 15 | body { 16 | font-family: $font_0, $font_1; 17 | margin: 2 0 0 0; 18 | } 19 | .tab { 20 | margin-bottom: 0px !important; 21 | text-align: center; 22 | list-style: none; 23 | padding: 0 0 0 10px; 24 | line-height: 24px; 25 | height: 26px; 26 | overflow: hidden; 27 | font-size: 12px; 28 | font-family: $font_2; 29 | position: relative; 30 | margin: 0; 31 | li { 32 | margin-left: 0px !important; 33 | margin-top: 2px !important; 34 | float: left; 35 | height: 24px; 36 | border: 1px solid $color_silver_chalice_approx; 37 | background: $color_celeste_approx; 38 | background: linear-gradient(top, $color_cararra_approx 50%, $color_celeste_approx 100%); 39 | display: inline-block; 40 | position: relative; 41 | z-index: 0; 42 | //Instead of the line below you could use @include border-top-left-radius($radius) 43 | border-top-left-radius: 6px; 44 | //Instead of the line below you could use @include border-top-right-radius($radius) 45 | border-top-right-radius: 6px; 46 | //Instead of the line below you could use @include box-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) 47 | box-shadow: 0 3px 3px $black_40, inset 0 1px 0 $white; 48 | //Instead of the line below you could use @include text-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) 49 | text-shadow: 0 1px $white; 50 | margin: 0 -5px; 51 | padding: 0 20px; 52 | &.active { 53 | background: $white; 54 | color: $color_mine_shaft_approx; 55 | z-index: 2; 56 | } 57 | &:before { 58 | left: -6px; 59 | border-width: 0 1px 1px 0; 60 | //Instead of the line below you could use @include box-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) 61 | box-shadow: 2px 2px 0 $color_celeste_approx; 62 | } 63 | &:after { 64 | right: -6px; 65 | border-width: 0 0 1px 1px; 66 | //Instead of the line below you could use @include box-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) 67 | box-shadow: -2px 2px 0 $color_celeste_approx; 68 | } 69 | } 70 | a { 71 | color: $color_fuscous_gray_approx; 72 | text-decoration: none; 73 | } 74 | &:before { 75 | position: absolute; 76 | content: " "; 77 | width: 100%; 78 | bottom: 0; 79 | left: 0; 80 | border-bottom: 1px solid $color_silver_chalice_approx; 81 | z-index: 1; 82 | } 83 | } 84 | .tabcontent { 85 | margin-top: -1px; 86 | } 87 | -------------------------------------------------------------------------------- /styles/style.css: -------------------------------------------------------------------------------- 1 | body{font-family:Helvetica,sans-serif;margin:2 0 0 0}.tab{margin-bottom:0 !important;text-align:center;list-style:none;padding:0 0 0 10px;line-height:24px;height:26px;overflow:hidden;font-size:12px;font-family:verdana;position:relative;margin:0}.tab li{margin-left:0 !important;float:left;height:24px;border:1px solid #aaa;background:#d1d1d1;background:linear-gradient(top, #ececec 50%, #d1d1d1);display:inline-block;position:relative;z-index:0;border-top-left-radius:6px;border-top-right-radius:6px;box-shadow:0 3px 3px rgba(0,0,0,0.4),inset 0 1px 0 #fff;text-shadow:0 1px #fff;margin:0 -5px;padding:0 20px}.tab li.active{background:#fff;color:#333;z-index:2}.tab li:before{left:-6px;border-width:0 1px 1px 0;box-shadow:2px 2px 0 #d1d1d1}.tab li:after{right:-6px;border-width:0 0 1px 1px;box-shadow:-2px 2px 0 #d1d1d1}.tab a{color:#555;text-decoration:none}.tab:before{position:absolute;content:" ";width:100%;bottom:0;left:0;border-bottom:1px solid #aaa;z-index:1}.tabcontent{margin-top:-1px} 2 | -------------------------------------------------------------------------------- /styles/style.scss: -------------------------------------------------------------------------------- 1 | //colors 2 | $color_silver_chalice_approx: #aaa; 3 | $color_celeste_approx: #d1d1d1; 4 | $color_cararra_approx: #ececec; 5 | $white: #fff; 6 | $black_40: rgba(0, 0, 0, 0.4); 7 | $color_fuscous_gray_approx: #555; 8 | $color_mine_shaft_approx: #333; 9 | 10 | //fonts 11 | $font_0: Helvetica; 12 | $font_1: sans-serif; 13 | $font_2: verdana; 14 | 15 | body { 16 | font-family: $font_0, $font_1; 17 | margin: 2 0 0 0; 18 | } 19 | .tab { 20 | margin-bottom: 0px !important; 21 | text-align: center; 22 | list-style: none; 23 | padding: 0 0 0 10px; 24 | line-height: 24px; 25 | height: 26px; 26 | overflow: hidden; 27 | font-size: 12px; 28 | font-family: $font_2; 29 | position: relative; 30 | margin: 0; 31 | li { 32 | margin-left: 0px !important; 33 | float: left; 34 | height: 24px; 35 | border: 1px solid $color_silver_chalice_approx; 36 | background: $color_celeste_approx; 37 | background: linear-gradient(top, $color_cararra_approx 50%, $color_celeste_approx 100%); 38 | display: inline-block; 39 | position: relative; 40 | z-index: 0; 41 | //Instead of the line below you could use @include border-top-left-radius($radius) 42 | border-top-left-radius: 6px; 43 | //Instead of the line below you could use @include border-top-right-radius($radius) 44 | border-top-right-radius: 6px; 45 | //Instead of the line below you could use @include box-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) 46 | box-shadow: 0 3px 3px $black_40, inset 0 1px 0 $white; 47 | //Instead of the line below you could use @include text-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) 48 | text-shadow: 0 1px $white; 49 | margin: 0 -5px; 50 | padding: 0 20px; 51 | &.active { 52 | background: $white; 53 | color: $color_mine_shaft_approx; 54 | z-index: 2; 55 | } 56 | &:before { 57 | left: -6px; 58 | border-width: 0 1px 1px 0; 59 | //Instead of the line below you could use @include box-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) 60 | box-shadow: 2px 2px 0 $color_celeste_approx; 61 | } 62 | &:after { 63 | right: -6px; 64 | border-width: 0 0 1px 1px; 65 | //Instead of the line below you could use @include box-shadow($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10) 66 | box-shadow: -2px 2px 0 $color_celeste_approx; 67 | } 68 | } 69 | a { 70 | color: $color_fuscous_gray_approx; 71 | text-decoration: none; 72 | } 73 | &:before { 74 | position: absolute; 75 | content: " "; 76 | width: 100%; 77 | bottom: 0; 78 | left: 0; 79 | border-bottom: 1px solid $color_silver_chalice_approx; 80 | z-index: 1; 81 | } 82 | } 83 | .tabcontent { 84 | margin-top: -1px; 85 | } 86 | -------------------------------------------------------------------------------- /test.bat: -------------------------------------------------------------------------------- 1 | pip freeze 2 | nosetests --with-coverage --cover-package pyexcel_handsontable --cover-package tests tests docs/source pyexcel_handsontable && flake8 . --exclude=.moban.d,docs --builtins=unicode,xrange,long 3 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | pip freeze 2 | nosetests --with-coverage --cover-package pyexcel_handsontable --cover-package tests tests docs/source pyexcel_handsontable && flake8 . --exclude=.moban.d,docs --builtins=unicode,xrange,long 3 | -------------------------------------------------------------------------------- /tests/fixtures/book.handsontable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 27 | 28 |
    29 |
    30 |
    31 | 32 | 33 |
    34 |
    35 |
    36 | 37 | 38 |
    39 |
    40 |
    41 | 42 | 43 | 44 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /tests/fixtures/sheet.jupyter_notebook: -------------------------------------------------------------------------------- 1 | 2 | 33 | 50 | 57 | 58 |
    59 |
    60 |
    61 | 62 | 101 | 102 | -------------------------------------------------------------------------------- /tests/fixtures/sheet_rendering.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 17 | 18 |
    19 |
    20 |
    21 | 22 | 23 | 24 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /tests/fixtures/sheet_rendering_custom_urls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 16 | 17 | 18 |
    19 |
    20 |
    21 | 22 | 23 | 46 | 66 | -------------------------------------------------------------------------------- /tests/fixtures/sheet_rendering_embed.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |
    11 |
    12 |
    13 | 14 | 15 | 16 | 54 | 55 | -------------------------------------------------------------------------------- /tests/mytestwrapper.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from unittest import TestCase 4 | from unittest.mock import patch 5 | 6 | PY2 = sys.version_info[0] == 2 7 | PY26 = PY2 and sys.version_info[1] < 7 8 | 9 | 10 | DEFAULT_CONFIG = ( 11 | '{"rowHeaders": true, ' 12 | + '"readOnly": true, ' 13 | + '"colHeaders": true, ' 14 | + '"preventOverflow": "hornizontal"}' 15 | ) 16 | 17 | 18 | class MyBaseCase(TestCase): 19 | def setUp(self): 20 | self.maxDiff = None 21 | self._test_file = "test.handsontable.html" 22 | self.patcher1 = patch( 23 | "pyexcel_handsontable.handsontable._generate_uuid" 24 | ) 25 | self.fake_uuid = self.patcher1.start() 26 | 27 | def customAssertMultiLineEqual(self, a, b): 28 | if PY26: 29 | self.assertEqual(a, b) 30 | else: 31 | self.assertMultiLineEqual(a, b) 32 | 33 | def tearDown(self): 34 | self.patcher1.stop() 35 | if os.path.exists(self._test_file): 36 | os.unlink(self._test_file) 37 | 38 | def compareTwoFiles(self, filea, fileb): 39 | with open(filea, "r") as f: 40 | actual = f.read() 41 | with open(fileb, "r") as f: 42 | expected = f.read() 43 | self.customAssertMultiLineEqual(expected, actual) 44 | 45 | 46 | class MyTestCase(MyBaseCase): 47 | def setUp(self): 48 | super(MyTestCase, self).setUp() 49 | self.patcher2 = patch("pyexcel_handsontable.handsontable._dump_dict") 50 | self.dump_dict = self.patcher2.start() 51 | 52 | def tearDown(self): 53 | self.patcher2.stop() 54 | super(MyTestCase, self).tearDown() 55 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | nose 2 | mock;python_version<"3" 3 | codecov 4 | coverage 5 | flake8 6 | black 7 | isort 8 | collective.checkdocs 9 | pygments 10 | moban 11 | moban_jinja2_github 12 | -------------------------------------------------------------------------------- /tests/test_book.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | import pyexcel 4 | from mytestwrapper import DEFAULT_CONFIG, MyBaseCase, MyTestCase 5 | 6 | 7 | class TestStyle(MyBaseCase): 8 | def test_book_renderring_style(self): 9 | self.fake_uuid.side_effect = ["1", "2", "3", "4"] 10 | styles = {"styled sheet": {"colWidths": [100, 100]}} 11 | expected = 'var customStyle = {"colWidths": [100, 100]}' 12 | book = pyexcel.Book() 13 | book += pyexcel.Sheet([[1]]) 14 | book += pyexcel.Sheet([[2]], name="styled sheet") 15 | book += pyexcel.Sheet([[3]]) 16 | book.save_as(self._test_file, styles=styles) 17 | with codecs.open(self._test_file, "r", encoding="utf-8") as f: 18 | content = f.read() 19 | assert expected in content 20 | 21 | 22 | class TestBook(MyTestCase): 23 | def test_book_renderring(self): 24 | self.fake_uuid.side_effect = ["1", "2", "3", "4"] 25 | self.dump_dict.return_value = DEFAULT_CONFIG 26 | book = pyexcel.Book() 27 | book += pyexcel.Sheet([[1]]) 28 | book += pyexcel.Sheet([[2]]) 29 | book += pyexcel.Sheet([[3]]) 30 | book.save_as(self._test_file) 31 | self.compareTwoFiles( 32 | self._test_file, "tests/fixtures/book.handsontable.html" 33 | ) 34 | 35 | def test_book_in_jupyter_renderring(self): 36 | self.fake_uuid.side_effect = ["1", "2", "3", "4"] 37 | self.dump_dict.return_value = DEFAULT_CONFIG 38 | book = pyexcel.Book() 39 | book += pyexcel.Sheet([[1]]) 40 | book += pyexcel.Sheet([[2]], name="pyexcel sheet_1") 41 | book += pyexcel.Sheet([[3]], name="pyexcel sheet_2") 42 | actual = book.handsontable 43 | test_fixture = "tests/fixtures/book.jupyter_notebook" 44 | with codecs.open(test_fixture, "r", encoding="utf-8") as f: 45 | expected = f.read() 46 | self.customAssertMultiLineEqual(expected, actual) 47 | -------------------------------------------------------------------------------- /tests/test_sheet.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | from textwrap import dedent 3 | 4 | import pyexcel 5 | from mytestwrapper import DEFAULT_CONFIG, MyTestCase 6 | 7 | 8 | class TestSheet(MyTestCase): 9 | def test_rendering(self): 10 | self.fake_uuid.return_value = "1" 11 | self.dump_dict.return_value = DEFAULT_CONFIG 12 | s = pyexcel.Sheet([[1]]) 13 | s.save_as(self._test_file) 14 | with codecs.open(self._test_file, "r", encoding="utf-8") as f: 15 | content = f.read() 16 | assert "" in content 17 | 18 | def test_rendering_custom_urls(self): 19 | self.fake_uuid.return_value = "1" 20 | self.dump_dict.return_value = DEFAULT_CONFIG 21 | s = pyexcel.Sheet([[1]]) 22 | actual = s.get_handsontable_html(js_url="js", css_url="css") 23 | expected = dedent( 24 | """ 25 | 26 | 27 | """ 28 | ) 29 | assert expected in actual 30 | 31 | def test_rendering_embed(self): 32 | self.fake_uuid.return_value = "1" 33 | self.dump_dict.return_value = DEFAULT_CONFIG 34 | s = pyexcel.Sheet([[1]]) 35 | actual = s.get_handsontable_html(embed=True) 36 | assert "" not in actual 37 | 38 | def test_jupyter_rendering(self): 39 | self.fake_uuid.return_value = "1" 40 | self.dump_dict.return_value = DEFAULT_CONFIG 41 | s = pyexcel.Sheet([[1]]) 42 | actual = s.handsontable 43 | assert ( 44 | "/nbextensions/pyexcel-handsontable/handsontable.min" in actual 45 | ) # flake8: noqa 46 | --------------------------------------------------------------------------------