Move HTML from the old transform sheet.
[librarian.git] / src / librarian / elements / base.py
index 46ae29f..2349f16 100644 (file)
@@ -1,13 +1,28 @@
-# -*- coding: utf-8
-
+# 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
 from librarian import dcparser, RDFNS
 from librarian.util import get_translation
 
 import re
 from lxml import etree
 from librarian import dcparser, RDFNS
 from librarian.util import get_translation
 
+def last_words(text, n):
+    words = []
+    for w in reversed(text.split()):
+        words.append(w)
+        if len(w) > 2:
+            n -= 1
+            if not n: break
+    if n:
+        return n, text
+    else:
+        return n, ' '.join(reversed(words))
+
 
 class WLElement(etree.ElementBase):
     SECTION_PRECEDENCE = None
 
 class WLElement(etree.ElementBase):
     SECTION_PRECEDENCE = None
+    ASIDE = False
 
     TXT_TOP_MARGIN = 0
     TXT_BOTTOM_MARGIN = 0
 
     TXT_TOP_MARGIN = 0
     TXT_BOTTOM_MARGIN = 0
@@ -25,13 +40,14 @@ class WLElement(etree.ElementBase):
    
     CAN_HAVE_TEXT = True
     STRIP = False
    
     CAN_HAVE_TEXT = True
     STRIP = False
+    NUMBERING = None
 
     text_substitutions = [
 
     text_substitutions = [
-        (u'---', u'—'),
-        (u'--', u'–'),
-        #(u'...', u'…'),  # Temporary turnoff for epub
-        (u',,', u'„'),
-        (u'"', u'”'),
+        ('---', '—'),
+        ('--', '–'),
+        #('...', '…'),  # Temporary turnoff for epub
+        (',,', '„'),
+        ('"', '”'),
         ('\ufeff', ''),
 
         ("'", "\u2019"),    # This was enabled for epub.
         ('\ufeff', ''),
 
         ("'", "\u2019"),    # This was enabled for epub.
@@ -70,6 +86,15 @@ class WLElement(etree.ElementBase):
         except AttributeError:
             return parent.in_context_of(setting)
 
         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:
     def signal(self, signal):
         parent = self.getparent()
         if parent is not None:
@@ -101,10 +126,11 @@ class WLElement(etree.ElementBase):
             newt = ''
             wlist = re.compile(r'\w+|[^\w]', re.UNICODE).findall(text)
             for w in wlist:
             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
 
             text = newt
 
-        text = re.sub(r'(?<=\s\w)\s+', u'\u00A0', text)
+        if builder.orphans:
+            text = re.sub(r'(?<=\s\w)\s+', '\u00A0', text)
 
         return text
 
 
         return text
 
@@ -120,6 +146,9 @@ class WLElement(etree.ElementBase):
         for i, child in enumerate(self):
             if isinstance(child, WLElement):
                 getattr(child, build_method)(builder)
         for i, child in enumerate(self):
             if isinstance(child, WLElement):
                 getattr(child, build_method)(builder)
+            # 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:
             if self.CAN_HAVE_TEXT and child.tail:
                 text = self.normalize_text(child.tail, builder)
                 if self.STRIP and i == child_count - 1:
@@ -149,14 +178,21 @@ class WLElement(etree.ElementBase):
         attr = self.HTML_ATTR.copy()
         if self.HTML_CLASS:
             attr['class'] = self.HTML_CLASS
         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 '_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):
         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,
         if self.HTML_TAG:
             builder.start_element(
                 self.HTML_TAG,
@@ -182,7 +218,7 @@ class WLElement(etree.ElementBase):
         # TEMPORARY
         self.CAN_HAVE_TEXT = True
         self.STRIP = False
         # TEMPORARY
         self.CAN_HAVE_TEXT = True
         self.STRIP = False
-       
+
         start_chunk = self.EPUB_START_CHUNK and isinstance(self.getparent(), Master)
 
         if start_chunk:
         start_chunk = self.EPUB_START_CHUNK and isinstance(self.getparent(), Master)
 
         if start_chunk:
@@ -204,6 +240,11 @@ class WLElement(etree.ElementBase):
             attr = self.get_epub_attr(builder)
             if fragment:
                 attr['id'] = fragment
             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
             builder.start_element(
                 self.EPUB_TAG,
                 attr
@@ -221,7 +262,7 @@ class WLElement(etree.ElementBase):
         if self.SECTION_PRECEDENCE:
             assert isinstance(self.getparent(), (Master, DlugiCytat, PoezjaCyt, Footnote)), \
                     'Header {} inside a <{}> instead of a master.'.format(
         if self.SECTION_PRECEDENCE:
             assert isinstance(self.getparent(), (Master, DlugiCytat, PoezjaCyt, Footnote)), \
                     'Header {} inside a <{}> instead of a master.'.format(
-                            etree.tostring(self), self.getparent().tag)
+                            etree.tostring(self, encoding='unicode'), self.getparent().tag)
 
         for c in self:
             if isinstance(c, WLElement):
 
         for c in self:
             if isinstance(c, WLElement):
@@ -233,3 +274,98 @@ class WLElement(etree.ElementBase):
         for e in self:
             if isinstance(e, WLElement):
                 e.sanitize()
         for e in self:
             if isinstance(e, WLElement):
                 e.sanitize()
+
+    def snip(self, words, before=None, sub=False):
+        if sub and self.ASIDE:
+            return words, []
+
+        snippet = []
+        if before is not None:
+            i = self.index(before)
+        else:
+            i = len(self)
+
+        while i > 0:
+            i -= 1
+            if self[i].tail:
+                if words:
+                    words, text = last_words(self[i].tail, words)
+                    snippet = [('text', text)] + snippet
+
+            if words:
+                words, subsnip = self[i].snip(words, sub=True)
+                snippet = subsnip + snippet
+
+        if words and self.text:
+            words, text = last_words(self.text, words)
+            snippet = [('text', text)] + snippet
+                    
+        snippet = [('start', self.tag, self.attrib)] + snippet + [('end',)]
+
+        if not sub and words and not self.ASIDE:
+            # do we dare go up?
+            parent = self.getparent()
+            if parent is not None and parent.CAN_HAVE_TEXT:
+                words, parsnip = parent.snip(words, before=self)
+                return words, parsnip[:-1] + snippet + parsnip[-1:]
+
+        return words, snippet
+
+    def get_snippet(self, words=15):
+        from librarian.parser import parser
+
+        words, snippet = self.getparent().snip(words=words, before=self)
+        
+        cursor = snipelem = parser.makeelement('snippet')
+        snipelem._meta_object = self.meta
+        for s in snippet:
+            if s[0] == 'start':
+                elem = parser.makeelement(s[1], **s[2])
+                cursor.append(elem)
+                cursor = elem
+            elif s[0] == 'end':
+                cursor = cursor.getparent()
+            else:
+                if len(cursor):
+                    cursor[-1].tail = (cursor[-1].tail or '') + s[1]
+                else:
+                    cursor.text = (cursor.text or '') + s[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, builder):
+        numbering = self.numbering
+        if numbering:
+            number = str(builder.counters[numbering])
+            self.attrib['_id'] = self.id_prefix + number
+            builder.counters[numbering] += 1
+
+            if numbering == 'main':
+                self.attrib['_visible_numbering'] = str(builder.counters['_visible'])
+                builder.counters['_visible'] += 1
+
+            if numbering == 'fn':
+                self.attrib['_visible_numbering'] = number
+
+    def get_link(self):
+        return self.attrib.get('_id') or self.getparent().get_link()
+
+
+class Snippet(WLElement):
+    pass