├── .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 | [](http://badge.fury.io/py/json2xls)
5 | [](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 | [](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 |
--------------------------------------------------------------------------------