Implement cross-references

This commit is contained in:
RunasSudo 2019-09-14 23:02:14 +10:00
parent 036846fcce
commit 04a7e52f2e
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
7 changed files with 135 additions and 6 deletions

View File

@ -22,7 +22,7 @@ This is the *blah blah*.
hijkl hijkl
Continued Cross reference to `rule 1`_
(2) DEF (2) DEF

Binary file not shown.

View File

@ -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 }}

View File

@ -40,4 +40,22 @@ with renderer_cls() as renderer:
doc.author = rawdoc.get('author', '') doc.author = rawdoc.get('author', '')
doc.footer = rawdoc.get('footer', '') 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)) print(renderer.render(doc))

View File

@ -14,6 +14,8 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import hashlib
import mistletoe import mistletoe
import mistletoe.latex_renderer import mistletoe.latex_renderer
@ -35,6 +37,8 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
def __init__(self, *extras): def __init__(self, *extras):
super().__init__(*extras) super().__init__(*extras)
self.render_map['CrossReference'] = self.render_cross_reference
self.render_map['NumberedHeading'] = self.render_numbered_heading self.render_map['NumberedHeading'] = self.render_numbered_heading
self.render_map['Subrules'] = self.render_subrules self.render_map['Subrules'] = self.render_subrules
self.render_map['SubrulesItem'] = self.render_subrules_item self.render_map['SubrulesItem'] = self.render_subrules_item
@ -48,6 +52,20 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
result = result.replace('★★★', r'\texorpdfstring{\freeserif ★★★}{★★★}') result = result.replace('★★★', r'\texorpdfstring{\freeserif ★★★}{★★★}')
return result 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): def render_heading(self, token):
if token.level == 1: if token.level == 1:
heading_last, self.heading_last = self.heading_last, True 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): 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: if token.level == 1:
# Part # Part
heading_last, self.heading_last = self.heading_last, True 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, label=token.label,
content=self.render_inner(token) content=self.render_inner(token)
) )
@ -80,7 +105,8 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
if token.level == 2: if token.level == 2:
# Division # Division
heading_last, self.heading_last = self.heading_last, True 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', space_above='1cm plus 0.3cm minus 0.3cm' if not heading_last else '0cm',
label=token.label, label=token.label,
content=self.render_inner(token) content=self.render_inner(token)
@ -89,7 +115,8 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
if token.level == 3: if token.level == 3:
# Section # Section
heading_last, self.heading_last = self.heading_last, False 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, label=token.label,
content=self.render_inner(token) content=self.render_inner(token)
) )
@ -122,7 +149,14 @@ class LaTeXRenderer(mistletoe.latex_renderer.LaTeXRenderer):
def render_subrules_item(self, token): def render_subrules_item(self, token):
if token.label: 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', parskip=r'\parskip',# if token.level <= 1 else '0cm',
label=token.label, label=token.label,
level=token.level, level=token.level,

View File

@ -46,6 +46,14 @@ class NumberedHeading(mistletoe.block_token.BlockToken):
return level, label, content 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) mistletoe.block_token.add_token(NumberedHeading)
class Subrules(mistletoe.block_token.BlockToken): class Subrules(mistletoe.block_token.BlockToken):
@ -116,6 +124,27 @@ class SubrulesItem(mistletoe.block_token.BlockToken):
return level, label, children 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): class Note(mistletoe.block_token.BlockToken):
pattern = re.compile(r'(\t*)([0-9A-Z ]+):\s+(.+)') 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.remove_token(mistletoe.block_token.Table)
mistletoe.block_token.add_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)

View File

@ -38,6 +38,8 @@ class RTFRenderer(mistletoe.base_renderer.BaseRenderer):
def __init__(self, *extras): def __init__(self, *extras):
super().__init__(*extras) super().__init__(*extras)
self.render_map['CrossReference'] = self.render_cross_reference
self.render_map['NumberedHeading'] = self.render_numbered_heading self.render_map['NumberedHeading'] = self.render_numbered_heading
self.render_map['Subrules'] = self.render_subrules self.render_map['Subrules'] = self.render_subrules
self.render_map['SubrulesItem'] = self.render_subrules_item self.render_map['SubrulesItem'] = self.render_subrules_item
@ -74,6 +76,12 @@ class RTFRenderer(mistletoe.base_renderer.BaseRenderer):
def render_escape_sequence(self, token): def render_escape_sequence(self, token):
raise Exception('NYI') 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): def render_heading(self, token):
if token.level == 1: if token.level == 1:
# Part # Part