1 # -*- coding: utf-8 -*-
 
   3 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
 
   4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 
  11 from lxml import etree
 
  12 from librarian.parser import WLDocument
 
  13 from librarian import XHTMLNS, ParseError
 
  14 from librarian import functions
 
  16 from lxml.etree import XMLSyntaxError, XSLTApplyError
 
  18 functions.reg_substitute_entities()
 
  19 functions.reg_person_name()
 
  22     'legacy': 'xslt/book2html.xslt',
 
  23     'full': 'xslt/wl2html_full.xslt',
 
  24     'partial': 'xslt/wl2html_partial.xslt'
 
  27 def get_stylesheet(name):
 
  28     return os.path.join(os.path.dirname(__file__), STYLESHEETS[name])
 
  30 def html_has_content(text):
 
  31     return etree.ETXPath('//p|//{%(ns)s}p|//h1|//{%(ns)s}h1' % {'ns': str(XHTMLNS)})(text)
 
  33 def transform(input, output_filename=None, is_file=True, \
 
  34     parse_dublincore=True, stylesheet='legacy', options={}, flags=None):
 
  35     """Transforms file input_filename in XML to output_filename in XHTML.
 
  37     If output_filename is None, returns an XML,
 
  38     otherwise returns True if file has been written,False if it hasn't.
 
  39     File won't be written if it has no content.
 
  43         style_filename = get_stylesheet(stylesheet)
 
  44         style = etree.parse(style_filename)
 
  47             document = WLDocument.from_file(input, True, \
 
  48                 parse_dublincore=parse_dublincore)
 
  50             document = WLDocument.from_string(input, True, \
 
  51                 parse_dublincore=parse_dublincore)
 
  55                 document.edoc.getroot().set(flag, 'yes')
 
  57         document.clean_ed_note()
 
  59         result = document.transform(style, **options)
 
  60         del document # no longer needed large object :)
 
  62         if html_has_content(result):
 
  63             add_anchors(result.getroot())
 
  64             add_table_of_contents(result.getroot())
 
  66             if output_filename is not None:
 
  67                 result.write(output_filename, method='html', xml_declaration=False, pretty_print=True, encoding='utf-8')
 
  72             if output_filename is not None:
 
  77         raise ValueError("'%s' is not a valid stylesheet.")
 
  78     except (XMLSyntaxError, XSLTApplyError), e:
 
  81 class Fragment(object):
 
  82     def __init__(self, id, themes):
 
  83         super(Fragment, self).__init__()
 
  88     def append(self, event, element):
 
  89         self.events.append((event, element))
 
  91     def closed_events(self):
 
  93         for event, element in self.events:
 
  95                 stack.append(('end', element))
 
 100                     print 'CLOSED NON-OPEN TAG:', element
 
 103         return self.events + stack
 
 107         for event, element in self.closed_events():
 
 109                 result.append(u'<%s %s>' % (element.tag, ' '.join('%s="%s"' % (k, v) for k, v in element.attrib.items())))
 
 111                     result.append(element.text)
 
 113                 result.append(u'</%s>' % element.tag)
 
 115                     result.append(element.tail)
 
 117                 result.append(element)
 
 119         return ''.join(result)
 
 121     def __unicode__(self):
 
 122         return self.to_string()
 
 125 def extract_fragments(input_filename):
 
 126     """Extracts theme fragments from input_filename."""
 
 128     closed_fragments = {}
 
 130     # iterparse would die on a HTML document
 
 131     parser = etree.HTMLParser(encoding='utf-8')
 
 132     buf = cStringIO.StringIO()
 
 133     buf.write(etree.tostring(etree.parse(input_filename, parser).getroot()[0][0], encoding='utf-8'))
 
 136     for event, element in etree.iterparse(buf, events=('start', 'end')):
 
 137         # Process begin and end elements
 
 138         if element.get('class', '') in ('theme-begin', 'theme-end'):
 
 139             if not event == 'end': continue # Process elements only once, on end event
 
 142             if element.get('class', '') == 'theme-begin':
 
 143                 fragment = Fragment(id=element.get('fid'), themes=element.text)
 
 146                 if element.getparent().get('id', None) != 'book-text':
 
 147                     parents = [element.getparent()]
 
 148                     while parents[-1].getparent().get('id', None) != 'book-text':
 
 149                         parents.append(parents[-1].getparent())
 
 152                     for parent in parents:
 
 153                         fragment.append('start', parent)
 
 155                 open_fragments[fragment.id] = fragment
 
 157             # Close existing fragment
 
 160                     fragment = open_fragments[element.get('fid')]
 
 162                     print '%s:closed not open fragment #%s' % (input_filename, element.get('fid'))
 
 164                     closed_fragments[fragment.id] = fragment
 
 165                     del open_fragments[fragment.id]
 
 167             # Append element tail to lost_text (we don't want to lose any text)
 
 169                 for fragment_id in open_fragments:
 
 170                     open_fragments[fragment_id].append('text', element.tail)
 
 173         # Process all elements except begin and end
 
 175             # Omit annotation tags
 
 176             if len(element.get('name', '')) or element.get('class', '') == 'annotation':
 
 177                 if event == 'end' and element.tail:
 
 178                     for fragment_id in open_fragments:
 
 179                         open_fragments[fragment_id].append('text', element.tail)
 
 181                 for fragment_id in open_fragments:
 
 182                     open_fragments[fragment_id].append(event, copy.copy(element))
 
 184     return closed_fragments, open_fragments
 
 187 def add_anchor(element, prefix, with_link=True, with_target=True, link_text=None):
 
 189         if link_text is None:
 
 191         anchor = etree.Element('a', href='#%s' % prefix)
 
 192         anchor.set('class', 'anchor')
 
 193         anchor.text = unicode(link_text)
 
 195             anchor.tail = element.text
 
 197         element.insert(0, anchor)
 
 200         anchor_target = etree.Element('a', name='%s' % prefix)
 
 201         anchor_target.set('class', 'target')
 
 202         anchor_target.text = u' '
 
 204             anchor_target.tail = element.text
 
 206         element.insert(0, anchor_target)
 
 209 def any_ancestor(element, test):
 
 210     for ancestor in element.iterancestors():
 
 216 def add_anchors(root):
 
 218     for element in root.iterdescendants():
 
 219         if any_ancestor(element, lambda e: e.get('class') in ('note', 'motto', 'motto_podpis', 'dedication')
 
 220         or e.get('id') == 'nota_red'
 
 221         or e.tag == 'blockquote'):
 
 224         if element.tag == 'p' and 'verse' in element.get('class', ''):
 
 225             if counter == 1 or counter % 5 == 0:
 
 226                 add_anchor(element, "f%d" % counter, link_text=counter)
 
 228         elif 'paragraph' in element.get('class', ''):
 
 229             add_anchor(element, "f%d" % counter, link_text=counter)
 
 233 def add_table_of_contents(root):
 
 236     for element in root.iterdescendants():
 
 237         if element.tag in ('h2', 'h3'):
 
 238             if any_ancestor(element, lambda e: e.get('id') in ('footnotes',) or e.get('class') in ('person-list',)):
 
 241             if element.tag == 'h3' and len(sections) and sections[-1][1] == 'h2':
 
 242                 sections[-1][3].append((counter, element.tag, ''.join(element.xpath('text()')), []))
 
 244                 sections.append((counter, element.tag, ''.join(element.xpath('text()')), []))
 
 245             add_anchor(element, "s%d" % counter, with_link=False)
 
 248     toc = etree.Element('div')
 
 250     toc_header = etree.SubElement(toc, 'h2')
 
 251     toc_header.text = u'Spis treści'
 
 252     toc_list = etree.SubElement(toc, 'ol')
 
 254     for n, section, text, subsections in sections:
 
 255         section_element = etree.SubElement(toc_list, 'li')
 
 256         add_anchor(section_element, "s%d" % n, with_target=False, link_text=text)
 
 259             subsection_list = etree.SubElement(section_element, 'ol')
 
 260             for n, subsection, text, _ in subsections:
 
 261                 subsection_element = etree.SubElement(subsection_list, 'li')
 
 262                 add_anchor(subsection_element, "s%d" % n, with_target=False, link_text=text)
 
 267 def extract_annotations(html_path):
 
 268     """For each annotation, yields a tuple: anchor, text, html."""
 
 269     parser = etree.HTMLParser(encoding='utf-8')
 
 270     tree = etree.parse(html_path, parser)
 
 271     footnotes = tree.find('//*[@id="footnotes"]')
 
 272     if footnotes is not None:
 
 273         for footnote in footnotes.findall('div'):
 
 274             anchor = footnote.find('a[@name]').get('name')
 
 276             text_str = etree.tostring(footnote, method='text', encoding='utf-8').strip()
 
 277             html_str = etree.tostring(footnote, method='html', encoding='utf-8')
 
 278             yield anchor, text_str, html_str