├── excel2table ├── __init__.py ├── templates │ ├── excel2table │ │ ├── main.js │ │ ├── buttons.print.min.js │ │ ├── buttons.chart.min.js │ │ ├── dataTables.scroller.min.js │ │ ├── dataTables.buttons.min.js │ │ ├── buttons.html5.min.js │ │ └── jquery.dataTables.min.js │ ├── notebook_template.j2 │ └── template.j2 ├── cli.py └── convert.py ├── _config.yml ├── setup.cfg ├── tests ├── __init__.py ├── requirements.txt └── test_cli.py ├── Makefile ├── sample ├── goog.ods ├── goog.xls ├── goog.xlsx ├── table.gif ├── sample-utf8.csv └── no_header.csv ├── requirements.txt ├── TODO.md ├── MANIFEST.in ├── test.sh ├── CONTRIBUTORS.md ├── CHANGELOG.rst ├── .travis.yml ├── .gitignore ├── LICENSE ├── README.rst └── setup.py /excel2table/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # for python 2.7 only 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | test: 4 | bash test.sh 5 | 6 | -------------------------------------------------------------------------------- /sample/goog.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyexcel/excel2table/HEAD/sample/goog.ods -------------------------------------------------------------------------------- /sample/goog.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyexcel/excel2table/HEAD/sample/goog.xls -------------------------------------------------------------------------------- /sample/goog.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyexcel/excel2table/HEAD/sample/goog.xlsx -------------------------------------------------------------------------------- /sample/table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyexcel/excel2table/HEAD/sample/table.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | jinja2 3 | pyexcel 4 | pyexcel-xls 5 | pyexcel-odsr 6 | six 7 | -------------------------------------------------------------------------------- /sample/sample-utf8.csv: -------------------------------------------------------------------------------- 1 | email,name 2 | abc@example.com,測試一 3 | def@example.com,測試二 4 | ghi@example.com,測試三 5 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [ ] Custom css style 2 | - [ ] Multiple CSV files to multiple tables 3 | - [ ] Concat multiple CSV files to single table 4 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | nose 2 | mock;python_version<"3" 3 | codecov 4 | coverage 5 | flake8 6 | lxml>=4.0.0 7 | ordereddict;python_version<"2.7" 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include excel2table *.j2 *.js *.py 2 | include LICENSE 3 | include README.rst 4 | include CHANGELOG.rst 5 | recursive-include tests * 6 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | pip freeze 2 | nosetests --with-cov --cover-package excel2table --cover-package tests && flake8 . --exclude=.moban.d --builtins=unicode,xrange,long 3 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## People contributed to this project (sorted alphabetically) 2 | 3 | - cclauss([@cclauss](https://github.com/cclauss)) 4 | - Vivek R([@vividvilla](https://github.com/vividvilla)) (Author) 5 | - C.W.([@chfw](https://github.com/chfw)) -------------------------------------------------------------------------------- /excel2table/templates/excel2table/main.js: -------------------------------------------------------------------------------- 1 | define(["require", "exports"], function (require, exports) { 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var version = '2.3.0'; 5 | function load_ipython_extension() { 6 | console.log("excel2table " + version + " (echarts 3.6.2) has been loaded"); 7 | } 8 | exports.load_ipython_extension = load_ipython_extension; 9 | }); 10 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Change log 2 | ============= 3 | 4 | 2.3.2 - 16.03.2019 5 | ---------------------- 6 | 7 | #. add tests in tar ball 8 | 9 | 2.3.0 - 15.03.2019 10 | ------------------------ 11 | 12 | Added 13 | ******* 14 | 15 | #. added this file into sour tar ball. 16 | #. fix problems with pyexcel-sortable & python3 `PR17 `_ 17 | 18 | 2.2.1 - 21.02.2017 19 | ------------------------ 20 | 21 | Added 22 | ******* 23 | 24 | #. Visualize stock data with candlestick, bar and line 25 | csvtotable `PR 24 `_ 26 | 27 | Updated 28 | ************ 29 | 30 | #. Update baseline of csvtotable to 2.1.1 31 | -------------------------------------------------------------------------------- /excel2table/templates/notebook_template.j2: -------------------------------------------------------------------------------- 1 | 13 | 14 | {%- if caption %} 15 | 16 | {%- endif %} 17 |
{{ caption }}
18 |
19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: xenial 3 | language: python 4 | notifications: 5 | email: false 6 | python: 7 | - &pypy2 pypy2.7-6.0 8 | - &pypy3 pypy3.5-6.0 9 | - 3.8-dev 10 | - 3.7 11 | - 3.6 12 | - 3.5 13 | - 2.7 14 | before_install: 15 | - cd $HOME 16 | - "if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then deactivate && wget https://bitbucket.org/squeaky/portable-pypy/downloads/pypy-5.7.1-linux_x86_64-portable.tar.bz2 -O - | tar -jxf - && echo 'Setting up aliases...' && ln -s pypy-5.7.1-linux_x86_64-portable pypy2-latest && export PATH=$HOME/pypy2-latest/bin/:$PATH && virtualenv --no-site-packages --python ~/pypy2-latest/bin/pypy pypy2-env && echo 'Creating custom env...' && source pypy2-env/bin/activate && python -V; fi" 17 | - cd - 18 | - if [[ $TRAVIS_PYTHON_VERSION == "2.6" ]]; then pip install flake8==2.6.2; fi 19 | - if [[ -f min_requirements.txt && "$MINREQ" -eq 1 ]]; then 20 | mv min_requirements.txt requirements.txt ; 21 | fi 22 | - test ! -f rnd_requirements.txt || pip install --no-deps -r rnd_requirements.txt 23 | - test ! -f rnd_requirements.txt || pip install -r rnd_requirements.txt ; 24 | - pip install -r tests/requirements.txt 25 | script: 26 | - make test 27 | after_success: 28 | codecov 29 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | from mock import patch 3 | from nose.tools import eq_ 4 | from excel2table.cli import cli 5 | from click.testing import CliRunner 6 | 7 | 8 | def test_cli(): 9 | runner = CliRunner() 10 | 11 | result = runner.invoke(cli, ['sample/goog.ods', 'goog.html']) 12 | eq_(result.exit_code, 0) 13 | os.unlink('goog.html') 14 | 15 | 16 | @patch('click.getchar') 17 | def test_cli_prompt(fake_getchar): 18 | fake_getchar.return_value = 'y' 19 | runner = CliRunner() 20 | 21 | result = runner.invoke(cli, ['sample/goog.ods', 'goog.html']) 22 | result = runner.invoke(cli, ['sample/goog.ods', 'goog.html']) 23 | eq_(result.exit_code, 0) 24 | os.unlink('goog.html') 25 | 26 | 27 | def test_vs(): 28 | runner = CliRunner() 29 | 30 | result = runner.invoke(cli, ['-vs', '100', 'sample/goog.ods', 'goog.html']) 31 | eq_(result.exit_code, 0) 32 | os.unlink('goog.html') 33 | 34 | 35 | def test_vs_disabled(): 36 | runner = CliRunner() 37 | 38 | result = runner.invoke(cli, ['-vs', '-1', 'sample/goog.ods', 'goog.html']) 39 | eq_(result.exit_code, 0) 40 | os.unlink('goog.html') 41 | 42 | 43 | @patch('webbrowser.open') 44 | @patch('time.sleep', side_effect=KeyboardInterrupt()) 45 | def test_serve(fake_time, fake_webbrowser): 46 | runner = CliRunner() 47 | 48 | result = runner.invoke(cli, ['-s', 'sample/goog.ods']) 49 | eq_(result.exit_code, 0) 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # April 2016 2 | # reference: https://github.com/github/gitignore/blob/master/Python.gitignore 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | 72 | # IPython Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # dotenv 82 | .env 83 | 84 | # virtualenv 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | *~ 94 | commons/ 95 | commons 96 | .moban.hashes 97 | doc/build/ 98 | cover/ 99 | coverage/ 100 | 101 | # osx 102 | .DS_Store 103 | 104 | # emacs 105 | *~ 106 | 107 | -------------------------------------------------------------------------------- /excel2table/templates/excel2table/buttons.print.min.js: -------------------------------------------------------------------------------- 1 | (function(d){"function"===typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(f){return d(f,window,document)}):"object"===typeof exports?module.exports=function(f,b){f||(f=window);if(!b||!b.fn.dataTable)b=require("datatables.net")(f,b).$;b.fn.dataTable.Buttons||require("datatables.net-buttons")(f,b);return d(b,f,f.document)}:d(jQuery,window,document)})(function(d,f,b){var i=d.fn.dataTable,h=b.createElement("a"),m=function(a){h.href=a;a=h.host;-1===a.indexOf("/")&& 2 | 0!==h.pathname.indexOf("/")&&(a+="/");return h.protocol+"//"+a+h.pathname+h.search};i.ext.buttons.print={className:"buttons-print",text:function(a){return a.i18n("buttons.print","Print")},action:function(a,b,h,e){var c=b.buttons.exportData(e.exportOptions),k=function(a,c){for(var b="",d=0,e=a.length;d"+a[d]+"";return b+""},a='';e.header&&(a+=""+k(c.header,"th")+"");for(var a=a+"",l=0,i=c.body.length;l< 3 | i;l++)a+=k(c.body[l],"td");a+="";e.footer&&c.footer&&(a+=""+k(c.footer,"th")+"");var g=f.open("",""),c=e.title;"function"===typeof c&&(c=c());-1!==c.indexOf("*")&&(c=c.replace("*",d("title").text()));g.document.close();var j=""+c+"";d("style, link").each(function(){var a=j,b=d(this).clone()[0];"link"===b.nodeName.toLowerCase()&&(b.href=m(b.href));j=a+b.outerHTML});try{g.document.head.innerHTML=j}catch(n){d(g.document.head).html(j)}g.document.body.innerHTML="

"+ 4 | c+"

"+("function"===typeof e.message?e.message(b,h,e):e.message)+"
"+a;d(g.document.body).addClass("dt-print-view");d("img",g.document.body).each(function(a,b){b.setAttribute("src",m(b.getAttribute("src")))});e.customize&&e.customize(g);setTimeout(function(){e.autoPrint&&(g.print(),g.close())},250)},title:"*",message:"",exportOptions:{},header:!0,footer:!1,autoPrint:!0,customize:null};return i.Buttons}); 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Onni Software Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | The MIT License (MIT) 24 | 25 | Its base project is released under the following license 26 | 27 | Copyright (c) 2017 Vivek R 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy 30 | of this software and associated documentation files (the "Software"), to deal 31 | in the Software without restriction, including without limitation the rights 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 33 | copies of the Software, and to permit persons to whom the Software is 34 | furnished to do so, subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in all 37 | copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 45 | SOFTWARE. 46 | 47 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Excel2Table 2 | ============ 3 | 4 | .. image:: https://api.travis-ci.org/pyexcel/excel2table.svg?branch=master 5 | :target: http://travis-ci.org/pyexcel/excel2table 6 | 7 | .. image:: https://codecov.io/gh/pyexcel/excel2table/branch/master/graph/badge.svg 8 | :target: https://codecov.io/gh/pyexcel/excel2table 9 | 10 | 11 | Simple command-line utility to convert csv, xls, xlsx, ods files to searchable and 12 | sortable HTML table. Supports large datasets and horizontal scrolling for large number of columns. 13 | 14 | It is a variant of `csvtotable `_. 15 | 16 | Demo 17 | ---- 18 | 19 | `Here is a demo`_ of `sample ods`_ file converted to HTML table with charts. 20 | 21 | .. image:: https://user-images.githubusercontent.com/4280312/30404140-006c035e-98dd-11e7-9cfd-1fcf3e405e2f.gif 22 | 23 | Installation 24 | ------------ 25 | 26 | :: 27 | 28 | pip install --upgrade excel2table 29 | 30 | 31 | Get started 32 | ----------- 33 | 34 | :: 35 | 36 | excel2table --help 37 | 38 | Convert ``data.ods`` file to ``data.html`` file 39 | 40 | :: 41 | 42 | excel2table data.ods data.html 43 | 44 | Open output file in a web browser instead of writing to a file 45 | 46 | :: 47 | 48 | excel2table data.ods --serve 49 | 50 | Options 51 | ------- 52 | 53 | :: 54 | 55 | -c, --caption Table caption 56 | -d, --delimiter CSV delimiter. Defaults to ',' 57 | -e, --encoding CSV encoding. Defaults to 'utf-8'. 58 | -q, --quotechar Quote chracter. Defaults to '"' 59 | -dl, --display-length Number of rows to show by default. Defaults to -1 (show all rows) 60 | -o, --overwrite Overwrite the output file if exists. Defaults to false. 61 | -s, --serve Open html output in a web browser. 62 | -h, --height Table height in px or in %. Default is 75% of the page. 63 | -p, --pagination Enable/disable pagination. Enabled by default. 64 | -vs, --virtual-scroll Number of rows after which virtual scroll is enabled. Default is set to 1000 rows. 65 | Set it to -1 to disable and 0 to always enable. 66 | -nh, --no-header Show default headers instead of picking first row as header. Disabled by default. 67 | -e, --export Enable filtered rows export options. 68 | -eo, --export-options Enable specific export options. By default shows all. 69 | For multiple options use -eo flag multiple times. For ex. -eo json -eo csv 70 | 71 | Credits 72 | ------- 73 | `Datatables`_ 74 | 75 | .. _Here is a demo: https://github.com/pyexcel/excel2table/master/sample/goog.html 76 | .. _sample ods: https://github.com/pyexcel/excel2table/blob/master/sample/goog.ods 77 | .. _Datatables: https://datatables.net 78 | -------------------------------------------------------------------------------- /excel2table/cli.py: -------------------------------------------------------------------------------- 1 | import os 2 | import click 3 | 4 | from excel2table import convert 5 | 6 | 7 | # Prompt for file overwrite 8 | def prompt_overwrite(file_name): 9 | # Skip if file doesn't exist 10 | if not os.path.exists(file_name): 11 | return True 12 | 13 | # Prompt for file overwrite if outfile already exists 14 | fmt = "File ({}) already exists. Do you want to overwrite? (y/n): " 15 | message = fmt.format(file_name) 16 | 17 | click.secho(message, nl=False, fg="red") 18 | choice = click.getchar() 19 | click.echo() 20 | 21 | if choice not in ("y", "Y"): 22 | return False 23 | 24 | return True 25 | 26 | 27 | @click.command() 28 | @click.argument("input_file", type=click.Path(exists=True)) 29 | @click.argument("output_file", type=click.Path(), required=False) 30 | @click.option("-c", "--caption", type=str, help="Table caption") 31 | @click.option("-d", "--delimiter", type=str, default=",", help="CSV delimiter") 32 | @click.option("-e", "--encoding", type=str, default="utf-8", 33 | help="CSV encoding") 34 | @click.option("-q", "--quotechar", type=str, default='"', 35 | help="String used to quote fields containing special characters") 36 | @click.option("-dl", "--display-length", type=int, default=-1, 37 | help=("Number of rows to show by default. " 38 | "Defaults to -1 (show all rows)")) 39 | @click.option("-o", "--overwrite", default=False, is_flag=True, 40 | help="Overwrite the output file if exisits.") 41 | @click.option("-s", "--serve", default=False, is_flag=True, 42 | help="Open output html in browser instead of writing to file.") 43 | @click.option("-h", "--height", type=str, help="Table height in px or in %.") 44 | @click.option("-p", "--pagination", default=True, is_flag=True, 45 | help="Enable/disable table pagination.") 46 | @click.option("-vs", "--virtual-scroll", type=int, default=1000, 47 | help=("Number of rows after which virtual scroll is enabled." 48 | "Set it to -1 to disable and 0 to always enable.")) 49 | @click.option("-nh", "--no-header", default=False, is_flag=True, 50 | help="Disable displaying first row as headers.") 51 | @click.option("-e", "--export", default=True, is_flag=True, 52 | help="Enable filtered rows export options.") 53 | @click.option("-eo", "--export-options", 54 | type=click.Choice(["copy", "csv", "json", "print", "chart"]), 55 | multiple=True, 56 | help=("Enable specific export options. By default shows all. " + 57 | "For multiple options use -eo flag multiple times." + 58 | " For ex. -eo json -eo csv")) 59 | def cli(*args, **kwargs): 60 | """ 61 | CSVtoTable commandline utility. 62 | """ 63 | # Convert CSV file 64 | content = convert.convert(kwargs.pop("input_file"), **kwargs) 65 | 66 | # Serve the temporary file in browser. 67 | if kwargs["serve"]: 68 | convert.serve(content) 69 | # Write to output file 70 | elif kwargs["output_file"]: 71 | # Check if file can be overwrite 72 | if (not kwargs["overwrite"] and 73 | not prompt_overwrite(kwargs["output_file"])): 74 | raise click.Abort() 75 | 76 | convert.save(kwargs["output_file"], content) 77 | click.secho("File converted successfully: {}".format( 78 | kwargs["output_file"]), fg="green") 79 | else: 80 | # If its not server and output file is missing then raise error 81 | raise click.BadOptionUsage("Missing argument \"output_file\".") 82 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from setuptools import setup 4 | 5 | try: 6 | from jupyterpip import cmdclass 7 | except Exception: 8 | import subprocess 9 | import importlib 10 | 11 | subprocess.check_call([sys.executable, '-m', 12 | 'pip', 'install', 'jupyter-pip']) 13 | cmdclass = importlib.import_module( 14 | 'pyecharts_jupyter_installer').cmdclass 15 | 16 | 17 | _version = "2.3.2" 18 | 19 | 20 | def read_files(*files): 21 | """Read files into setup""" 22 | text = "" 23 | for single_file in files: 24 | content = read(single_file) 25 | text = text + content + "\n" 26 | return text 27 | 28 | 29 | def read(afile): 30 | """Read a file into setup""" 31 | with open(afile, 'r') as opened_file: 32 | content = filter_out_test_code(opened_file) 33 | content = "".join(list(content)) 34 | return content 35 | 36 | 37 | def filter_out_test_code(file_handle): 38 | found_test_code = False 39 | for line in file_handle.readlines(): 40 | if line.startswith('.. testcode:'): 41 | found_test_code = True 42 | continue 43 | if found_test_code is True: 44 | if line.startswith(' '): 45 | continue 46 | else: 47 | empty_line = line.strip() 48 | if len(empty_line) == 0: 49 | continue 50 | else: 51 | found_test_code = False 52 | yield line 53 | else: 54 | for keyword in ['|version|', '|today|']: 55 | if keyword in line: 56 | break 57 | else: 58 | yield line 59 | 60 | 61 | setup( 62 | name="excel2table", 63 | test_suite="tests", 64 | version=_version, 65 | description="Simple commandline utility to convert excel files" 66 | "to searchable and sortable HTML table.", 67 | author="Vivek R", 68 | author_email="vividvilla@gmail.com", 69 | maintainer="C. W.", 70 | maintainer_email="wangc_2011@hotmail.com", 71 | url="https://github.com/pyexcel/excel2table", 72 | packages=["excel2table"], 73 | include_package_data=True, 74 | long_description=read_files("README.rst", "CHANGELOG.rst"), 75 | download_url="https://github.com/pyexcel/excel2table/archive/{}.tar.gz" 76 | .format(_version), 77 | license="MIT", 78 | classifiers=[ 79 | "Development Status :: 4 - Beta", 80 | "Intended Audience :: Developers", 81 | "Programming Language :: Python", 82 | "Natural Language :: English", 83 | "License :: OSI Approved :: MIT License", 84 | "Programming Language :: Python :: 2.6", 85 | "Programming Language :: Python :: 2.7", 86 | "Programming Language :: Python :: 3.3", 87 | "Programming Language :: Python :: 3.4", 88 | "Programming Language :: Python :: 3.5", 89 | "Topic :: Software Development :: Libraries :: Python Modules", 90 | "Topic :: Software Development :: Libraries" 91 | ], 92 | install_requires=["click >= 6.7", "jinja2 >= 2.8.0", 93 | "pyexcel >= 0.5.0", "six >= 1.10.0", 94 | "pyexcel-xls >= 0.4.0", "pyexcel-odsr >= 0.4.0"], 95 | entry_points={ 96 | "console_scripts": [ 97 | "excel2table = excel2table.cli:cli", 98 | ] 99 | }, 100 | cmdclass=cmdclass('excel2table/templates/excel2table', 101 | enable='excel2table/main') 102 | ) 103 | -------------------------------------------------------------------------------- /excel2table/templates/excel2table/buttons.chart.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Chart button for Buttons and DataTables. 3 | * By C.W., Onni software Ltd. New BSD License 4 | */ 5 | !function(a){"function"==typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(b){return a(b,window,document)}):"object"==typeof exports?module.exports=function(b,c){return b||(b=window),c&&c.fn.dataTable||(c=require("datatables.net")(b,c).$),c.fn.dataTable.Buttons||require("datatables.net-buttons")(b,c),a(c,b,b.document)}:a(jQuery,window,document)}(function(a,b,c,d){"use strict";function e(b){a("#table_filter").css("display",b),a(".dataTables_scroll").css("display",b),a(".dataTables_info").css("display",b),a("#table_paginate").css("display",b)}function f(b,d){var e=b.buttons.exportData(a.extend({decodeEntities:!1},d.exportOptions));e.body.sort(function(a,b){var c=new Date(a[0]).getTime(),d=new Date(b[0]).getTime();return c===d?0:d>c?-1:1});var f=a("caption").text(),g=e.body.map(function(a,b){return a[0]}),i=e.body.map(function(a,b){return[a[1],a[4],a[3],a[2]]}),j=e.body.map(function(a,b){return a[5]}),k=e.body.map(function(a,b){return a[6]});h.dataZoom[0].startValue=g[0],h.dataZoom[0].endValue=g[g.length-1],h.xAxis[0].data=g,h.series[0].data=i,h.series[0].name=f,h.series[1].data=j,h.series[1].name=f+" adjacent close",h.series[2].data=k,h.series[2].name=f+" volume",h.title[0].text=f;var l=echarts.init(c.getElementById("echart"));l.setOption(h)}var g=a.fn.dataTable,h=(c.createElement("a"),{_index_flag:5158195,backgroundColor:"#fff",color:["#c23531","#2f4554","#61a0a8","#d48265","#749f83","#ca8622","#bda29a","#6e7074","#546570","#c4ccd3","#f05b72","#ef5b9c","#f47920","#905a3d","#fab27b","#2a5caa","#444693","#726930","#b2d235","#6d8346","#ac6767","#1d953f","#6950a1","#918597","#f6f5ec"],dataZoom:[{endValue:"",orient:"horizontal",show:!0,startValue:"",type:"slider"}],legend:[{data:["K"],left:"center",orient:"horizontal",selectedMode:"multiple",show:!1,textStyle:{color:"#333",fontSize:12},top:"top"}],series:[{data:[],indexflag:5158195,markLine:{data:[]},markPoint:{data:[]},name:"Google Stock",type:"candlestick"},{symbol:"emptyCircle",xAxisIndex:0,step:!1,yAxisIndex:0,data:[],stack:"",showSymbol:!0,markLine:{data:[]},name:"x",type:"line",smooth:!1,label:{emphasis:{show:!0},normal:{position:"top",textStyle:{color:"#000",fontSize:12},formatter:null,show:!1}},markPoint:{data:[]},areaStyle:{},lineStyle:{normal:{opacity:1,width:1,type:"solid",curveness:0,color:"#039"}}},{markLine:{data:[]},itemStyle:{normal:{color:"#61a0a8"}},yAxisIndex:1,name:"Volume",markPoint:{data:[]},data:[],xAxisIndex:0,type:"bar",stack:"",label:{emphasis:{show:!0},normal:{position:"top",textStyle:{color:"#000",fontSize:12},formatter:null,show:!1}}}],title:[{left:"center",subtext:"",subtextStyle:{color:"#000",fontSize:12},text:"Google stock",textStyle:{color:"#000",fontSize:18},top:"auto"}],toolbox:{feature:{saveAsImage:{show:!0,title:"Download"}},left:"95%",orient:"vertical",show:!0,top:"center"},tooltip:{axisPointer:{type:"line"},formatter:d,textStyle:{color:"#fff",fontSize:14},trigger:"item",triggerOn:"mousemove|click"},xAxis:[{axisLabel:{interval:"auto",margin:8,rotate:0},axisTick:{alignWithLabel:!1},data:[],lineStyle:{color:"#039"},inverse:!1,max:d,min:d,name:"",nameGap:25,nameLocation:"middle",nameTextStyle:{fontSize:14},position:d,boundaryGap:!0,scale:!0,type:"category"}],yAxis:[{axisLabel:{formatter:"{value} ",interval:"auto",margin:8,rotate:0},axisTick:{alignWithLabel:!1},inverse:!1,max:d,min:d,name:"",nameGap:25,nameLocation:"middle",nameTextStyle:{fontSize:14},position:d,scale:!0,splitArea:{show:!0},type:"value"},{nameLocation:"middle",axisTick:{alignWithLabel:!1},inverse:!1,name:"",min:null,max:null,axisLabel:{formatter:"{value} ",margin:8,rotate:0,interval:"auto"},nameTextStyle:{fontSize:14},nameGap:25,position:null,type:"value"},{nameLocation:"middle",axisTick:{alignWithLabel:!1},inverse:!1,name:"",min:null,max:null,axisLabel:{formatter:"{value}",margin:8,rotate:0,interval:"auto"},nameTextStyle:{fontSize:14},nameGap:25,position:null,type:"value"}]});return g.ext.buttons.chart={className:"buttons-chart",text:function(a){return a.i18n("buttons.chart","chart")},action:function(b,c,d,g){"chart"===d.text()?(e("none"),a("#echart").attr("_echarts_instance_")?(a("#echart").css("visibility","visible"),d.hasChart=!0):(a("#echart").css("height",a(".dataTables_scroll").css("height")),f(c,g)),d.text("table")):(e("block"),a("#echart").css("visibility","hidden"),d.text("chart")),console.log(d.text())},title:"*",messageTop:"*",messageBottom:"*",exportOptions:{},header:!0,footer:!1,autoPrint:!0,customize:d},g.Buttons}); 6 | -------------------------------------------------------------------------------- /excel2table/templates/template.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} 6 | 7 | 8 |
9 | {%- if caption %} 10 | 11 | {%- endif %} 12 |
{{ caption }}
13 | 14 | 15 | 16 | 17 | {%- if virtual_scroll %} 18 | 19 | {%- endif %} 20 | {%- if enable_export %} 21 | 22 | 23 | 24 | 25 | {%- endif %} 26 | 31 | 241 |
242 | 243 | 244 | -------------------------------------------------------------------------------- /excel2table/convert.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import re 3 | import os 4 | import six 5 | import uuid 6 | import json 7 | import time 8 | import logging 9 | import tempfile 10 | import webbrowser 11 | from io import open 12 | import pyexcel as p 13 | from datetime import date, datetime 14 | from jinja2 import Environment, FileSystemLoader, select_autoescape 15 | 16 | logging.basicConfig() 17 | logger = logging.getLogger(__package__) 18 | 19 | package_path = os.path.dirname(os.path.abspath(__file__)) 20 | templates_dir = os.path.join(package_path, "templates") 21 | 22 | # Initialize Jinja 2 env 23 | env = Environment( 24 | loader=FileSystemLoader(templates_dir), 25 | autoescape=select_autoescape(["html", "xml", "j2"]) 26 | ) 27 | 28 | # Regex to match src property in script tags 29 | js_src_pattern = re.compile(r'', 30 | re.IGNORECASE | re.MULTILINE) 31 | # Path to JS files inside templates 32 | js_files_path = os.path.join(package_path, templates_dir) 33 | 34 | 35 | class DateTimeEncoder(json.JSONEncoder): 36 | def default(self, obj): 37 | if isinstance(obj, date): 38 | date_string = obj.strftime('%Y-%m-%d') 39 | return date_string 40 | if isinstance(obj, datetime): 41 | datetime_string = obj.strftime("%Y-%m-%d %H:%M:%S") 42 | return datetime_string 43 | return json.JSONEncoder.default(self, obj) 44 | 45 | 46 | def convert(input_file_name, delimiter=',', quotechar='|', 47 | encoding='utf-8', **kwargs): 48 | """ 49 | Convert excel file to HTML table 50 | """ 51 | 52 | if six.PY2: 53 | delimiter = delimiter.encode('utf-8') 54 | quotechar = quotechar.encode('utf-8') 55 | 56 | # Read CSV and form a header and rows list 57 | excel_sheet = p.get_sheet(file_name=input_file_name, 58 | name_columns_by_row=0, encoding=encoding, 59 | delimiter=str(delimiter), quotechar=quotechar) 60 | csv_headers = [] 61 | if not kwargs.get("no_header"): 62 | # Read header from first line 63 | csv_headers = excel_sheet.colnames 64 | 65 | # Set default column name if header is not present 66 | if not csv_headers and len(excel_sheet.number_of_rows()) > 1: 67 | csv_headers = ["Column {}".format((n+1)) 68 | for n in range(len(excel_sheet.number_of_columns()))] 69 | # Render csv to HTML 70 | html = render_template(csv_headers, excel_sheet.array[1:], **kwargs) 71 | 72 | # Freeze all JS files in template 73 | return freeze_js(html) 74 | 75 | 76 | def save(file_name, content): 77 | """Save content to a file""" 78 | with open(file_name, "w", encoding="utf-8") as output_file: 79 | output_file.write(content) 80 | return output_file.name 81 | 82 | 83 | def serve(content): 84 | """Write content to a temp file and serve it in browser""" 85 | temp_folder = tempfile.gettempdir() 86 | temp_file_name = tempfile.gettempprefix() + str(uuid.uuid4()) + ".html" 87 | # Generate a file path with a random name in temporary dir 88 | temp_file_path = os.path.join(temp_folder, temp_file_name) 89 | 90 | # save content to temp file 91 | save(temp_file_path, content) 92 | 93 | # Open templfile in a browser 94 | webbrowser.open("file://{}".format(temp_file_path)) 95 | 96 | # Block the thread while content is served 97 | try: 98 | while True: 99 | time.sleep(1) 100 | except KeyboardInterrupt: 101 | # cleanup the temp file 102 | os.remove(temp_file_path) 103 | 104 | 105 | def render_template(table_headers, table_items, notebook=False, **options): 106 | """ 107 | Render Jinja2 template 108 | """ 109 | caption = options.get("caption") or "Table" 110 | display_length = options.get("display_length") or -1 111 | height = options.get("height") or "70vh" 112 | default_length_menu = [-1, 10, 25, 50] 113 | pagination = options.get("pagination") 114 | virtual_scroll_limit = options.get("virtual_scroll") 115 | 116 | # Change % to vh 117 | height = height.replace("%", "vh") 118 | 119 | # Header columns 120 | columns = [] 121 | for header in table_headers: 122 | columns.append({"title": header}) 123 | 124 | # Data table options 125 | datatable_options = { 126 | "columns": columns, 127 | "data": table_items, 128 | "iDisplayLength": display_length, 129 | "sScrollX": "100%", 130 | "sScrollXInner": "100%" 131 | } 132 | 133 | # Enable virtual scroll for rows bigger than 1000 rows 134 | is_paging = pagination 135 | virtual_scroll = False 136 | scroll_y = height 137 | 138 | if virtual_scroll_limit: 139 | if virtual_scroll_limit != -1 and len(table_items) > virtual_scroll_limit: # noqa: E501 140 | virtual_scroll = True 141 | display_length = -1 142 | 143 | fmt = ("\nVirtual scroll is enabled since number of rows exceeds {limit}." # noqa: E501 144 | " You can set custom row limit by setting flag -vs, --virtual-scroll." # noqa: E501 145 | " Virtual scroll can be disabled by setting the value to -1 and set it to 0 to always enable.") # noqa: E501 146 | logger.warn(fmt.format(limit=virtual_scroll_limit)) 147 | 148 | if not is_paging: 149 | fmt = "\nPagination can not be disabled in virtual scroll mode." 150 | logger.warn(fmt) 151 | 152 | is_paging = True 153 | 154 | if is_paging and not virtual_scroll: 155 | # Add display length to the default display length menu 156 | length_menu = [] 157 | if display_length != -1: 158 | length_menu = sorted(default_length_menu + [display_length]) 159 | else: 160 | length_menu = default_length_menu 161 | 162 | # Set label as "All" it display length is -1 163 | length_menu_label = [str("All") if i == -1 else i for i in length_menu] 164 | datatable_options["lengthMenu"] = [length_menu, length_menu_label] 165 | datatable_options["iDisplayLength"] = display_length 166 | 167 | if is_paging: 168 | datatable_options["paging"] = True 169 | else: 170 | datatable_options["paging"] = False 171 | 172 | if scroll_y: 173 | datatable_options["scrollY"] = scroll_y 174 | 175 | if virtual_scroll: 176 | datatable_options["scroller"] = True 177 | datatable_options["bPaginate"] = False 178 | datatable_options["deferRender"] = True 179 | datatable_options["bLengthChange"] = False 180 | 181 | enable_export = options.get("export") 182 | if enable_export: 183 | if options["export_options"]: 184 | allowed = list(options["export_options"]) 185 | else: 186 | allowed = ["copy", "csv", "json", "print", "chart"] 187 | 188 | datatable_options["dom"] = "Bfrtip" 189 | datatable_options["buttons"] = allowed 190 | 191 | datatable_options_json = json.dumps(datatable_options, 192 | cls=DateTimeEncoder, 193 | separators=(",", ":")) 194 | 195 | if notebook: 196 | template = env.get_template("notebook.j2") 197 | else: 198 | template = env.get_template("template.j2") 199 | 200 | return template.render(title=caption or "Table", 201 | caption=caption, 202 | datatable_options=datatable_options_json, 203 | virtual_scroll=virtual_scroll, 204 | enable_export=enable_export) 205 | 206 | 207 | def freeze_js(html): 208 | """ 209 | Freeze all JS assets to the rendered html itself. 210 | """ 211 | matches = js_src_pattern.finditer(html) 212 | 213 | if not matches: 214 | return html 215 | 216 | # Reverse regex matches to replace match string with respective JS content 217 | for match in reversed(tuple(matches)): 218 | # JS file name 219 | file_name = match.group(1) 220 | file_path = os.path.join(js_files_path, file_name) 221 | 222 | with open(file_path, "r", encoding="utf-8") as f: 223 | file_content = f.read() 224 | # Replace matched string with inline JS 225 | fmt = '' 226 | js_content = fmt.format(file_content) 227 | html = html[:match.start()] + js_content + html[match.end():] 228 | 229 | return html 230 | -------------------------------------------------------------------------------- /excel2table/templates/excel2table/dataTables.scroller.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Scroller 1.4.2 3 | ©2011-2016 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(e){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(h){return e(h,window,document)}):"object"===typeof exports?module.exports=function(h,j){h||(h=window);if(!j||!j.fn.dataTable)j=require("datatables.net")(h,j).$;return e(j,h,h.document)}:e(jQuery,window,document)})(function(e,h,j,l){var m=e.fn.dataTable,g=function(a,b){this instanceof g?(b===l&&(b={}),this.s={dt:e.fn.dataTable.Api(a).settings()[0],tableTop:0,tableBottom:0,redrawTop:0,redrawBottom:0,autoHeight:!0, 6 | viewportRows:0,stateTO:null,drawTO:null,heights:{jump:null,page:null,virtual:null,scroll:null,row:null,viewport:null},topRowFloat:0,scrollDrawDiff:null,loaderVisible:!1},this.s=e.extend(this.s,g.oDefaults,b),this.s.heights.row=this.s.rowHeight,this.dom={force:j.createElement("div"),scroller:null,table:null,loader:null},this.s.dt.oScroller||(this.s.dt.oScroller=this,this._fnConstruct())):alert("Scroller warning: Scroller must be initialised with the 'new' keyword.")};e.extend(g.prototype,{fnRowToPixels:function(a, 7 | b,c){a=c?this._domain("virtualToPhysical",a*this.s.heights.row):this.s.baseScrollTop+(a-this.s.baseRowTop)*this.s.heights.row;return b||b===l?parseInt(a,10):a},fnPixelsToRow:function(a,b,c){var d=a-this.s.baseScrollTop,a=c?this._domain("physicalToVirtual",a)/this.s.heights.row:d/this.s.heights.row+this.s.baseRowTop;return b||b===l?parseInt(a,10):a},fnScrollToRow:function(a,b){var c=this,d=!1,f=this.fnRowToPixels(a),i=a-(this.s.displayBuffer-1)/2*this.s.viewportRows;0>i&&(i=0);if((f>this.s.redrawBottom|| 8 | ftable",this.dom.scroller)[0];this.dom.table.style.position="absolute";this.dom.table.style.top="0px";this.dom.table.style.left="0px";e(this.s.dt.nTableWrapper).addClass("DTS");this.s.loadingIndicator&&(this.dom.loader=e('
'+this.s.dt.oLanguage.sLoadingRecords+"
").css("display", 11 | "none"),e(this.dom.scroller.parentNode).css("position","relative").append(this.dom.loader));this.s.heights.row&&"auto"!=this.s.heights.row&&(this.s.autoHeight=!1);this.fnMeasure(!1);this.s.ingnoreScroll=!0;this.s.stateSaveThrottle=this.s.dt.oApi._fnThrottle(function(){a.s.dt.oApi._fnSaveState(a.s.dt)},500);e(this.dom.scroller).on("scroll.DTS",function(){a._fnScroll.call(a)});e(this.dom.scroller).on("touchstart.DTS",function(){a._fnScroll.call(a)});this.s.dt.aoDrawCallback.push({fn:function(){a.s.dt.bInitialised&& 12 | a._fnDrawCallback.call(a)},sName:"Scroller"});e(h).on("resize.DTS",function(){a.fnMeasure(false);a._fnInfo()});var b=!0;this.s.dt.oApi._fnCallbackReg(this.s.dt,"aoStateSaveParams",function(c,d){if(b&&a.s.dt.oLoadedState){d.iScroller=a.s.dt.oLoadedState.iScroller;d.iScrollerTopRow=a.s.dt.oLoadedState.iScrollerTopRow;b=false}else{d.iScroller=a.dom.scroller.scrollTop;d.iScrollerTopRow=a.s.topRowFloat}},"Scroller_State");this.s.dt.oLoadedState&&(this.s.topRowFloat=this.s.dt.oLoadedState.iScrollerTopRow|| 13 | 0);e(this.s.dt.nTable).one("init.dt",function(){a.fnMeasure()});this.s.dt.aoDestroyCallback.push({sName:"Scroller",fn:function(){e(h).off("resize.DTS");e(a.dom.scroller).off("touchstart.DTS scroll.DTS");e(a.s.dt.nTableWrapper).removeClass("DTS");e("div.DTS_Loading",a.dom.scroller.parentNode).remove();e(a.s.dt.nTable).off("init.dt");a.dom.table.style.position="";a.dom.table.style.top="";a.dom.table.style.left=""}})}else this.s.dt.oApi._fnLog(this.s.dt,0,"Pagination must be enabled for Scroller")}, 14 | _fnScroll:function(){var a=this,b=this.s.heights,c=this.dom.scroller.scrollTop,d;if(!this.s.skip&&!this.s.ingnoreScroll)if(this.s.dt.bFiltered||this.s.dt.bSorted)this.s.lastScrollTop=0;else{this._fnInfo();clearTimeout(this.s.stateTO);this.s.stateTO=setTimeout(function(){a.s.dt.oApi._fnSaveState(a.s.dt)},250);if(cthis.s.redrawBottom){var f=Math.ceil((this.s.displayBuffer-1)/2*this.s.viewportRows);Math.abs(c-this.s.lastScrollTop)>b.viewport||this.s.ani?(d=parseInt(this._domain("physicalToVirtual", 15 | c)/b.row,10)-f,this.s.topRowFloat=this._domain("physicalToVirtual",c)/b.row):(d=this.fnPixelsToRow(c)-f,this.s.topRowFloat=this.fnPixelsToRow(c,!1));0>=d?d=0:d+this.s.dt._iDisplayLength>this.s.dt.fnRecordsDisplay()?(d=this.s.dt.fnRecordsDisplay()-this.s.dt._iDisplayLength,0>d&&(d=0)):0!==d%2&&d++;if(d!=this.s.dt._iDisplayStart&&(this.s.tableTop=e(this.s.dt.nTable).offset().top,this.s.tableBottom=e(this.s.dt.nTable).height()+this.s.tableTop,b=function(){if(a.s.scrollDrawReq===null)a.s.scrollDrawReq= 16 | c;a.s.dt._iDisplayStart=d;a.s.dt.oApi._fnDraw(a.s.dt)},this.s.dt.oFeatures.bServerSide?(clearTimeout(this.s.drawTO),this.s.drawTO=setTimeout(b,this.s.serverWait)):b(),this.dom.loader&&!this.s.loaderVisible))this.dom.loader.css("display","block"),this.s.loaderVisible=!0}else this.s.topRowFloat=this._domain("physicalToVirtual",c)/b.row;this.s.lastScrollTop=c;this.s.stateSaveThrottle()}},_domain:function(a,b){var c=this.s.heights,d;if(c.virtual===c.scroll)return b;var e=(c.scroll-c.viewport)/2,i=(c.virtual- 17 | c.viewport)/2;d=i/(e*e);if("virtualToPhysical"===a){if(bb?c.scroll:2*e-Math.pow(b/d,0.5)}if("physicalToVirtual"===a){if(bb?c.virtual:2*i-b*b*d}},_fnDrawCallback:function(){var a=this,b=this.s.heights,c=this.dom.scroller.scrollTop,d=e(this.s.dt.nTable).height(),f=this.s.dt._iDisplayStart,i=this.s.dt._iDisplayLength,g=this.s.dt.fnRecordsDisplay();this.s.skip=!0;this._fnScrollForce();c=0===f?this.s.topRowFloat*b.row:f+i>=g? 18 | b.scroll-(g-this.s.topRowFloat)*b.row:this._domain("virtualToPhysical",this.s.topRowFloat*b.row);this.dom.scroller.scrollTop=c;this.s.baseScrollTop=c;this.s.baseRowTop=this.s.topRowFloat;var h=c-(this.s.topRowFloat-f)*b.row;0===f?h=0:f+i>=g&&(h=b.scroll-d);this.dom.table.style.top=h+"px";this.s.tableTop=h;this.s.tableBottom=d+this.s.tableTop;d=(c-this.s.tableTop)*this.s.boundaryScale;this.s.redrawTop=c-d;this.s.redrawBottom=c+d;this.s.skip=!1;this.s.dt.oFeatures.bStateSave&&null!==this.s.dt.oLoadedState&& 19 | "undefined"!=typeof this.s.dt.oLoadedState.iScroller?((c=(this.s.dt.sAjaxSource||a.s.dt.ajax)&&!this.s.dt.oFeatures.bServerSide?!0:!1)&&2==this.s.dt.iDraw||!c&&1==this.s.dt.iDraw)&&setTimeout(function(){e(a.dom.scroller).scrollTop(a.s.dt.oLoadedState.iScroller);a.s.redrawTop=a.s.dt.oLoadedState.iScroller-b.viewport/2;setTimeout(function(){a.s.ingnoreScroll=!1},0)},0):a.s.ingnoreScroll=!1;this.s.dt.oFeatures.bInfo&&setTimeout(function(){a._fnInfo.call(a)},0);this.dom.loader&&this.s.loaderVisible&& 20 | (this.dom.loader.css("display","none"),this.s.loaderVisible=!1)},_fnScrollForce:function(){var a=this.s.heights;a.virtual=a.row*this.s.dt.fnRecordsDisplay();a.scroll=a.virtual;1E6this.s.heights.row?a.scroll+"px":this.s.heights.row+"px"},_fnCalcRowHeight:function(){var a=this.s.dt,b=a.nTable,c=b.cloneNode(!1),d=e("").appendTo(c),f=e('
');for(e("tbody tr:lt(4)",b).clone().appendTo(d);3>e("tr",d).length;)d.append(" ");e("div."+a.oClasses.sScrollBody,f).append(c);a=this.s.dt.nHolding||b.parentNode;e(a).is(":visible")||(a="body");f.appendTo(a);this.s.heights.row=e("tr",d).eq(1).outerHeight();f.remove()},_fnInfo:function(){if(this.s.dt.oFeatures.bInfo){var a=this.s.dt,b=a.oLanguage,c=this.dom.scroller.scrollTop,d=Math.floor(this.fnPixelsToRow(c,!1,this.s.ani)+1),f=a.fnRecordsTotal(), 22 | i=a.fnRecordsDisplay(),c=Math.ceil(this.fnPixelsToRow(c+this.s.heights.viewport,!1,this.s.ani)),c=i").addClass(this.c.dom.container.className)},this._constructor()};a.extend(i.prototype,{action:function(a,b){var c=this._nodeToButton(a);return b===d?c.conf.action:(c.conf.action=b,this)},active:function(b,c){var e=this._nodeToButton(b),f=this.c.dom.button.active,g=a(e.node);return c===d?g.hasClass(f):(g.toggleClass(f,c===d?!0:c),this)},add:function(a,b){var c=this.s.buttons;if("string"==typeof b){for(var d=b.split("-"),e=this.s,f=0,g=d.length-1;g>f;f++)e=e.buttons[1*d[f]];c=e.buttons,b=1*d[d.length-1]}return this._expandButton(c,a,!1,b),this._draw(),this},container:function(){return this.dom.container},disable:function(b){var c=this._nodeToButton(b);return a(c.node).addClass(this.c.dom.button.disabled),this},destroy:function(){a("body").off("keyup."+this.s.namespace);var b,c,d=this.s.buttons.slice();for(b=0,c=d.length;c>b;b++)this.remove(d[b].node);this.dom.container.remove();var e=this.s.dt.settings()[0];for(b=0,c=e.length;c>b;b++)if(e.inst===this){e.splice(b,1);break}return this},enable:function(b,c){if(c===!1)return this.disable(b);var d=this._nodeToButton(b);return a(d.node).removeClass(this.c.dom.button.disabled),this},name:function(){return this.c.name},node:function(b){var c=this._nodeToButton(b);return a(c.node)},processing:function(b,c){var e=this._nodeToButton(b);return c===d?a(e.node).hasClass("processing"):(a(e.node).toggleClass("processing",c),this)},remove:function(b){var c=this._nodeToButton(b),d=this._nodeToHost(b),e=this.s.dt;if(c.buttons.length)for(var f=c.buttons.length-1;f>=0;f--)this.remove(c.buttons[f].node);c.conf.destroy&&c.conf.destroy.call(e.button(b),e,a(b),c.conf),this._removeKey(c.conf),a(c.node).remove();var g=a.inArray(c,d);return d.splice(g,1),this},text:function(b,c){var e=this._nodeToButton(b),f=this.c.dom.collection.buttonLiner,g=e.inCollection&&f&&f.tag?f.tag:this.c.dom.buttonLiner.tag,h=this.s.dt,i=a(e.node),j=function(a){return"function"==typeof a?a(h,i,e.conf):a};return c===d?j(e.conf.text):(e.conf.text=c,g?i.children(g).html(j(c)):i.html(j(c)),this)},_constructor:function(){var b=this,d=this.s.dt,e=d.settings()[0],f=this.c.buttons;e._buttons||(e._buttons=[]),e._buttons.push({inst:this,name:this.c.name});for(var g=0,h=f.length;h>g;g++)this.add(f[g]);d.on("destroy",function(){b.destroy()}),a("body").on("keyup."+this.s.namespace,function(a){if(!c.activeElement||c.activeElement===c.body){var d=String.fromCharCode(a.keyCode).toLowerCase();-1!==b.s.listenKeys.toLowerCase().indexOf(d)&&b._keypress(d,a)}})},_addKey:function(b){b.key&&(this.s.listenKeys+=a.isPlainObject(b.key)?b.key.key:b.key)},_draw:function(a,b){a||(a=this.dom.container,b=this.s.buttons),a.children().detach();for(var c=0,d=b.length;d>c;c++)a.append(b[c].inserter),a.append(" "),b[c].buttons&&b[c].buttons.length&&this._draw(b[c].collection,b[c].buttons)},_expandButton:function(b,c,e,f){for(var g=this.s.dt,h=0,i=a.isArray(c)?c:[c],j=0,k=i.length;k>j;j++){var l=this._resolveExtends(i[j]);if(l)if(a.isArray(l))this._expandButton(b,l,e,f);else{var m=this._buildButton(l,e);if(m){if(f!==d?(b.splice(f,0,m),f++):b.push(m),m.conf.buttons){var n=this.c.dom.collection;m.collection=a("<"+n.tag+"/>").addClass(n.className).attr("role","menu"),m.conf._collection=m.collection,this._expandButton(m.buttons,m.conf.buttons,!0,f)}l.init&&l.init.call(g.button(m.node),g,a(m.node),l),h++}}}},_buildButton:function(b,c){var d=this.c.dom.button,e=this.c.dom.buttonLiner,f=this.c.dom.collection,h=this.s.dt,i=function(a){return"function"==typeof a?a(h,k,b):a};if(c&&f.button&&(d=f.button),c&&f.buttonLiner&&(e=f.buttonLiner),b.available&&!b.available(h,b))return!1;var j=function(b,c,d,e){e.action.call(c.button(d),b,c,d,e),a(c.table().node()).triggerHandler("buttons-action.dt",[c.button(d),c,d,e])},k=a("<"+d.tag+"/>").addClass(d.className).attr("tabindex",this.s.dt.settings()[0].iTabIndex).attr("aria-controls",this.s.dt.table().node().id).on("click.dtb",function(a){a.preventDefault(),!k.hasClass(d.disabled)&&b.action&&j(a,h,k,b),k.blur()}).on("keyup.dtb",function(a){13===a.keyCode&&!k.hasClass(d.disabled)&&b.action&&j(a,h,k,b)});if("a"===d.tag.toLowerCase()&&k.attr("href","#"),e.tag){var l=a("<"+e.tag+"/>").html(i(b.text)).addClass(e.className);"a"===e.tag.toLowerCase()&&l.attr("href","#"),k.append(l)}else k.html(i(b.text));b.enabled===!1&&k.addClass(d.disabled),b.className&&k.addClass(b.className),b.titleAttr&&k.attr("title",i(b.titleAttr)),b.namespace||(b.namespace=".dt-button-"+g++);var m,n=this.c.dom.buttonContainer;return m=n&&n.tag?a("<"+n.tag+"/>").addClass(n.className).append(k):k,this._addKey(b),{conf:b,node:k.get(0),inserter:m,buttons:[],inCollection:c,collection:null}},_nodeToButton:function(a,b){b||(b=this.s.buttons);for(var c=0,d=b.length;d>c;c++){if(b[c].node===a)return b[c];if(b[c].buttons.length){var e=this._nodeToButton(a,b[c].buttons);if(e)return e}}},_nodeToHost:function(a,b){b||(b=this.s.buttons);for(var c=0,d=b.length;d>c;c++){if(b[c].node===a)return b;if(b[c].buttons.length){var e=this._nodeToHost(a,b[c].buttons);if(e)return e}}},_keypress:function(b,c){var d=function(d,e){if(d.key)if(d.key===b)a(e).click();else if(a.isPlainObject(d.key)){if(d.key.key!==b)return;if(d.key.shiftKey&&!c.shiftKey)return;if(d.key.altKey&&!c.altKey)return;if(d.key.ctrlKey&&!c.ctrlKey)return;if(d.key.metaKey&&!c.metaKey)return;a(e).click()}},e=function(a){for(var b=0,c=a.length;c>b;b++)d(a[b].conf,a[b].node),a[b].buttons.length&&e(a[b].buttons)};e(this.s.buttons)},_removeKey:function(b){if(b.key){var c=a.isPlainObject(b.key)?b.key.key:b.key,d=this.s.listenKeys.split(""),e=a.inArray(c,d);d.splice(e,1),this.s.listenKeys=d.join("")}},_resolveExtends:function(b){var c,e,f=this.s.dt,g=function(c){for(var e=0;!a.isPlainObject(c)&&!a.isArray(c);){if(c===d)return;if("function"==typeof c){if(c=c(f,b),!c)return!1}else if("string"==typeof c){if(!h[c])throw"Unknown button type: "+c;c=h[c]}if(e++,e>30)throw"Buttons: Too many iterations"}return a.isArray(c)?c:a.extend({},c)};for(b=g(b);b&&b.extend;){if(!h[b.extend])throw"Cannot extend unknown button type: "+b.extend;var i=g(h[b.extend]);if(a.isArray(i))return i;if(!i)return!1;var j=i.className;b=a.extend({},i,b),j&&b.className!==j&&(b.className=j+" "+b.className);var k=b.postfixButtons;if(k){for(b.buttons||(b.buttons=[]),c=0,e=k.length;e>c;c++)b.buttons.push(k[c]);b.postfixButtons=null}var l=b.prefixButtons;if(l){for(b.buttons||(b.buttons=[]),c=0,e=l.length;e>c;c++)b.buttons.splice(c,0,l[c]);b.prefixButtons=null}b.extend=i.extend}return b}}),i.background=function(b,c,e){e===d&&(e=400),b?a("
").addClass(c).css("display","none").appendTo("body").fadeIn(e):a("body > div."+c).fadeOut(e,function(){a(this).removeClass(c).remove()})},i.instanceSelector=function(b,c){if(!b)return a.map(c,function(a){return a.inst});var d=[],e=a.map(c,function(a){return a.name}),f=function(b){if(a.isArray(b))for(var g=0,h=b.length;h>g;g++)f(b[g]);else if("string"==typeof b)if(-1!==b.indexOf(","))f(b.split(","));else{var i=a.inArray(a.trim(b),e);-1!==i&&d.push(c[i].inst)}else"number"==typeof b&&d.push(c[b].inst)};return f(b),d},i.buttonSelector=function(b,c){for(var e=[],f=function(a,b,c){for(var e,g,h=0,i=b.length;i>h;h++)e=b[h],e&&(g=c!==d?c+h:h+"",a.push({node:e.node,name:e.conf.name,idx:g}),e.buttons&&f(a,e.buttons,g+"-"))},g=function(b,c){var h,i,j=[];f(j,c.s.buttons);var k=a.map(j,function(a){return a.node});if(a.isArray(b)||b instanceof a)for(h=0,i=b.length;i>h;h++)g(b[h],c);else if(null===b||b===d||"*"===b)for(h=0,i=j.length;i>h;h++)e.push({inst:c,node:j[h].node});else if("number"==typeof b)e.push({inst:c,node:c.s.buttons[b].node});else if("string"==typeof b)if(-1!==b.indexOf(",")){var l=b.split(",");for(h=0,i=l.length;i>h;h++)g(a.trim(l[h]),c)}else if(b.match(/^\d+(\-\d+)*$/)){var m=a.map(j,function(a){return a.idx});e.push({inst:c,node:j[a.inArray(b,m)].node})}else if(-1!==b.indexOf(":name")){var n=b.replace(":name","");for(h=0,i=j.length;i>h;h++)j[h].name===n&&e.push({inst:c,node:j[h].node})}else a(k).filter(b).each(function(){e.push({inst:c,node:this})});else if("object"==typeof b&&b.nodeName){var o=a.inArray(b,k);-1!==o&&e.push({inst:c,node:k[o]})}},h=0,i=b.length;i>h;h++){var j=b[h];g(c,j)}return e},i.defaults={buttons:["copy","excel","csv","pdf","print","chart"],name:"main",tabIndex:0,dom:{container:{tag:"div",className:"dt-buttons"},collection:{tag:"div",className:"dt-button-collection"},button:{tag:"a",className:"dt-button",active:"active",disabled:"disabled"},buttonLiner:{tag:"span",className:""}}},i.version="1.4.1",a.extend(h,{collection:{text:function(a){return a.i18n("buttons.collection","Collection")},className:"buttons-collection",action:function(c,d,e,f){var g=e,h=g.offset(),j=a(d.table().container()),k=!1;a("div.dt-button-background").length&&(k=a(".dt-button-collection").offset(),a("body").trigger("click.dtb-collection")),f._collection.addClass(f.collectionLayout).css("display","none").appendTo("body").fadeIn(f.fade);var l=f._collection.css("position");if(k&&"absolute"===l)f._collection.css({top:k.top,left:k.left});else if("absolute"===l){f._collection.css({top:h.top+g.outerHeight(),left:h.left});var m=j.offset().top+j.height(),n=h.top+g.outerHeight()+f._collection.outerHeight(),o=n-m,p=h.top-f._collection.outerHeight(),q=j.offset().top,r=q-p;o>r&&f._collection.css("top",h.top-f._collection.outerHeight()-5);var s=h.left+f._collection.outerWidth(),t=j.offset().left+j.width();s>t&&f._collection.css("left",h.left-(s-t))}else{var u=f._collection.height()/2;u>a(b).height()/2&&(u=a(b).height()/2),f._collection.css("marginTop",-1*u)}f.background&&i.background(!0,f.backgroundClassName,f.fade),setTimeout(function(){a("div.dt-button-background").on("click.dtb-collection",function(){}),a("body").on("click.dtb-collection",function(b){var c=a.fn.addBack?"addBack":"andSelf";a(b.target).parents()[c]().filter(f._collection).length||(f._collection.fadeOut(f.fade,function(){f._collection.detach()}),a("div.dt-button-background").off("click.dtb-collection"),i.background(!1,f.backgroundClassName,f.fade),a("body").off("click.dtb-collection"),d.off("buttons-action.b-internal"))})},10),f.autoClose&&d.on("buttons-action.b-internal",function(){a("div.dt-button-background").click()})},background:!0,collectionLayout:"",backgroundClassName:"dt-button-background",autoClose:!1,fade:400},copy:function(a,b){return h.copyHtml5?"copyHtml5":h.copyFlash&&h.copyFlash.available(a,b)?"copyFlash":void 0},csv:function(a,b){return h.csvHtml5&&h.csvHtml5.available(a,b)?"csvHtml5":h.csvFlash&&h.csvFlash.available(a,b)?"csvFlash":void 0},excel:function(a,b){return h.excelHtml5&&h.excelHtml5.available(a,b)?"excelHtml5":h.excelFlash&&h.excelFlash.available(a,b)?"excelFlash":void 0},pdf:function(a,b){return h.pdfHtml5&&h.pdfHtml5.available(a,b)?"pdfHtml5":h.pdfFlash&&h.pdfFlash.available(a,b)?"pdfFlash":void 0},chart:function(a,b){return h.kline&&h.kline.available(a,b)?"chart":void 0},pageLength:function(b){var c=b.settings()[0].aLengthMenu,d=a.isArray(c[0])?c[0]:c,e=a.isArray(c[0])?c[1]:c,f=function(a){return a.i18n("buttons.pageLength",{"-1":"Show all rows",_:"Show %d rows"},a.page.len())};return{extend:"collection",text:f,className:"buttons-page-length",autoClose:!0,buttons:a.map(d,function(a,b){return{text:e[b],className:"button-page-length",action:function(b,c){c.page.len(a).draw()},init:function(b,c,d){var e=this,f=function(){e.active(b.page.len()===a)};b.on("length.dt"+d.namespace,f),f()},destroy:function(a,b,c){a.off("length.dt"+c.namespace)}}}),init:function(a,b,c){var d=this;a.on("length.dt"+c.namespace,function(){d.text(f(a))})},destroy:function(a,b,c){a.off("length.dt"+c.namespace)}}}}),e.Api.register("buttons()",function(a,b){b===d&&(b=a,a=d),this.selector.buttonGroup=a;var c=this.iterator(!0,"table",function(c){return c._buttons?i.buttonSelector(i.instanceSelector(a,c._buttons),b):void 0},!0);return c._groupSelector=a,c}),e.Api.register("button()",function(a,b){var c=this.buttons(a,b);return c.length>1&&c.splice(1,c.length),c}),e.Api.registerPlural("buttons().active()","button().active()",function(a){return a===d?this.map(function(a){return a.inst.active(a.node)}):this.each(function(b){b.inst.active(b.node,a)})}),e.Api.registerPlural("buttons().action()","button().action()",function(a){return a===d?this.map(function(a){return a.inst.action(a.node)}):this.each(function(b){b.inst.action(b.node,a)})}),e.Api.register(["buttons().enable()","button().enable()"],function(a){return this.each(function(b){b.inst.enable(b.node,a)})}),e.Api.register(["buttons().disable()","button().disable()"],function(){return this.each(function(a){a.inst.disable(a.node)})}),e.Api.registerPlural("buttons().nodes()","button().node()",function(){var b=a();return a(this.each(function(a){b=b.add(a.inst.node(a.node))})),b}),e.Api.registerPlural("buttons().processing()","button().processing()",function(a){return a===d?this.map(function(a){return a.inst.processing(a.node)}):this.each(function(b){b.inst.processing(b.node,a)})}),e.Api.registerPlural("buttons().text()","button().text()",function(a){return a===d?this.map(function(a){return a.inst.text(a.node)}):this.each(function(b){b.inst.text(b.node,a)})}),e.Api.registerPlural("buttons().trigger()","button().trigger()",function(){return this.each(function(a){a.inst.node(a.node).trigger("click")})}),e.Api.registerPlural("buttons().containers()","buttons().container()",function(){var b=a(),c=this._groupSelector;return this.iterator(!0,"table",function(a){if(a._buttons)for(var d=i.instanceSelector(c,a._buttons),e=0,f=d.length;f>e;e++)b=b.add(d[e].container())}),b}),e.Api.register("button().add()",function(a,b){var c=this.context;if(c.length){var d=i.instanceSelector(this._groupSelector,c[0]._buttons);d.length&&d[0].add(b,a)}return this.button(this._groupSelector,a)}),e.Api.register("buttons().destroy()",function(){return this.pluck("inst").unique().each(function(a){a.destroy()}),this}),e.Api.registerPlural("buttons().remove()","buttons().remove()",function(){return this.each(function(a){a.inst.remove(a.node)}),this});var j;e.Api.register("buttons.info()",function(b,c,e){var f=this;return b===!1?(a("#datatables_buttons_info").fadeOut(function(){a(this).remove()}),clearTimeout(j),j=null,this):(j&&clearTimeout(j),a("#datatables_buttons_info").length&&a("#datatables_buttons_info").remove(),b=b?"

"+b+"

":"",a('
').html(b).append(a("
")["string"==typeof c?"html":"append"](c)).css("display","none").appendTo("body").fadeIn(),e!==d&&0!==e&&(j=setTimeout(function(){f.buttons.info(!1)},e)),this)}),e.Api.register("buttons.exportData()",function(a){return this.context.length?p(new e.Api(this.context[0]),a):void 0}),e.Api.register("buttons.exportInfo()",function(a){return a||(a={}),{filename:k(a),title:m(a),messageTop:n(this,a.messageTop||a.message,"top"),messageBottom:n(this,a.messageBottom,"bottom")}});var k=function(b){var c="*"===b.filename&&"*"!==b.title&&b.title!==d?b.title:b.filename;if("function"==typeof c&&(c=c()),c===d||null===c)return null;-1!==c.indexOf("*")&&(c=a.trim(c.replace("*",a("title").text()))),c=c.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g,"");var e=l(b.extension);return e||(e=""),c+e},l=function(a){return null===a||a===d?null:"function"==typeof a?a():a},m=function(b){var c=l(b.title);return null===c?null:-1!==c.indexOf("*")?c.replace("*",a("title").text()||"Exported data"):c},n=function(b,c,d){var e=l(c);if(null===e)return null;var f=a("caption",b.table().container()).eq(0);if("*"===e){var g=f.css("caption-side");return g!==d?null:f.length?f.text():""}return e},o=a("