Source code for json2xls.json2xls

# !/usr/bin/env python
# -*- coding:utf-8 -*-
import collections
import json
import os
from collections import OrderedDict
from functools import partial

import click
import requests

import xlwt
from xlwt import Workbook


[docs]class Json2Xls(object): """Json2Xls API 接口 :param string xls_filename: 指定需要生成的的excel的文件名 :param string json_data: 指定json数据来源, 可以是一个返回json的url, 也可以是一行json字符串, 也可以是一个包含每行一个json的文本文件 :param string method: 当数据来源为url时,请求url的方法, 默认为get请求 :param dict params: get请求参数,默认为 :py:class:`None` :param dict post_data: post请求参数,默认为 :py:class:`None` :param dict headers: 请求url时的HTTP头信息 (字典、json或文件) :param bool form_encoded: post请求时是否作为表单请求,默认为 :py:class:`False` :param string sheet_name: Excel的sheet名称,默认为 sheet0 :param string title_style: Excel的表头样式,默认为 :py:class:`None` :param function json_dumps: 带ensure_ascii参数的json.dumps(), 默认参数值为 :py:class:`False` :param function json_loads: 带object_pairs_hook参数的json.loads(),默认为保持原始排序 :param bool dumps: 生成excel时对表格内容执行json_dumps,默认为 :py:class:`False` """ def __init__(self, xls_filename, json_data, method='get', params=None, post_data=None, headers=None, form_encoded=False, dumps=False, sheet_name='sheet0', title_style=None): self.json_dumps = partial(json.dumps, ensure_ascii=False) self.json_loads = partial(json.loads, object_pairs_hook=OrderedDict) self.sheet_name = sheet_name self.xls_filename = xls_filename self.json_data = json_data self.method = method self.params = params self.post_data = post_data self.headers = headers self.form_encoded = form_encoded self.dumps = dumps self.__check_file_suffix() self.book = Workbook(encoding='utf-8', style_compression=2) self.sheet = self.book.add_sheet(self.sheet_name) self.start_row = 0 self.title_style = xlwt.easyxf( title_style or 'font: name Arial, bold on;' 'align: vert centre, horiz center;' 'borders: top 1, bottom 1, left 1, right 1;' 'pattern: pattern solid, fore_colour lime;') def __check_file_suffix(self): suffix = self.xls_filename.split('.')[-1] if '.' not in self.xls_filename: self.xls_filename += '.xls' elif suffix != 'xls': raise Exception('filename suffix must be .xls') def __get_json(self): data = None try: data = self.json_loads(self.json_data) except: if os.path.isfile(self.json_data): with open(self.json_data, 'r') as source: try: data = self.json_loads( source.read().decode('utf-8').replace('\n', '')) except: source.seek(0) data = [self.json_loads(line.decode('utf-8')) for line in source] else: if os.path.isfile(self.headers): with open(self.headers) as headers_txt: self.headers = self.json_loads( headers_txt.read().decode('utf-8').replace('\n', '')) elif isinstance(self.headers, basestring): self.headers = self.json_loads(self.headers) try: if self.method.lower() == 'get': resp = requests.get(self.json_data, params=self.params, headers=self.headers) data = resp.json() else: if isinstance(self.post_data, basestring) and os.path.isfile( self.post_data): with open(self.post_data, 'r') as source: self.post_data = self.json_loads( source.read().decode('utf-8').replace('\n', '')) if not self.form_encoded: self.post_data = self.json_dumps(self.post_data) resp = requests.post(self.json_data, data=self.post_data, headers=self.headers) data = resp.json() except Exception as e: print e return data def __fill_title(self, data): '''生成默认title''' data = self.flatten(data) for index, key in enumerate(data.keys()): if self.dumps: key = self.json_dumps(key) try: self.sheet.col(index).width = (len(key) + 1) * 256 except: pass self.sheet.row(self.start_row).write(index, key, self.title_style) self.start_row += 1 def __fill_data(self, data): '''生成默认sheet''' data = self.flatten(data) for index, value in enumerate(data.values()): if self.dumps: value = self.json_dumps(value) self.auto_width(self.start_row, index, value) self.sheet.row(self.start_row).write(index, value) self.start_row += 1
[docs] def auto_width(self, row, col, value): '''单元格宽度自动伸缩 :param int row: 单元格所在行下标 :param int col: 单元格所在列下标 :param int value: 单元格中的内容 ''' try: self.sheet.row(row).height_mismatch = True # self.sheet.row(row).height = 0 width = self.sheet.col(col).width new_width = min((len(value) + 1) * 256, 256 * 50) self.sheet.col(col).width = width \ if width > new_width else new_width except: pass
[docs] def flatten(self, data_dict, parent_key='', sep='.'): '''对套嵌的dict进行flatten处理为单层dict :param dict data_dict: 需要处理的dict数据。 :param str parent_key: 上层字典的key,默认为空字符串。 :param str sep: 套嵌key flatten后的分割符, 默认为“.” 。 ''' out = {} def _flatten(x, parent_key, sep): if isinstance(x, collections.MutableMapping): for a in x: _flatten(x[a], parent_key + a + sep, sep) elif isinstance(x, collections.MutableSequence): i = 0 for a in x: _flatten(a, parent_key + str(i) + sep, sep) i += 1 else: out[str(parent_key[:-1])] = str(x) _flatten(data_dict, parent_key, sep) return OrderedDict(out)
[docs] def make(self, title_callback=None, body_callback=None): '''生成Excel。 :param func title_callback: 自定义生成Execl表头的回调函数。 默认为 :py:class:`None`,即采用默认方法生成 :param func body_callback: 自定义生成Execl内容的回调函数。 默认为 :py:class:`None`,即采用默认方法生成 ''' data = self.__get_json() if not isinstance(data, (dict, list)): raise Exception('The %s is not a valid json format' % type(data)) if isinstance(data, dict): data = [data] if title_callback is not None: title_callback(self, data[0]) else: self.__fill_title(data[0]) if body_callback is not None: for d in data: body_callback(self, d) else: for d in data: self.__fill_data(d) self.book.save(self.xls_filename)
@click.command() @click.argument('xls_filename') @click.argument('json_data') @click.option('--method', '-m', default='get') @click.option('--params', '-p', default=None) @click.option('--post_data', '-d', default=None) @click.option('--headers', '-h', default=None) @click.option('--sheet', '-s', default='sheet0') @click.option('--style', '-S', default=None) @click.option('--form_encoded', '-f', is_flag=True) @click.option('--dumps', '-D', is_flag=True) def make(xls_filename, json_data, method, params, post_data, headers, sheet, style, form_encoded, dumps): Json2Xls(xls_filename, json_data, method=method, params=params, post_data=post_data, headers=headers, form_encoded=form_encoded, dumps=dumps, sheet_name=sheet, title_style=style).make() if __name__ == '__main__': make()