Coverage for src/pyTRLCConverter/docx_converter.py: 95%
64 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 10:59 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 10:59 +0000
1"""Converter to Word docx format.
3 Author: Norbert Schulz (norbert.schulz@newtec.de)
4"""
6# pyTRLCConverter - A tool to convert TRLC files to specific formats.
7# Copyright (c) 2024 - 2025 NewTec GmbH
8#
9# This file is part of pyTRLCConverter program.
10#
11# The pyTRLCConverter program is free software: you can redistribute it and/or modify it under
12# the terms of the GNU General Public License as published by the Free Software Foundation,
13# either version 3 of the License, or (at your option) any later version.
14#
15# The pyTRLCConverter program is distributed in the hope that it will be useful, but
16# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along with pyTRLCConverter.
20# If not, see <https://www.gnu.org/licenses/>.
22# Imports **********************************************************************
23import os
24from typing import Optional
25import docx
26from pyTRLCConverter.base_converter import BaseConverter
27from pyTRLCConverter.logger import log_verbose
28from pyTRLCConverter.ret import Ret
29from pyTRLCConverter.trlc_helper import Record_Object
31# Variables ********************************************************************
33# Classes **********************************************************************
35class DocxConverter(BaseConverter):
36 """
37 Converter to docx format.
38 """
40 OUTPUT_FILE_NAME_DEFAULT = "output.docx"
42 def __init__(self, args: any) -> None:
43 # lobster-trace: SwRequirements.sw_req_no_prj_spec
44 # lobster-trace: SwRequirements.sw_req_docx
45 # lobster-trace: SwRequirements.sw_req_docx_template
46 """
47 Initialize the docx converter.
49 Args:
50 args (any): The parsed program arguments.
51 """
52 super().__init__(args)
54 if args.template is not None:
55 log_verbose(f"Loading template file {args.template}.")
57 self._docx = docx.Document(docx=args.template)
59 # Ensure default table style is present in the document.
60 if not 'Table Grid' in self._docx.styles:
61 self._docx.styles.add_style('Table Grid', docx.enum.style.WD_STYLE_TYPE.TABLE, builtin=True)
63 @staticmethod
64 def get_subcommand() -> str:
65 # lobster-trace: SwRequirements.sw_req_docx
66 """ Return subcommand token for this converter.
68 Returns:
69 Ret: Status
70 """
71 return "docx"
73 @staticmethod
74 def get_description() -> str:
75 # lobster-trace: SwRequirements.sw_req_docx
76 """ Return converter description.
78 Returns:
79 Ret: Status
80 """
81 return "Convert into docx format."
83 @classmethod
84 def register(cls, args_parser: any) -> None:
85 # lobster-trace: SwRequirements.sw_req_docx
86 """Register converter specific argument parser.
88 Args:
89 args_parser (any): Argument parser
90 """
91 super().register(args_parser)
93 BaseConverter._parser.add_argument(
94 "-t",
95 "--template",
96 type=str,
97 default=None,
98 required=False,
99 help="Load the given docx file as a template to append to."
100 )
101 BaseConverter._parser.add_argument(
102 "-n",
103 "--name",
104 type=str,
105 default=DocxConverter.OUTPUT_FILE_NAME_DEFAULT,
106 required=False,
107 help="Name of the generated output file inside the output folder " \
108 f"(default = {DocxConverter.OUTPUT_FILE_NAME_DEFAULT})."
109 )
111 def convert_section(self, section: str, level: int) -> Ret:
112 # lobster-trace: SwRequirements.sw_req_docx_section
113 """Process the given section item.
115 Args:
116 section (str): The section name
117 level (int): The section indentation level
119 Returns:
120 Ret: Status
121 """
122 self._docx.add_heading(section, level)
124 return Ret.OK
126 def convert_record_object_generic(self, record: Record_Object, level: int, translation: Optional[dict]) -> Ret:
127 # lobster-trace: SwRequirements.sw_req_docx_record
128 """
129 Process the given record object in a generic way.
131 The handler is called by the base converter if no specific handler is
132 defined for the record type.
134 Args:
135 record (Record_Object): The record object.
136 level (int): The record level.
137 translation (Optional[dict]): Translation dictionary for the record object.
138 If None, no translation is applied.
141 Returns:
142 Ret: Status
143 """
144 return self._convert_record_object(record, level, translation)
146 def finish(self) -> Ret:
147 # lobster-trace: SwRequirements.sw_req_docx_file
148 """Finish the conversion.
150 Returns:
151 Ret: Status
152 """
153 result = Ret.ERROR
155 if self._docx is not None:
156 output_file_name = self._args.name
157 if 0 < len(self._args.out):
158 output_file_name = os.path.join(self._args.out, self._args.name)
160 log_verbose(f"Writing docx {output_file_name}.")
161 self._docx.save(output_file_name)
162 self._docx = None
163 result = Ret.OK
165 return result
167 def _convert_record_object(self, record: Record_Object, level: int, translation: Optional[dict]) -> Ret:
168 # lobster-trace: SwRequirements.sw_req_docx_record
169 """
170 Process the given record object.
172 Args:
173 record (Record_Object): The record object.
174 level (int): The record level.
175 translation (Optional[dict]): Translation dictionary for the record object.
176 If None, no translation is applied.
178 Returns:
179 Ret: Status
180 """
181 self._docx.add_heading(f"{record.name} ({record.n_typ.name})", level + 1)
182 attributes = record.to_python_dict()
184 table = self._docx.add_table(rows=1, cols=2)
185 table.style = 'Table Grid'
186 table.autofit = True
188 # Set table headers
189 header_cells = table.rows[0].cells
190 header_cells[0].text = "Element"
191 header_cells[1].text = "Value"
193 # Populate table with attribute key-value pairs
194 for key, value in attributes.items():
195 if value is None:
196 value = self._empty_attribute_value
198 if translation is not None:
199 if key in translation:
200 key = translation[key]
202 cells = table.add_row().cells
203 cells[0].text = key
204 cells[1].text = str(value)
206 # Add a paragraph with the record object location
207 p = self._docx.add_paragraph()
208 p.add_run(f"from {record.location.file_name}:{record.location.line_no}").italic = True
210 return Ret.OK
213# Functions ********************************************************************
215# Main *************************************************************************