From 66f101c8b4ff15f5eeeb3add255155e72d21bf65 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 21 Jul 2025 19:15:26 +0100 Subject: [PATCH 1/4] Fix two syntax errors in the FFML documentation. --- src/calibre/gui2/dialogs/template_general_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/dialogs/template_general_info.py b/src/calibre/gui2/dialogs/template_general_info.py index 39cda1572e..b1d2fceecb 100644 --- a/src/calibre/gui2/dialogs/template_general_info.py +++ b/src/calibre/gui2/dialogs/template_general_info.py @@ -90,12 +90,12 @@ will become an empty line in the output. [*][B]URLs.[/B] The syntax is similar to BBCODE: ``[URL href="http..."]Link text[/URL]``.\ Example: ``[URL href="https://en.wikipedia.org/wiki/ISO_8601"]ISO[/URL]`` produces [URL href="https://en.wikipedia.org/wiki/ISO_8601"]ISO[/URL] [*][B]Internal function reference links[/B]. These are links to formatter function -documentation. The syntax is the same as guilabel. Example: ``:ref:\`get_note\```. +documentation. The syntax is the same as guilabel. Example: ``:ref:`get_note` ``. The characters '()' are automatically added to the function name when displayed. For HTML it generates the same as the inline program code text operator (\`\`) with no link. Example: ``:ref:`add` `` produces ``add()``. For RST it generates a ``:ref:`` reference that works only in an RST document -containing formatter function documentation. Example: ``:ref:\`get_note\``` +containing formatter function documentation. Example: ``:ref:`get_note` `` generates \:ref\:\`get_note() \` [*][B]Example program code text blocks.[/B] Surround the code block with ``[CODE]`` and ``[/CODE]`` tags. These tags must be first on a line. Example: From 8013777a0f390121a69c4caef882eaa66f2ac999 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 21 Jul 2025 19:16:34 +0100 Subject: [PATCH 2/4] Add "document_to_transifex()" and "tree_to_transifex()" methods. --- src/calibre/utils/ffml_processor.py | 80 +++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/calibre/utils/ffml_processor.py b/src/calibre/utils/ffml_processor.py index 64d3cfca3c..15cd814bfe 100644 --- a/src/calibre/utils/ffml_processor.py +++ b/src/calibre/utils/ffml_processor.py @@ -361,6 +361,86 @@ class FFMLProcessor: result = f'{fname}{result[paren:]}' return result + def tree_to_transifex(self, tree, depth=0): + ''' + Given a Formatter Function Markup Language (FFML) parse tree, return a + string containing an encoding suitable for transifex. Simplified + explanation: non-significant newlines are removed, collapsing a series + of lines into a single line. There are no semantic changes. Assuming + correct FFML input, transifex'ed output re-run through the FFML + processor produces html and rst documents identical to the original + FFML input. + + :param tree: the parsed FFML. + :param depth: the recursion level. This is used for debugging. + + :return: a string containing the HTML text + ''' + result = '' + if tree.node_kind() == NodeKinds.TEXT: + t = tree.text() + t = t.replace('\n', ' ') + result += t + if tree.node_kind() == NodeKinds.BOLD_TEXT: + result += f'[B]{tree.text()}[/B]' + elif tree.node_kind() == NodeKinds.BLANK_LINE: + result += '\n\n' + elif tree.node_kind() == NodeKinds.CHARACTER: + result += '\\' + tree.text() + elif tree.node_kind() == NodeKinds.CODE_TEXT: + t = tree.text() + if t.endswith('`'): + t = t + ' ' + result += f'``{t}``' + elif tree.node_kind() == NodeKinds.CODE_BLOCK: + result += "\n[CODE]\n" + tree.text().replace('[/CODE]', '[\/CODE]') + "[/CODE]\n" + elif tree.node_kind() == NodeKinds.END_SUMMARY: + result += '[/]' + elif tree.node_kind() == NodeKinds.ERROR_TEXT: + result += f'{tree.text()}' + elif tree.node_kind() == NodeKinds.GUI_LABEL: + result += f':guilabel:`{tree.text()}`' + elif tree.node_kind() == NodeKinds.ITALIC_TEXT: + result += f'`{tree.text()}`' + elif tree.node_kind() == NodeKinds.LIST: + result += '[LIST]\n' + for child in tree.children(): + result += '[*]' + t = self.tree_to_transifex(child, depth=depth+1) + if t.endswith('\n\n'): + t = t[0:-1] + result += t + result += '[/LIST]\n' + elif tree.node_kind() == NodeKinds.REF: + result += f':ref:`{tree.text()}`' + elif tree.node_kind() == NodeKinds.URL: + result += f'[URL href="{tree.url()}"]{tree.label()}[/URL]' + elif tree.node_kind() == NodeKinds.LIST_ITEM: + for child in tree.children(): + result += self.tree_to_transifex(child, depth=depth+1) + result += '\n' + elif tree.node_kind() == NodeKinds.DOCUMENT: + for child in tree.children(): + result += self.tree_to_transifex(child, depth=depth+1) + return result + + def document_to_transifex(self, document, name, safe=True): + ''' + Given a document in the Formatter Function Markup Language (FFML), return + that document suitable for transifex. + + :param document: the text in FFML. + :param name: the name of the document, used during error + processing. It is usually the name of the function. + :param safe: if true, do not propagate exceptions. Instead attempt to + recover using the English version as well as display an error. + + :return: a string containing the output for transifex. + + ''' + tree = self.parse_document(document, name, safe=safe) + return self.tree_to_transifex(tree, 0) + def tree_to_rst(self, tree, indent, result=None): ''' Given a Formatter Function Markup Language (FFML) parse tree, return From 665221c4cdc0afb5aadb6c877ffa0e488fc6b8fc Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 21 Jul 2025 19:32:03 +0100 Subject: [PATCH 3/4] Fix escape sequence problem --- src/calibre/utils/ffml_processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/ffml_processor.py b/src/calibre/utils/ffml_processor.py index 15cd814bfe..892c09a8bd 100644 --- a/src/calibre/utils/ffml_processor.py +++ b/src/calibre/utils/ffml_processor.py @@ -393,7 +393,7 @@ class FFMLProcessor: t = t + ' ' result += f'``{t}``' elif tree.node_kind() == NodeKinds.CODE_BLOCK: - result += "\n[CODE]\n" + tree.text().replace('[/CODE]', '[\/CODE]') + "[/CODE]\n" + result += "\n[CODE]\n" + tree.text().replace('[/CODE]', r'[\/CODE]') + "[/CODE]\n" elif tree.node_kind() == NodeKinds.END_SUMMARY: result += '[/]' elif tree.node_kind() == NodeKinds.ERROR_TEXT: From efae3e8382a340a95ad07f237cc7d7b812353318 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 21 Jul 2025 19:38:08 +0100 Subject: [PATCH 4/4] String change. --- src/calibre/gui2/dialogs/template_general_info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/template_general_info.py b/src/calibre/gui2/dialogs/template_general_info.py index b1d2fceecb..f82246bef5 100644 --- a/src/calibre/gui2/dialogs/template_general_info.py +++ b/src/calibre/gui2/dialogs/template_general_info.py @@ -100,7 +100,8 @@ generates \:ref\:\`get_note() \` [*][B]Example program code text blocks.[/B] Surround the code block with ``[CODE]`` and ``[/CODE]`` tags. These tags must be first on a line. Example: [CODE] -[CODE]program: +[CODE] +program: get_note('authors', 'Isaac Asimov', 1) [\/CODE] [/CODE]