Implement cross-references
This commit is contained in:
parent
036846fcce
commit
04a7e52f2e
@ -22,7 +22,7 @@ This is the *blah blah*.
|
|||||||
|
|
||||||
hijkl
|
hijkl
|
||||||
|
|
||||||
Continued
|
Cross reference to `rule 1`_
|
||||||
|
|
||||||
(2) DEF
|
(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.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))
|
||||||
|
@ -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,
|
||||||
|
@ -45,6 +45,14 @@ class NumberedHeading(mistletoe.block_token.BlockToken):
|
|||||||
content = '★★★'
|
content = '★★★'
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@ -115,6 +123,27 @@ class SubrulesItem(mistletoe.block_token.BlockToken):
|
|||||||
level -= 1
|
level -= 1
|
||||||
|
|
||||||
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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user