+# This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
+#
import copy
import re
from lxml import etree
EPUB_ATTR = {}
EPUB_CLASS = None
EPUB_START_CHUNK = False
-
+
+ FB2_TAG = None
+
CAN_HAVE_TEXT = True
STRIP = False
+ NUMBERING = None
text_substitutions = [
- (u'---', u'—'),
- (u'--', u'–'),
- #(u'...', u'…'), # Temporary turnoff for epub
- (u',,', u'„'),
- (u'"', u'”'),
+ ('---', '—'),
+ ('--', '–'),
+ ('...', '…'),
+ (',,', '„'),
+ ('"', '”'),
('\ufeff', ''),
-
- ("'", "\u2019"), # This was enabled for epub.
+ ("'", "\u2019"),
]
@property
except AttributeError:
return parent.in_context_of(setting)
+ def get_context_map(self, setting, key, default=None):
+ parent = self.getparent()
+ if parent is None:
+ return default
+ try:
+ return getattr(parent, setting)[key]
+ except AttributeError:
+ return parent.get_context_map(setting, key, default)
+
def signal(self, signal):
parent = self.getparent()
if parent is not None:
text = text or ''
for e, s in self.text_substitutions:
text = text.replace(e, s)
- # FIXME: TEmporary turnoff
-# text = re.sub(r'\s+', ' ', text)
-### TODO: Added now for epub
+
+ if getattr(builder, 'normalize_whitespace', False):
+ text = re.sub(r'\s+', ' ', text)
if getattr(builder, 'hyphenator', None) is not None:
newt = ''
wlist = re.compile(r'\w+|[^\w]', re.UNICODE).findall(text)
for w in wlist:
- newt += builder.hyphenator.inserted(w, u'\u00AD')
+ newt += builder.hyphenator.inserted(w, '\u00AD')
text = newt
if builder.orphans:
- text = re.sub(r'(?<=\s\w)\s+', u'\u00A0', text)
+ text = re.sub(r'(?<=\s\w)\s+', '\u00A0', text)
return text
- def _build_inner(self, builder, build_method):
+ def build_inner(self, builder):
+ build_method = builder.build_method_fn
child_count = len(self)
if self.CAN_HAVE_TEXT and self.text:
text = self.normalize_text(self.text, builder)
text = text.rstrip()
builder.push_text(text)
for i, child in enumerate(self):
+ real_child_count = 0
if isinstance(child, WLElement):
getattr(child, build_method)(builder)
+ self.after_child(builder, real_child_count)
+ real_child_count += 1
+
+ # FIXME base builder api
+ elif getattr(builder, 'debug', False) and child.tag is etree.Comment:
+ builder.process_comment(child)
if self.CAN_HAVE_TEXT and child.tail:
text = self.normalize_text(child.tail, builder)
if self.STRIP and i == child_count - 1:
text = text.rstrip()
builder.push_text(text)
- def _txt_build_inner(self, builder):
- self._build_inner(builder, 'txt_build')
+ def after_child(self, builder, child_count):
+ fn = getattr(builder, 'after_child_fn', None)
+ if fn:
+ getattr(self, builder.after_child_fn)(builder, child_count)
+
+ def txt_after_child(self, builder, child_count):
+ pass
+
+ def txt_build_inner(self, builder):
+ self.build_inner(builder)
def txt_build(self, builder):
- if hasattr(self, 'TXT_LEGACY_TOP_MARGIN'):
- builder.push_legacy_margin(self.TXT_LEGACY_TOP_MARGIN)
- else:
- builder.push_margin(self.TXT_TOP_MARGIN)
+ builder.push_margin(self.TXT_TOP_MARGIN)
builder.push_text(self.TXT_PREFIX, True)
- self._txt_build_inner(builder)
+ self.txt_build_inner(builder)
builder.push_text(self.TXT_SUFFIX, True)
- if hasattr(self, 'TXT_LEGACY_BOTTOM_MARGIN'):
- builder.push_legacy_margin(self.TXT_LEGACY_BOTTOM_MARGIN)
- else:
- builder.push_margin(self.TXT_BOTTOM_MARGIN)
+ builder.push_margin(self.TXT_BOTTOM_MARGIN)
- def _html_build_inner(self, builder):
- self._build_inner(builder, 'html_build')
+ def html_build_inner(self, builder):
+ self.build_inner(builder)
def get_html_attr(self, builder):
attr = self.HTML_ATTR.copy()
if self.HTML_CLASS:
attr['class'] = self.HTML_CLASS
- # always copy the id attribute (?)
- if self.attrib.get('id'):
- attr['id'] = self.attrib['id']
- elif getattr(self, 'SHOULD_HAVE_ID', False) and '_compat_section_id' in self.attrib:
- attr['id'] = self.attrib['_compat_section_id']
+ if builder.with_ids:
+ # always copy the id attribute (?)
+ if self.attrib.get('id'):
+ attr['id'] = self.attrib['id']
+ if self.attrib.get('_id'):
+ attr['id'] = self.attrib['_id']
return attr
def html_build(self, builder):
+ # Do we need a number?
+ numbering = self.numbering
+ if numbering == 'main':
+ if builder.with_numbering and self.has_visible_numbering:
+ builder.add_visible_number(self)
+
if self.HTML_TAG:
builder.start_element(
self.HTML_TAG,
self.get_html_attr(builder),
)
- self._html_build_inner(builder)
+ self.html_build_inner(builder)
if self.HTML_TAG:
builder.end_element()
- def _epub_build_inner(self, builder):
- self._build_inner(builder, 'epub_build')
+ def fb2_build(self, builder):
+ if self.SECTION_PRECEDENCE:
+ builder.start_section(self.SECTION_PRECEDENCE)
+ builder.start_element('title')
+ builder.start_element('p')
+
+ if self.FB2_TAG:
+ builder.start_element(
+ self.FB2_TAG,
+ #self.get_fb2_attr(builder),
+ )
+
+ self.build_inner(builder)
+ if self.FB2_TAG:
+ builder.end_element()
+ if self.SECTION_PRECEDENCE:
+ builder.end_element()
+ builder.end_element()
+
+ def epub_build_inner(self, builder):
+ self.build_inner(builder)
def get_epub_attr(self, builder):
attr = self.EPUB_ATTR.copy()
# TEMPORARY
self.CAN_HAVE_TEXT = True
self.STRIP = False
-
+
start_chunk = self.EPUB_START_CHUNK and isinstance(self.getparent(), Master)
if start_chunk:
attr = self.get_epub_attr(builder)
if fragment:
attr['id'] = fragment
+ if builder.debug:
+ chunkno, sourceline = 0, self.sourceline
+ if builder.splits:
+ chunkno, sourceline = len(builder.splits), sourceline - builder.splits[-1]
+ attr['data-debug'] = f'{chunkno}:{sourceline}'
builder.start_element(
self.EPUB_TAG,
attr
)
- self._epub_build_inner(builder)
+ self.epub_build_inner(builder)
if self.EPUB_TAG:
builder.end_element()
# do we dare go up?
parent = self.getparent()
if parent is not None and parent.CAN_HAVE_TEXT:
- print(etree.tostring(self, encoding='unicode'))
- assert False
words, parsnip = parent.snip(words, before=self)
return words, parsnip[:-1] + snippet + parsnip[-1:]
return snipelem
+ @property
+ def numbering(self):
+ numbering = self.NUMBERING
+ if numbering is None or self.in_context_of('DISABLE_NUMBERING'):
+ return None
+ numbering = self.get_context_map('SUPPRESS_NUMBERING', numbering, numbering)
+ return numbering
+
+ @property
+ def id_prefix(self):
+ prefix = self.numbering
+ if prefix == 'main':
+ # TODO: self.context.main_numbering_prefix
+ prefix = 'f' # default numbering prefix
+ return prefix
+
+ def assign_id(self, document):
+ numbering = self.numbering
+ if numbering:
+ number = str(document.counters[numbering])
+ self.attrib['_id'] = self.id_prefix + number
+ document.counters[numbering] += 1
+
+ if numbering == 'main':
+ self.attrib['_visible_numbering'] = str(document.counters['_visible'])
+ document.counters['_visible'] += 1
+
+ if numbering == 'fn':
+ self.attrib['_visible_numbering'] = number
+
def get_link(self):
- sec = getattr(self, 'SHOULD_HAVE_ID', False) and self.attrib.get('_compat_section_id')
- if sec:
- return sec
- parent_index = self.getparent().index(self)
- if parent_index:
- return self.getparent()[parent_index - 1].get_link()
- else:
- return self.getparent().get_link()
+ return self.attrib.get('_id') or self.getparent().get_link()
class Snippet(WLElement):