Metadata-Version: 2.4
Name: cloze
Version: 0.11.0
Summary: A python package about automatic report, include automatic docx, automatic xlsx, and business rule sql engine.
Author: cloze
Author-email: yaomsn@live.cn
Keywords: automatic report,automatic form,automatic xlsx,business rule engine,RPA
Platform: any
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: pandas
Requires-Dist: sqlalchemy
Requires-Dist: python-docx
Requires-Dist: openpyxl
Dynamic: author
Dynamic: author-email
Dynamic: description
Dynamic: description-content-type
Dynamic: keywords
Dynamic: platform
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# 基于text模板的文本生成
## text模板
- 在`text模板`中，采用占位符标记出需要替换的内容，占位符标记默认为`{}`，也可以定义为`{{}}`，占位符中的内容遵循`python语法`。
| 占位符内容 | 解释 |
| ---- | ---- |
| {a} | 用变量a的值替换占位符。 |
| {len(data)} | data中的数据条数。 |
| {score_1.a} | 取score_1的a值。 |
| {datetime['year']} | 取datetime的year值，datetime为`dict`或`pandas.Series`。|
| {data.loc[:,['x']]} | 取data的x列所有行，data为`pandas.DataFrame`。 |
| {data.drop('x').idxmax()} | data剔除x后，取最大值的索引，data为`pandas.Series`。 |
| {round(data['x'].sum()/10000,2)} | data的x列求和后除以10000，保留2位小数，data为`pandas.DataFrame`。 |
| {sum([len(v) for k,v in data.items()])} | 取出data中每一项的数据条数求和，data为`dict`或`pandas.DataFrame`。 |
| {data.sum(axis=1).rank(ascending=False).to_frame().astype(int).T} | data按行求和倒序排名后，转成`pandas.DataFrame`，转成`int`型，并转置。 |
| {data[data['x']>40]['y'].value_counts().idxmax()[0] if len(data[data['x']>40])>0 else ''} | 当data['x']>40不为空时，对data['x']>40筛选后的y值进行数量统计，取出数量最多的一个值；否则取空字符串。 |
| {'、'.join(['%s%s%%'%(index,row['x']) for index,row in data.sort_values('x'),ascending=False).iterrows()])} | data按x列倒序排列后，逐行取出index和x列的值拼接成字符串，每个拼接的字符串用'、'隔开。 |
| {'；'.join(['%s%i条（%s）'%(name,len(group),'、'.join(group.apply(lambda x:'%s有%s'%(x['名称'],x['数量']),axis=1))) for name,group in data.groupby('地市')])} | data按'地市'分组后，逐行取出'名称'和'数量'的值拼接成字符串，地市内拼接的字符串用'、'隔开，地市间用';'隔开。 |

## 变量准备
- 变量准备在`python`中完成。
- 需准备好`text模板`占位符内所涉及的变量。

## 文本生成
- 按照模板定义占位符标记。
- 传入`text模板`内容、变量字典，即可完成`基于text模板的文本生成`。
- 程序会返回未被处理的占位符。

```
from cloze.template_text import TemplateText
import pandas

#变量准备
data=pandas.Series({'a':1})

#定义占位符标记
template=TemplateText(pattern='{}')

#基于text模板的文本生成
template(template='这是一个测试{data.a}', #模板
         env={'data':data})              #变量字典

```

# 基于docx模板的文档生成

## docx模板
- 在docx文件中，采用占位符标记出需要替换的内容，参见`基于text模板的文本生成`的`text模板`。
- `docx模板`可支持标题、段落、表格、图片、脚注等不同文档内容。
- 占位符替换文档内容时，不改变格式。
- 图片可自适应页面或单元格的宽度。
- 表格可自适应增加单元格，增加的单元格的格式与占位符所在的单元格保持一致。
- `docx模板`参见`cloze/template.docx`

## 变量准备
- 参见`基于text模板的文本生成`的`变量准备`。

## 文档生成
- 按照模板定义占位符标记。
- 传入模板路径、保存路径、变量字典，即可完成`基于docx模板的文档生成`。
- 插入图片时，先将图片保存为`BytesIO`对象，然后将`BytesIO`对象放到变量字典中。
- 程序会返回未被处理的占位符。

```
import cloze
from cloze.template_docx import TemplateDocx
import pandas
import os

#变量准备
score_1=pandas.Series({'a':1,'b':2})
score_2=pandas.DataFrame([[7,8,9],[10,11,12],[13,14,15]])

data=pandas.DataFrame({"x": range(3),"y": range(3)})

#变量准备 图片
from io import BytesIO
plot_1=BytesIO()
ax = data.y.plot()
fig = ax.get_figure()
fig.savefig(plot_1)

#定义占位符标记；参数recurse表示表格中数据经过一次填充后，是否还对其中可能存在的占位符进行处理。
template=TemplateDocx(pattern='{}',recurse=False)

#基于docx模板的文档生成
template(template_path=f'{os.path.dirname(cloze.__file__)}/example/Template.docx', #模板路径
         save_path='Save.docx',                                            #保存路径
         env={'data':data,                                                 #变量字典
              'score_1':score_1,
              'score_2':score_2,
              'plot_1':plot_1,
              'date_1':'2026-01-01',
              'article_title':'xxxx',
              'org_name':'yyyy',
              'title_1':'zzzz',
              'title_2':'abcdefg',
              'title_3':'hijklmn',
              'para_1':'opqrst'})

```

## 表格
- 文档中的表格有时用以拆分内容，有时用以展示数据。用于拆分内容时，单元格内的占位符与普通段落中的处理方式一致。用以展示数据时，支持多行多列的批量占位，同时可自适应增加行列。
- *一行一列*，单个单元格内的占位符，无特殊要求。
- *多行一列*，变量为多行一列的`pandas.DataFrame`，占位符写在要填充的第一个单元格里，填充内容的格式与占位符保持一致。
- *一行多列*，变量为一行多列的`pandas.DataFrame`，占位符写在要填充的第一个单元格里，填充内容的格式与占位符保持一致。
- *多行多列*，变量为多行多列的`pandas.DataFrame`，占位符写在要填充的左上角第一个单元格里，填充内容的格式与占位符保持一致。
- 当`pandas.DataFrame`变量的行列数超过模板中的表格行/列数时，会自动增加行/列，并进行填充，新增单元格的格式与占位符所在单元格保持一致。

## 图片
- 普通段落中图片的宽度与页面版式的宽度保持一致。
- 表格内图片的宽度与所在单元格的宽度保持一致。
- 如果要控制图片的尺寸和位置，可以文档中插入表格，设置好表格的位置和列宽，将图片占位符放在表格内，即可控制图片在文档中的位置和尺寸。

# 基于xlsx模板的报表生成

## xlsx模板
- 参见`基于docx模板的文档生成`的`docx模板`。不同之处是`xlsx模板`不支持脚注、不支持图片自适应宽度、单元格的格式不与占位符所在的单元格保持一致。
- `xlsx模板`参见`cloze/template.xlsx`

## 变量准备
- 参见基于text模板的文本生成的变量准备。

## 报表生成
- 按照模板定义占位符标记。
- 传入模板路径、保存路径、变量字典，即可完成`基于xlsx模板的报表生成`。
- 插入图片时，先将图片保存为`BytesIO`对象，然后将`BytesIO`对象放到变量字典中。
- 程序会返回未被处理的占位符。

```
import cloze
from cloze.template_xlsx import TemplateXlsx
import pandas
import os

#变量准备
data1=pandas.Series(range(100),index=['a%i'%i for i in range(100)])
data2=pandas.DataFrame({"x": range(10),"y": range(10)})

#变量准备 图片
from io import BytesIO
plot=BytesIO()
data2.y.plot().get_figure().savefig(plot)

#定义占位符标记；参数restore表示是否恢复模板中的excel元素关系；参数recurse表示表格中数据经过一次填充后，是否还对其中可能存在的占位符进行处理。
template=TemplateXlsx(pattern='{}',restore=True,recurse=False)

# 基于xlsx模板的报表生成
template(template_path=f'{os.path.dirname(cloze.__file__)}/example/Template.xlsx', #模板路径
         save_path='Save.xlsx',                                            #保存路径
         env={'data1':data1,                                               #变量字典
              'data2':data2,
              'plot':plot})

```

## 表格
- *多行一列*，变量为多行一列的`pandas.DataFrame`，占位符写在要填充的第一个单元格里，填充内容的格式由所在单元格确定。
- *一行多列*，变量为一行多列的`pandas.DataFrame`，占位符写在要填充的第一个单元格里，填充内容的格式由所在单元格确定。
- *多行多列*，变量为多行多列的`pandas.DataFrame`，占位符写在要填充的左上角第一个单元格里，填充内容的格式由所在单元格确定。

# 基于xlsx与docx模板结合的文档图表生成
文档生成时，经常会遇到采用新数据更新文档中的内容，且对图表的格式有一定要求。此时可先将数据更新到`xlsx模板`中，生成新图表，再插入到docx文档中。
- 第一步，制作`xlsx模板`、`docx模板`，参见`基于xlsx模板的报表生成`的`xlsx模板`、`基于docx模板的文档生成`的`docx模板`，在`xlsx模板`中，将需要插入docx文档的图表放置在打印页面的第一页。
- 第二步，更新`xlsx模板`中的数据，生成更新后的图表。
- 第三步，采用`libreoffice`，将更新后的图表，导出为图片。
- 第四步，将图片截取后保存为`BytesIO`对象，并更新到文档中。

```
import cloze
from cloze.template_xlsx import TemplateXlsx
from cloze.template_docx import TemplateDocx
import pandas
import os

#更新xlsx模板中的数据，生成更新后的图表。
data1=pandas.DataFrame([[19,29,39,49,95],[6,7,8,9,10]],index=['a','b']).T
data2=pandas.DataFrame([[6,7,8,9,10],[1,2,3,4,5]]).T

template=TemplateXlsx()
template(template_path=f'{os.path.dirname(cloze.__file__)}/example/Template.xlsx',
         save_path='Save.xlsx',
         env={'data1':data1,
              'data2':data2})

#采用libreoffice，将更新后的图表，导出为图片。
import subprocess
subprocess.run(["C:\Program_Files\LibreOffice\program\soffice.exe",
                "--headless",
                "--convert-to", "png",
                'Save.xlsx'])

#将图片截取后保存为BytesIO对象，并更新到文档中。
from io import BytesIO
from PIL import Image

plot_1=BytesIO()
image=Image.open('Save.png')
image = image.crop((30,630,880,1080))
image.save(plot_1, format='png')

template=TemplateDocx()
template(template_path=f'{os.path.dirname(cloze.__file__)}/example/Template.docx',
         save_path='Save.docx',
         env={'data1':data1,
              'data2':data2,
              'plot_1':plot_1})
```

# 基于sql语法的pandas.DataFrame查询

- 采用sql语句的方式从`pandas.DataFrame`中查询数据
- sql语法遵循`sqlite`
- 支持单表查询、多表联查

```
from cloze.pandas_query import PandasQuery
import pandas

data1=pandas.DataFrame({"列1": range(100),"y": range(100)})
data2=pandas.DataFrame({"x": range(100),"y": range(100)})

query=PandasQuery()
result=query(query='select data1.*,data2.* from data1,data2 where data1.y=data2.y;', #sql语句
             env={'data1':data1,'data2':data2})                                      #变量词典定义表名

```

# excel报表公式转数值
采用openpyxl对excel中的数据更新后，需要打开excel文件并保存后，公式计算结果才会更新，通过calc_excel可在不打开excel的情况下，将公式转换成计算结果。

```
import cloze
import os

cloze.calc_excel(path=f'{os.path.dirname(cloze.__file__)}/example/Formula.xlsx',
                 save_path='Save.xlsx')

```

# 将excel中的公式读取为数值
- read_excel(path, data_only=True, **kwargs)
    - path, 文件路径
    - data_only=True, 是否转数值, 默认为True; 如果为False, 与pandas.read_excel参数为engine='openpyxl'、engine_kwargs={'data_only':False}效果相同。
    - kwargs, data_only=False时kwargs支持pandas.read_excel的参数, data_only=True时kwargs支持sheet_name、skiprows、header三个参数。
    
```
import cloze
import os

cloze.read_excel(f'{os.path.dirname(cloze.__file__)}/example/Formula.xlsx','text.xlsx')

```
