Implement cross-references
This commit is contained in:
parent
036846fcce
commit
04a7e52f2e
@ -22,7 +22,7 @@ This is the *blah blah*.
|
||||
|
||||
hijkl
|
||||
|
||||
Continued
|
||||
Cross reference to `rule 1`_
|
||||
|
||||
(2) DEF
|
||||
|
||||
|
BIN
example.pdf
BIN
example.pdf
Binary file not shown.
@ -1 +1 @@
|
||||
{\rtf1\deff0{\fonttbl{\f0 TeX Gyre Heros{\*\falt FreeSans}{\*\falt Liberation Sans}{\*\falt Arial};}}\paperw11905\paperh16837\margl1133\margr1133\margt1133\margb1133{\header\f0\fs16\tqr\tx9637 Example Legal Document\tab }{\footer\f0\fs16\tqr\tx9637 \tab\chpgn}\sa198\fs1\~\fs24 {\fs20\li566{\b Note:} This is a note.\par}{\sb566\keepn\b\fs26\qc\caps Part 1\u8212?Preliminary\par}{\keepn\b\fi-566\li566 1\tab Short title\par}{\li566 This is the {\i blah blah}.\par}{\keepn\b\fi-566\li566 2\tab Another heading\par}{\fi-566\li1133 (1)\tab ABC\par}{\li1133 Continued\par}{\fi-566\li1700 (a)\tab asdf\par}{\fi-566\li1700 (b)\tab abcdefg\par}{\li1700 hijkl\par}{\li1133 Continued\par}{\fi-566\li1133 (2)\tab DEF\par}{\keepn\b\fi-566\li566 3\tab Table example\par}{\sa0\trowd\trgaph120\trleft566\clbrdrt\brdrs\clbrdrb\brdrs\cellx2380\clbrdrt\brdrs\clbrdrb\brdrs\cellx9636 {\b Column 1\intbl\cell}{\b Column 2\intbl\cell}\row{\b This is a long column header that will overflow onto multiple lines\intbl\cell}{\b Second column\intbl\cell}\row {1. First row\intbl\cell}{Foo bar\intbl\cell}\row {2. Second row\intbl\cell}{Baz qux\intbl\cell}\row }}
|
||||
{\rtf1\deff0{\fonttbl{\f0 TeX Gyre Heros{\*\falt FreeSans}{\*\falt Liberation Sans}{\*\falt Arial};}}\paperw11905\paperh16837\margl1133\margr1133\margt1133\margb1133{\header\f0\fs16\tqr\tx9637 Example Legal Document\tab }{\footer\f0\fs16\tqr\tx9637 \tab\chpgn}\sa198\fs1\~\fs24 {\fs20\li566{\b Note:} This is a note.\par}{\sb566\keepn\b\fs26\qc\caps Part 1\u8212?Preliminary\par}{\keepn\b\fi-566\li566 1\tab Short title\par}{\li566 This is the {\i blah blah}.\par}{\keepn\b\fi-566\li566 2\tab Another heading\par}{\fi-566\li1133 (1)\tab ABC\par}{\li1133 Continued\par}{\fi-566\li1700 (a)\tab asdf\par}{\fi-566\li1700 (b)\tab abcdefg\par}{\li1700 hijkl\par}{\li1133 Cross reference to rule\u160?1\par}{\fi-566\li1133 (2)\tab DEF\par}{\keepn\b\fi-566\li566 3\tab Table example\par}{\sa0\trowd\trgaph120\trleft566\clbrdrt\brdrs\clbrdrb\brdrs\cellx2380\clbrdrt\brdrs\clbrdrb\brdrs\cellx9636 {\b Column 1\intbl\cell}{\b Column 2\intbl\cell}\row{\b This is a long column header that will overflow onto multiple lines\intbl\cell}{\b Second column\intbl\cell}\row {1. First row\intbl\cell}{Foo bar\intbl\cell}\row {2. Second row\intbl\cell}{Baz qux\intbl\cell}\row }}
|
||||
|
@ -40,4 +40,22 @@ with renderer_cls() as renderer:
|
||||
doc.author = rawdoc.get('author', '')
|
||||
doc.footer = rawdoc.get('footer', '')
|
||||
|
||||
doc.full_label_map = {}
|
||||
|
||||
# Preprocess custom tokens
|
||||
def walk_elem(elem, parent=None):
|
||||
elem.doc = doc
|
||||
elem.parent = parent
|
||||
|
||||
if isinstance(elem, legal_token.NumberedHeading) or isinstance(elem, legal_token.SubrulesItem):
|
||||
full_label = elem.full_label()
|
||||
if full_label:
|
||||
doc.full_label_map[full_label] = elem
|
||||
|
||||
if hasattr(elem, 'children'):
|
||||
for child in elem.children:
|
||||
walk_elem(child, elem)
|
||||
|
||||
walk_elem(doc)
|
||||
|
||||
print(renderer.render(doc))
|
||||
|
@ -14,6 +14,8 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import hashlib
|
||||
|
||||
import mistletoe
|
||||
import mistletoe.latex_renderer
|
||||
|
||||
@ -35,6 +37,8 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
|
||||
def __init__(self, *extras):
|
||||
super().__init__(*extras)
|
||||
|
||||
self.render_map['CrossReference'] = self.render_cross_reference
|
||||
|
||||
self.render_map['NumberedHeading'] = self.render_numbered_heading
|
||||
self.render_map['Subrules'] = self.render_subrules
|
||||
self.render_map['SubrulesItem'] = self.render_subrules_item
|
||||
@ -48,6 +52,20 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
|
||||
result = result.replace('★★★', r'\texorpdfstring{\freeserif ★★★}{★★★}')
|
||||
return result
|
||||
|
||||
def render_cross_reference(self, token):
|
||||
reference = token.get_reference()
|
||||
if not reference:
|
||||
raise Exception('Unable to resolve reference "{}"'.format(token.reference_num))
|
||||
|
||||
sha = hashlib.sha256()
|
||||
sha.update(reference.full_label().encode('utf-8'))
|
||||
|
||||
return format(r'\hyperlink{<linkname>}{<reference_type><reference_num>}',
|
||||
linkname=sha.hexdigest(),
|
||||
reference_type=(token.reference_type + '~') if token.reference_type else '',
|
||||
reference_num=token.reference_num
|
||||
)
|
||||
|
||||
def render_heading(self, token):
|
||||
if token.level == 1:
|
||||
heading_last, self.heading_last = self.heading_last, True
|
||||
@ -69,10 +87,17 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
|
||||
)
|
||||
|
||||
def render_numbered_heading(self, token):
|
||||
sha = hashlib.sha256()
|
||||
sha.update(token.full_label().encode('utf-8'))
|
||||
hyperlink = format(r'\hypertarget{<linkname>}{}',
|
||||
linkname=sha.hexdigest()
|
||||
)
|
||||
|
||||
if token.level == 1:
|
||||
# Part
|
||||
heading_last, self.heading_last = self.heading_last, True
|
||||
return format(r'{\par\vspace{1cm plus 0.3cm minus 0.3cm}\bfseries\fontsize{13pt}{15pt}\selectfont\centering\uppercase{Part <label>—<content>}\phantomsection\addcontentsline{toc}{section}{Part <label>—<content>}\nopagebreak\par}',
|
||||
return format(r'{\par\vspace{1cm plus 0.3cm minus 0.3cm}<hyperlink>\bfseries\fontsize{13pt}{15pt}\selectfont\centering\uppercase{Part <label>—<content>}\phantomsection\addcontentsline{toc}{section}{Part <label>—<content>}\nopagebreak\par}',
|
||||
hyperlink=hyperlink,
|
||||
label=token.label,
|
||||
content=self.render_inner(token)
|
||||
)
|
||||
@ -80,7 +105,8 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
|
||||
if token.level == 2:
|
||||
# Division
|
||||
heading_last, self.heading_last = self.heading_last, True
|
||||
return format(r'{\par\vspace{<space_above>}\bfseries\fontsize{13pt}{15pt}\selectfont\centering Division <label>—<content>\phantomsection\addcontentsline{toc}{subsection}{Division <label>—<content>}\nopagebreak\par}',
|
||||
return format(r'{\par\vspace{<space_above>}<hyperlink>\bfseries\fontsize{13pt}{15pt}\selectfont\centering Division <label>—<content>\phantomsection\addcontentsline{toc}{subsection}{Division <label>—<content>}\nopagebreak\par}',
|
||||
hyperlink=hyperlink,
|
||||
space_above='1cm plus 0.3cm minus 0.3cm' if not heading_last else '0cm',
|
||||
label=token.label,
|
||||
content=self.render_inner(token)
|
||||
@ -89,7 +115,8 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
|
||||
if token.level == 3:
|
||||
# Section
|
||||
heading_last, self.heading_last = self.heading_last, False
|
||||
return format(r'{\par\leftskip=\quotemargin\bfseries\makebox[<lmarg>][l]{<label>}<content>\phantomsection\addcontentsline{toc}{subsubsection}{\protect\numberline{<label>} <content>}\nopagebreak\par}',
|
||||
return format(r'{\par<hyperlink>\leftskip=\quotemargin\bfseries\makebox[<lmarg>][l]{<label>}<content>\phantomsection\addcontentsline{toc}{subsubsection}{\protect\numberline{<label>} <content>}\nopagebreak\par}',
|
||||
hyperlink=hyperlink,
|
||||
label=token.label,
|
||||
content=self.render_inner(token)
|
||||
)
|
||||
@ -122,7 +149,14 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
|
||||
|
||||
def render_subrules_item(self, token):
|
||||
if token.label:
|
||||
return format(r'\N{\par\leftskip=\dimexpr\quotemargin+<lmarg>+<level>cm\relax\hangindent=1cm\parskip=<parskip>\makebox[<lmarg>][l]{<label>}<content>\par}\N',
|
||||
sha = hashlib.sha256()
|
||||
sha.update(token.full_label().encode('utf-8'))
|
||||
hyperlink = format(r'\hypertarget{<linkname>}{}',
|
||||
linkname=sha.hexdigest()
|
||||
)
|
||||
|
||||
return format(r'\N{\par<hyperlink>\leftskip=\dimexpr\quotemargin+<lmarg>+<level>cm\relax\hangindent=1cm\parskip=<parskip>\makebox[<lmarg>][l]{<label>}<content>\par}\N',
|
||||
hyperlink=hyperlink,
|
||||
parskip=r'\parskip',# if token.level <= 1 else '0cm',
|
||||
label=token.label,
|
||||
level=token.level,
|
||||
|
@ -46,6 +46,14 @@ class NumberedHeading(mistletoe.block_token.BlockToken):
|
||||
|
||||
return level, label, content
|
||||
|
||||
def full_label(self):
|
||||
if not self.label:
|
||||
return None
|
||||
if not isinstance(self.parent, mistletoe.block_token.Document):
|
||||
return None
|
||||
|
||||
return self.label
|
||||
|
||||
mistletoe.block_token.add_token(NumberedHeading)
|
||||
|
||||
class Subrules(mistletoe.block_token.BlockToken):
|
||||
@ -116,6 +124,27 @@ class SubrulesItem(mistletoe.block_token.BlockToken):
|
||||
|
||||
return level, label, children
|
||||
|
||||
def full_label(self):
|
||||
if not self.label:
|
||||
return None
|
||||
if not isinstance(self.parent.parent, mistletoe.block_token.Document):
|
||||
return None
|
||||
|
||||
labels = [self.label]
|
||||
|
||||
# Subrules items
|
||||
cur_level = self.level
|
||||
for child in reversed(self.parent.children[0:self.parent.children.index(self)]):
|
||||
if child.level < cur_level and child.label:
|
||||
labels.append(child.label)
|
||||
cur_level = child.level
|
||||
|
||||
# Section
|
||||
section = next(x for x in reversed(self.parent.parent.children[0:self.parent.parent.children.index(self.parent)]) if isinstance(x, NumberedHeading))
|
||||
labels.append(section.label)
|
||||
|
||||
return ''.join(reversed(labels))
|
||||
|
||||
class Note(mistletoe.block_token.BlockToken):
|
||||
pattern = re.compile(r'(\t*)([0-9A-Z ]+):\s+(.+)')
|
||||
|
||||
@ -213,3 +242,43 @@ class TableCell(mistletoe.block_token.BlockToken):
|
||||
|
||||
mistletoe.block_token.remove_token(mistletoe.block_token.Table)
|
||||
mistletoe.block_token.add_token(Table)
|
||||
|
||||
class CrossReference(mistletoe.span_token.SpanToken):
|
||||
pattern = re.compile(r'`(?:([A-Za-z]+?)\s+)?([0-9A-Za-z\(\)]+?)`_')
|
||||
pattern_parts = re.compile(r'^[0-9A-Za-z]+|\([0-9A-Za-z]+\)')
|
||||
|
||||
def __init__(self, match):
|
||||
self.reference_type = match.group(1)
|
||||
self.reference_num = match.group(2)
|
||||
self.reference_parts = re.findall(self.pattern_parts, self.reference_num)
|
||||
|
||||
def get_reference(self):
|
||||
parent = None
|
||||
|
||||
elem = self.parent
|
||||
while elem is not None:
|
||||
if isinstance(elem, SubrulesItem):
|
||||
parent = elem
|
||||
break
|
||||
elem = elem.parent
|
||||
|
||||
if parent is None:
|
||||
# Might be a freestanding rule
|
||||
section = next((x for x in reversed(self.parent.parent.children[0:self.parent.parent.children.index(self.parent)]) if isinstance(x, NumberedHeading)), None)
|
||||
if section:
|
||||
parent = section
|
||||
|
||||
if parent and parent.full_label():
|
||||
basenum = re.findall(self.pattern_parts, parent.full_label())
|
||||
else:
|
||||
basenum = []
|
||||
|
||||
# Try possibilities
|
||||
for index in reversed(range(0, len(basenum) + 1)):
|
||||
try_ref = ''.join(basenum[:index] + self.reference_parts)
|
||||
if try_ref in self.doc.full_label_map:
|
||||
return self.doc.full_label_map[try_ref]
|
||||
|
||||
return None
|
||||
|
||||
mistletoe.span_token.add_token(CrossReference)
|
||||
|
@ -38,6 +38,8 @@ class RTFRenderer(mistletoe.base_renderer.BaseRenderer):
|
||||
def __init__(self, *extras):
|
||||
super().__init__(*extras)
|
||||
|
||||
self.render_map['CrossReference'] = self.render_cross_reference
|
||||
|
||||
self.render_map['NumberedHeading'] = self.render_numbered_heading
|
||||
self.render_map['Subrules'] = self.render_subrules
|
||||
self.render_map['SubrulesItem'] = self.render_subrules_item
|
||||
@ -74,6 +76,12 @@ class RTFRenderer(mistletoe.base_renderer.BaseRenderer):
|
||||
def render_escape_sequence(self, token):
|
||||
raise Exception('NYI')
|
||||
|
||||
def render_cross_reference(self, token):
|
||||
return '{reference_type}{reference_num}'.format(
|
||||
reference_type=(token.reference_type + ' ') if token.reference_type else '',
|
||||
reference_num=token.reference_num
|
||||
)
|
||||
|
||||
def render_heading(self, token):
|
||||
if token.level == 1:
|
||||
# Part
|
||||
|
Loading…
Reference in New Issue
Block a user