2

I have a custom node defined in an extension (in a similar way as TODO example on sphinx page).

Problem is that this extension is used for html output via visit_my_node/depart_my_node methods, but I would have liked to implement a similar thing for docxbuilder.

I saw in docxbuilder documentation that I should probably use a translator, but I can't really figure out how, being not very familiar with the concept.

I also saw this topic on SO, suggesting I should maybe rebuild the builder, but I wanted to maybe get some valuable opinion on translator.

Below, you'll find a code example (a bit old but you should get the idea of what is done) of the extension implementation for the custom node

from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive


class myNode(nodes.General, nodes.Element):
    def __init__(self, options, *args, **kwargs):
        super(myNode, self).__init__(options, *args, **kwargs)
        self.__options = options

    def get_options(self):
        return self.__options
    
    
class myDirective(Directive):
    """
    Requirement entry.
    """
    
    has_content = True
    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec = {
        'name': directives.unchanged_required,
        'title': directives.unchanged_required,
        'parents': directives.unchanged} # boolean}

    def run(self):
        my_node = myNode(self.options)
        self.state.nested_parse(self.content, self.content_offset, my_node )
        return [my_node]
    
def visit_my_node(self, node):
    
    name = node.get_options()['name']
    
    if len(node.get_options()['parent']) > 0:
        parent = ", ".join([x.strip() for x in node.get_options()['parent'].split(',')])
    else:
        parent = "design"
    
    self.body.append('<div class="mynode">\n')
    self.body.append('\t<p class="my-header"><strong>' + name + '; ' + parent + '</strong></p>\n')
    self.body.append('\t<div class="my-content">\n')

def depart_my_node(self):
    self.body.append('\t</div>\n')
    self.body.append('\t<p class="my-footer"><strong>END</strong></p>\n')
    self.body.append('</div>\n')

def setup(app):
    app.add_node(myNode,
                 html=(visit_my_node, depart_my_node))
                 
    app.add_directive("mynode", myDirective)
    
    return {'version' : '0.1'}
ThylowZ
  • 55
  • 7
  • 1
    Should the conversion from `html` to `docx` converter solve your issue? – Bilal Qandeel Sep 02 '22 at 13:24
  • Sorry, I don't understand your question? I use docxbuilder extension to create a docx delivery of my spec, but it does not take into account the custom node that I shared in this topic. What is the html to docx converter you are refering to? – ThylowZ Sep 05 '22 at 12:39
  • I mean that the output that shows in your script creates an `html` formatted document. Instead of restructuring this output into `docx`, you can simply convert the `html` into `docx` directly. – Bilal Qandeel Sep 05 '22 at 14:21
  • https://pypi.org/project/htmldocx/ – Bilal Qandeel Sep 05 '22 at 14:21
  • OK, not sure it's what I'm looking for, for several reasons. I might give it a try but I'd prefer to handle this through extensions and custom directives if there is a way to. – ThylowZ Sep 05 '22 at 15:14
  • Still was unable to find a way to make it word, htmltodocx is not what I need. – ThylowZ Jan 05 '23 at 17:17

1 Answers1

1

If your output code is not too complex (for instance just some div-container, paragraphs, some text) I would try to avoid the custom builder visitor functions. Mainly as you need to maintain one solution for each builder, and as there are many builders, you may miss some.

I would try to transform your custom directive node to some standard docutils nodes, so that each builder can handle it.

So you can add a handler to the sphinx event doctree-resolved, then you search for your custom node in the current doctree. This node get then replaced by a nested list of standard docutil nodes, e.g. node.replace_self(docutils.nodes.Text('Some simple text')).

The creation of this nested docutils-node-tree for your directive may be more complex than just adding a HTML-string in a visitor function. But the benefit is, you have to do it only once and support 100% of all Sphinx builders out of the box.

Disclaimer: I'm a maintainer of Sphinx-Needs, which is using the above mechanism for all its directives.

danwos
  • 416
  • 3
  • 12
  • Thank you for your answer, although I'm not really sure how to transform from a custom to a standard docutils node. I've been able to implement a working solution by going through docxbuilder source code and trying to understand the code I had to use to make it work, and it seems to be OK. But it might not be the easiest to maintain long-term, so I might be interested by your suggestion. – ThylowZ Jan 09 '23 at 14:19
  • Btw, I will test sphinx-needs because I didn't know about the module but it seems to really cover my needs. Again, thank you for your answer. – ThylowZ Jan 31 '23 at 16:35
  • 1
    Thanks for your insight. I tried to used Sphinx-Needs in a first time which almost made me do perfectly what I wanted, but I did not need my directive to be put in a table for several reason. So I came back here to understand precisely what you suggest to do, and understood it better reading your amazing Sphinx-Need (maybe one day I'll definitely swap). In the end I was able to do precisely what I wanted thanks to you. Thank you very much sir! – ThylowZ Mar 01 '23 at 10:53