Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/docutils/transforms/references.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
| author | shellac |
|---|---|
| date | Sat, 02 May 2020 07:14:21 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:26e78fe6e8c4 |
|---|---|
| 1 # $Id: references.py 8387 2019-09-06 13:16:34Z milde $ | |
| 2 # Author: David Goodger <goodger@python.org> | |
| 3 # Copyright: This module has been placed in the public domain. | |
| 4 | |
| 5 """ | |
| 6 Transforms for resolving references. | |
| 7 """ | |
| 8 | |
| 9 __docformat__ = 'reStructuredText' | |
| 10 | |
| 11 import sys | |
| 12 import re | |
| 13 from docutils import nodes, utils | |
| 14 from docutils.transforms import TransformError, Transform | |
| 15 | |
| 16 | |
| 17 class PropagateTargets(Transform): | |
| 18 | |
| 19 """ | |
| 20 Propagate empty internal targets to the next element. | |
| 21 | |
| 22 Given the following nodes:: | |
| 23 | |
| 24 <target ids="internal1" names="internal1"> | |
| 25 <target anonymous="1" ids="id1"> | |
| 26 <target ids="internal2" names="internal2"> | |
| 27 <paragraph> | |
| 28 This is a test. | |
| 29 | |
| 30 PropagateTargets propagates the ids and names of the internal | |
| 31 targets preceding the paragraph to the paragraph itself:: | |
| 32 | |
| 33 <target refid="internal1"> | |
| 34 <target anonymous="1" refid="id1"> | |
| 35 <target refid="internal2"> | |
| 36 <paragraph ids="internal2 id1 internal1" names="internal2 internal1"> | |
| 37 This is a test. | |
| 38 """ | |
| 39 | |
| 40 default_priority = 260 | |
| 41 | |
| 42 def apply(self): | |
| 43 for target in self.document.traverse(nodes.target): | |
| 44 # Only block-level targets without reference (like ".. target:"): | |
| 45 if (isinstance(target.parent, nodes.TextElement) or | |
| 46 (target.hasattr('refid') or target.hasattr('refuri') or | |
| 47 target.hasattr('refname'))): | |
| 48 continue | |
| 49 assert len(target) == 0, 'error: block-level target has children' | |
| 50 next_node = target.next_node(ascend=True) | |
| 51 # Do not move names and ids into Invisibles (we'd lose the | |
| 52 # attributes) or different Targetables (e.g. footnotes). | |
| 53 if (next_node is not None and | |
| 54 ((not isinstance(next_node, nodes.Invisible) and | |
| 55 not isinstance(next_node, nodes.Targetable)) or | |
| 56 isinstance(next_node, nodes.target))): | |
| 57 next_node['ids'].extend(target['ids']) | |
| 58 next_node['names'].extend(target['names']) | |
| 59 # Set defaults for next_node.expect_referenced_by_name/id. | |
| 60 if not hasattr(next_node, 'expect_referenced_by_name'): | |
| 61 next_node.expect_referenced_by_name = {} | |
| 62 if not hasattr(next_node, 'expect_referenced_by_id'): | |
| 63 next_node.expect_referenced_by_id = {} | |
| 64 for id in target['ids']: | |
| 65 # Update IDs to node mapping. | |
| 66 self.document.ids[id] = next_node | |
| 67 # If next_node is referenced by id ``id``, this | |
| 68 # target shall be marked as referenced. | |
| 69 next_node.expect_referenced_by_id[id] = target | |
| 70 for name in target['names']: | |
| 71 next_node.expect_referenced_by_name[name] = target | |
| 72 # If there are any expect_referenced_by_... attributes | |
| 73 # in target set, copy them to next_node. | |
| 74 next_node.expect_referenced_by_name.update( | |
| 75 getattr(target, 'expect_referenced_by_name', {})) | |
| 76 next_node.expect_referenced_by_id.update( | |
| 77 getattr(target, 'expect_referenced_by_id', {})) | |
| 78 # Set refid to point to the first former ID of target | |
| 79 # which is now an ID of next_node. | |
| 80 target['refid'] = target['ids'][0] | |
| 81 # Clear ids and names; they have been moved to | |
| 82 # next_node. | |
| 83 target['ids'] = [] | |
| 84 target['names'] = [] | |
| 85 self.document.note_refid(target) | |
| 86 | |
| 87 | |
| 88 class AnonymousHyperlinks(Transform): | |
| 89 | |
| 90 """ | |
| 91 Link anonymous references to targets. Given:: | |
| 92 | |
| 93 <paragraph> | |
| 94 <reference anonymous="1"> | |
| 95 internal | |
| 96 <reference anonymous="1"> | |
| 97 external | |
| 98 <target anonymous="1" ids="id1"> | |
| 99 <target anonymous="1" ids="id2" refuri="http://external"> | |
| 100 | |
| 101 Corresponding references are linked via "refid" or resolved via "refuri":: | |
| 102 | |
| 103 <paragraph> | |
| 104 <reference anonymous="1" refid="id1"> | |
| 105 text | |
| 106 <reference anonymous="1" refuri="http://external"> | |
| 107 external | |
| 108 <target anonymous="1" ids="id1"> | |
| 109 <target anonymous="1" ids="id2" refuri="http://external"> | |
| 110 """ | |
| 111 | |
| 112 default_priority = 440 | |
| 113 | |
| 114 def apply(self): | |
| 115 anonymous_refs = [] | |
| 116 anonymous_targets = [] | |
| 117 for node in self.document.traverse(nodes.reference): | |
| 118 if node.get('anonymous'): | |
| 119 anonymous_refs.append(node) | |
| 120 for node in self.document.traverse(nodes.target): | |
| 121 if node.get('anonymous'): | |
| 122 anonymous_targets.append(node) | |
| 123 if len(anonymous_refs) \ | |
| 124 != len(anonymous_targets): | |
| 125 msg = self.document.reporter.error( | |
| 126 'Anonymous hyperlink mismatch: %s references but %s ' | |
| 127 'targets.\nSee "backrefs" attribute for IDs.' | |
| 128 % (len(anonymous_refs), len(anonymous_targets))) | |
| 129 msgid = self.document.set_id(msg) | |
| 130 for ref in anonymous_refs: | |
| 131 prb = nodes.problematic( | |
| 132 ref.rawsource, ref.rawsource, refid=msgid) | |
| 133 prbid = self.document.set_id(prb) | |
| 134 msg.add_backref(prbid) | |
| 135 ref.replace_self(prb) | |
| 136 return | |
| 137 for ref, target in zip(anonymous_refs, anonymous_targets): | |
| 138 target.referenced = 1 | |
| 139 while True: | |
| 140 if target.hasattr('refuri'): | |
| 141 ref['refuri'] = target['refuri'] | |
| 142 ref.resolved = 1 | |
| 143 break | |
| 144 else: | |
| 145 if not target['ids']: | |
| 146 # Propagated target. | |
| 147 target = self.document.ids[target['refid']] | |
| 148 continue | |
| 149 ref['refid'] = target['ids'][0] | |
| 150 self.document.note_refid(ref) | |
| 151 break | |
| 152 | |
| 153 | |
| 154 class IndirectHyperlinks(Transform): | |
| 155 | |
| 156 """ | |
| 157 a) Indirect external references:: | |
| 158 | |
| 159 <paragraph> | |
| 160 <reference refname="indirect external"> | |
| 161 indirect external | |
| 162 <target id="id1" name="direct external" | |
| 163 refuri="http://indirect"> | |
| 164 <target id="id2" name="indirect external" | |
| 165 refname="direct external"> | |
| 166 | |
| 167 The "refuri" attribute is migrated back to all indirect targets | |
| 168 from the final direct target (i.e. a target not referring to | |
| 169 another indirect target):: | |
| 170 | |
| 171 <paragraph> | |
| 172 <reference refname="indirect external"> | |
| 173 indirect external | |
| 174 <target id="id1" name="direct external" | |
| 175 refuri="http://indirect"> | |
| 176 <target id="id2" name="indirect external" | |
| 177 refuri="http://indirect"> | |
| 178 | |
| 179 Once the attribute is migrated, the preexisting "refname" attribute | |
| 180 is dropped. | |
| 181 | |
| 182 b) Indirect internal references:: | |
| 183 | |
| 184 <target id="id1" name="final target"> | |
| 185 <paragraph> | |
| 186 <reference refname="indirect internal"> | |
| 187 indirect internal | |
| 188 <target id="id2" name="indirect internal 2" | |
| 189 refname="final target"> | |
| 190 <target id="id3" name="indirect internal" | |
| 191 refname="indirect internal 2"> | |
| 192 | |
| 193 Targets which indirectly refer to an internal target become one-hop | |
| 194 indirect (their "refid" attributes are directly set to the internal | |
| 195 target's "id"). References which indirectly refer to an internal | |
| 196 target become direct internal references:: | |
| 197 | |
| 198 <target id="id1" name="final target"> | |
| 199 <paragraph> | |
| 200 <reference refid="id1"> | |
| 201 indirect internal | |
| 202 <target id="id2" name="indirect internal 2" refid="id1"> | |
| 203 <target id="id3" name="indirect internal" refid="id1"> | |
| 204 """ | |
| 205 | |
| 206 default_priority = 460 | |
| 207 | |
| 208 def apply(self): | |
| 209 for target in self.document.indirect_targets: | |
| 210 if not target.resolved: | |
| 211 self.resolve_indirect_target(target) | |
| 212 self.resolve_indirect_references(target) | |
| 213 | |
| 214 def resolve_indirect_target(self, target): | |
| 215 refname = target.get('refname') | |
| 216 if refname is None: | |
| 217 reftarget_id = target['refid'] | |
| 218 else: | |
| 219 reftarget_id = self.document.nameids.get(refname) | |
| 220 if not reftarget_id: | |
| 221 # Check the unknown_reference_resolvers | |
| 222 for resolver_function in \ | |
| 223 self.document.transformer.unknown_reference_resolvers: | |
| 224 if resolver_function(target): | |
| 225 break | |
| 226 else: | |
| 227 self.nonexistent_indirect_target(target) | |
| 228 return | |
| 229 reftarget = self.document.ids[reftarget_id] | |
| 230 reftarget.note_referenced_by(id=reftarget_id) | |
| 231 if isinstance(reftarget, nodes.target) \ | |
| 232 and not reftarget.resolved and reftarget.hasattr('refname'): | |
| 233 if hasattr(target, 'multiply_indirect'): | |
| 234 #and target.multiply_indirect): | |
| 235 #del target.multiply_indirect | |
| 236 self.circular_indirect_reference(target) | |
| 237 return | |
| 238 target.multiply_indirect = 1 | |
| 239 self.resolve_indirect_target(reftarget) # multiply indirect | |
| 240 del target.multiply_indirect | |
| 241 if reftarget.hasattr('refuri'): | |
| 242 target['refuri'] = reftarget['refuri'] | |
| 243 if 'refid' in target: | |
| 244 del target['refid'] | |
| 245 elif reftarget.hasattr('refid'): | |
| 246 target['refid'] = reftarget['refid'] | |
| 247 self.document.note_refid(target) | |
| 248 else: | |
| 249 if reftarget['ids']: | |
| 250 target['refid'] = reftarget_id | |
| 251 self.document.note_refid(target) | |
| 252 else: | |
| 253 self.nonexistent_indirect_target(target) | |
| 254 return | |
| 255 if refname is not None: | |
| 256 del target['refname'] | |
| 257 target.resolved = 1 | |
| 258 | |
| 259 def nonexistent_indirect_target(self, target): | |
| 260 if target['refname'] in self.document.nameids: | |
| 261 self.indirect_target_error(target, 'which is a duplicate, and ' | |
| 262 'cannot be used as a unique reference') | |
| 263 else: | |
| 264 self.indirect_target_error(target, 'which does not exist') | |
| 265 | |
| 266 def circular_indirect_reference(self, target): | |
| 267 self.indirect_target_error(target, 'forming a circular reference') | |
| 268 | |
| 269 def indirect_target_error(self, target, explanation): | |
| 270 naming = '' | |
| 271 reflist = [] | |
| 272 if target['names']: | |
| 273 naming = '"%s" ' % target['names'][0] | |
| 274 for name in target['names']: | |
| 275 reflist.extend(self.document.refnames.get(name, [])) | |
| 276 for id in target['ids']: | |
| 277 reflist.extend(self.document.refids.get(id, [])) | |
| 278 if target['ids']: | |
| 279 naming += '(id="%s")' % target['ids'][0] | |
| 280 msg = self.document.reporter.error( | |
| 281 'Indirect hyperlink target %s refers to target "%s", %s.' | |
| 282 % (naming, target['refname'], explanation), base_node=target) | |
| 283 msgid = self.document.set_id(msg) | |
| 284 for ref in utils.uniq(reflist): | |
| 285 prb = nodes.problematic( | |
| 286 ref.rawsource, ref.rawsource, refid=msgid) | |
| 287 prbid = self.document.set_id(prb) | |
| 288 msg.add_backref(prbid) | |
| 289 ref.replace_self(prb) | |
| 290 target.resolved = 1 | |
| 291 | |
| 292 def resolve_indirect_references(self, target): | |
| 293 if target.hasattr('refid'): | |
| 294 attname = 'refid' | |
| 295 call_method = self.document.note_refid | |
| 296 elif target.hasattr('refuri'): | |
| 297 attname = 'refuri' | |
| 298 call_method = None | |
| 299 else: | |
| 300 return | |
| 301 attval = target[attname] | |
| 302 for name in target['names']: | |
| 303 reflist = self.document.refnames.get(name, []) | |
| 304 if reflist: | |
| 305 target.note_referenced_by(name=name) | |
| 306 for ref in reflist: | |
| 307 if ref.resolved: | |
| 308 continue | |
| 309 del ref['refname'] | |
| 310 ref[attname] = attval | |
| 311 if call_method: | |
| 312 call_method(ref) | |
| 313 ref.resolved = 1 | |
| 314 if isinstance(ref, nodes.target): | |
| 315 self.resolve_indirect_references(ref) | |
| 316 for id in target['ids']: | |
| 317 reflist = self.document.refids.get(id, []) | |
| 318 if reflist: | |
| 319 target.note_referenced_by(id=id) | |
| 320 for ref in reflist: | |
| 321 if ref.resolved: | |
| 322 continue | |
| 323 del ref['refid'] | |
| 324 ref[attname] = attval | |
| 325 if call_method: | |
| 326 call_method(ref) | |
| 327 ref.resolved = 1 | |
| 328 if isinstance(ref, nodes.target): | |
| 329 self.resolve_indirect_references(ref) | |
| 330 | |
| 331 | |
| 332 class ExternalTargets(Transform): | |
| 333 | |
| 334 """ | |
| 335 Given:: | |
| 336 | |
| 337 <paragraph> | |
| 338 <reference refname="direct external"> | |
| 339 direct external | |
| 340 <target id="id1" name="direct external" refuri="http://direct"> | |
| 341 | |
| 342 The "refname" attribute is replaced by the direct "refuri" attribute:: | |
| 343 | |
| 344 <paragraph> | |
| 345 <reference refuri="http://direct"> | |
| 346 direct external | |
| 347 <target id="id1" name="direct external" refuri="http://direct"> | |
| 348 """ | |
| 349 | |
| 350 default_priority = 640 | |
| 351 | |
| 352 def apply(self): | |
| 353 for target in self.document.traverse(nodes.target): | |
| 354 if target.hasattr('refuri'): | |
| 355 refuri = target['refuri'] | |
| 356 for name in target['names']: | |
| 357 reflist = self.document.refnames.get(name, []) | |
| 358 if reflist: | |
| 359 target.note_referenced_by(name=name) | |
| 360 for ref in reflist: | |
| 361 if ref.resolved: | |
| 362 continue | |
| 363 del ref['refname'] | |
| 364 ref['refuri'] = refuri | |
| 365 ref.resolved = 1 | |
| 366 | |
| 367 | |
| 368 class InternalTargets(Transform): | |
| 369 | |
| 370 default_priority = 660 | |
| 371 | |
| 372 def apply(self): | |
| 373 for target in self.document.traverse(nodes.target): | |
| 374 if not target.hasattr('refuri') and not target.hasattr('refid'): | |
| 375 self.resolve_reference_ids(target) | |
| 376 | |
| 377 def resolve_reference_ids(self, target): | |
| 378 """ | |
| 379 Given:: | |
| 380 | |
| 381 <paragraph> | |
| 382 <reference refname="direct internal"> | |
| 383 direct internal | |
| 384 <target id="id1" name="direct internal"> | |
| 385 | |
| 386 The "refname" attribute is replaced by "refid" linking to the target's | |
| 387 "id":: | |
| 388 | |
| 389 <paragraph> | |
| 390 <reference refid="id1"> | |
| 391 direct internal | |
| 392 <target id="id1" name="direct internal"> | |
| 393 """ | |
| 394 for name in target['names']: | |
| 395 refid = self.document.nameids.get(name) | |
| 396 reflist = self.document.refnames.get(name, []) | |
| 397 if reflist: | |
| 398 target.note_referenced_by(name=name) | |
| 399 for ref in reflist: | |
| 400 if ref.resolved: | |
| 401 continue | |
| 402 if refid: | |
| 403 del ref['refname'] | |
| 404 ref['refid'] = refid | |
| 405 ref.resolved = 1 | |
| 406 | |
| 407 | |
| 408 class Footnotes(Transform): | |
| 409 | |
| 410 """ | |
| 411 Assign numbers to autonumbered footnotes, and resolve links to footnotes, | |
| 412 citations, and their references. | |
| 413 | |
| 414 Given the following ``document`` as input:: | |
| 415 | |
| 416 <document> | |
| 417 <paragraph> | |
| 418 A labeled autonumbered footnote referece: | |
| 419 <footnote_reference auto="1" id="id1" refname="footnote"> | |
| 420 <paragraph> | |
| 421 An unlabeled autonumbered footnote referece: | |
| 422 <footnote_reference auto="1" id="id2"> | |
| 423 <footnote auto="1" id="id3"> | |
| 424 <paragraph> | |
| 425 Unlabeled autonumbered footnote. | |
| 426 <footnote auto="1" id="footnote" name="footnote"> | |
| 427 <paragraph> | |
| 428 Labeled autonumbered footnote. | |
| 429 | |
| 430 Auto-numbered footnotes have attribute ``auto="1"`` and no label. | |
| 431 Auto-numbered footnote_references have no reference text (they're | |
| 432 empty elements). When resolving the numbering, a ``label`` element | |
| 433 is added to the beginning of the ``footnote``, and reference text | |
| 434 to the ``footnote_reference``. | |
| 435 | |
| 436 The transformed result will be:: | |
| 437 | |
| 438 <document> | |
| 439 <paragraph> | |
| 440 A labeled autonumbered footnote referece: | |
| 441 <footnote_reference auto="1" id="id1" refid="footnote"> | |
| 442 2 | |
| 443 <paragraph> | |
| 444 An unlabeled autonumbered footnote referece: | |
| 445 <footnote_reference auto="1" id="id2" refid="id3"> | |
| 446 1 | |
| 447 <footnote auto="1" id="id3" backrefs="id2"> | |
| 448 <label> | |
| 449 1 | |
| 450 <paragraph> | |
| 451 Unlabeled autonumbered footnote. | |
| 452 <footnote auto="1" id="footnote" name="footnote" backrefs="id1"> | |
| 453 <label> | |
| 454 2 | |
| 455 <paragraph> | |
| 456 Labeled autonumbered footnote. | |
| 457 | |
| 458 Note that the footnotes are not in the same order as the references. | |
| 459 | |
| 460 The labels and reference text are added to the auto-numbered ``footnote`` | |
| 461 and ``footnote_reference`` elements. Footnote elements are backlinked to | |
| 462 their references via "refids" attributes. References are assigned "id" | |
| 463 and "refid" attributes. | |
| 464 | |
| 465 After adding labels and reference text, the "auto" attributes can be | |
| 466 ignored. | |
| 467 """ | |
| 468 | |
| 469 default_priority = 620 | |
| 470 | |
| 471 autofootnote_labels = None | |
| 472 """Keep track of unlabeled autonumbered footnotes.""" | |
| 473 | |
| 474 symbols = [ | |
| 475 # Entries 1-4 and 6 below are from section 12.51 of | |
| 476 # The Chicago Manual of Style, 14th edition. | |
| 477 '*', # asterisk/star | |
| 478 u'\u2020', # dagger † | |
| 479 u'\u2021', # double dagger ‡ | |
| 480 u'\u00A7', # section mark § | |
| 481 u'\u00B6', # paragraph mark (pilcrow) ¶ | |
| 482 # (parallels ['||'] in CMoS) | |
| 483 '#', # number sign | |
| 484 # The entries below were chosen arbitrarily. | |
| 485 u'\u2660', # spade suit ♠ | |
| 486 u'\u2665', # heart suit ♥ | |
| 487 u'\u2666', # diamond suit ♦ | |
| 488 u'\u2663', # club suit ♣ | |
| 489 ] | |
| 490 | |
| 491 def apply(self): | |
| 492 self.autofootnote_labels = [] | |
| 493 startnum = self.document.autofootnote_start | |
| 494 self.document.autofootnote_start = self.number_footnotes(startnum) | |
| 495 self.number_footnote_references(startnum) | |
| 496 self.symbolize_footnotes() | |
| 497 self.resolve_footnotes_and_citations() | |
| 498 | |
| 499 def number_footnotes(self, startnum): | |
| 500 """ | |
| 501 Assign numbers to autonumbered footnotes. | |
| 502 | |
| 503 For labeled autonumbered footnotes, copy the number over to | |
| 504 corresponding footnote references. | |
| 505 """ | |
| 506 for footnote in self.document.autofootnotes: | |
| 507 while True: | |
| 508 label = str(startnum) | |
| 509 startnum += 1 | |
| 510 if label not in self.document.nameids: | |
| 511 break | |
| 512 footnote.insert(0, nodes.label('', label)) | |
| 513 for name in footnote['names']: | |
| 514 for ref in self.document.footnote_refs.get(name, []): | |
| 515 ref += nodes.Text(label) | |
| 516 ref.delattr('refname') | |
| 517 assert len(footnote['ids']) == len(ref['ids']) == 1 | |
| 518 ref['refid'] = footnote['ids'][0] | |
| 519 footnote.add_backref(ref['ids'][0]) | |
| 520 self.document.note_refid(ref) | |
| 521 ref.resolved = 1 | |
| 522 if not footnote['names'] and not footnote['dupnames']: | |
| 523 footnote['names'].append(label) | |
| 524 self.document.note_explicit_target(footnote, footnote) | |
| 525 self.autofootnote_labels.append(label) | |
| 526 return startnum | |
| 527 | |
| 528 def number_footnote_references(self, startnum): | |
| 529 """Assign numbers to autonumbered footnote references.""" | |
| 530 i = 0 | |
| 531 for ref in self.document.autofootnote_refs: | |
| 532 if ref.resolved or ref.hasattr('refid'): | |
| 533 continue | |
| 534 try: | |
| 535 label = self.autofootnote_labels[i] | |
| 536 except IndexError: | |
| 537 msg = self.document.reporter.error( | |
| 538 'Too many autonumbered footnote references: only %s ' | |
| 539 'corresponding footnotes available.' | |
| 540 % len(self.autofootnote_labels), base_node=ref) | |
| 541 msgid = self.document.set_id(msg) | |
| 542 for ref in self.document.autofootnote_refs[i:]: | |
| 543 if ref.resolved or ref.hasattr('refname'): | |
| 544 continue | |
| 545 prb = nodes.problematic( | |
| 546 ref.rawsource, ref.rawsource, refid=msgid) | |
| 547 prbid = self.document.set_id(prb) | |
| 548 msg.add_backref(prbid) | |
| 549 ref.replace_self(prb) | |
| 550 break | |
| 551 ref += nodes.Text(label) | |
| 552 id = self.document.nameids[label] | |
| 553 footnote = self.document.ids[id] | |
| 554 ref['refid'] = id | |
| 555 self.document.note_refid(ref) | |
| 556 assert len(ref['ids']) == 1 | |
| 557 footnote.add_backref(ref['ids'][0]) | |
| 558 ref.resolved = 1 | |
| 559 i += 1 | |
| 560 | |
| 561 def symbolize_footnotes(self): | |
| 562 """Add symbols indexes to "[*]"-style footnotes and references.""" | |
| 563 labels = [] | |
| 564 for footnote in self.document.symbol_footnotes: | |
| 565 reps, index = divmod(self.document.symbol_footnote_start, | |
| 566 len(self.symbols)) | |
| 567 labeltext = self.symbols[index] * (reps + 1) | |
| 568 labels.append(labeltext) | |
| 569 footnote.insert(0, nodes.label('', labeltext)) | |
| 570 self.document.symbol_footnote_start += 1 | |
| 571 self.document.set_id(footnote) | |
| 572 i = 0 | |
| 573 for ref in self.document.symbol_footnote_refs: | |
| 574 try: | |
| 575 ref += nodes.Text(labels[i]) | |
| 576 except IndexError: | |
| 577 msg = self.document.reporter.error( | |
| 578 'Too many symbol footnote references: only %s ' | |
| 579 'corresponding footnotes available.' % len(labels), | |
| 580 base_node=ref) | |
| 581 msgid = self.document.set_id(msg) | |
| 582 for ref in self.document.symbol_footnote_refs[i:]: | |
| 583 if ref.resolved or ref.hasattr('refid'): | |
| 584 continue | |
| 585 prb = nodes.problematic( | |
| 586 ref.rawsource, ref.rawsource, refid=msgid) | |
| 587 prbid = self.document.set_id(prb) | |
| 588 msg.add_backref(prbid) | |
| 589 ref.replace_self(prb) | |
| 590 break | |
| 591 footnote = self.document.symbol_footnotes[i] | |
| 592 assert len(footnote['ids']) == 1 | |
| 593 ref['refid'] = footnote['ids'][0] | |
| 594 self.document.note_refid(ref) | |
| 595 footnote.add_backref(ref['ids'][0]) | |
| 596 i += 1 | |
| 597 | |
| 598 def resolve_footnotes_and_citations(self): | |
| 599 """ | |
| 600 Link manually-labeled footnotes and citations to/from their | |
| 601 references. | |
| 602 """ | |
| 603 for footnote in self.document.footnotes: | |
| 604 for label in footnote['names']: | |
| 605 if label in self.document.footnote_refs: | |
| 606 reflist = self.document.footnote_refs[label] | |
| 607 self.resolve_references(footnote, reflist) | |
| 608 for citation in self.document.citations: | |
| 609 for label in citation['names']: | |
| 610 if label in self.document.citation_refs: | |
| 611 reflist = self.document.citation_refs[label] | |
| 612 self.resolve_references(citation, reflist) | |
| 613 | |
| 614 def resolve_references(self, note, reflist): | |
| 615 assert len(note['ids']) == 1 | |
| 616 id = note['ids'][0] | |
| 617 for ref in reflist: | |
| 618 if ref.resolved: | |
| 619 continue | |
| 620 ref.delattr('refname') | |
| 621 ref['refid'] = id | |
| 622 assert len(ref['ids']) == 1 | |
| 623 note.add_backref(ref['ids'][0]) | |
| 624 ref.resolved = 1 | |
| 625 note.resolved = 1 | |
| 626 | |
| 627 | |
| 628 class CircularSubstitutionDefinitionError(Exception): pass | |
| 629 | |
| 630 | |
| 631 class Substitutions(Transform): | |
| 632 | |
| 633 """ | |
| 634 Given the following ``document`` as input:: | |
| 635 | |
| 636 <document> | |
| 637 <paragraph> | |
| 638 The | |
| 639 <substitution_reference refname="biohazard"> | |
| 640 biohazard | |
| 641 symbol is deservedly scary-looking. | |
| 642 <substitution_definition name="biohazard"> | |
| 643 <image alt="biohazard" uri="biohazard.png"> | |
| 644 | |
| 645 The ``substitution_reference`` will simply be replaced by the | |
| 646 contents of the corresponding ``substitution_definition``. | |
| 647 | |
| 648 The transformed result will be:: | |
| 649 | |
| 650 <document> | |
| 651 <paragraph> | |
| 652 The | |
| 653 <image alt="biohazard" uri="biohazard.png"> | |
| 654 symbol is deservedly scary-looking. | |
| 655 <substitution_definition name="biohazard"> | |
| 656 <image alt="biohazard" uri="biohazard.png"> | |
| 657 """ | |
| 658 | |
| 659 default_priority = 220 | |
| 660 """The Substitutions transform has to be applied very early, before | |
| 661 `docutils.tranforms.frontmatter.DocTitle` and others.""" | |
| 662 | |
| 663 def apply(self): | |
| 664 defs = self.document.substitution_defs | |
| 665 normed = self.document.substitution_names | |
| 666 subreflist = list(self.document.traverse(nodes.substitution_reference)) | |
| 667 nested = {} | |
| 668 for ref in subreflist: | |
| 669 refname = ref['refname'] | |
| 670 key = None | |
| 671 if refname in defs: | |
| 672 key = refname | |
| 673 else: | |
| 674 normed_name = refname.lower() | |
| 675 if normed_name in normed: | |
| 676 key = normed[normed_name] | |
| 677 if key is None: | |
| 678 msg = self.document.reporter.error( | |
| 679 'Undefined substitution referenced: "%s".' | |
| 680 % refname, base_node=ref) | |
| 681 msgid = self.document.set_id(msg) | |
| 682 prb = nodes.problematic( | |
| 683 ref.rawsource, ref.rawsource, refid=msgid) | |
| 684 prbid = self.document.set_id(prb) | |
| 685 msg.add_backref(prbid) | |
| 686 ref.replace_self(prb) | |
| 687 else: | |
| 688 subdef = defs[key] | |
| 689 parent = ref.parent | |
| 690 index = parent.index(ref) | |
| 691 if ('ltrim' in subdef.attributes | |
| 692 or 'trim' in subdef.attributes): | |
| 693 if index > 0 and isinstance(parent[index - 1], | |
| 694 nodes.Text): | |
| 695 parent[index - 1] = parent[index - 1].rstrip() | |
| 696 if ('rtrim' in subdef.attributes | |
| 697 or 'trim' in subdef.attributes): | |
| 698 if (len(parent) > index + 1 | |
| 699 and isinstance(parent[index + 1], nodes.Text)): | |
| 700 parent[index + 1] = parent[index + 1].lstrip() | |
| 701 subdef_copy = subdef.deepcopy() | |
| 702 try: | |
| 703 # Take care of nested substitution references: | |
| 704 for nested_ref in subdef_copy.traverse( | |
| 705 nodes.substitution_reference): | |
| 706 nested_name = normed[nested_ref['refname'].lower()] | |
| 707 if nested_name in nested.setdefault(nested_name, []): | |
| 708 raise CircularSubstitutionDefinitionError | |
| 709 else: | |
| 710 nested[nested_name].append(key) | |
| 711 nested_ref['ref-origin'] = ref | |
| 712 subreflist.append(nested_ref) | |
| 713 except CircularSubstitutionDefinitionError: | |
| 714 parent = ref.parent | |
| 715 if isinstance(parent, nodes.substitution_definition): | |
| 716 msg = self.document.reporter.error( | |
| 717 'Circular substitution definition detected:', | |
| 718 nodes.literal_block(parent.rawsource, | |
| 719 parent.rawsource), | |
| 720 line=parent.line, base_node=parent) | |
| 721 parent.replace_self(msg) | |
| 722 else: | |
| 723 # find original ref substitution which cased this error | |
| 724 ref_origin = ref | |
| 725 while ref_origin.hasattr('ref-origin'): | |
| 726 ref_origin = ref_origin['ref-origin'] | |
| 727 msg = self.document.reporter.error( | |
| 728 'Circular substitution definition referenced: ' | |
| 729 '"%s".' % refname, base_node=ref_origin) | |
| 730 msgid = self.document.set_id(msg) | |
| 731 prb = nodes.problematic( | |
| 732 ref.rawsource, ref.rawsource, refid=msgid) | |
| 733 prbid = self.document.set_id(prb) | |
| 734 msg.add_backref(prbid) | |
| 735 ref.replace_self(prb) | |
| 736 else: | |
| 737 ref.replace_self(subdef_copy.children) | |
| 738 # register refname of the replacment node(s) | |
| 739 # (needed for resolution of references) | |
| 740 for node in subdef_copy.children: | |
| 741 if isinstance(node, nodes.Referential): | |
| 742 # HACK: verify refname attribute exists. | |
| 743 # Test with docs/dev/todo.txt, see. |donate| | |
| 744 if 'refname' in node: | |
| 745 self.document.note_refname(node) | |
| 746 | |
| 747 | |
| 748 class TargetNotes(Transform): | |
| 749 | |
| 750 """ | |
| 751 Creates a footnote for each external target in the text, and corresponding | |
| 752 footnote references after each reference. | |
| 753 """ | |
| 754 | |
| 755 default_priority = 540 | |
| 756 """The TargetNotes transform has to be applied after `IndirectHyperlinks` | |
| 757 but before `Footnotes`.""" | |
| 758 | |
| 759 | |
| 760 def __init__(self, document, startnode): | |
| 761 Transform.__init__(self, document, startnode=startnode) | |
| 762 | |
| 763 self.classes = startnode.details.get('class', []) | |
| 764 | |
| 765 def apply(self): | |
| 766 notes = {} | |
| 767 nodelist = [] | |
| 768 for target in self.document.traverse(nodes.target): | |
| 769 # Only external targets. | |
| 770 if not target.hasattr('refuri'): | |
| 771 continue | |
| 772 names = target['names'] | |
| 773 refs = [] | |
| 774 for name in names: | |
| 775 refs.extend(self.document.refnames.get(name, [])) | |
| 776 if not refs: | |
| 777 continue | |
| 778 footnote = self.make_target_footnote(target['refuri'], refs, | |
| 779 notes) | |
| 780 if target['refuri'] not in notes: | |
| 781 notes[target['refuri']] = footnote | |
| 782 nodelist.append(footnote) | |
| 783 # Take care of anonymous references. | |
| 784 for ref in self.document.traverse(nodes.reference): | |
| 785 if not ref.get('anonymous'): | |
| 786 continue | |
| 787 if ref.hasattr('refuri'): | |
| 788 footnote = self.make_target_footnote(ref['refuri'], [ref], | |
| 789 notes) | |
| 790 if ref['refuri'] not in notes: | |
| 791 notes[ref['refuri']] = footnote | |
| 792 nodelist.append(footnote) | |
| 793 self.startnode.replace_self(nodelist) | |
| 794 | |
| 795 def make_target_footnote(self, refuri, refs, notes): | |
| 796 if refuri in notes: # duplicate? | |
| 797 footnote = notes[refuri] | |
| 798 assert len(footnote['names']) == 1 | |
| 799 footnote_name = footnote['names'][0] | |
| 800 else: # original | |
| 801 footnote = nodes.footnote() | |
| 802 footnote_id = self.document.set_id(footnote) | |
| 803 # Use uppercase letters and a colon; they can't be | |
| 804 # produced inside names by the parser. | |
| 805 footnote_name = 'TARGET_NOTE: ' + footnote_id | |
| 806 footnote['auto'] = 1 | |
| 807 footnote['names'] = [footnote_name] | |
| 808 footnote_paragraph = nodes.paragraph() | |
| 809 footnote_paragraph += nodes.reference('', refuri, refuri=refuri) | |
| 810 footnote += footnote_paragraph | |
| 811 self.document.note_autofootnote(footnote) | |
| 812 self.document.note_explicit_target(footnote, footnote) | |
| 813 for ref in refs: | |
| 814 if isinstance(ref, nodes.target): | |
| 815 continue | |
| 816 refnode = nodes.footnote_reference(refname=footnote_name, auto=1) | |
| 817 refnode['classes'] += self.classes | |
| 818 self.document.note_autofootnote_ref(refnode) | |
| 819 self.document.note_footnote_ref(refnode) | |
| 820 index = ref.parent.index(ref) + 1 | |
| 821 reflist = [refnode] | |
| 822 if not utils.get_trim_footnote_ref_space(self.document.settings): | |
| 823 if self.classes: | |
| 824 reflist.insert(0, nodes.inline(text=' ', Classes=self.classes)) | |
| 825 else: | |
| 826 reflist.insert(0, nodes.Text(' ')) | |
| 827 ref.parent.insert(index, reflist) | |
| 828 return footnote | |
| 829 | |
| 830 | |
| 831 class DanglingReferences(Transform): | |
| 832 | |
| 833 """ | |
| 834 Check for dangling references (incl. footnote & citation) and for | |
| 835 unreferenced targets. | |
| 836 """ | |
| 837 | |
| 838 default_priority = 850 | |
| 839 | |
| 840 def apply(self): | |
| 841 visitor = DanglingReferencesVisitor( | |
| 842 self.document, | |
| 843 self.document.transformer.unknown_reference_resolvers) | |
| 844 self.document.walk(visitor) | |
| 845 # *After* resolving all references, check for unreferenced | |
| 846 # targets: | |
| 847 for target in self.document.traverse(nodes.target): | |
| 848 if not target.referenced: | |
| 849 if target.get('anonymous'): | |
| 850 # If we have unreferenced anonymous targets, there | |
| 851 # is already an error message about anonymous | |
| 852 # hyperlink mismatch; no need to generate another | |
| 853 # message. | |
| 854 continue | |
| 855 if target['names']: | |
| 856 naming = target['names'][0] | |
| 857 elif target['ids']: | |
| 858 naming = target['ids'][0] | |
| 859 else: | |
| 860 # Hack: Propagated targets always have their refid | |
| 861 # attribute set. | |
| 862 naming = target['refid'] | |
| 863 self.document.reporter.info( | |
| 864 'Hyperlink target "%s" is not referenced.' | |
| 865 % naming, base_node=target) | |
| 866 | |
| 867 | |
| 868 class DanglingReferencesVisitor(nodes.SparseNodeVisitor): | |
| 869 | |
| 870 def __init__(self, document, unknown_reference_resolvers): | |
| 871 nodes.SparseNodeVisitor.__init__(self, document) | |
| 872 self.document = document | |
| 873 self.unknown_reference_resolvers = unknown_reference_resolvers | |
| 874 | |
| 875 def unknown_visit(self, node): | |
| 876 pass | |
| 877 | |
| 878 def visit_reference(self, node): | |
| 879 if node.resolved or not node.hasattr('refname'): | |
| 880 return | |
| 881 refname = node['refname'] | |
| 882 id = self.document.nameids.get(refname) | |
| 883 if id is None: | |
| 884 for resolver_function in self.unknown_reference_resolvers: | |
| 885 if resolver_function(node): | |
| 886 break | |
| 887 else: | |
| 888 if refname in self.document.nameids: | |
| 889 msg = self.document.reporter.error( | |
| 890 'Duplicate target name, cannot be used as a unique ' | |
| 891 'reference: "%s".' % (node['refname']), base_node=node) | |
| 892 else: | |
| 893 msg = self.document.reporter.error( | |
| 894 'Unknown target name: "%s".' % (node['refname']), | |
| 895 base_node=node) | |
| 896 msgid = self.document.set_id(msg) | |
| 897 prb = nodes.problematic( | |
| 898 node.rawsource, node.rawsource, refid=msgid) | |
| 899 try: | |
| 900 prbid = node['ids'][0] | |
| 901 except IndexError: | |
| 902 prbid = self.document.set_id(prb) | |
| 903 msg.add_backref(prbid) | |
| 904 node.replace_self(prb) | |
| 905 else: | |
| 906 del node['refname'] | |
| 907 node['refid'] = id | |
| 908 self.document.ids[id].note_referenced_by(id=id) | |
| 909 node.resolved = 1 | |
| 910 | |
| 911 visit_footnote_reference = visit_citation_reference = visit_reference |
