text: """
      sphinx.builders.latex.transforms
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
      Transforms for LaTeX builder.
  
      :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
      :license: BSD, see LICENSE for details.
  """
  
  from typing import cast
  
  from docutils import nodes
  
  from sphinx import addnodes
  from sphinx.builders.latex.nodes import (
      captioned_literal_block, footnotemark, footnotetext, math_reference, thebibliography
  )
  from sphinx.transforms import SphinxTransform
  from sphinx.util.nodes import NodeMatcher
  
  if False:
      # For type annotation
      from typing import Any, Dict, List, Set, Tuple, Union  # NOQA
  
  URI_SCHEMES = ('mailto:', 'http:', 'https:', 'ftp:')
  
  
  class FootnoteDocnameUpdater(SphinxTransform):
      """Add docname to footnote and footnote_reference nodes."""
      default_priority = 700
      TARGET_NODES = (nodes.footnote, nodes.footnote_reference)
  
      def apply(self, **kwargs):
          # type: (Any) -> None
          matcher = NodeMatcher(*self.TARGET_NODES)
          for node in self.document.traverse(matcher):  # type: nodes.Element
              node['docname'] = self.env.docname
  
  
  class ShowUrlsTransform(SphinxTransform):
      """Expand references to inline text or footnotes.
  
      For more information, see :confval:`latex_show_urls`.
  
      .. note:: This transform is used for integrated doctree
      """
      default_priority = 400
  
      # references are expanded to footnotes (or not)
      expanded = False
  
      def apply(self, **kwargs):
          # type: (Any) -> None
          try:
              # replace id_prefix temporarily
              settings = self.document.settings  # type: Any
              id_prefix = settings.id_prefix
              settings.id_prefix = 'show_urls'
  
              self.expand_show_urls()
              if self.expanded:
                  self.renumber_footnotes()
          finally:
              # restore id_prefix
              settings.id_prefix = id_prefix
  
      def expand_show_urls(self):
          # type: () -> None
          show_urls = self.config.latex_show_urls
          if show_urls is False or show_urls == 'no':
              return
  
          for node in self.document.traverse(nodes.reference):
              uri = node.get('refuri', '')
              if uri.startswith(URI_SCHEMES):
                  if uri.startswith('mailto:'):
                      uri = uri[7:]
                  if node.astext() != uri:
                      index = node.parent.index(node)
                      docname = self.get_docname_for_node(node)
                      if show_urls == 'footnote':
                          fn, fnref = self.create_footnote(uri, docname)
                          node.parent.insert(index + 1, fn)
                          node.parent.insert(index + 2, fnref)
  
                          self.expanded = True
                      else:  # all other true values (b/w compat)
                          textnode = nodes.Text(" (%s)" % uri)
                          node.parent.insert(index + 1, textnode)
  
      def get_docname_for_node(self, node):
          # type: (nodes.Node) -> str
          while node:
              if isinstance(node, nodes.document):
                  return self.env.path2doc(node['source'])
              elif isinstance(node, addnodes.start_of_file):
                  return node['docname']
              else:
                  node = node.parent
  
          return None  # never reached here. only for type hinting
  
      def create_footnote(self, uri, docname):
          # type: (str, str) -> Tuple[nodes.footnote, nodes.footnote_reference]
          reference = nodes.reference('', nodes.Text(uri), refuri=uri, nolinkurl=True)
          footnote = nodes.footnote(uri, auto=1, docname=docname)
          footnote['names'].append('#')
          footnote += nodes.label('', '#')
          footnote += nodes.paragraph('', '', reference)
          self.document.note_autofootnote(footnote)
  
          footnote_ref = nodes.footnote_reference('[#]_', auto=1,
                                                  refid=footnote['ids'][0], docname=docname)
          footnote_ref += nodes.Text('#')
          self.document.note_autofootnote_ref(footnote_ref)
          footnote.add_backref(footnote_ref['ids'][0])
  
          return footnote, footnote_ref
  
      def renumber_footnotes(self):
          # type: () -> None
          collector = FootnoteCollector(self.document)
          self.document.walkabout(collector)
  
          num = 0
          for footnote in collector.auto_footnotes:
              # search unused footnote number
              while True:
                  num += 1
                  if str(num) not in collector.used_footnote_numbers:
                      break
  
              # assign new footnote number
              old_label = cast(nodes.label, footnote[0])
              old_label.replace_self(nodes.label('', str(num)))
              if old_label in footnote['names']:
                  footnote['names'].remove(old_label.astext())
              footnote['names'].append(str(num))
  
              # update footnote_references by new footnote number
              docname = footnote['docname']
              for ref in collector.footnote_refs:
                  if docname == ref['docname'] and footnote['ids'][0] == ref['refid']:
                      ref.remove(ref[0])
                      ref += nodes.Text(str(num))
  
  
  class FootnoteCollector(nodes.NodeVisitor):
      """Collect footnotes and footnote references on the document"""
  
      def __init__(self, document):
          # type: (nodes.document) -> None
          self.auto_footnotes = []            # type: List[nodes.footnote]
          self.used_footnote_numbers = set()  # type: Set[str]
          self.footnote_refs = []             # type: List[nodes.footnote_reference]
          super().__init__(document)
  
      def unknown_visit(self, node):
          # type: (nodes.Node) -> None
          pass
  
      def unknown_departure(self, node):
          # type: (nodes.Node) -> None
          pass
  
      def visit_footnote(self, node):
          # type: (nodes.footnote) -> None
          if node.get('auto'):
              self.auto_footnotes.append(node)
          else:
              for name in node['names']:
                  self.used_footnote_numbers.add(name)
  
      def visit_footnote_reference(self, node):
          # type: (nodes.footnote_reference) -> None
          self.footnote_refs.append(node)
  
  
  class LaTeXFootnoteTransform(SphinxTransform):
      """Convert footnote definitions and references to appropriate form to LaTeX.
  
      * Replace footnotes on restricted zone (e.g. headings) by footnotemark node.
        In addition, append a footnotetext node after the zone.
  
        Before::
  
            <section>
                <title>
                    headings having footnotes
                    <footnote_reference>
                        1
                <footnote ids="1">
                    <label>
                        1
                    <paragraph>
                        footnote body
  
        After::
  
            <section>
                <title>
                    headings having footnotes
                    <footnotemark>
                        1
                <footnotetext>
                    footnote body
                <footnotetext>
                    <label>
                        1
                    <paragraph>
                        footnote body
  
      * Integrate footnote definitions and footnote references to single footnote node
  
        Before::
  
            blah blah blah
            <footnote_reference refid="id1">
                1
            blah blah blah ...
  
            <footnote ids="1">
                <label>
                    1
                <paragraph>
                    footnote body
  
        After::
  
            blah blah blah
            <footnote ids="1">
                <label>
                    1
                <paragraph>
                    footnote body
            blah blah blah ...
  
      * Replace second and subsequent footnote references which refers same footnote definition
        by footnotemark node.
  
        Before::
  
            blah blah blah
            <footnote_reference refid="id1">
                1
            blah blah blah
            <footnote_reference refid="id1">
                1
            blah blah blah ...
  
            <footnote ids="1">
                <label>
                    1
                <paragraph>
                    footnote body
  
        After::
  
            blah blah blah
            <footnote ids="1">
                <label>
                    1
                <paragraph>
                    footnote body
            blah blah blah
            <footnotemark>
                1
            blah blah blah ...
  
      * Remove unreferenced footnotes
  
        Before::
  
            <footnote ids="1">
                <label>
                    1
                <paragraph>
                    Unreferenced footnote!
  
        After::
  
            <!-- nothing! -->
  
      * Move footnotes in a title of table or thead to head of tbody
  
        Before::
  
            <table>
                <title>
                    title having footnote_reference
                    <footnote_reference refid="1">
                        1
                <tgroup>
                    <thead>
                        <row>
                            <entry>
                                header having footnote_reference
                                <footnote_reference refid="2">
                                    2
                    <tbody>
                        <row>
                        ...
  
            <footnote ids="1">
                <label>
                    1
                <paragraph>
                    footnote body
  
            <footnote ids="2">
                <label>
                    2
                <paragraph>
                    footnote body
  
        After::
  
            <table>
                <title>
                    title having footnote_reference
                    <footnotemark>
                        1
                <tgroup>
                    <thead>
                        <row>
                            <entry>
                                header having footnote_reference
                                <footnotemark>
                                    2
                    <tbody>
                        <footnotetext>
                            <label>
                                1
                            <paragraph>
                                footnote body
  
                        <footnotetext>
                            <label>
                                2
                            <paragraph>
                                footnote body
                        <row>
                        ...
      """
  
      default_priority = 600
  
      def apply(self, **kwargs):
          # type: (Any) -> None
          footnotes = list(self.document.traverse(nodes.footnote))
          for node in footnotes:
              node.parent.remove(node)
  
          visitor = LaTeXFootnoteVisitor(self.document, footnotes)
          self.document.walkabout(visitor)
  
  
  class LaTeXFootnoteVisitor(nodes.NodeVisitor):
      def __init__(self, document, footnotes):
          # type: (nodes.document, List[nodes.footnote]) -> None
          self.appeared = set()       # type: Set[Tuple[str, str]]
          self.footnotes = footnotes  # type: List[nodes.footnote]
          self.pendings = []          # type: List[nodes.footnote]
          self.table_footnotes = []   # type: List[nodes.footnote]
          self.restricted = None      # type: nodes.Element
          super().__init__(document)
  
      def unknown_visit(self, node):
          # type: (nodes.Node) -> None
          pass
  
      def unknown_departure(self, node):
          # type: (nodes.Node) -> None
          pass
  
      def restrict(self, node):
          # type: (nodes.Element) -> None
          if self.restricted is None:
              self.restricted = node
  
      def unrestrict(self, node):
          # type: (nodes.Element) -> None
          if self.restricted == node:
              self.restricted = None
              pos = node.parent.index(node)
              for i, footnote, in enumerate(self.pendings):
                  fntext = footnotetext('', *footnote.children)
                  node.parent.insert(pos + i + 1, fntext)
              self.pendings = []
  
      def visit_figure(self, node):
          # type: (nodes.figure) -> None
          self.restrict(node)
  
      def depart_figure(self, node):
          # type: (nodes.figure) -> None
          self.unrestrict(node)
  
      def visit_term(self, node):
          # type: (nodes.term) -> None
          self.restrict(node)
  
      def depart_term(self, node):
          # type: (nodes.term) -> None
          self.unrestrict(node)
  
      def visit_caption(self, node):
          # type: (nodes.caption) -> None
          self.restrict(node)
  
      def depart_caption(self, node):
          # type: (nodes.caption) -> None
          self.unrestrict(node)
  
      def visit_title(self, node):
          # type: (nodes.title) -> None
          if isinstance(node.parent, (nodes.section, nodes.table)):
              self.restrict(node)
  
      def depart_title(self, node):
          # type: (nodes.title) -> None
          if isinstance(node.parent, nodes.section):
              self.unrestrict(node)
          elif isinstance(node.parent, nodes.table):
              self.table_footnotes += self.pendings
              self.pendings = []
              self.unrestrict(node)
  
      def visit_thead(self, node):
          # type: (nodes.thead) -> None
          self.restrict(node)
  
      def depart_thead(self, node):
          # type: (nodes.thead) -> None
          self.table_footnotes += self.pendings
          self.pendings = []
          self.unrestrict(node)
  
      def depart_table(self, node):
          # type: (nodes.table) -> None
          tbody = list(node.traverse(nodes.tbody))[0]
          for footnote in reversed(self.table_footnotes):
              fntext = footnotetext('', *footnote.children)
              tbody.insert(0, fntext)
  
          self.table_footnotes = []
  
      def visit_footnote(self, node):
          # type: (nodes.footnote) -> None
          self.restrict(node)
  
      def depart_footnote(self, node):
          # type: (nodes.footnote) -> None
          self.unrestrict(node)
  
      def visit_footnote_reference(self, node):
          # type: (nodes.footnote_reference) -> None
          number = node.astext().strip()
          docname = node['docname']
          if self.restricted:
              mark = footnotemark('', number)
              node.replace_self(mark)
              if (docname, number) not in self.appeared:
                  footnote = self.get_footnote_by_reference(node)
                  self.pendings.append(footnote)
          elif (docname, number) in self.appeared:
              mark = footnotemark('', number)
              node.replace_self(mark)
          else:
              footnote = self.get_footnote_by_reference(node)
              self.footnotes.remove(footnote)
              node.replace_self(footnote)
              footnote.walkabout(self)
  
          self.appeared.add((docname, number))
          raise nodes.SkipNode
  
      def get_footnote_by_reference(self, node):
          # type: (nodes.footnote_reference) -> nodes.footnote
          docname = node['docname']
          for footnote in self.footnotes:
              if docname == footnote['docname'] and footnote['ids'][0] == node['refid']:
                  return footnote
  
          return None
  
  
  class BibliographyTransform(SphinxTransform):
      """Gather bibliography entries to tail of document.
  
      Before::
  
          <document>
              <paragraph>
                  blah blah blah
              <citation>
                  ...
              <paragraph>
                  blah blah blah
              <citation>
                  ...
              ...
  
      After::
  
          <document>
              <paragraph>
                  blah blah blah
              <paragraph>
                  blah blah blah
              ...
              <thebibliography>
                  <citation>
                      ...
                  <citation>
                      ...
      """
      default_priority = 750
  
      def apply(self, **kwargs):
          # type: (Any) -> None
          citations = thebibliography()
          for node in self.document.traverse(nodes.citation):
              node.parent.remove(node)
              citations += node
  
          if len(citations) > 0:
              self.document += citations
  
  
  class CitationReferenceTransform(SphinxTransform):
      """Replace pending_xref nodes for citation by citation_reference.
  
      To handle citation reference easily on LaTeX writer, this converts
      pending_xref nodes to citation_reference.
      """
      default_priority = 5  # before ReferencesResolver
  
      def apply(self, **kwargs):
          # type: (Any) -> None
          if self.app.builder.name != 'latex':
              return
  
          matcher = NodeMatcher(addnodes.pending_xref, refdomain='std', reftype='citation')
          citations = self.env.get_domain('std').data['citations']
          for node in self.document.traverse(matcher):  # type: addnodes.pending_xref
              docname, labelid, _ = citations.get(node['reftarget'], ('', '', 0))
              if docname:
                  citation_ref = nodes.citation_reference('', '', *node.children,
                                                          docname=docname, refname=labelid)
                  node.replace_self(citation_ref)
  
  
  class MathReferenceTransform(SphinxTransform):
      """Replace pending_xref nodes for math by math_reference.
  
      To handle math reference easily on LaTeX writer, this converts pending_xref
      nodes to math_reference.
      """
      default_priority = 5  # before ReferencesResolver
  
      def apply(self, **kwargs):
          # type: (Any) -> None
          if self.app.builder.name != 'latex':
              return
  
          equations = self.env.get_domain('math').data['objects']
          for node in self.document.traverse(addnodes.pending_xref):
              if node['refdomain'] == 'math' and node['reftype'] in ('eq', 'numref'):
                  docname, _ = equations.get(node['reftarget'], (None, None))
                  if docname:
                      refnode = math_reference('', docname=docname, target=node['reftarget'])
                      node.replace_self(refnode)
  
  
  class LiteralBlockTransform(SphinxTransform):
      """Replace container nodes for literal_block by captioned_literal_block."""
      default_priority = 400
  
      def apply(self, **kwargs):
          # type: (Any) -> None
          if self.app.builder.name != 'latex':
              return
  
          matcher = NodeMatcher(nodes.container, literal_block=True)
          for node in self.document.traverse(matcher):  # type: nodes.container
              newnode = captioned_literal_block('', *node.children, **node.attributes)
              node.replace_self(newnode)
  
  
  class DocumentTargetTransform(SphinxTransform):
      """Add :doc label to the first section of each document."""
      default_priority = 400
  
      def apply(self, **kwargs):
          # type: (Any) -> None
          if self.app.builder.name != 'latex':
              return
  
          for node in self.document.traverse(addnodes.start_of_file):
              section = node.next_node(nodes.section)
              if section:
                  section['ids'].append(':doc')  # special label for :doc:
  