{"id":37165328,"url":"https://github.com/probesys/lotemplate","last_synced_at":"2026-01-14T19:36:49.698Z","repository":{"id":116094961,"uuid":"585221689","full_name":"Probesys/lotemplate","owner":"Probesys","description":"LOTemplate is document generator used to create documents programatically (ODT, DOCX, PDF) from a template (DOCX or ODT) and a json file using LibreOffice in headless mode","archived":false,"fork":false,"pushed_at":"2025-12-12T14:16:15.000Z","size":10085,"stargazers_count":40,"open_issues_count":0,"forks_count":3,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-12-14T05:31:23.126Z","etag":null,"topics":["api-rest","cli-tool","document-generator","libreoffice","office","templates","templating-engine","text-document"],"latest_commit_sha":null,"homepage":"","language":"Rich Text Format","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Probesys.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-01-04T16:14:54.000Z","updated_at":"2025-12-12T14:14:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"7c1dc73d-4733-4f3c-885c-241423177b0a","html_url":"https://github.com/Probesys/lotemplate","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/Probesys/lotemplate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Probesys%2Flotemplate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Probesys%2Flotemplate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Probesys%2Flotemplate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Probesys%2Flotemplate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Probesys","download_url":"https://codeload.github.com/Probesys/lotemplate/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Probesys%2Flotemplate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28432655,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T18:57:19.464Z","status":"ssl_error","status_checked_at":"2026-01-14T18:52:48.501Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-rest","cli-tool","document-generator","libreoffice","office","templates","templating-engine","text-document"],"created_at":"2026-01-14T19:36:48.996Z","updated_at":"2026-01-14T19:36:49.689Z","avatar_url":"https://github.com/Probesys.png","language":"Rich Text Format","readme":"LOTemplate (for Libre Office Template)\n======================================\n\n**Warning** : This readme is for the version 2.x of LoTemplate. There are breaking changes between versions 1.x and 2.x. See [UPGRADE.md](UPGRADE.md) documentation. You can also see the [CHANGELOG.md](CHANGELOG.md) for the versions.\n\n\n[![Unittest](https://github.com/Probesys/lotemplate/actions/workflows/unittest.yml/badge.svg)](https://github.com/Probesys/lotemplate/actions/workflows/unittest.yml)\n\n\u003ca name=\"principles\"\u003e\u003c/a\u003ePrinciples\n----------\n\nLOTemplate is document generator used to create documents programatically (ODT, DOCX,ODS, XLSX, PDF) from an office template and a json file.\n\n```mermaid\n---\ntitle: Word / Writer document\n---\n\nflowchart LR\n    template[\"Word Template\u003cbr/\u003e(DOCX or ODT)\"]\n    json[\"Data\u003cbr/\u003e(JSON)\"]\n    lotemplate[\"LO Template\u003cbr/\u003e(accessible by API or CLI)\"]\n    generatedFile[\"Generated File\u003cbr/\u003e(PDF, DOCX, ODT, RTF,...)\"]\n    \n    template --\u003e lotemplate\n    json --\u003e lotemplate\n    lotemplate --\u003e generatedFile\n```\n\n```mermaid\n---\ntitle: Excel / Calc document\n---\n\n flowchart LR\n    calc_template[\"Excel Template\u003cbr/\u003e(ODS or XLSX)\"]\n    calc_json[\"Data\u003cbr/\u003e(JSON)\"]\n    calc_lotemplate[\"LO Template\u003cbr/\u003e(accessible by API or CLI)\"]\n    calc_generatedFile[\"Generated File\u003cbr/\u003e(ODS, XLSX, PDF, csv,...)\"]\n    \n    calc_template --\u003e calc_lotemplate\n    calc_json --\u003e calc_lotemplate\n    calc_lotemplate --\u003e calc_generatedFile\n```\n\nWhat makes this tool different from others are the following features :\n\n* The templates are in office format (ods,odt, docx, xlsx, ... ) format\n* Word Template can have complex structures (variables, loop, conditions, counters, html,...) \n* The tool can scan the template to extract the variables sheet\n* The tool can be called by an API, a CLI or a python module.\n* The tool uses a real LibreOffice headless to fill the templates. Then the output formats are all the LibreOffice supported formats (docx, xlsx, pdf, odt, ods, text, rtf, html, ...)\n\nThe tool is written in Python and use a real LibreOffice headless to fill the templates.\n\nTable of content\n----------------\n\n* [Principles](#principles)\n* [Quick Start](#quick_start)\n* [API and CLI Usage](#api-and-cli-usage)\n* [DOCX and ODT Template syntax and examples](#docx-and-odt-template-syntax)\n* [XLSX and ODS Template syntax and examples](#xlsx-and-ods-template-syntax)\n* [Add watermark when output in pdf](#watermark)\n* [Supported formats](#supported-formats)\n* [Doc for developpers of lotemplate](#doc-for-devs)\n* [Unsolvable problems](#unsolvable-problems)\n* [Installation without Docker](#installation_without_docker)\n* [External documentations](#external-documentations)\n\n\n\u003ca name=\"quick_start\"\u003e\u003c/a\u003eQuick start\n-----------\n\n### Run the project with docker compose\n\nUse the docker-compose.yml at the root of the project. Configure the .env file\n\nrun the service\n\n```shell\ndocker-compose up -d\n```\n\n### Use the API\n\n```bash\n# creation of a directory\ncurl -X PUT -H 'secretkey: lopassword' -H 'directory: test_dir1' http://localhost:8000/\n# {\"directory\":\"test_dir1\",\"message\":\"Successfully created\"}\n```\n\nLet's imagine we have an file basic_test.odt (created by libreoffice) like this :\n\n```\nTest document\n\nlet’s see if the tag $my_tag is replaced and this $other_tag is detected.\n[if $my_tag == foo]My tag is foo[endif]\n[if $my_tag != foo]My tag is not foo[endif]\n```\n\nUpload this file to lotemplate\n\n```bash\n# upload a template\ncurl -X PUT -H 'secretkey: lopassword' -F file=@/tmp/basic_test.odt http://localhost:8000/test_dir1\n# {\"file\":\"basic_test.odt\",\"message\":\"Successfully uploaded\",\"variables\":{\"my_tag\":{\"type\":\"text\",\"value\":\"\"},\"other_tag\":{\"type\":\"text\",\"value\":\"\"}}}\n\n# generate a file titi.odt from a template and a json content\ncurl -X POST \\\n    -H 'secretkey: lopassword' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\"name\":\"my_file.odt\",\"variables\":{\"my_tag\":{\"type\":\"text\",\"value\":\"foo\"},\"other_tag\":{\"type\":\"text\",\"value\":\"bar\"}}}' \\\n    --output titi.odt http://localhost:8000/test_dir1/basic_test.odt \n```\n\nAfter the operation, you get the file titi.odt with this content :\n\n```\nTest document\n\nlet’s see if the tag foo is replaced and this bar is detected.\n\nMy tag is foo\n\n```\n\n\n\n\u003ca name=\"api-and-cli-usage\"\u003e\u003c/a\u003eAPI and CLI Usage\n-------------------------------------\n\n### With the API\n\n#### Examples of curl requests\n\n```bash\n# creation of a directory\ncurl -X PUT -H 'secretkey: my_secret_key' -H 'directory: test_dir1' http://lotemplate:8000/\n# {\"directory\":\"test_dir1\",\"message\":\"Successfully created\"}\ncurl -X PUT -H 'secretkey: my_secret_key' -H 'directory: test_dir2' http://lotemplate:8000/\n# {\"directory\":\"test_dir2\",\"message\":\"Successfully created\"}\n\n# look at the created directories\ncurl -X GET -H 'secretkey: my_secret_key' http://lotemplate:8000/\n# [\"test_dir2\",\"test_dir1\"]\n\n# delete a directory (and it's content\ncurl -X DELETE -H 'secretkey: my_secret_key' http://lotemplate:8000/test_dir2\n# {\"directory\":\"test_dir2\",\"message\":\"The directory and all his content has been deleted\"}\n\n# look at the directories\ncurl -X GET -H 'secretkey: my_secret_key' http://lotemplate:8000/\n# [\"test_dir1\"]\n```\n\nLet's imagine we have an odt file (created by libreoffice) like this :\n\n```\nTest document\n\nlet’s see if the tag $my_tag is replaced and this $other_tag is detected.\n```\n\nUpload this file to lotemplate\n\n```bash\n# upload a template\ncurl -X PUT -H 'secretkey: my_secret_key' -F file=@/tmp/basic_test.odt http://lotemplate:8000/test_dir1\n{\"file\":\"basic_test.odt\",\"message\":\"Successfully uploaded\",\"variables\":{\"my_tag\":{\"type\":\"text\",\"value\":\"\"},\"other_tag\":{\"type\":\"text\",\"value\":\"\"}}}\n\n# analyse an existing file and get variables\ncurl -X GET -H 'secretkey: my_secret_key'  http://lotemplate:8000/test_dir1/basic_test.odt\n# {\"file\":\"basic_test.odt\",\"message\":\"Successfully scanned\",\"variables\":{\"my_tag\":{\"type\":\"text\",\"value\":\"\"},\"other_tag\":{\"type\":\"text\",\"value\":\"\"}}}\n\n# generate a file titi.odt from a template and a json content\n curl -X POST -H 'secretkey: my_secret_key' -H 'Content-Type: application/json' -d '{\"name\":\"my_file.odt\",\"variables\":{\"my_tag\":{\"type\":\"text\",\"value\":\"foo\"},\"other_tag\":{\"type\":\"text\",\"value\":\"bar\"}}}' --output titi.odt http://lotemplate:8000/test_dir1/basic_test.odt \n```\n\nAfter the operation, you get the file titi.odt with this content :\n\n```\nTest document\n\nlet’s see if the tag foo is replaced and this bar is detected.\n```\n\n#### API reference\n\nThen use the following routes :\n\n*all routes take a secret key in the header, key `secretkey`, that correspond to the secret key configured in the \n[.env](.env) file. If no secret key is configured, the secret key isn't required at request.*\n\n- `/`\n  - `PUT` : take a directory name in the headers, key 'directory'. Creates a directory with the specified name\n  - `GET` : returns the list of existing directories\n\n- `/\u003cdirectory\u003e` : directory correspond to an existing directory\n  - `GET` : returns a list of existing templates within the directory, with their scanned variables\n  - `PUT` : take a file in the body, key 'file'. Uploads the given file in the directory, and returns the saved file \n    name and its scanned variables\n  - `DELETE` : deletes the specified directory, and all its contents\n  - `PATCH` : take a name in the headers, key 'name'. Rename the directory with the specified name.\n\n- `/\u003cdirectory\u003e/\u003cfile\u003e` : directory correspond to an existing directory, and file to an existing file within the \n  directory\n  - `GET` : returns the file and the scanned variables of the file\n  - `DELETE` : deletes the specified file\n  - `PATCH` : take a file in the body, key 'file'. replace the existing file with the given file. \n    returns the file and the scanned variables of the file\n  - `POST` : take a json in the raw body.\n    fills the template with the values given in the json. returns the filled document(s).\n- `/\u003cdirectory\u003e/\u003cfile\u003e/download` : directory correspond to an existing directory, and file to an existing file within \n  the directory\n  - `GET` : returns the original template file, as it was sent\n\nyou may wish to deploy the API on your server. \n[Here's how to do it](https://flask.palletsprojects.com/en/2.0.x/deploying/) - \n*but don't forget that you should have soffice installed on the server*\n\nYou can also change the flask options - like port and ip - in the [.flaskenv](.flaskenv) file.\nIf you're deploying the app with Docker, port and ip are editable in the [Dockerfile](Dockerfile).\nYou can also specify the host and port used to run and connect to soffice as command line arguments,\nor in a config file (`config.yml`/`config.ini`/`config` or specified via --config).\n\n\n\n### Execute and use the CLI\n\nRun the script with the following arguments :\n```\nusage: lotemplate_cli.py [-h] [--json_file JSON_FILE [JSON_FILE ...]]\n                         [--json JSON [JSON ...]] [--output OUTPUT]\n                         [--config CONFIG] [--host HOST] [--port PORT]\n                         [--scan] [--force_replacement] template_file\n\npositional arguments:\n  template_file         Template file to scan or fill\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --json_file JSON_FILE , -jf JSON_FILE\n                        Json files that must fill the template, if any\n  --json JSON , -j JSON\n                        Json strings that must fill the template, if any\n  --json_watermark_file JSON_FILE, -jwf JSON_FILE\n                        Json files to configure pdf watermark, if any\n  --json_watermark JSON , -jw JSON\n                        Json strings to configure pdf watermark, if any\n  --output OUTPUT, -o OUTPUT\n                        Names of the filled files, if the template should\n                        be filled. supported formats: pdf, html, docx, png, odt\n  --config CONFIG, -c CONFIG\n                        Configuration file path\n  --host HOST           Host address to use for the libreoffice connection\n  --port PORT           Port to use for the libreoffice connexion\n  --scan, -s            Specify if the program should just scan the template\n  --cpu                 Specify the number of libreoffice to start, default 0 is\n                        the number of CPU and return the information, or fill it.\n  --force_replacement, -f\n                        Specify if the program should ignore the scan's result\n```\n\nArgs that start with '--' (e.g. --json) can also be set in a config file\n(`config.yml`/`config.ini`/`config` or specified via --config). Config file syntax allows: key=value,\nflag=true, stuff=[a,b,c] (for details, [see syntax](https://pypi.org/project/ConfigArgParse/)).\nIf an arg is specified in more than one place, then commandline values\noverride config file values which override defaults.\n\nAll the specified files can be local or network-based.\n\nGet a file to fill with the `--scan` argument, and fill the fields you want. Add elements to the list\nof an array to dynamically add rows. Then pass the file, and the completed json file(s) (using `--json_file`)\nto fill it.\n\n\n\u003ca name=\"docx-and-odt-template-syntax\"\u003e\u003c/a\u003eDOCX and ODT Template syntax and examples\n----------------------------------------------------------\n\n### text variables\n\nPut `$variable` in the document is enough to add the variable 'variable'.\n\nA variable name is only composed by chars, letters or underscores.\n\nYou can also use \"function variables\". It is exactly the same as simple variables\nbut with a syntax that allows you a more flexible variable name :\n\nexamples :\n\n```\n# simple variable\n$my_var\n$MyVar99_2020\n\n# basic function variable\n$my_var(firstName)\n$my_var(address.city)\n\n# you have to escape parenthesis inside the parameter in the\n# variable name with a backslash\n$my_var(firstName\\(robert\\))\n```\n\nThen in the json, function variable are working exactly like simple variables.\n\n```json\n{\n  \"my_var\": {\n    \"type\": \"text\",\n    \"value\": \"my value\"\n  },\n  \"MyVar99_2020\": {\n    \"type\": \"text\",\n    \"value\": \"my value\"\n  },\n  \"my_var(firstName)\": {\n    \"type\": \"text\",\n    \"value\": \"my value\"\n  },\n  \"my_var(address.city)\": {\n    \"type\": \"text\",\n    \"value\": \"my value\"\n  },\n  \"my_var(firstName\\\\(robert\\\\))\": {\n    \"type\": \"text\",\n    \"value\": \"my value\"\n  }\n}\n```\n\n### html variables\n\nHtml variables are exactly like text variables, but the html is interpreted when\nthe variable is replaced in the document.\n\nTo declare a variable as an html variable, we only have to change the type in the json and send \"html\" instead of \"text\".\n\n```json\n{\n  \"my_var\": {\n    \"type\": \"html\",\n    \"value\": \"my value with \u003cstrong\u003ehtml formatting\u003c/strong\u003e\"\n  }\n}\n```\n\nLimitation : Html is not interpreted into \"shape content\". For example for a text associated to a rectangle inserted into the document.\n\nDue to  a bug with the \"paste HTML\" function of libreoffice,  we have to add # a \u0026nbsp; at the beginning of the string to make it work. Without that, the first element of a list # `\u003cul\u003e\u003cli\u003e...\u003c/li\u003e\u003c/ul\u003e`  is displayed without the bullet point. This is the less visible workaround I found. html_string = '\u0026nbsp;' + html_string\nIf you want to disable this. you can set DISABLE_HTML_HACK to true  (look a the .env file)\n\n\n### image variables\n\nAdd any image in the document, and put in the title of the alt text of the image (If your are using MsOffice you can use the Description field)\n(properties) '$' followed by the desired name ('$image' for example to add the image 'image')\n\n### dynamic arrays\n\nYou can add an unknown number of rows to the array but only on the last line.\nAdd the dynamic variables in the last row of the table, exactly like text variables, but with a '\u0026' instead of a the '$'\n\n### if statement\n\nYou can use if statement in order to display or to hide a part of your document.\n\nThere is many operators : \n\n* `==` : test if the var is equal to the value (case-insensitive)\n* `!=` : test if the var is not equal to the value (case-insensitive)\n* `===` : test if the var is equal to the value (case-sensitive)\n* `!==` : test if the var is not equal to the value (case-sensitive)\n* `IS_EMPTY` : check if the var is empty (empty means empty or only spaces, tabs or newlines)\n* `IS_NOT_EMPTY` : check if the var is not empty (empty means empty or only spaces, tabs or newlines)\n* `CONTAINS` : check if the var contains the value (case-insensitive)\n* `NOT_CONTAINS` : check if the var does not contain the value (case-insensitive)\n\n```\n[if $my_var == my_value]\n$my_var equals my_value (case insensitive)\n[endif]\n\n[if $my_var === my_value]\n$my_var equals my_value (case sensitive)\n[endif]\n\n[if $my_var != my_value]\n$my_var does not equals my_value (case insensitive)\n[endif]\n\n[if $my_var !== my_value]\n$my_var does not equals my_value (case sensitive)\n[endif]\n\n[if $my_var CONTAINS y_VAl]\nThis part will be displayed if $my_var contains y_VAl (case insensitive)\n[endif]\n\n[if $my_var NOT_CONTAINS dlfksqjqm]\nThis part will be displayed if $my_var does not contain dlfksqjqm\n[endif]\n\n[if $my_var IS_EMPTY]\nThis part will be displayed if my_var is empty (empty means empty or only spaces, tabs or newlines)\n[endif]\n\n[if $my_var IS_NOT_EMPTY]\nThis part will be displayed if my_var is empty (empty means empty or only spaces, tabs or newlines)\n[endif]\n```\n\nYou can put an if statement inside another if statement\n\n```\n[if $foo == my_value]\n[if $bar == my_value2]\nhere you have your document\n[endif]\n[if $bar == my_value3]\nhere you have your document\n[endif]\n[endif]\n```\n\n### for statement\n\nYou can use for statement in order to display a part of your\ndocument multiple times.\n\nWARNING : the for system loses the formating of the template. If you want a\nspecific formating, you have to put it in an HTML statement.\n\nYou have to send an array in the json file with a dict inside the array\n\n```json\n{\n  \"tutu\": {\n    \"type\": \"array\",\n    \"value\": [\n      {\n        \"firstName\": \"perso 1\",\n        \"lastName\": \"string 1\",\n        \"address\": {\n          \"street1\": \"8 rue de la paix\",\n          \"street2\": \"\",\n          \"zip\": \"75008\",\n          \"city\": \"Paris\",\n          \"state\": \"Ile de France\"\n        }\n      },\n      {\n        \"firstName\": \"perso 2\",\n        \"lastName\": \"lastname with \u003c and \u003e\",\n        \"address\": {\n          \"street1\": \"12 avenue Jean Jaurès\",\n          \"street2\": \"\",\n          \"zip\": \"38000\",\n          \"city\": \"Grenoble\",\n          \"state\": \"Isère\"\n        }\n      }\n    ]\n  }\n}\n```\n\nThen in your template file you can use the for like this :\n\n```\nTests of for statements\n\n[for $tutu]\nAssociate number [forindex]\nfirst name : [foritem firstName] \nlast name escaped by default [foritem lastName]\nlast name escaped html [foritem lastName escape_html]\nlast name not escaped [foritem lastName raw]\nAddress : \n[foritem address.street1]\n[foritem address.zip] [foritem address.city]\n[endfor]\n```\n\n* `[forindex]` : this is a counter beginning at 0 indicating the iteration count.\n* `[foritem firstName]` : variable firstName of the current iteration.\n* `[foritem lastName escape_html]` : variable lastName of the current iteration escaped by html.\n* `[foritem lastName raw]` : variable lastName of the current iteration not escaped.\n* `[foritem address.street1]` : variable address.street1 of the current iteration when you have a hierarchy\n\nNote : you can use if inside for statements\n\nHere we display only the people living in Grenoble\n```\n[for $tutu]\n[if [foritem address.city] == Grenoble]\nfirst name : [foritem firstName] \nlast name : [foritem lastName]\nAddress : \n[foritem address.street1]\n[foritem address.zip] [foritem address.city]\n[endif]\n[endfor]\n```\n\nHere we display only the first element of the array\n```\n[for $tutu]\n[if [forindex] == 0]\nfirst name : [foritem firstName] \nlast name : [foritem lastName]\n[endif]\n[endfor]\n```\n\nNote : If you are using `[forindex]` inside a variable name, the variable\nis excluded from the parsing of the template. It allows you to create a\ndynamic variable name inside a for loop. Ex : `$my_var(people.[forindex].name)` is\nexcluded from the variable parsing.\n\n### html statement\n\nYou can use html statement in order to display a part of your document with a specific formating.\n\nHere is some examples that ca be use inside an odt template\n\n```\n[html]\n\u003ctable\u003e\n\u003ctr\u003e\n  \u003ctd\u003eFirst Name\u003c/td\u003e\n  \u003ctd\u003eLast Name\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003eFirst Name\u003c/td\u003e\n  \u003ctd\u003eLast Name\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n[endhtml]\n```\n\nThen all the html content is interpreted and pasted as html. It is then rendered\nas a formated text (a table in this example) inside the document.\n\nYou can also display a variable that contains an html content inside an html statement\n\n```\n[html]\n$my_html_variable\n[endhtml]\n```\n\nwith the associated json :\n\n```json\n{\n  \"tutu\": {\n    \"type\": \"text\",\n    \"value\": \"my \u003cstrong\u003ehtml formated\u003c/strong\u003e content\"\n  }\n}\n```\n\nAs the for statement removes formatting, you can use the html statement combined with the\nfor statement to display a table with a specific formating.\n\nLet's see an example with the following json :\n\n```json\n{\n  \"tutu\": {\n    \"type\": \"array\",\n    \"value\": [\n      {\n        \"firstName\": \"perso 1\",\n        \"lastName\": \"string 1\",\n        \"address\": {\n          \"street1\": \"8 rue de la paix\",\n          \"street2\": \"\",\n          \"zip\": \"75008\",\n          \"city\": \"Paris\",\n          \"state\": \"Ile de France\"\n        }\n      },\n      {\n        \"firstName\": \"perso 2\",\n        \"lastName\": \"lastname with \u003c and \u003e\",\n        \"address\": {\n          \"street1\": \"12 avenue Jean Jaurès\",\n          \"street2\": \"\",\n          \"zip\": \"38000\",\n          \"city\": \"Grenoble\",\n          \"state\": \"Isère\"\n        }\n      }\n    ]\n  }\n}\n```\n\nand the odt template :\n\n```\n[html]\n\u003ctable\u003e\n\u003ctr\u003e\n  \u003ctd\u003eFirst Name\u003c/td\u003e\n  \u003ctd\u003eLast Name\u003c/td\u003e\n\u003c/tr\u003e\n[for $tutu]\n\u003ctr\u003e\n  \u003ctd\u003e[foritem firstName escape_html]\u003c/td\u003e\n  \u003ctd\u003e[foritem lastName escape_html]\u003c/td\u003e\n\u003c/tr\u003e\n[endfor]\n\u003c/table\u003e\n[endhtml]\n```\n\n### counter statement\n\nYou can use counter statement in order to display values that will be incremented step by step in your document. It\ncan be used for example to have a heading automatic numbering.\n\n**WARNING** : generally you don't have to use this counter feature. You can use the automatic numbering of Word or Libre Office\n\n#### basic usage of counter\n\nIn your odt, you can use :\n\n```\nchapter [counter chapter] : introduction\n\nchapter [counter chapter] : context\n\n[counter paragraph] : geopolitical context\n\n[counter paragraph] : geographical context\n\n[counter paragraph] : economical context\n\nchapter [counter chapter] : analysis\n[counter.reset paragraph]\n[counter paragraph] : geopolitical analysis\n\n[counter paragraph] : geographical analysis\n\n[counter paragraph] : economical analysis\n```\n\nIt will be transformed in : \n\n```\nchapter 1 : introduction\n\nchapter 2 : context\n\n1 : geopolitical context\n\n2 : geographical context\n\n3 : economical context\n\nchapter 3 : analysis\n\n1 : geopolitical analysis\n\n2 : geographical analysis\n\n3 : economical analysis\n```\n\nThe possible syntaxe are :\n\n* `[counter counter_name]` : increment the counter \"counter_name\" and display it\n* `[counter counter_name hidden]` : increment the counter \"counter_name\" without displaying it\n* `[counter.reset counter_name]` : reset the counter \"counter_name\" to 0\n* `[counter.last counter_name]` : display the last value of the counter \"counter_name\" without incrementing it\n* `[counter.format counter_name format_name]` : change the format of the counter\n    * `[counter.format counter_name number]` : the counter is displayed as a number (default)\n    * `[counter.format counter_name letter_lowercase]` : the counter is displayed as a letter (a, b, c, ...)\n    * `[counter.format counter_name letter_uppercase]` : the counter is displayed as a letter (A, B, C, ...)\n\n\n#### You can display hierarchical counters by just using `counter.last`:\n\n```\nchapter [counter chapter] : introduction\n\nchapter [counter chapter] : context\n\n[counter.last chapter].[counter paragraph] : geopolitical context\n\n[counter.last chapter].[counter paragraph] : geographical context\n\n[counter.last chapter].[counter paragraph] : economical context\n\nchapter [counter chapter] : analysis\n[counter.reset paragraph]\n[counter.last chapter].[counter paragraph] : geopolitical analysis\n\n[counter.last chapter].[counter paragraph] : geographical analysis\n\n[counter.last chapter].[counter paragraph] : economical analysis\n```\n\nThe result will be\n\n```\nchapter 1 : introduction\n\nchapter 2 : context\n\n2.1 : geopolitical context\n\n2.2 : geographical context\n\n2.3 : economical context\n\nchapter 3 : analysis\n\n3.1 : geopolitical analysis\n\n3.2 : geographical analysis\n\n3.3 : economical analysis\n```\n\n#### count the number of elements of a list\n\n```\n[counter.reset iterator]\n\n[for $solutions]\n\nTitle : [foritem title]\n\nContent : [foritem paragraph]\n\n[counter iterator hidden]\n[endfor]\n\nwe displayed [counter.last iterator] solutions\n```\n\n\n\u003ca name=\"xlsx-and-ods-template-syntax\"\u003e\u003c/a\u003eXLSX and ODS Template syntax and examples\n----------------------------------------------------------\n\nThe idea is to generate a real CALC / Excel file from a template, with potentially several sheets, variables to replace, dynamic tables, operations, etc.\n\nThe replacements are done in all the sheets of the document.\n\n### Simple variables replacements\n\nIf you have a excel template like this :\n\n\u003ctable\u003e\n\u003ctr\u003e\n  \u003ctd\u003eName\u003c/td\u003e\n  \u003ctd\u003e$myname\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003eHours\u003c/td\u003e\n  \u003ctd\u003e$myhours\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003edays\u003c/td\u003e\n  \u003ctd\u003e=B2/7 (displays \"#VALUE!\")\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\nAnd a json file like this :\n\n```json\n{\n  \"name\": \"simple_vars_result.xlsx\",\n  \"variables\": {\n    \"myhours\": {\n      \"type\": \"text\",\n      \"value\": \"12\"\n    },\n    \"myname\": {\n      \"type\": \"text\",\n      \"value\": \"Gérard\"\n    }\n  }\n}\n```\n\nYou obtain a excel like this\n\n\u003ctable\u003e\n\u003ctr\u003e\n  \u003ctd\u003eName\u003c/td\u003e\n  \u003ctd\u003eGérard\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003eHours\u003c/td\u003e\n  \u003ctd\u003e12\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003edays\u003c/td\u003e\n  \u003ctd\u003e=B2/7 (displays \"1.71428571428571\")\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\nYou can format the cells, add formulas, etc. Everything is kept in the final document.\n\n### Dynamic tables\n\n#### Example with a document LibreOffice Calc as a template\n\nDynamic tables are a bit more tricky. We are using \"Named ranges\".\n\nLets say you have a calc (.ods) document like this :\n\n\u003ctable\u003e\n\u003ctr\u003e\n  \u003ctd\u003eArticle\u003c/td\u003e\n  \u003ctd\u003eUnit price\u003c/td\u003e\n  \u003ctd\u003eQuantity\u003c/td\u003e\n  \u003ctd\u003eTotal\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003e\u0026name\u003c/td\u003e\n  \u003ctd\u003e\u0026unitPrice\u003c/td\u003e\n  \u003ctd\u003e\u0026quantity\u003c/td\u003e\n  \u003ctd\u003e=B2*C2\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003e\u003c/td\u003e\n  \u003ctd\u003e\u003c/td\u003e\n  \u003ctd\u003eTotal Price\u003c/td\u003e\n  \u003ctd\u003e=SUM(INDEX(loop_down_article, ,4))\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\nNote 1 : the `\u0026` is used to indicate that the cell content is a array.\nNote 2 : the SUM is strange : it is related to a \"name range\"\n\nIn order to explain to lotemplate that you want to duplicate the line 2 to the bottom, you have to create a named range that is named \"loop_down_article\". For that :\n- select the cells that you want to be duplicated (A2 to D2 in this example)\n- go to Sheet -\u003e Named ranges and expressions -\u003e Define\n- give a name to the range (loop_down_article in this example)\n\n![named_ranges_screenshot_1](doc/assets/named_ranges_1.png)\n\n![named_ranges_screenshot_2](doc/assets/named_ranges_2.png)\n\nNote : in the name of the range, \"loop_down_\" says that we want to insert the lines to the bottom. There is only two possibilities : \"loop_down_\" and \"loop_right_\". Le last part (article in the example) is only here to give a unique name to the range.\n\nNow that we have a named range, we can use it to calculate the total price of the lines. The formula `=SUM(INDEX(loop_down_article, ,4))` will sum all the values of the 4th column of the named range \"loop_down_article\".\n\nThen you can use the following json file :\n\n```json\n{\n  \"name\": \"calc_table_formula.html\",\n  \"variables\": {\n    \"loop_down_article\": {\n      \"type\": \"object\",\n      \"value\": {\n        \"name\": {\n          \"type\": \"table\",\n          \"value\": [\n            \"appel\",\n            \"banana\",\n            \"melon\",\n            \"lemon\"\n          ]\n        },\n        \"unitPrice\": {\n          \"type\": \"table\",\n          \"value\": [\n            \"1\",\n            \"1.5\",\n            \"3.2\",\n            \"0.8\"\n          ]\n        },\n        \"quantity\": {\n          \"type\": \"table\",\n          \"value\": [\n            \"4\",\n            \"6\",\n            \"2\",\n            \"1\"\n          ]\n        }\n      }\n    }\n  }\n}\n```\n\nThe result of the generation will be :\n\n\u003ctable\u003e\n\u003ctr\u003e\n  \u003ctd\u003eArticle\u003c/td\u003e\n  \u003ctd\u003eUnit price\u003c/td\u003e\n  \u003ctd\u003eQuantity\u003c/td\u003e\n  \u003ctd\u003eTotal\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003eapple\u003c/td\u003e\n  \u003ctd\u003e1\u003c/td\u003e\n  \u003ctd\u003e4\u003c/td\u003e\n  \u003ctd\u003e=B2*C2 (display 4)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003ebanana\u003c/td\u003e\n  \u003ctd\u003e1.5\u003c/td\u003e\n  \u003ctd\u003e6\u003c/td\u003e\n  \u003ctd\u003e=B2*C2 (display 9)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003emelon\u003c/td\u003e\n  \u003ctd\u003e3.2\u003c/td\u003e\n  \u003ctd\u003e2\u003c/td\u003e\n  \u003ctd\u003e=B2*C2 (display 6.4)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003elemon\u003c/td\u003e\n  \u003ctd\u003e0.8\u003c/td\u003e\n  \u003ctd\u003e1\u003c/td\u003e\n  \u003ctd\u003e=B2*C2 (display 0.8)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd\u003e\u003c/td\u003e\n  \u003ctd\u003e\u003c/td\u003e\n  \u003ctd\u003eTotal Price\u003c/td\u003e\n  \u003ctd\u003e=SUM(INDEX(loop_down_article, ,4)) (display 20.2)\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n#### Example with a document Excel as a template\n\nThe principle is exactly the same as for LibreOffice Calc exept for the creation of the named range.\n\n![excel_named_range screenshot 1](doc/assets/excel_named_range_1.png)\n\n![excel_named_range screenshot 2](doc/assets/excel_named_range_2.png)\n\n(sorry, my excel is in french...)\n\n\u003ca name=\"watermark\"\u003eAdd watermark when output in pdf\n\nYou can insert a watermark text in the page background when you are exporting in pdf.\n\n### Possible parameters\n\n| Name               | Description                                                                            | DefaultValue |\n|--------------------|----------------------------------------------------------------------------------------|--------------|\n| Watermark          | Specifies the text for a watermark to be drawn on every page of the exported PDF file. | empty        |\n| WatermarkColorRGB  | Specifies the color of the watermark text in RGB                                       |  236,236,236 |\n| WatermarkFontHeight| Specifies the font height of the watermark text.                                       |              |\n| WatermarkRotateAngle | Specifies angle of the watermark text in 1/10 degree.                                | 450          |\n| WatermarkFontName  | Specifies font name of the watermark text.                                             | Helvetica    |\n| TiledWatermark     | Specifies the tiled watermark text.                                                    |              |\n\n\n### Cli example\n\ncreate a file named watermark.json\n\n```json\n{\n\"Watermark\": \"ttottoto\",\n\"WatermarkColorRGB\" : \"236,0,0\"\n}\n\n```\nthen you can used the cli like this\n\n```bash\n./lotemplate_cli.py --template_file lotemplate/unittest/files/templates/text_vars.odt --json_file lotemplate/unittest/files/jsons/text_vars_valid.json --output toto.pdf -jwf 'watermark.json'\n\n```\n\n\n### API example\n\n\n```bash\n\n# generate a file titi.pdf from a template and a json content\n curl -X POST -H 'secretkey: DEFAULT_KEY' -H 'Content-Type: application/json' -d '{\"name\":\"my_file.pdf\",\"variables\":{\"my_tag\":{\"type\":\"text\",\"value\":\"foo\"},\"other_tag\":{\"type\":\"text\",\"value\":\"bar\"}},\"watermark\": {\"Watermark\": \"It s a draft\",\"WatermarkColorRGB\" : \"236,200,0\"}}' --output titi.pdf http://localhost:8000/test_dir1/test.odt\n\n```\n\n\u003ca name=\"supported-formats\"\u003e\u003c/a\u003eSupported formats\n-------------------------------------------------\n\n### Import\n| Format                  | ODT, OTT |ODS, ODST |XLSX, XLS | HTML | DOC, DOCX | RTF | TXT | OTHER |\n|-------------------------|----------|----------|----------|------|-----------|-----|-----|-------|\n| text variables support  | ✅        | ✅        | ✅        | ✅    | ✅         | ✅   | ✅   | ❌     |\n| dynamic tables support  | ✅        | ✅       | ✅       | ✅    | ✅         | ✅   | ❌   | ❌     |\n| image variables support | ✅        | ✅        | ✅        | ✅    | ✅         | ❌   | ❌   | ❌     |\n\n### Export\nFor Writer\nodt, pdf, html, docx.\nFor Calc\nods, xls, xlsx, html, csv\n\nOther formats can be easily added by adding the format information in the dictionary `formats` of the respective classes\nFormat information can be found on the \n[unoconv repo](https://github.com/unoconv/unoconv/blob/94161ec11ef583418a829fca188c3a878567ed84/unoconv#L391).\n\n\u003ca name=\"doc-for-devs\"\u003e\u003c/a\u003eDoc for developpers of lotemplate\n-----------------------------------------------------------\n\n### Run the tests\n\nYou need to have docker and docker-compose installed and then run\n\n```bash\nmake tests\n```\n\n### Installation with Docker for dev when your uid is not 1000\n\nfor this we use fixuid (https://github.com/boxboat/fixuid)\n\nyou have to define two env variable MY_UID and MY_GID with your uid and gid\ncopy docker-compose.override.yml.example to docker-compose.override.yml\n\n```shell\nexport MY_UID=$(id -u)\nexport MY_GID=$(id -g)\ncp docker-compose.override.yml.example docker-compose.override.yml\ndocker-compose up\n```\n\n\n\u003ca name=\"installation_without_docker\"\u003e\u003c/a\u003eInstallation without Docker\n---------------------------------------\n\n### Requirements\n\nFor Docker use of the API, you can skip this step.\n\n- LibreOffice (the console-line version will be enough)\n- python3.8 or higher\n- python3-uno\n- some python packages specified in [requirement.txt](requirements.txt) that you can install with\n  `pip install -r requirements.txt`. `Flask` and `Werkzeug` are optional, as they are used only for the API.\n\n```bash\n# on debian bookworm, you can use these commands\napt update\napt -y -t install bash python3 python3-uno python3-pip libreoffice-nogui\npip install -r requirements.txt\n```\n\n### Run the API\n\nRun the following command on your server :\n\n```shell\npython3 -m flask run\n```\n\nor simply\n\n```shell\nflask run\n```\n\nor, for Docker deployment:\n\n```shell\ndocker-compose up\n```\n\n\n\u003ca name=\"external-documentations\"\u003e\u003c/a\u003eExternal documentations\n---------------------------------------------------------\n\n\nFor Pyuno\n\n-  [LibreOffice SDK API Reference](https://api.libreoffice.org/docs/idl/ref/index.html)\n- [LibreOffice 24.2 API Documentation](https://api.libreoffice.org/) \n\n- [Libreoffice Development Wiki](https://wiki.documentfoundation.org/Development)\n- [JODConverter wiki for list formats compatibles with LibreOffice](https://github.com/sbraconnier/jodconverter/wiki/Getting-Started)\n- [The unoconv source code, written in python with PyUNO](https://github.com/unoconv/unoconv/blob/master/unoconv)\n- [Unoconv source code for list formats - and properties - compatible with LibreOffice for export](https://github.com/unoconv/unoconv/blob/94161ec11ef583418a829fca188c3a878567ed84/unoconv#L391)\n- [convert color RGB to long color use for LibreOffice](https://help.libreoffice.org/latest/fr/text/sbasic/shared/03010305.html)\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprobesys%2Flotemplate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprobesys%2Flotemplate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprobesys%2Flotemplate/lists"}