#!@PYTHON@ # # This file is part of LilyPond, the GNU music typesetter. # # Copyright (C) 2006--2020 John Mandereau # # LilyPond is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # LilyPond is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LilyPond. If not, see . """ Postprocess HTML files: add footer, tweak links, add language selection menu. """ import codecs import re import os import sys import time import langdefs def _doc(s): return s program_name = os.path.basename(sys.argv[0]) footer = ''' ''' footer_name_version = _doc('This page is for %(package_name)s-' '%(package_version)s (%(branch_str)s).') # ugh, must not have "_doc" in strings because it is naively replaced with "_" # in hacked gettext process footer_report_links = _doc('We welcome your aid; please ' 'help us by ' 'reporting errors to our ' 'bug list.') sidebar_version = _doc(' v%(package_version)s (%(branch_str)s).') bug_lilypond_url = 'https://lists.gnu.org/mailman/listinfo/bug-lilypond' help_us_url = 'https://lilypond.org/help-us.html' header_tag = '' header_tag_re = re.compile(header_tag) lang_available = _doc("Other languages: %s.") browser_lang = _doc('About automatic language selection.') browser_language_url = "http://www.lilypond.org/website/misc/browser-language" LANGUAGES_TEMPLATE = '''

%(language_available)s
%(browser_language)s

''' html_re = re.compile('(.*?)(?:[.]([^/.]*))?[.]html$') def build_pages_dict(filelist): """Build dictionary of available translations of each page. Returns: basename => list of languages dict""" pages_dict = {} language_codes = set([l.webext for l in langdefs.LANGUAGES]) for f in filelist: m = html_re.match(f) if m: g = m.groups() if len(g) <= 1 or g[1] is None: e = '' else: e = g[1] if e not in language_codes: continue if not g[0] in pages_dict: pages_dict[g[0]] = [e] else: pages_dict[g[0]].append(e) return pages_dict body_tag_re = re.compile('(?i)]*)>') html_tag_re = re.compile('(?i)') doctype_re = re.compile('(?i)\n' css_re = re.compile(r'(?i)]*)href="[^">]*?' r'(lilypond.*\.css)"([^>]*)>') end_head_tag_re = re.compile('(?i)') css_link = (' \n' ' \n') def add_header(s, prefix): """Add header (, doctype and CSS)""" header = "" if header_tag_re.search(s) == None: body = '' (s, n) = body_tag_re.subn(body + header, s, 1) if not n: (s, n) = html_tag_re.subn('' + header, s, 1) if not n: s = header + s if doctype_re.search(s) is None: s = doctype + header_tag + '\n' + s if css_re.search(s) is None: depth = (prefix.count('/') - 1) * '../' s = end_head_tag_re.sub((css_link % {'rel': depth}) + '', s) return s footer_insert_re = re.compile(r'') end_body_re = re.compile(r'(?i)') verifier_div = '
' verifier_re = re.compile(verifier_div) def add_footer(s, footer_text): """add footer""" # prefer inserting at FOOTER; this is necessary for the footer to # be positioned correctly relative to the nav frame in the manuals. (s, n) = footer_insert_re.subn(footer_text + '\n' + '', s, 1) if not n: (s, n) = verifier_re.subn(footer_text + '\n' + verifier_div, s, 1) if not n: (s, n) = end_body_re.subn(footer_text + '\n' + '', s, 1) if not n: # this happens with the HTML files under Documentation/misc s += footer_text return s def find_translations(pages_dict, prefix, lang_ext): """find available translations of a page""" available = [] for l in langdefs.LANGUAGES: e = l.webext if lang_ext != e: if e in pages_dict[prefix]: available.append(l) return available online_links_re = re.compile('''(href|src)=['"]\ ((?!Compiling-from-source.html")[^/][.]*[^.:'"]*)\ ([.]html)(#[^"']*|)['"]''') offline_links_re = re.compile('''href=['"]\ ((?!Compiling-from-source.html")(?![.]{2}/contributor)[^/][.]*[^.:'"]*)\ ([.]html)(#[^"\']*|)[\'"]''') big_page_name_re = re.compile('''(.+?)-big-page''') def process_i18n_big_page_links(pages_dict, match, prefix, lang_ext): big_page_name = big_page_name_re.match(match.group(1)) if big_page_name: destination_path = os.path.normpath(os.path.join( os.path.dirname(prefix), big_page_name.group(0))) if not (destination_path in pages_dict and lang_ext in pages_dict[destination_path]): return match.group(0) return ('href="' + match.group(1) + '.' + lang_ext + match.group(2) + match.group(3) + '"') def process_links(pages_dict, content, prefix, lang_ext, file_name, target): page_flavors = {} if target == 'online': # Strip .html, suffix for auto language selection (content # negotiation). The menu must keep the full extension, so do # this before adding the menu. page_flavors[file_name] = [lang_ext, online_links_re.sub('\\1="\\2\\4"', content)] elif target == 'offline': # in LANG doc index: don't rewrite .html suffixes # as not all .LANG.html pages exist; # the doc index should be translated and contain links # with the right suffixes # idem for NEWS if lang_ext == '': page_flavors[file_name] = [lang_ext, content] else: # For saving bandwidth and disk space, we don't duplicate big pages # in English, so we must process translated # big pages links differently. if 'big-page' in prefix: page_flavors[file_name] = [lang_ext, offline_links_re.sub( lambda match: process_i18n_big_page_links( pages_dict, match, prefix, lang_ext), content)] else: page_flavors[file_name] = [lang_ext, offline_links_re.sub( 'href="\\1.' + lang_ext + '\\2\\3"', content)] return page_flavors def add_menu(page_flavors, prefix, available, target, translation): for k in page_flavors: language_menu = '' if page_flavors[k][0] != '': t = translation[page_flavors[k][0]] else: t = _doc for lang in available: lang_file = lang.file_name(os.path.basename(prefix), '.html') if language_menu != '': language_menu += ', ' language_menu += '%s' % (lang_file, t(lang.name)) languages = '' if language_menu: browser_language = t(browser_lang) % browser_language_url language_available = t(lang_available) % language_menu languages = LANGUAGES_TEMPLATE % vars() full_footer = '' if 'web' not in prefix: full_footer += footer full_footer += languages full_footer = '''''' % full_footer page_flavors[k][1] = add_footer(page_flavors[k][1], full_footer) return page_flavors def process_html_files(pages_dict, package_name='', package_version='', target='offline'): """Add header, footer and tweak links to a number of HTML files Arguments: pages_dict: dict of filename => translations package_name=NAME set package_name to NAME package_version=VERSION set package version to VERSION targets=offline|online set page processing depending on the target offline is for reading HTML pages locally online is for hosting the HTML pages on a website with content negotiation """ versiontup = package_version.split('.') branch_str = _doc('stable-branch') if int(versiontup[1]) % 2: branch_str = _doc('development-branch') en_dict = { 'package_name': package_name, 'package_version': package_version, 'branch_str': branch_str, 'help_us_url': 'https://lilypond.org/help-us.html', 'bug_lilypond_url': 'https://lists.gnu.org/mailman/listinfo/bug-lilypond', 'footer_name_version': _doc ('This page is for %(package_name)s-' '%(package_version)s (%(branch_str)s).'), 'footer_report_links': _doc ('We welcome your aid; please ' 'help us by ' 'reporting errors to our ' 'bug list.'), } # language => (dict of str => str) subst = { '': en_dict, } for l in langdefs.translation: e = langdefs.LANGDICT[l].webext if e: subst[e] = { name: langdefs.translation[l](en_dict[name]) for name in en_dict} # Do deeper string formatting as early as possible, # so only one '%' formatting pass is needed later for e in subst: for k in ['footer_name_version', 'footer_report_links']: subst[e][k] = subst[e][k] % subst[e] for prefix, ext_list in list(pages_dict.items()): for lang_ext in ext_list: file_name = langdefs.lang_file_name(prefix, lang_ext, '.html') dest_time = 0 content = codecs.open(file_name, 'r', 'utf-8').read() content = content.replace('%', '%%') content = add_header(content, prefix) # add sidebar information content = content.replace('', sidebar_version) available = find_translations(pages_dict, prefix, lang_ext) page_flavors = process_links(pages_dict, content, prefix, lang_ext, file_name, target) # Add menu after stripping: must not have autoselection for language menu. page_flavors = add_menu( page_flavors, prefix, available, target, langdefs.translation) for k in page_flavors: page_flavors[k][1] = page_flavors[k][1] % subst[page_flavors[k][0]] # Must write to tmp file to avoid touching hardlinked files. out_f = codecs.open(k + ".tmp", 'w', 'utf-8') out_f.write(page_flavors[k][1]) out_f.close() os.rename(k + ".tmp", k) # if the page is translated, a .en.html symlink is necessary for content negotiation if target == 'online' and ext_list != [''] and not os.path.lexists(prefix + '.en.html'): os.symlink(os.path.basename(prefix) + '.html', prefix + '.en.html')