Skip to content

Commit ba95c34

Browse files
Merge pull request #39 from Probesys/calc
* Prepartion to upgrade to debian 13 and library upgrades * Add usage of Calc (ods, xlsx, ..) in lotemplate Add the possibility to launch multiple libreoffice and to cleanly loadbalance between them (see doc) * now not dic for json * create unittest for calc * refactor code for starting multi office and select random connexion * readd restart in case libreoffice died unexpectedly * add exception for tmpfile * fix import of function problem * ADD possibity of looping on table * ADD handle of deprecetion of dict in json * Correction of starting libreoffice * Add calc tests * now use jsondiff to compare json + bug correction in Calc Statement * add cache system for json plus unitest for it * refactor code for cache system * add ruff linter in CI * fixed code that was not closing the document when necessary and add function in cli and api for stat and clean wrong open lo doc * fix doc cli parameters * bugfix in the API when missing key in json * MAXTIME default value * fix bugs on error messages with the API * Change the syntax of the named ranges : loop_down_xxx * add usebruno tool * fix tests after renaming table_r/d by loop_down/right * Documentation * fix ruff --------- Co-authored-by: Philippe Le Van and Cyr
2 parents 4e1b211 + a178a1c commit ba95c34

File tree

73 files changed

+3171
-1000
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+3171
-1000
lines changed

.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/usebruno
2+
/venv
3+
/uploads
4+
/exports
5+
/.vscode
6+
/.local
7+
/.github
8+
/.config
9+
/.cache

.env

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
SECRET_KEY=DEFAULT_KEY
1+
NB_WORKERS=6
2+
MAXTIME=120
3+
SECRET_KEY=DEFAULT_KEY

.github/workflows/ruff.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: Ruff
2+
on: [ push, pull_request ]
3+
jobs:
4+
ruff:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v4
8+
- uses: astral-sh/ruff-action@v3

.github/workflows/unittest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- name: Checkout
11-
uses: actions/checkout@v3
11+
uses: actions/checkout@v4
1212
with:
1313
fetch-depth: 0
1414

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
__pycache__
22
.bash_history
3+
*.swp
4+
.viminfo
35
/.cache/
46
/.config/
57
.local
@@ -10,16 +12,21 @@ uploads/
1012
.fleet/
1113
.vscode/
1214
.~lock.*#
15+
.python_history
1316
exports/
1417
/config*
1518
/lotemplate/unittest/files/content/*.unittest.txt
1619
/lotemplate/unittest/files/content/*.unittest.odt
20+
/lotemplate/unittest/files/content/*.unittest.html
1721
/lotemplate/unittest/files/content/debug.docx
1822
/lotemplate/unittest/files/content/debug.odt
1923
/lotemplate/unittest/files/content/debug.json
2024
/lotemplate/unittest/files/content/*.unittest.odt
2125
/lotemplate/unittest/files/content/*.unittest.pdf
2226
/venv
27+
/lotemplate/unittest/files/content/e89fbedb61af3994184da3e5340bd9e9-calc_variables.ods.json
2328
/output*.*
2429
.fontconfig/
2530
docker-compose.override.yml
31+
/tmpfile/**
32+
!/tmpfile/.keep

API/utils.py

Lines changed: 76 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,40 @@
22
Copyright (C) 2023 Probesys
33
"""
44

5-
from flask import *
5+
from flask import Response, send_file
66

77
import lotemplate as ot
88

9-
import configargparse as cparse
109
import glob
1110
import os
1211
import sys
13-
import subprocess
14-
from time import sleep
1512
from typing import Union
16-
from zipfile import ZipFile
17-
18-
p = cparse.ArgumentParser(default_config_files=['config.yml', 'config.ini', 'config'])
19-
p.add_argument('--config', '-c', is_config_file=True, help='Configuration file path')
20-
p.add_argument('--host', default="localhost", help='Host address to use for the libreoffice connection')
21-
p.add_argument('--port', default="2002", help='Port to use for the libreoffice connexion')
22-
args = p.parse_known_args()[0]
23-
24-
os.makedirs("uploads", exist_ok=True)
25-
os.makedirs("exports", exist_ok=True)
26-
subprocess.call(
27-
f'soffice "--accept=socket,host={args.host},port={args.port};urp;StarOffice.ServiceManager" &', shell=True)
28-
sleep(3)
29-
cnx = ot.Connexion(args.host, args.port)
3013

14+
host='localhost'
15+
port='200'
16+
gworkers=0
17+
scannedjson=''
18+
maxtime=60
19+
def start_soffice(workers,jsondir,maxt=60):
20+
global gworkers
21+
global my_lo
22+
global scannedjson
23+
global maxtime
24+
maxtime=maxt
25+
scannedjson=jsondir
26+
gworkers=workers
27+
os.makedirs("uploads", exist_ok=True)
28+
os.makedirs("exports", exist_ok=True)
29+
os.makedirs(scannedjson, exist_ok=True)
30+
clean_temp_files()
31+
my_lo=ot.start_multi_office(nb_env=workers)
3132

32-
def restart_soffice() -> None:
33-
"""
34-
simply restart the soffice process
3533

36-
:return: None
37-
"""
38-
39-
clean_temp_files()
40-
subprocess.call(
41-
f'soffice "--accept=socket,host={cnx.host},port={cnx.port};urp;StarOffice.ServiceManager" &',
42-
shell=True
43-
)
44-
sleep(2)
45-
try:
46-
cnx.restart()
47-
except:
48-
pass
34+
def connexion():
35+
global my_lo
36+
cnx= ot.randomConnexion(my_lo)
4937

38+
return cnx
5039

5140
def clean_temp_files():
5241
"""
@@ -103,7 +92,6 @@ def error_format(exception: Exception, message: str = None) -> dict:
10392
{
10493
'error': type(exception).__name__,
10594
'code': exception.code if isinstance(exception, ot.errors.LotemplateError) else type(exception).__name__,
106-
# 'message': message or str(exception),
10795
'message': message or exception_message,
10896
'variables': exception.infos if isinstance(exception, ot.errors.LotemplateError) else {}
10997
}
@@ -148,23 +136,16 @@ def save_file(directory: str, f, name: str, error_caught=False) -> Union[tuple[d
148136
i += 1
149137
f.stream.seek(0)
150138
f.save(f"uploads/{directory}/{name}")
139+
140+
141+
cnx = connexion()
142+
global scannedjson
151143
try:
152-
with ot.Template(f"uploads/{directory}/{name}", cnx, True) as temp:
144+
with ot.TemplateFromExt(f"uploads/{directory}/{name}", cnx, True,scannedjson) as temp:
153145
values = temp.variables
154146
except ot.errors.TemplateError as e:
155147
delete_file(directory, name)
156148
return error_format(e), 415
157-
except ot.errors.UnoException as e:
158-
delete_file(directory, name)
159-
restart_soffice()
160-
if error_caught:
161-
return (
162-
error_format(e, "Internal server error on file opening. Please checks the README file, section "
163-
"'Unsolvable problems' for more informations."),
164-
500
165-
)
166-
else:
167-
return save_file(directory, f, name, True)
168149
except Exception as e:
169150
delete_file(directory, name)
170151
return error_format(e), 500
@@ -180,24 +161,14 @@ def scan_file(directory: str, file: str, error_caught=False) -> Union[tuple[dict
180161
:param error_caught: specify if an error was already caught
181162
:return: a json and optionally an int which represent the status code to return
182163
"""
183-
184-
try:
185-
with ot.Template(f"uploads/{directory}/{file}", cnx, True) as temp:
164+
cnx = connexion()
165+
global scannedjson
166+
with ot.TemplateFromExt(f"uploads/{directory}/{file}", cnx, True,scannedjson) as temp:
186167
variables = temp.variables
187-
except ot.errors.UnoException as e:
188-
restart_soffice()
189-
if error_caught:
190-
return (
191-
error_format(e, "Internal server error on file opening. Please checks the README file, section "
192-
"'Unsolvable problems' for more informations."),
193-
500
194-
)
195-
else:
196-
return scan_file(directory, file, True)
197168
return {'file': file, 'message': "Successfully scanned", 'variables': variables}
198169

199170

200-
def fill_file(directory: str, file: str, json, error_caught=False) -> Union[tuple[dict, int], dict, Response]:
171+
def fill_file(directory: str, file: str, json, error_caught=False) -> Union[tuple[dict, int], dict, tuple[str,Response]]:
201172
"""
202173
fill the specified file
203174
@@ -208,61 +179,48 @@ def fill_file(directory: str, file: str, json, error_caught=False) -> Union[tupl
208179
:return: a json and optionally an int which represent the status code to return
209180
"""
210181

211-
if type(json) != list or not json:
212-
return error_sim("JsonSyntaxError", 'api_invalid_base_value_type', "The json should be a non-empty array"), 415
213-
182+
if isinstance(json, list):
183+
json=json[0]
184+
print("####\nUsing a list of dict is DEPRECATED, you must directly send the dict.")
185+
print("See documentation.\n#######")
186+
cnx = connexion()
187+
global scannedjson
214188
try:
215-
with ot.Template(f"uploads/{directory}/{file}", cnx, True) as temp:
216-
217-
exports = []
218-
219-
for elem in json:
220-
221-
length = len(elem)
222-
is_name_present = type(elem.get("name")) is str
223-
is_variables_present = type(elem.get("variables")) is dict
224-
is_page_break_present = type(elem.get("page_break")) is bool
225-
226-
if (
227-
not is_name_present
228-
or not is_variables_present
229-
or ((length > 2 and not is_page_break_present) or (length > 3 and is_page_break_present))
230-
):
231-
return error_sim(
232-
"JsonSyntaxError",
233-
'api_invalid_instance_syntax',
234-
"Each instance of the array in the json should be an object containing only 'name' - "
235-
"a non-empty string, 'variables' - a non-empty object, and, optionally, 'page_break' - "
236-
"a boolean."), 415
237-
238-
try:
239-
json_variables = ot.convert_to_datas_template(elem["variables"])
240-
temp.search_error(json_variables)
241-
temp.fill(elem["variables"])
242-
if elem.get('page_break', False):
243-
temp.page_break()
244-
exports.append(temp.export("exports/" + elem["name"], should_replace=(
245-
True if len(json) == 1 else False)))
246-
except Exception as e:
247-
return error_format(e), 415
248-
249-
if len(exports) == 1:
250-
return send_file(exports[0], download_name=exports[0].split("/")[-1])
251-
else:
252-
with ZipFile('exports/export.zip', 'w') as zipped:
253-
for elem2 in exports:
254-
zipped.write(elem2, elem2.split("/")[-1])
255-
return send_file('exports/export.zip', 'export.zip')
256-
except ot.errors.UnoException as e:
257-
restart_soffice()
258-
if error_caught:
259-
return (
260-
error_format(e, "Internal server error on file opening. Please checks the README file, section "
261-
"'Unsolvable problems' for more informations."),
262-
500
263-
)
264-
else:
265-
return fill_file(directory, file, json, True)
266-
267-
268-
clean_temp_files()
189+
with ot.TemplateFromExt(f"uploads/{directory}/{file}", cnx, True,scannedjson) as temp:
190+
191+
length = len(json)
192+
is_name_present = type(json.get("name")) is str
193+
is_variables_present = type(json.get("variables")) is dict
194+
is_page_break_present = type(json.get("page_break")) is bool
195+
196+
if (
197+
not is_name_present
198+
or not is_variables_present
199+
or ((length > 2 and not is_page_break_present) or (length > 3 and is_page_break_present))
200+
):
201+
return 415, error_sim(
202+
"JsonSyntaxError",
203+
'api_invalid_instance_syntax',
204+
"Each instance of the array in the json should be an object containing only 'name' - "
205+
"a non-empty string, 'variables' - a non-empty object, and, optionally, 'page_break' - "
206+
"a boolean.")
207+
208+
try:
209+
json_variables = ot.convert_to_datas_template(json["variables"])
210+
temp.search_error(json_variables)
211+
temp.fill(json["variables"])
212+
if json.get('page_break', False):
213+
temp.page_break()
214+
export_file=temp.export(json["name"],"exports")
215+
export_name=json["name"]
216+
except Exception as e:
217+
if 'export_name' in locals():
218+
return ( export_file,error_format(e))
219+
else:
220+
return ( "nofile",error_format(e))
221+
222+
return (export_file,send_file(export_file, export_name))
223+
224+
except Exception as e:
225+
return error_format(e), 500
226+

CHANGELOG.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
Versions
2+
========
3+
4+
Note : the upgrade from version 1.x to 2.x is easy. There is no reason to stay to version 1.x.
5+
6+
The upgrade documentation is in the file [UPGRADE.md](UPGRADE.md).
7+
8+
Versions 2.x
9+
------------
10+
11+
- v2.0.0 : 01/01/2025
12+
- BC Break (easy to fix) : see [UPGRADE.md](UPGRADE.md)
13+
- We can now generate Calc / Excel files (from Calc templates)
14+
- Is multiThreaded : we can generate several files at the same time
15+
- Performances improvements
16+
- No BC Breaks for the templates
17+
- upgrade debian, LibreOffice, Python libs versions
18+
- for devs : added "use bruno" requests inside the repository
19+
20+
Versions 1.x
21+
------------
22+
23+
- v1.6.1 : 2024-04-12 : bugfix
24+
- fix the issue https://github.com/Probesys/lotemplate/issues/34 : too many endif bugg
25+
- v1.6.0 : 2024-04-11
26+
- allow put variables inside headers and footers
27+
- fix a bug when a variable is both inside the text content and inside a table (it should not arrive, but it is fixed)
28+
- a new unit test system based on PDF converted to text in order to test contents that are not converted to text with a simple saveAs
29+
- v1.5.2 : 2024-02-24 : Better README
30+
- Rewrite for a betterdocker DockerFile without bug
31+
- v1.5.1 : 2024-02-16 : Better README
32+
- Rewriting of the README file
33+
- v1.5.0 : 2024-02-12 : syntax error detection
34+
- add syntax error detection in if statements
35+
- add syntax error detection in for statements
36+
- come back to default libreoffice of Debian Bookworm (removed backports, incompatibility)
37+
- v1.4.1 : 2023-11-20 : micro-feature for counter and fix possible bug
38+
- use counters for counting elements of a list
39+
- fix possible bug with reset and last.
40+
- v1.4.0, 2023-11-17 : counters
41+
- add a counter system inside templates
42+
- add better scan for if statement. Raises an error if there is too many endif in the template.
43+
- speedup html statement replacement and scanning
44+
- speedup for statement replacement and scanning
45+
- tests of for scanning
46+
- internal : add scan testing inside content unit tests
47+
- v1.3.0, 2023-11-16 :
48+
- major refactoring. No evolution for the user.
49+
- new unit tests on tables and images
50+
- no BC Break (theoretically)
51+
- v1.2.8, 2023-09-01 :
52+
- fix bug in TextShape var replacement
53+
- v1.2.7, 2023-08-30 :
54+
- Upgrade to debian bookworm slim
55+
- v1.2.6, 2023-08-30 :
56+
- new comparators for if statements : ===, !==, CONTAINS, NOT_CONTAINS
57+
- variables of type "html" are now supported and copied as HTML
58+
- v1.2.5, 2023-07-17 : temporary fix for detecting endhtml and endfor
59+
- v1.2.4, 2023-07-09 : fix major bug in if statement scanning
60+
- v1.2.3, 2023-07-07 : no endif detection, performance improvement in if statement
61+
- v1.2.2, 2023-06-09 : bugfix html statement scan missing
62+
- v1.2.1, 2023-06-05 : little fix for CI
63+
- v1.2.0, 2023-06-04 : if statements inside for
64+
- v1.1.0, 2023-05-23 : recursive if statement
65+
- v1.0.1, 2023-05-05 : workaround, fix in html formatting
66+
- v1.0.0, 2023-05-03 : if statement, for statement, html statement
67+
- not numbered : about may 2022 : first version
68+
69+
### Possible futur evolutions
70+
71+
- Possibly to add dynamic images in tables
72+
- another way to make image variables that would be compatible with Microsoft Word and maybe other formats (example : set the variable name in the 'alternative text' field)
73+
- key system for each institution for security
74+
- handle bulleted lists using table like variables
75+
- use variable formatting instead of the one of the character before

Dockerfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
FROM debian:bookworm-slim as prod
1+
FROM debian:trixie-slim as prod
2+
3+
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
4+
25
RUN --mount=type=cache,id=apt-cache,target=/var/cache/apt,sharing=locked \
36
--mount=type=cache,id=apt-lib,target=/var/lib/apt,sharing=locked \
47
--mount=type=cache,id=debconf,target=/var/cache/debconf,sharing=locked \

0 commit comments

Comments
 (0)