├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── demo.py ├── docs ├── conf.py ├── index.rst ├── json2xls.rst └── modules.rst ├── json2xls ├── __init__.py └── json2xls.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── .DS_Store ├── callback_data.json ├── headers.json ├── line_data.json ├── list_data.json └── post_data.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # my 57 | *.xls 58 | *.xlsx 59 | .~lock* 60 | .DS_Store 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | language: python 4 | 5 | python: 6 | - "3.4" 7 | - "3.3" 8 | - "2.7" 9 | - "2.6" 10 | - "pypy" 11 | 12 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 13 | install: pip install -r requirements.txt 14 | 15 | # command to run tests, e.g. python setup.py test 16 | script: python setup.py test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, axiaoxin 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.md 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build docs clean 2 | 3 | help: 4 | @echo "clean - remove all build, test, coverage and Python artifacts" 5 | @echo "clean-build - remove build artifacts" 6 | @echo "clean-pyc - remove Python file artifacts" 7 | @echo "clean-test - remove test and coverage artifacts" 8 | @echo "lint - check style with flake8" 9 | @echo "test - run tests quickly with the default Python" 10 | @echo "test-all - run tests on every Python version with tox" 11 | @echo "coverage - check code coverage quickly with the default Python" 12 | @echo "docs - generate Sphinx HTML documentation, including API docs" 13 | @echo "release - package and upload a release" 14 | @echo "dist - package" 15 | 16 | clean: clean-build clean-pyc clean-test 17 | 18 | clean-build: 19 | rm -fr build/ 20 | rm -fr dist/ 21 | rm -fr *.egg-info 22 | 23 | clean-pyc: 24 | find . -name '*.pyc' -exec rm -f {} + 25 | find . -name '*.pyo' -exec rm -f {} + 26 | find . -name '*~' -exec rm -f {} + 27 | find . -name '__pycache__' -exec rm -fr {} + 28 | 29 | clean-test: 30 | rm -fr .tox/ 31 | rm -f .coverage 32 | rm -fr htmlcov/ 33 | 34 | lint: 35 | flake8 json2xls tests 36 | 37 | test: 38 | python setup.py test 39 | 40 | test-all: 41 | tox 42 | 43 | coverage: 44 | coverage run --source json2xls setup.py test 45 | coverage report -m 46 | coverage html 47 | open htmlcov/index.html 48 | 49 | docs: 50 | rm -f docs/json2xls.rst 51 | rm -f docs/modules.rst 52 | sphinx-apidoc -o docs/ json2xls 53 | $(MAKE) -C docs clean 54 | $(MAKE) -C docs html 55 | open docs/_build/html/index.html 56 | 57 | release: clean 58 | python setup.py sdist upload 59 | python setup.py bdist_wheel upload 60 | 61 | dist: clean 62 | python setup.py sdist 63 | python setup.py bdist_wheel 64 | ls -l dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | json2xls:Generate Excel by JSON data 2 | ==================================== 3 | 4 | [![](https://badge.fury.io/py/json2xls.png)](http://badge.fury.io/py/json2xls) 5 | [![](https://pypip.in/d/json2xls/badge.png)](https://pypi.python.org/pypi/json2xls) 6 | 7 | _ ____ _ 8 | (_)___ ___ _ __ |___ \__ _| |___ 9 | | / __|/ _ \| '_ \ __) \ \/ / / __| 10 | | \__ \ (_) | | | |/ __/ > <| \__ \ 11 | _/ |___/\___/|_| |_|_____/_/\_\_|___/ 12 | |__/ 13 | 14 | generate excel by json string or json file or url which return a json 15 | 16 | testing on python2.7 and python3.6.0 17 | 18 | **docs** 19 | 20 | **install** 21 | 22 | pip install json2xls 23 | 24 | or 25 | 26 | python setup.py install 27 | 28 | 29 | ## command usage: 30 | 31 | #### gen xls from json string 32 | 33 | json2xls cmd_str_test.xls '{"a":"a", "b":"b"}' 34 | json2xls cmd_str_test1.xls '[{"a":"a", "b":"b"},{"a":1, "b":2}]' 35 | 36 | #### gen xls from file: whole file is a complete json data 37 | 38 | json2xls cmd_list_test.xls "`cat tests/list_data.json`" 39 | 40 | #### gen xls from file: each line is a json data 41 | 42 | json2xls cmd_line_test.xls tests/line_data.json 43 | 44 | #### gen xls from a url which respond a json 45 | 46 | json2xls cmd_get_test.xls http://httpbin.org/get 47 | json2xls cmd_post_test.xls http://httpbin.org/post -m post -d '"hello json2xls"' -h "{'X-Token': 'bolobolomi'}" 48 | 49 | ## coding usage (python2.7): 50 | 51 | #### gen xls from json string 52 | 53 | #!/usr/bin/env python 54 | #-*- coding:utf-8 -*- 55 | 56 | from json2xls.json2xls import Json2Xls 57 | 58 | json_data = u'''[ 59 | {"姓名": "John", "年龄": 30, "性别": "男"}, 60 | {"姓名": "Alice", "年龄": 18, "性别": "女"} 61 | ]''' 62 | obj = Json2Xls('json_strlist_test.xls', json_data) 63 | obj.make() 64 | 65 | 66 | #### gen xls from a url which respond a json by GET 67 | 68 | params = { 69 | 'location': u'上海', 70 | 'output': 'json', 71 | 'ak': '5slgyqGDENN7Sy7pw29IUvrZ' 72 | } 73 | Json2Xls('url_get_test.xls', 74 | "http://httpbin.org/get", 75 | params=params).make() 76 | 77 | 78 | #### gen xls from a url which respond a json by POST 79 | 80 | post_data = { 81 | 'location': u'上海', 82 | 'output': 'json', 83 | 'ak': '5slgyqGDENN7Sy7pw29IUvrZ' 84 | } 85 | Json2Xls('url_post_test1.xls', 86 | "http://httpbin.org/post", 87 | method='post', 88 | post_data=post_data, 89 | form_encoded=True).make() 90 | 91 | #### gen xls from a url which respond a json by POST with file post data 92 | 93 | post_data = 'tests/post_data.json' 94 | Json2Xls('url_post_test2.xls', 95 | "http://httpbin.org/post", 96 | method='post', 97 | post_data=post_data, 98 | form_encoded=True).make() 99 | 100 | 101 | #### gen xls from file: whole file is a complete json data (从文件内容为一个json列表的文件生成excel) 102 | 103 | Json2Xls('json_list_test.xls', json_data='tests/list_data.json').make() 104 | 105 | #### gen xls from file: each line is a json data (从文件内容为每行一个的json字符串的文件生成excel) 106 | 107 | obj = Json2Xls('json_line_test.xls', json_data='tests/line_data.json') 108 | obj.make() 109 | 110 | #### gen custom excel by define your title and body callback function 111 | 112 | def title_callback(self, data): 113 | '''use one of data record to generate excel title''' 114 | self.sheet.write_merge(0, 0, 0, 3, 'title', self.title_style) 115 | self.sheet.write_merge(1, 2, 0, 0, 'tag', self.title_style) 116 | self.sheet.write_merge(1, 2, 1, 1, 'ner', self.title_style) 117 | self.sheet.write_merge(1, 1, 2, 3, 'comment', self.title_style) 118 | self.sheet.row(2).write(2, 'x', self.title_style) 119 | self.sheet.row(2).write(3, 'y', self.title_style) 120 | 121 | self.sheet.write_merge(0, 0, 4, 7, 'body', self.title_style) 122 | self.sheet.write_merge(1, 2, 4, 4, 'tag', self.title_style) 123 | self.sheet.write_merge(1, 2, 5, 5, 'ner', self.title_style) 124 | self.sheet.write_merge(1, 1, 6, 7, 'comment', self.title_style) 125 | self.sheet.row(2).write(6, 'x', self.title_style) 126 | self.sheet.row(2).write(7, 'y', self.title_style) 127 | 128 | self.start_row += 3 129 | 130 | def body_callback(self, data): 131 | 132 | key1 = ['title', 'body'] 133 | key2 = ['tag', 'ner', 'comment'] 134 | 135 | col = 0 136 | for ii, i in enumerate(key1): 137 | for ij, j in enumerate(key2): 138 | if j != 'comment': 139 | value = ', '.join(data[ii][i][j]) 140 | self.sheet.row(self.start_row).write(col, value) 141 | col += 1 142 | else: 143 | for x in data[ii][i][j].values(): 144 | self.sheet.row(self.start_row).write(col, x) 145 | col += 1 146 | self.start_row += 1 147 | 148 | data = 'tests/callback_data.json' 149 | j = Json2Xls('tests/callback.xls', data) 150 | j.make(title_callback=title_callback, body_callback=body_callback) 151 | 152 | 153 | Default request method is `get`, request argument pass by `params`. 154 | and the `post` method's request argument pass by `data`, you can use `-d` to pass request data in command line, the data should be json or file 155 | 156 | Default only support one layer json to generate the excel, the nested json will be flattened. if you want custom it, 157 | you can write the `title_callback` function and `body_callback` function, the pass them in the `make` function. 158 | for the `body_callback`, you just need to care one line data's write way, json2xls default think the data are all the same. 159 | 160 | The test demo data file is in `tests` dir. and `demo.py` is all coding example to gen xls 161 | 162 | ## Stargazers over time 163 | 164 | [![Stargazers over time](https://starchart.cc/axiaoxin/json2xls.svg)](https://starchart.cc/axiaoxin/json2xls) 165 | 166 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | from json2xls.json2xls import Json2Xls 5 | 6 | json_data = u'''[ 7 | {"姓名": "John", "年龄": 30, "性别": "男"}, 8 | {"姓名": "Alice", "年龄": 18, "性别": "女"} 9 | ]''' 10 | obj = Json2Xls('tests/json_strlist_test.xls', json_data) 11 | obj.make() 12 | 13 | params = { 14 | 'location': u'上海', 15 | 'output': 'json', 16 | 'ak': '5slgyqGDENN7Sy7pw29IUvrZ' 17 | } 18 | Json2Xls( 19 | 'tests/url_get_test.xls', "http://httpbin.org/get", params=params).make() 20 | 21 | obj = Json2Xls('tests/json_list_test.xls', json_data='tests/list_data.json') 22 | obj.make() 23 | 24 | obj = Json2Xls('tests/json_line_test.xls', json_data='tests/line_data.json') 25 | obj.make() 26 | 27 | post_data = { 28 | 'location': u'上海', 29 | 'output': 'json', 30 | 'ak': '5slgyqGDENN7Sy7pw29IUvrZ' 31 | } 32 | Json2Xls( 33 | 'tests/url_post_test1.xls', 34 | "http://httpbin.org/post", 35 | method='post', 36 | post_data=post_data, 37 | form_encoded=True).make() 38 | 39 | post_data = 'tests/post_data.json' 40 | Json2Xls( 41 | 'tests/url_post_test2.xls', 42 | "http://httpbin.org/post", 43 | method='post', 44 | post_data=post_data, 45 | form_encoded=True).make() 46 | 47 | 48 | def title_callback(self, data): 49 | '''use one of data record to generate excel title''' 50 | self.sheet.write_merge(0, 0, 0, 3, 'title', self.title_style) 51 | self.sheet.write_merge(1, 2, 0, 0, 'tag', self.title_style) 52 | self.sheet.write_merge(1, 2, 1, 1, 'ner', self.title_style) 53 | self.sheet.write_merge(1, 1, 2, 3, 'comment', self.title_style) 54 | self.sheet.row(2).write(2, 'x', self.title_style) 55 | self.sheet.row(2).write(3, 'y', self.title_style) 56 | 57 | self.sheet.write_merge(0, 0, 4, 7, 'body', self.title_style) 58 | self.sheet.write_merge(1, 2, 4, 4, 'tag', self.title_style) 59 | self.sheet.write_merge(1, 2, 5, 5, 'ner', self.title_style) 60 | self.sheet.write_merge(1, 1, 6, 7, 'comment', self.title_style) 61 | self.sheet.row(2).write(6, 'x', self.title_style) 62 | self.sheet.row(2).write(7, 'y', self.title_style) 63 | 64 | self.start_row += 3 65 | 66 | 67 | def body_callback(self, data): 68 | 69 | key1 = ['title', 'body'] 70 | key2 = ['tag', 'ner', 'comment'] 71 | 72 | col = 0 73 | for ii, i in enumerate(key1): 74 | for ij, j in enumerate(key2): 75 | if j != 'comment': 76 | value = ', '.join(data[ii][i][j]) 77 | self.sheet.row(self.start_row).write(col, value) 78 | col += 1 79 | else: 80 | for x in data[ii][i][j].values(): 81 | self.sheet.row(self.start_row).write(col, x) 82 | col += 1 83 | self.start_row += 1 84 | 85 | 86 | data = 'tests/callback_data.json' 87 | j = Json2Xls('tests/callback.xls', data) 88 | j.make(title_callback=title_callback, body_callback=body_callback) 89 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # complexity documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Jul 9 22:26:36 2013. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import sphinx_rtd_theme 19 | 20 | # If extensions (or modules to document with autodoc) are in another 21 | # directory, add these directories to sys.path here. If the directory is 22 | # relative to the documentation root, use os.path.abspath to make it 23 | # absolute, like shown here. 24 | #sys.path.insert(0, os.path.abspath('.')) 25 | 26 | # Get the project root dir, which is the parent dir of this 27 | cwd = os.getcwd() 28 | project_root = os.path.dirname(cwd) 29 | 30 | # Insert the project root dir as the first element in the PYTHONPATH. 31 | # This lets us ensure that the source package is imported, and that its 32 | # version is used. 33 | sys.path.insert(0, project_root) 34 | 35 | import json2xls 36 | 37 | # -- General configuration --------------------------------------------- 38 | 39 | # If your documentation needs a minimal Sphinx version, state it here. 40 | #needs_sphinx = '1.0' 41 | 42 | # Add any Sphinx extension module names here, as strings. They can be 43 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 44 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix of source filenames. 50 | source_suffix = '.rst' 51 | 52 | # The encoding of source files. 53 | #source_encoding = 'utf-8-sig' 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # General information about the project. 59 | project = u'json2xls' 60 | copyright = u'2014, axiaoxin' 61 | 62 | # The version info for the project you're documenting, acts as replacement 63 | # for |version| and |release|, also used in various other places throughout 64 | # the built documents. 65 | # 66 | # The short X.Y version. 67 | version = json2xls.__version__ 68 | # The full version, including alpha/beta/rc tags. 69 | release = json2xls.__version__ 70 | 71 | # The language for content autogenerated by Sphinx. Refer to documentation 72 | # for a list of supported languages. 73 | #language = None 74 | 75 | # There are two options for replacing |today|: either, you set today to 76 | # some non-false value, then it is used: 77 | #today = '' 78 | # Else, today_fmt is used as the format for a strftime call. 79 | #today_fmt = '%B %d, %Y' 80 | 81 | # List of patterns, relative to source directory, that match files and 82 | # directories to ignore when looking for source files. 83 | exclude_patterns = ['_build'] 84 | 85 | # The reST default role (used for this markup: `text`) to use for all 86 | # documents. 87 | #default_role = None 88 | 89 | # If true, '()' will be appended to :func: etc. cross-reference text. 90 | #add_function_parentheses = True 91 | 92 | # If true, the current module name will be prepended to all description 93 | # unit titles (such as .. function::). 94 | #add_module_names = True 95 | 96 | # If true, sectionauthor and moduleauthor directives will be shown in the 97 | # output. They are ignored by default. 98 | #show_authors = False 99 | 100 | # The name of the Pygments (syntax highlighting) style to use. 101 | pygments_style = 'sphinx' 102 | 103 | # A list of ignored prefixes for module index sorting. 104 | #modindex_common_prefix = [] 105 | 106 | # If true, keep warnings as "system message" paragraphs in the built 107 | # documents. 108 | #keep_warnings = False 109 | 110 | 111 | # -- Options for HTML output ------------------------------------------- 112 | 113 | # The theme to use for HTML and HTML Help pages. See the documentation for 114 | # a list of builtin themes. 115 | html_theme = 'sphinx_rtd_theme' 116 | 117 | # Theme options are theme-specific and customize the look and feel of a 118 | # theme further. For a list of options available for each theme, see the 119 | # documentation. 120 | #html_theme_options = {} 121 | 122 | # Add any paths that contain custom themes here, relative to this directory. 123 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 124 | 125 | # The name for this set of Sphinx documents. If None, it defaults to 126 | # " v documentation". 127 | #html_title = None 128 | 129 | # A shorter title for the navigation bar. Default is the same as 130 | # html_title. 131 | #html_short_title = None 132 | 133 | # The name of an image file (relative to this directory) to place at the 134 | # top of the sidebar. 135 | #html_logo = None 136 | 137 | # The name of an image file (within the static path) to use as favicon 138 | # of the docs. This file should be a Windows icon file (.ico) being 139 | # 16x16 or 32x32 pixels large. 140 | #html_favicon = None 141 | 142 | # Add any paths that contain custom static files (such as style sheets) 143 | # here, relative to this directory. They are copied after the builtin 144 | # static files, so a file named "default.css" will overwrite the builtin 145 | # "default.css". 146 | html_static_path = ['_static'] 147 | 148 | # If not '', a 'Last updated on:' timestamp is inserted at every page 149 | # bottom, using the given strftime format. 150 | #html_last_updated_fmt = '%b %d, %Y' 151 | 152 | # If true, SmartyPants will be used to convert quotes and dashes to 153 | # typographically correct entities. 154 | #html_use_smartypants = True 155 | 156 | # Custom sidebar templates, maps document names to template names. 157 | #html_sidebars = {} 158 | 159 | # Additional templates that should be rendered to pages, maps page names 160 | # to template names. 161 | #html_additional_pages = {} 162 | 163 | # If false, no module index is generated. 164 | #html_domain_indices = True 165 | 166 | # If false, no index is generated. 167 | #html_use_index = True 168 | 169 | # If true, the index is split into individual pages for each letter. 170 | #html_split_index = False 171 | 172 | # If true, links to the reST sources are added to the pages. 173 | #html_show_sourcelink = True 174 | 175 | # If true, "Created using Sphinx" is shown in the HTML footer. 176 | # Default is True. 177 | #html_show_sphinx = True 178 | 179 | # If true, "(C) Copyright ..." is shown in the HTML footer. 180 | # Default is True. 181 | #html_show_copyright = True 182 | 183 | # If true, an OpenSearch description file will be output, and all pages 184 | # will contain a tag referring to it. The value of this option 185 | # must be the base URL from which the finished HTML is served. 186 | #html_use_opensearch = '' 187 | 188 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 189 | #html_file_suffix = None 190 | 191 | # Output file base name for HTML help builder. 192 | htmlhelp_basename = 'json2xlsdoc' 193 | 194 | 195 | # -- Options for LaTeX output ------------------------------------------ 196 | 197 | latex_elements = { 198 | # The paper size ('letterpaper' or 'a4paper'). 199 | #'papersize': 'letterpaper', 200 | 201 | # The font size ('10pt', '11pt' or '12pt'). 202 | #'pointsize': '10pt', 203 | 204 | # Additional stuff for the LaTeX preamble. 205 | #'preamble': '', 206 | } 207 | 208 | # Grouping the document tree into LaTeX files. List of tuples 209 | # (source start file, target name, title, author, documentclass 210 | # [howto/manual]). 211 | latex_documents = [ 212 | ('index', 'json2xls.tex', 213 | u'json2xls Documentation', 214 | u'axiaoxin', 'manual'), 215 | ] 216 | 217 | # The name of an image file (relative to this directory) to place at 218 | # the top of the title page. 219 | #latex_logo = None 220 | 221 | # For "manual" documents, if this is true, then toplevel headings 222 | # are parts, not chapters. 223 | #latex_use_parts = False 224 | 225 | # If true, show page references after internal links. 226 | #latex_show_pagerefs = False 227 | 228 | # If true, show URL addresses after external links. 229 | #latex_show_urls = False 230 | 231 | # Documents to append as an appendix to all manuals. 232 | #latex_appendices = [] 233 | 234 | # If false, no module index is generated. 235 | #latex_domain_indices = True 236 | 237 | 238 | # -- Options for manual page output ------------------------------------ 239 | 240 | # One entry per manual page. List of tuples 241 | # (source start file, name, description, authors, manual section). 242 | man_pages = [ 243 | ('index', 'json2xls', 244 | u'json2xls Documentation', 245 | [u'axiaoxin'], 1) 246 | ] 247 | 248 | # If true, show URL addresses after external links. 249 | #man_show_urls = False 250 | 251 | 252 | # -- Options for Texinfo output ---------------------------------------- 253 | 254 | # Grouping the document tree into Texinfo files. List of tuples 255 | # (source start file, target name, title, author, 256 | # dir menu entry, description, category) 257 | texinfo_documents = [ 258 | ('index', 'json2xls', 259 | u'json2xls Documentation', 260 | u'axiaoxin', 261 | 'json2xls', 262 | 'One line description of project.', 263 | 'Miscellaneous'), 264 | ] 265 | 266 | # Documents to append as an appendix to all manuals. 267 | #texinfo_appendices = [] 268 | 269 | # If false, no module index is generated. 270 | #texinfo_domain_indices = True 271 | 272 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 273 | #texinfo_show_urls = 'footnote' 274 | 275 | # If true, do not generate a @detailmenu in the "Top" node's menu. 276 | #texinfo_no_detailmenu = False 277 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: json2xls 2 | 3 | API 4 | --- 5 | 6 | .. autoclass:: json2xls.Json2Xls 7 | :members: 8 | :member-order: bysource 9 | -------------------------------------------------------------------------------- /docs/json2xls.rst: -------------------------------------------------------------------------------- 1 | json2xls package 2 | ================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | json2xls.json2xls module 8 | ------------------------ 9 | 10 | .. automodule:: json2xls.json2xls 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: json2xls 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | json2xls 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | json2xls 8 | -------------------------------------------------------------------------------- /json2xls/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | """ 4 | json2xls 5 | =========== 6 | 7 | 根据json数据生成excel表格,默认支持生成单层json的Excel,多层json会默认被转化为单层,也可以自定义多层样式的生成方法。 8 | 9 | json数据来源可以是一个返回json的url,也可以是一行json字符串,也可以是一个包含每行一个json的文本文件 10 | 11 | 安装 12 | ---- 13 | 14 | :py:mod:`json2xls` 代码托管在 `GitHub`_,并且已经发布到 `PyPI`_,可以直接通过 `pip` 安装:: 15 | 16 | $ pip install json2xls 17 | 18 | 源码安装:: 19 | 20 | $ python setup.py install 21 | 22 | :py:mod:`json2xls` 以 MIT 协议发布。 23 | 24 | .. _GitHub: https://github.com/axiaoxin/json2xls 25 | .. _PyPI: https://pypi.python.org/pypi/json2xls 26 | 27 | 使用教程 28 | -------- 29 | 30 | API调用:: 31 | 32 | from json2xls import Json2Xls 33 | 34 | # 从json字符串生成excel 35 | json_data = u'''[ 36 | {"姓名": "John", "年龄": 30, "性别": "男"}, 37 | {"姓名": "Alice", "年龄": 18, "性别": "女"} 38 | ]''' 39 | obj = Json2Xls('tests/json_strlist_test.xls', json_data) 40 | obj.make() 41 | 42 | 43 | # 从get请求返回的json生成excel 44 | params = { 45 | 'location': u'上海', 46 | 'output': 'json', 47 | 'ak': '5slgyqGDENN7Sy7pw29IUvrZ' 48 | } 49 | Json2Xls('tests/url_get_test.xls', "http://httpbin.org/get", params=params).make() 50 | 51 | 52 | # 从post请求返回的json生成excel 53 | post_data = { 54 | 'location': u'上海', 55 | 'output': 'json', 56 | 'ak': '5slgyqGDENN7Sy7pw29IUvrZ' 57 | } 58 | Json2Xls('tests/url_post_test1.xls', "http://httpbin.org/post", method='post', post_data=post_data, form_encoded=True).make() 59 | # 如果post_data很复杂很长可以写到一个文件里 60 | post_data = 'tests/post_data.json' 61 | Json2Xls('tests/url_post_test2.xls', "http://httpbin.org/post", method='post', post_data=post_data, form_encoded=True).make() 62 | 63 | 64 | # 从文件内容为每行一个的json字符串的文件生成excel 65 | obj = Json2Xls('tests/json_line_test.xls', json_data='tests/line_data.json') 66 | obj.make() 67 | # 从文件内容为一个json列表的文件生成excel 68 | Json2Xls('tests/json_list_test.xls', json_data='tests/list_data.json').make() 69 | 70 | # 自定义生成excel 71 | def title_callback(self, data): 72 | '''use one of data record to generate excel title''' 73 | self.sheet.write_merge(0, 0, 0, 3, 'title', self.title_style) 74 | self.sheet.write_merge(1, 2, 0, 0, 'tag', self.title_style) 75 | self.sheet.write_merge(1, 2, 1, 1, 'ner', self.title_style) 76 | self.sheet.write_merge(1, 1, 2, 3, 'comment', self.title_style) 77 | self.sheet.row(2).write(2, 'x', self.title_style) 78 | self.sheet.row(2).write(3, 'y', self.title_style) 79 | 80 | self.sheet.write_merge(0, 0, 4, 7, 'body', self.title_style) 81 | self.sheet.write_merge(1, 2, 4, 4, 'tag', self.title_style) 82 | self.sheet.write_merge(1, 2, 5, 5, 'ner', self.title_style) 83 | self.sheet.write_merge(1, 1, 6, 7, 'comment', self.title_style) 84 | self.sheet.row(2).write(6, 'x', self.title_style) 85 | self.sheet.row(2).write(7, 'y', self.title_style) 86 | 87 | self.start_row += 3 88 | 89 | def body_callback(self, data): 90 | 91 | key1 = ['title', 'body'] 92 | key2 = ['tag', 'ner', 'comment'] 93 | 94 | col = 0 95 | for ii, i in enumerate(key1): 96 | for ij, j in enumerate(key2): 97 | if j != 'comment': 98 | value = ', '.join(data[ii][i][j]) 99 | self.sheet.row(self.start_row).write(col, value) 100 | col += 1 101 | else: 102 | for x in data[ii][i][j].values(): 103 | self.sheet.row(self.start_row).write(col, x) 104 | col += 1 105 | self.start_row += 1 106 | 107 | data = 'tests/callback_data.json' 108 | j = Json2Xls('tests/callback.xls', data) 109 | j.make(title_callback=title_callback, body_callback=body_callback) 110 | 111 | 命令行:: 112 | 113 | # from json string 114 | json2xls tests/cmd_str_test.xls '{"a":"a", "b":"b"}' 115 | json2xls tests/cmd_str_test1.xls '[{"a":"a", "b":"b"},{"a":1, "b":2}]' 116 | 117 | # from file: whole file is a complete json data 118 | json2xls tests/cmd_list_test.xls "`cat tests/list_data.json`" 119 | 120 | # from file: each line is a json data 121 | json2xls tests/cmd_line_test.xls tests/line_data.json 122 | 123 | # from url 124 | json2xls tests/cmd_get_test.xls http://httpbin.org/get 125 | json2xls tests/cmd_post_test.xls http://httpbin.org/post -m post -d '"hello json2xls"' -h "{'X-Token': 'bolobolomi'}" 126 | 127 | """ 128 | 129 | __author__ = 'axiaoxin' 130 | __email__ = '254606826@qq.com' 131 | __version__ = '1.0.0' 132 | 133 | from .json2xls import Json2Xls 134 | -------------------------------------------------------------------------------- /json2xls/json2xls.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | import collections 4 | import json 5 | import os 6 | from collections import OrderedDict 7 | from functools import partial 8 | 9 | import click 10 | import requests 11 | import xlwt 12 | from xlwt import Workbook 13 | 14 | 15 | class Json2Xls(object): 16 | """Json2Xls API 接口 17 | 18 | :param string xls_filename: 指定需要生成的的excel的文件名 19 | 20 | :param string json_data: 指定json数据来源, 21 | 可以是一个返回json的url, 22 | 也可以是一行json字符串, 23 | 也可以是一个包含每行一个json的文本文件 24 | 25 | :param string method: 当数据来源为url时,请求url的方法, 26 | 默认为get请求 27 | 28 | :param dict params: get请求参数,默认为 :py:class:`None` 29 | 30 | :param dict post_data: post请求参数,默认为 :py:class:`None` 31 | 32 | :param dict headers: 请求url时的HTTP头信息 (字典、json或文件) 33 | 34 | :param bool form_encoded: post请求时是否作为表单请求,默认为 :py:class:`False` 35 | 36 | :param string sheet_name: Excel的sheet名称,默认为 sheet0 37 | 38 | :param string title_style: Excel的表头样式,默认为 :py:class:`None` 39 | 40 | :param function json_dumps: 带ensure_ascii参数的json.dumps(), 41 | 默认参数值为 :py:class:`False` 42 | 43 | :param function json_loads: 带object_pairs_hook参数的json.loads(),默认为保持原始排序 44 | 45 | :param bool dumps: 生成excel时对表格内容执行json_dumps,默认为 :py:class:`False` 46 | """ 47 | 48 | def __init__(self, 49 | xls_filename, 50 | json_data, 51 | method='get', 52 | params=None, 53 | post_data=None, 54 | headers=None, 55 | form_encoded=False, 56 | dumps=False, 57 | sheet_name='sheet0', 58 | title_style=None): 59 | self.json_dumps = partial(json.dumps, ensure_ascii=False) 60 | self.json_loads = partial(json.loads, object_pairs_hook=OrderedDict) 61 | 62 | self.sheet_name = sheet_name 63 | self.xls_filename = xls_filename 64 | self.json_data = json_data 65 | self.method = method 66 | self.params = params 67 | self.post_data = post_data 68 | self.headers = headers 69 | self.form_encoded = form_encoded 70 | self.dumps = dumps 71 | 72 | self.__check_file_suffix() 73 | 74 | self.book = Workbook(encoding='utf-8', style_compression=2) 75 | self.sheet = self.book.add_sheet(self.sheet_name) 76 | 77 | self.start_row = 0 78 | 79 | self.title_style = xlwt.easyxf( 80 | title_style or 'font: name Arial, bold on;' 81 | 'align: vert centre, horiz center;' 82 | 'borders: top 1, bottom 1, left 1, right 1;' 83 | 'pattern: pattern solid, fore_colour lime;') 84 | 85 | def __check_file_suffix(self): 86 | suffix = self.xls_filename.split('.')[-1] 87 | if '.' not in self.xls_filename: 88 | self.xls_filename += '.xls' 89 | elif suffix != 'xls': 90 | raise Exception('filename suffix must be .xls') 91 | 92 | def __get_json(self): 93 | data = None 94 | try: 95 | data = self.json_loads(self.json_data) 96 | except Exception: 97 | if os.path.isfile(self.json_data): 98 | with open(self.json_data, 'r') as source: 99 | try: 100 | data = self.json_loads(source.read().replace('\n', '')) 101 | except Exception: 102 | source.seek(0) 103 | data = [self.json_loads(line) for line in source] 104 | else: 105 | if self.headers and os.path.isfile(self.headers): 106 | with open(self.headers) as headers_txt: 107 | self.headers = self.json_loads( 108 | headers_txt.read().decode('utf-8').replace( 109 | '\n', '')) 110 | elif isinstance(self.headers, ("".__class__, u"".__class__)): 111 | self.headers = self.json_loads(self.headers) 112 | try: 113 | if self.method.lower() == 'get': 114 | resp = requests.get( 115 | self.json_data, 116 | params=self.params, 117 | headers=self.headers) 118 | data = resp.json() 119 | else: 120 | if isinstance( 121 | self.post_data, 122 | ("".__class__, u"".__class__)) and os.path.isfile( 123 | self.post_data): 124 | with open(self.post_data, 'r') as source: 125 | self.post_data = self.json_loads( 126 | source.read().replace( 127 | '\n', '')) 128 | if not self.form_encoded: 129 | self.post_data = self.json_dumps(self.post_data) 130 | resp = requests.post( 131 | self.json_data, 132 | data=self.post_data, 133 | headers=self.headers) 134 | data = resp.json() 135 | except Exception as e: 136 | print(e) 137 | return data 138 | 139 | def __fill_title(self, data): 140 | '''生成默认title''' 141 | data = self.flatten(data) 142 | for index, key in enumerate(data.keys()): 143 | if self.dumps: 144 | key = self.json_dumps(key) 145 | try: 146 | self.sheet.col(index).width = (len(key) + 1) * 256 147 | except Exception: 148 | pass 149 | self.sheet.row(self.start_row).write(index, key.decode('utf-8'), 150 | self.title_style) 151 | self.start_row += 1 152 | 153 | def __fill_data(self, data): 154 | '''生成默认sheet''' 155 | data = self.flatten(data) 156 | for index, value in enumerate(data.values()): 157 | if self.dumps: 158 | value = self.json_dumps(value) 159 | self.auto_width(self.start_row, index, value) 160 | self.sheet.row(self.start_row).write(index, value) 161 | 162 | self.start_row += 1 163 | 164 | def auto_width(self, row, col, value): 165 | '''单元格宽度自动伸缩 166 | 167 | :param int row: 单元格所在行下标 168 | 169 | :param int col: 单元格所在列下标 170 | 171 | :param int value: 单元格中的内容 172 | ''' 173 | 174 | try: 175 | self.sheet.row(row).height_mismatch = True 176 | # self.sheet.row(row).height = 0 177 | width = self.sheet.col(col).width 178 | new_width = min((len(value) + 1) * 256, 256 * 50) 179 | self.sheet.col(col).width = width \ 180 | if width > new_width else new_width 181 | except Exception: 182 | pass 183 | 184 | def flatten(self, data_dict, parent_key='', sep='.'): 185 | '''对套嵌的dict进行flatten处理为单层dict 186 | 187 | :param dict data_dict: 需要处理的dict数据。 188 | 189 | :param str parent_key: 上层字典的key,默认为空字符串。 190 | 191 | :param str sep: 套嵌key flatten后的分割符, 默认为“.” 。 192 | ''' 193 | 194 | out = {} 195 | 196 | def _flatten(x, parent_key, sep): 197 | if isinstance(x, collections.MutableMapping): 198 | for a in x: 199 | _flatten(x[a], parent_key + a + sep, sep) 200 | elif isinstance(x, collections.MutableSequence): 201 | i = 0 202 | for a in x: 203 | _flatten(a, parent_key + str(i) + sep, sep) 204 | i += 1 205 | else: 206 | if not isinstance(x, ("".__class__, u"".__class__)): 207 | x = str(x) 208 | out[parent_key[:-1].encode('utf-8')] = x 209 | 210 | _flatten(data_dict, parent_key, sep) 211 | return OrderedDict(out) 212 | 213 | def make(self, title_callback=None, body_callback=None): 214 | '''生成Excel。 215 | 216 | :param func title_callback: 自定义生成Execl表头的回调函数。 217 | 默认为 :py:class:`None`,即采用默认方法生成 218 | 219 | :param func body_callback: 自定义生成Execl内容的回调函数。 220 | 默认为 :py:class:`None`,即采用默认方法生成 221 | ''' 222 | 223 | data = self.__get_json() 224 | if not isinstance(data, (dict, list)): 225 | raise Exception('The %s is not a valid json format' % type(data)) 226 | if isinstance(data, dict): 227 | data = [data] 228 | 229 | if title_callback is not None: 230 | title_callback(self, data[0]) 231 | else: 232 | self.__fill_title(data[0]) 233 | 234 | if body_callback is not None: 235 | for d in data: 236 | body_callback(self, d) 237 | else: 238 | for d in data: 239 | self.__fill_data(d) 240 | 241 | self.book.save(self.xls_filename) 242 | 243 | 244 | @click.command() 245 | @click.argument('xls_filename') 246 | @click.argument('json_data') 247 | @click.option('--method', '-m', default='get') 248 | @click.option('--params', '-p', default=None) 249 | @click.option('--post_data', '-d', default=None) 250 | @click.option('--headers', '-h', default=None) 251 | @click.option('--sheet', '-s', default='sheet0') 252 | @click.option('--style', '-S', default=None) 253 | @click.option('--form_encoded', '-f', is_flag=True) 254 | @click.option('--dumps', '-D', is_flag=True) 255 | def make(xls_filename, json_data, method, params, post_data, headers, sheet, 256 | style, form_encoded, dumps): 257 | Json2Xls( 258 | xls_filename, 259 | json_data, 260 | method=method, 261 | params=params, 262 | post_data=post_data, 263 | headers=headers, 264 | form_encoded=form_encoded, 265 | dumps=dumps, 266 | sheet_name=sheet, 267 | title_style=style).make() 268 | 269 | 270 | if __name__ == '__main__': 271 | make() 272 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | requests 3 | sphinx-rtd-theme 4 | xlwt 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import re 5 | 6 | try: 7 | from setuptools import setup 8 | except ImportError: 9 | from distutils.core import setup 10 | 11 | readme = 'https://github.com/axiaoxin/json2xls/blob/master/README.md' 12 | 13 | with open(os.path.join(os.path.dirname(__file__), 14 | 'json2xls/__init__.py')) as f: 15 | version = re.search(r'__version__ = \'(.*?)\'', f.read()).group(1) 16 | 17 | requirements = ["click", "requests", "xlwt"] 18 | 19 | test_requirements = [ 20 | # TODO: put package test requirements here 21 | ] 22 | 23 | setup( 24 | name='json2xls', 25 | version=version, 26 | description='generate excel by json', 27 | long_description=readme, 28 | author='axiaoxin', 29 | author_email='254606826@qq.com', 30 | url='https://github.com/axiaoxin/json2xls', 31 | packages=[ 32 | 'json2xls', 33 | ], 34 | package_dir={'json2xls': 'json2xls'}, 35 | include_package_data=True, 36 | install_requires=requirements, 37 | license="BSD", 38 | zip_safe=False, 39 | keywords='json2xls', 40 | classifiers=[ 41 | 'Development Status :: 2 - Pre-Alpha', 42 | 'Intended Audience :: Developers', 43 | 'License :: OSI Approved :: BSD License', 44 | 'Natural Language :: English', 45 | 'Programming Language :: Python :: 2.7', 46 | 'Programming Language :: Python :: 3.6', 47 | ], 48 | test_suite='tests', 49 | tests_require=test_requirements, 50 | entry_points={'console_scripts': ['json2xls = json2xls.json2xls:make']}) 51 | -------------------------------------------------------------------------------- /tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axiaoxin/json2xls/fefce7b6ae16bd7d93796a9bd22cab77156a7500/tests/.DS_Store -------------------------------------------------------------------------------- /tests/callback_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | { 4 | "title": 5 | { 6 | "tag": ["title_tag1", "title_tag2"], 7 | "ner": ["title_ner1", "title_ner2"], 8 | "comment": { "good": "100", "bad": "20"} 9 | } 10 | }, 11 | { 12 | "body": 13 | { 14 | "tag": ["body_tag1", "body_tag2"], 15 | "ner": ["body_ner1", "body_ner2"], 16 | "comment": { "good": "85", "bad": "60"} 17 | } 18 | } 19 | ], 20 | [ 21 | { 22 | "title": 23 | { 24 | "tag": ["1title_tag1", "1title_tag2"], 25 | "ner": ["1title_ner1", "1title_ner2"], 26 | "comment": { "good": "1100", "bad": "120"} 27 | } 28 | }, 29 | { 30 | "body": 31 | { 32 | "tag": ["1body_tag1", "1body_tag2"], 33 | "ner": ["1body_ner1", "1body_ner2"], 34 | "comment": { "good": "185", "bad": "160"} 35 | } 36 | } 37 | ] 38 | ] 39 | -------------------------------------------------------------------------------- /tests/headers.json: -------------------------------------------------------------------------------- 1 | {"X-Token": "bolobolomi"} 2 | -------------------------------------------------------------------------------- /tests/line_data.json: -------------------------------------------------------------------------------- 1 | {"a":"a", "b":"b"} 2 | {"a":"1", "b":"2"} 3 | -------------------------------------------------------------------------------- /tests/list_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "a": "a", 4 | "b":"b" 5 | }, 6 | { 7 | "a":"1", 8 | "b":"2" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /tests/post_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "location": "上海", 3 | "output": "json", 4 | "ak": "5slgyqGDENN7Sy7pw29IUvrZ" 5 | } 6 | 7 | --------------------------------------------------------------------------------