尝试将 "pending_xref" 节点追加到 signnode 时发生异常

An exception occured when trying append "pending_xref" node to signode

我正在尝试将 pending_xref 节点附加到 handle_signature[ 的 signnode 函数,但在 docutils 脚本中出现异常:

Exception occurred: File "\python\python37-32\lib\site-packages\docutils\nodes.py", line 569, in __getitem__

return self.children[key] IndexError: list index out of range

我的代码:

def handle_signature(self, sig, signode):
   if 'mod' in self.options:
      signode += nodes.Text(self.options['mod'] + ' ')
   signode += nodes.Text('Some text ')
   names, _, _ = Obj.parse_signature(sig)
   if names:
      ns = names.strip().split('.')
      for cl in ns:
         # because off this, i'm get an error
         signode += addnodes.pending_xref('', refdomain='mydomain',
         reftype='ahg', reftarget=cl, modname=None, classname=None)
   return sig

在没有 += addnode.pending_xref() 的情况下一切正常(并且其他节点成功追加,除了这个)。

In nodes.py when exception occures, index equals to 0 (but it's nothing tells me).

您可以在 pastebin 上使用我的脚本重现此内容。对于测试,只需使用指令 .. sphinxtest:recipe:: TestRecipe。在我的例子中 make html 之后,我得到了上面的错误。

完整代码:

import re

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

from docutils.statemachine import ViewList
from sphinx.locale import _
from sphinx.domains import Domain, Index, ObjType
from sphinx.domains.std import StandardDomain
from sphinx.roles import XRefRole
from sphinx.directives import ObjectDescription
from sphinx.util.docfields import DocFieldTransformer
from sphinx.util.nodes import make_refnode, nested_parse_with_titles
from sphinx import addnodes


class RecipeObject(ObjectDescription):
    has_content = True
    required_arguments = 1
    option_spec = {}
    final_argument_whitespace = False

    def run(self):
        # Default run function, but added: before_sig
        if ':' in self.name:
            self.domain, self.objtype = self.name.split(':', 1)
        else:
            self.domain, self.objtype = '', self.name
        self.indexnode = addnodes.index(entries=[])

        node = addnodes.desc()
        node.document = self.state.document
        node['domain'] = self.domain
        node['objtype'] = node['desctype'] = self.objtype
        node['noindex'] = noindex = ('noindex' in self.options)

        self.names = []  # type: List[unicode]
        signatures = self.get_signatures()
        for i, sig in enumerate(signatures):
            beforesignode = nodes.container(sig)
            signode = addnodes.desc_signature(sig, '')
            signode['first'] = False
            node.append(beforesignode)
            node.append(signode)
            self.before_sig(beforesignode)
            try:
                name = self.handle_signature(sig, signode)
            except ValueError:
                signode.clear()
                signode += addnodes.desc_name(sig, sig)
                continue
            if name not in self.names:
                self.names.append(name)
                if not noindex:
                    self.add_target_and_index(name, sig, signode)

        contentnode = addnodes.desc_content()
        node.append(contentnode)
        if self.names:
            self.env.temp_data['object'] = self.names[0]
        self.before_content()
        self.state.nested_parse(self.content, self.content_offset, contentnode)
        DocFieldTransformer(self).transform_all(contentnode)
        self.env.temp_data['object'] = None
        self.after_content()
        return [self.indexnode, node]

    def before_sig(self, signode):
        """
        Called before main ``signode`` appends
        """
        pass

    def handle_signature(self, sig, signode):
        signode += nodes.Text('recipe ')
        names = sig.strip().split('.')
        for nm in names:
            print(nm)
            signode += addnodes.pending_xref('', refdomain='sphinxtest', reftype='rec', 
                                            reftarget=nm, modname=None, classname=None)
        return sig

    def add_target_and_index(self, name, sig, signode):
        anchor = '{}-{}'.format(self.objtype, sig)
        full_name = self.get_full_name(name)
        if anchor not in self.state.document.ids:
            signode['names'].append(anchor)
            signode['ids'].append(anchor)
            signode['first'] = not self.names
            self.state.document.note_explicit_target(signode)

            objects = self.env.domaindata['sphinxtest']['objects']
            objects[name] = (self.env.docname, self.objtype, anchor)

        index_text = self.get_index_text(name)
        if index_text:
            self.indexnode['entries'].append(('single', index_text, full_name, full_name, None))

    def get_full_name(self, name):
        return '{}.{}'.format(self.objtype, name)

    def get_index_text(self, cls_name):
        name = _('{} (Recipe)').format(cls_name)
        return name


class RecipeDomain(Domain):
    name = 'sphinxtest'
    label = 'Test'

    roles = {
        'rec': XRefRole(),
    }

    object_types = {
        'recipe': ObjType(_('recipe'), 'rec', 'obj'),
    }

    directives = {
        'recipe': RecipeObject,
    }

    initial_data = {
        'objects': {}
    }

    def clear_doc(self, docname):
        for name, (doc, objtype, anchor) in self.data['objects'].copy().items():
            if doc == docname:
                del self.data['objects'][name]

    def get_objects(self):
        for name, (docname, objtype, anchor) in self.data['objects'].items():
            yield (name, name, objtype, docname, anchor, 1)

    def resolve_xref(self, env, fromdocname, builder,
                     typ, target, node, contnode):
        print('Resolving xref of target: ' + target)
        match = [(name, sig, typ, docname, anchor, prio)
                 for name, sig, typ, docname, anchor, prio
                 in self.get_objects() if sig == target]

        if len(match) > 0:
            print('Match = {}'.format(match))
            todocname = match[0][3]
            targ = match[0][4]

            refnode = make_refnode(builder, fromdocname, todocname, targ, contnode, targ)
            print('RefNode: {}'.format(refnode))

            return make_refnode(builder, fromdocname, todocname, targ, contnode, targ)

        return None

    def merge_domaindata(self, docnames, otherdata):
        pass

    def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
        pass


def setup(app):
    app.add_domain(RecipeDomain)
    return {
        'version': '0.1',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }

通过添加一些文本元素作为子元素解决:

refnode = addnodes.pending_xref('', refdomain='mydomain', reftype='typ', reftarget=t, modname=None, classname=None)
refnode += nodes.Text('Title')