Engine for LaTeX songbooks http://www.patacrep.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

201 lines
6.8 KiB

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Manage indexes.
Generate indexes files for the songbook compilation. This is a replacement for
the original makeindex program written in C that produces an index file (.sbx)
from a file generated by the latex compilation of the songbook (.sxd).
"""
from unidecode import unidecode
import locale
import re
import sys
from songbook_core.authors import processauthors
from songbook_core.plastex import simpleparse
EOL = "\n"
# Pattern set to ignore latex command in title prefix
KEYWORD_PATTERN = re.compile(r"^%(\w+)\s?(.*)$")
FIRST_LETTER_PATTERN = re.compile(r"^(?:\{?\\\w+\}?)*[^\w]*(\w)")
def sortkey(value):
"""From a title, return something usable for sorting.
It handles locale (but
don't forget to call locale.setlocale(locale.LC_ALL, '')). It also handles
the sort with latex escape sequences.
"""
return locale.strxfrm(unidecode(simpleparse(value).replace(' ', 'A')))
def process_sxd(filename):
"""Parse sxd file.
Return an Index object.
"""
index_file = open(filename)
data = []
for line in index_file:
data.append(line.strip())
index_file.close()
i = 1
idx = Index(data[0])
while len(data) > i and data[i].startswith('%'):
keywords = KEYWORD_PATTERN.match(data[i]).groups()
idx.add_keyword(keywords[0], keywords[1])
i += 1
idx.compile_keywords()
for i in range(i, len(data), 3):
entry = data[i:i + 3]
idx.add(entry[0], entry[1], entry[2])
return idx
class Index(object):
"""Title, author or scripture Index representation."""
def __init__(self, indextype):
self.data = dict()
self.keywords = dict()
self.prefix_patterns = []
self.authwords = {"after": [], "ignore": [], "sep": []}
if indextype == "TITLE INDEX DATA FILE":
self.indextype = "TITLE"
elif indextype == "SCRIPTURE INDEX DATA FILE":
self.indextype = "SCRIPTURE"
elif indextype == "AUTHOR INDEX DATA FILE":
self.indextype = "AUTHOR"
else:
self.indextype = ""
@staticmethod
def get_first_letter(key):
"""Return the uppercase first letter of key."""
letter = FIRST_LETTER_PATTERN.match(key).group(1)
if re.match(r'\d', letter):
letter = '0-9'
return letter.upper()
def add_keyword(self, key, word):
"""Add 'word' to self.keywords[key]."""
if not key in self.keywords.keys():
self.keywords[key] = []
self.keywords[key].append(word)
def compile_keywords(self):
"""Turn keywords (self.keywords) into regular expressions."""
if self.indextype == "TITLE":
if 'prefix' in self.keywords:
for prefix in self.keywords['prefix']:
self.prefix_patterns.append(re.compile(
r"^({prefix})(\b|\\)(\s*.*)$".format(prefix=prefix)
))
if self.indextype == "AUTHOR":
for key in self.keywords:
if key in self.authwords:
self.authwords[key] = self.keywords[key]
for word in self.authwords.keys():
if word in self.keywords:
if word == "after":
self.authwords[word] = [
re.compile(r"^.*{after}\b(.*)".format(after=after))
for after in self.keywords[word]
]
elif word == "sep":
self.authwords[word] = [" {sep}".format(sep=sep)
for sep in self.authwords[word]
] + [","]
self.authwords[word] = [
re.compile(r"^(.*){sep} (.*)$".format(sep=sep))
for sep in self.authwords[word]
]
else:
self.authwords[word] = self.keywords[word]
def _raw_add(self, key, number, link):
"""Add a song to the list.
No processing is done one data. It is added raw. See add() for a
similar method with processing.
"""
first = self.get_first_letter(key)
if not first in self.data.keys():
self.data[first] = dict()
if not key in self.data[first].keys():
self.data[first][key] = []
self.data[first][key].append({'num': number, 'link': link})
def add(self, key, number, link):
"""Add a song to the list.
Process data before adding it.
"""
if self.indextype == "TITLE":
# Removing prefixes before titles
for pattern in self.prefix_patterns:
match = pattern.match(key)
if match:
self._raw_add(
"{} ({})".format(
match.group(2) + match.group(3),
match.group(1)),
number, link)
return
self._raw_add(key, number, link)
if self.indextype == "AUTHOR":
# Processing authors
for author in processauthors(
key,
**self.authwords):
self._raw_add(author, number, link)
@staticmethod
def ref_to_str(ref):
"""Return the LaTeX code corresponding to the reference."""
if sys.version_info >= (2, 6):
return r'\hyperlink{{{0[link]}}}{{{0[num]}}}'.format(ref)
else:
return r'\hyperlink{%(link)s}{%(num)s}' % ref
def entry_to_str(self, key, entry):
"""Return the LaTeX code corresponding to the entry."""
if sys.version_info >= (2, 6):
return unicode(r'\idxentry{{{0}}}{{{1}}}' + EOL).format(
key,
r'\\'.join([self.ref_to_str(ref) for ref in entry]),
)
else:
return unicode(r'\idxentry{%s}{%s}' + EOL) % (
key,
r'\\'.join([self.ref_to_str(ref) for ref in entry]),
)
def idxblock_to_str(self, letter, entries):
"""Return the LaTeX code corresponding to an index block.
Here, an index block is a letter, and all data beginning with this
letter.
"""
string = r'\begin{idxblock}{' + letter + '}' + EOL
for key in sorted(entries.keys(), key=sortkey):
string += self.entry_to_str(key, entries[key])
string += r'\end{idxblock}' + EOL
return string
def entries_to_str(self):
"""Return the LaTeX code corresponding to the index."""
string = ""
for letter in sorted(self.data.keys()):
string += self.idxblock_to_str(letter, self.data[letter])
return string