Update of /cvs-repository/Zope/lib/python/docutils/writers In directory cvs.zope.org:/tmp/cvs-serv27461/writers
Added Files: Tag: Zope-2_7-branch null.py newlatex2e.py Log Message: added === Added File Zope/lib/python/docutils/writers/null.py === # Author: David Goodger # Contact: [EMAIL PROTECTED] # Revision: $Revision: 1.1.2.1 $ # Date: $Date: 2005/10/09 14:45:39 $ # Copyright: This module has been placed in the public domain. """ A do-nothing Writer. """ from docutils import writers class Writer(writers.Writer): supported = ('null',) """Formats this writer supports.""" config_section = 'null writer' config_section_dependencies = ('writers',) def translate(self): pass === Added File Zope/lib/python/docutils/writers/newlatex2e.py === """ :Author: Felix Wiemann :Contact: [EMAIL PROTECTED] :Revision: $Revision: 1.1.2.1 $ :Date: $Date: 2005/10/09 14:45:39 $ :Copyright: This module has been placed in the public domain. LaTeX2e document tree Writer. """ # Thanks to Engelbert Gruber and various contributors for the original # LaTeX writer, some code and many ideas of which have been used for # this writer. __docformat__ = 'reStructuredText' from __future__ import nested_scopes import re import os.path from types import ListType import docutils from docutils import nodes, writers, utils class Writer(writers.Writer): supported = ('newlatex', 'newlatex2e') """Formats this writer supports.""" settings_spec = ( 'LaTeX-Specific Options', 'The LaTeX "--output-encoding" default is "latin-1:strict". ' 'Note that this LaTeX writer is still EXPERIMENTAL.', (('Specify a stylesheet file. The path is used verbatim to include ' 'the file. Overrides --stylesheet-path.', ['--stylesheet'], {'default': '', 'metavar': '<file>', 'overrides': 'stylesheet_path'}), ('Specify a stylesheet file, relative to the current working ' 'directory. Overrides --stylesheet.', ['--stylesheet-path'], {'metavar': '<file>', 'overrides': 'stylesheet'}), ('Specify a uesr stylesheet file. See --stylesheet.', ['--user-stylesheet'], {'default': '', 'metavar': '<file>', 'overrides': 'user_stylesheet_path'}), ('Specify a user stylesheet file. See --stylesheet-path.', ['--user-stylesheet-path'], {'metavar': '<file>', 'overrides': 'user_stylesheet'}) ),) settings_defaults = {'output_encoding': 'latin-1', 'trim_footnote_reference_space': 1, # Currently unsupported: 'docinfo_xform': 0, # During development: 'traceback': 1} relative_path_settings = ('stylesheet_path',) config_section = 'newlatex2e writer' config_section_dependencies = ('writers',) output = None """Final translated form of `document`.""" def __init__(self): writers.Writer.__init__(self) self.translator_class = LaTeXTranslator def translate(self): visitor = self.translator_class(self.document) self.document.walkabout(visitor) assert not visitor.context, 'context not empty: %s' % visitor.context self.output = visitor.astext() self.head = visitor.header self.body = visitor.body class Babel: """Language specifics for LaTeX.""" # country code by a.schlock. # partly manually converted from iso and babel stuff, dialects and some _ISO639_TO_BABEL = { 'no': 'norsk', # added by hand ( forget about nynorsk?) 'gd': 'scottish', # added by hand 'hu': 'magyar', # added by hand 'pt': 'portuguese',# added by hand 'sl': 'slovenian', 'af': 'afrikaans', 'bg': 'bulgarian', 'br': 'breton', 'ca': 'catalan', 'cs': 'czech', 'cy': 'welsh', 'da': 'danish', 'fr': 'french', # french, francais, canadien, acadian 'de': 'ngerman', # rather than german # ngerman, naustrian, german, germanb, austrian 'el': 'greek', 'en': 'english', # english, USenglish, american, UKenglish, british, canadian 'eo': 'esperanto', 'es': 'spanish', 'et': 'estonian', 'eu': 'basque', 'fi': 'finnish', 'ga': 'irish', 'gl': 'galician', 'he': 'hebrew', 'hr': 'croatian', 'hu': 'hungarian', 'is': 'icelandic', 'it': 'italian', 'la': 'latin', 'nl': 'dutch', 'pl': 'polish', 'pt': 'portuguese', 'ro': 'romanian', 'ru': 'russian', 'sk': 'slovak', 'sr': 'serbian', 'sv': 'swedish', 'tr': 'turkish', 'uk': 'ukrainian' } def __init__(self, lang): self.language = lang def get_language(self): if self._ISO639_TO_BABEL.has_key(self.language): return self._ISO639_TO_BABEL[self.language] else: # Support dialects. l = self.language.split("_")[0] if self._ISO639_TO_BABEL.has_key(l): return self._ISO639_TO_BABEL[l] return None class LaTeXException(Exception): """ Exception base class to for exceptions which influence the automatic generation of LaTeX code. """ class SkipAttrParentLaTeX(LaTeXException): """ Do not generate \Dattr and \renewcommand{\Dparent}{...} for this node. To be raised from before_... methods. """ class SkipParentLaTeX(LaTeXException): """ Do not generate \renewcommand{\DNparent}{...} for this node. To be raised from before_... methods. """ class LaTeXTranslator(nodes.SparseNodeVisitor): # Start with left double quote. left_quote = 1 def __init__(self, document): nodes.NodeVisitor.__init__(self, document) self.settings = document.settings self.header = [] self.body = [] self.context = [] self.stylesheet_path = utils.get_stylesheet_reference( self.settings, os.path.join(os.getcwd(), 'dummy')) if self.stylesheet_path: self.settings.record_dependencies.add(self.stylesheet_path) # This ugly hack will be cleaned up when refactoring the # stylesheet mess. self.settings.stylesheet = self.settings.user_stylesheet self.settings.stylesheet_path = self.settings.user_stylesheet_path self.user_stylesheet_path = utils.get_stylesheet_reference( self.settings, os.path.join(os.getcwd(), 'dummy')) if self.user_stylesheet_path: self.settings.record_dependencies.add(self.user_stylesheet_path) self.write_header() for key, value in self.character_map.items(): self.character_map[key] = '{%s}' % value def write_header(self): a = self.header.append a('%% Generated by Docutils %s <http://docutils.sourceforge.net>.\n' % docutils.__version__) if self.user_stylesheet_path: a('% User stylesheet:') a(r'\input{%s}' % self.user_stylesheet_path) a('% Docutils stylesheet:') a(r'\input{%s}' % self.stylesheet_path) a('') a('% Definitions for Docutils Nodes:') for node_name in nodes.node_class_names: a(r'\providecommand{\DN%s}[1]{#1}' % node_name.replace('_', '')) a('') a('% Auxiliary definitions:') a(r'\providecommand{\Dsetattr}[2]{}') a(r'\providecommand{\Dparent}{} % variable') a(r'\providecommand{\Dattr}[5]{#5}') a(r'\providecommand{\Dattrlen}{} % variable') a(r'\providecommand{\Dtitleastext}{x}') a(r'\providecommand{\Dsinglebackref}{} % variable') a(r'\providecommand{\Dmultiplebackrefs}{} % variable') a('\n\n') def to_latex_encoding(self,docutils_encoding): """ Translate docutils encoding name into latex's. Default fallback method is remove "-" and "_" chars from docutils_encoding. """ tr = { "iso-8859-1": "latin1", # west european "iso-8859-2": "latin2", # east european "iso-8859-3": "latin3", # esperanto, maltese "iso-8859-4": "latin4", # north european,scandinavian, baltic "iso-8859-5": "iso88595", # cyrillic (ISO) "iso-8859-9": "latin5", # turkish "iso-8859-15": "latin9", # latin9, update to latin1. "mac_cyrillic": "maccyr", # cyrillic (on Mac) "windows-1251": "cp1251", # cyrillic (on Windows) "koi8-r": "koi8-r", # cyrillic (Russian) "koi8-u": "koi8-u", # cyrillic (Ukrainian) "windows-1250": "cp1250", # "windows-1252": "cp1252", # "us-ascii": "ascii", # ASCII (US) # unmatched encodings #"": "applemac", #"": "ansinew", # windows 3.1 ansi #"": "ascii", # ASCII encoding for the range 32--127. #"": "cp437", # dos latine us #"": "cp850", # dos latin 1 #"": "cp852", # dos latin 2 #"": "decmulti", #"": "latin10", #"iso-8859-6": "" # arabic #"iso-8859-7": "" # greek #"iso-8859-8": "" # hebrew #"iso-8859-10": "" # latin6, more complete iso-8859-4 } if tr.has_key(docutils_encoding.lower()): return tr[docutils_encoding.lower()] return docutils_encoding.translate(string.maketrans("",""),"_-").lower() def language_label(self, docutil_label): return self.language.labels[docutil_label] # To do: Use unimap.py from TeXML instead. Have to deal with # legal cruft before, because it's LPGL. character_map_string = r""" \ \textbackslash { \{ } \} $ \$ & \& % \% # \# [ [ ] ] - - ` ` ' ' , , " " | \textbar < \textless > \textgreater ^ \textasciicircum ~ \textasciitilde _ \Dtextunderscore """ #special_map = {'\n': ' ', '\r': ' ', '\t': ' ', '\v': ' ', '\f': ' '} unicode_map = { u'\u00A0': '~', u'\u2009': '{\\,}', u'\u2013': '{--}', u'\u2014': '{---}', u'\u2018': '`', u'\u2019': '\'', u'\u201A': ',', u'\u201C': '``', u'\u201D': "''", u'\u201E': ',,', u'\u2020': '{\\dag}', u'\u2021': '{\\ddag}', u'\u2026': '{\\dots}', u'\u2122': '{\\texttrademark}', u'\u21d4': '{$\\Leftrightarrow$}', } character_map = {} for pair in character_map_string.strip().split('\n'): char, replacement = pair.split() character_map[char] = replacement character_map.update(unicode_map) #character_map.update(special_map) def encode(self, text, attval=0): """ Encode special characters in ``text`` and return it. If attval is true, preserve as much as possible verbatim (used in attribute value encoding). """ if not attval: get = self.character_map.get else: # According to # <http://www-h.eng.cam.ac.uk/help/tpl/textprocessing/teTeX/latex/latex2e-html/ltx-164.html>, # the following characters are special: # $ % & ~ _ ^ \ { } # These work without special treatment in macro parameters: # $, &, ~, _, ^ get = {'#': '\\#', '%': '\\%', # We cannot do anything about backslashes. '\\': '', '{': '\\{', '}': '\\}', # The quotation mark may be redefined by babel. '"': '"{}', }.get text = ''.join([get(c, c) for c in text]) if (self.literal_block or self.inline_literal) and not attval: # NB: We can have inline literals within literal blocks. # Shrink '\r\n'. text = text.replace('\r\n', '\n') # Convert space. If "{ }~~~~~" is wrapped (at the # brace-enclosed space "{ }"), the following non-breaking # spaces ("~~~~") do *not* wind up at the beginning of the # next line. Also note that, for some not-so-obvious # reason, no hyphenation is done if the breaking space ("{ # }") comes *after* the non-breaking spaces. if self.literal_block: # Replace newlines with real newlines. text = text.replace('\n', '\mbox{}\\\\') firstspace = '~' else: firstspace = '{ }' text = re.sub(r'\s+', lambda m: firstspace + '~' * (len(m.group()) - 1), text) # Protect hyphens; if we don't, line breaks will be # possible at the hyphens and even the \textnhtt macro # from the hyphenat package won't change that. text = text.replace('-', r'\mbox{-}') text = text.replace("'", r'{\Dtextliteralsinglequote}') return text else: if not attval: # Replace space with single protected space. text = re.sub(r'\s+', '{ }', text) # Replace double quotes with macro calls. L = [] for part in text.split('"'): if L: # Insert quote. L.append(self.left_quote and r'\Dtextleftdblquote' or r'\Dtextrightdblquote') self.left_quote = not self.left_quote L.append(part) return ''.join(L) else: return text def astext(self): return '\n'.join(self.header) + (''.join(self.body)) def append(self, text, newline='%\n'): """ Append text, stripping newlines, producing nice LaTeX code. """ lines = [' ' * self.indentation_level + line + newline for line in text.splitlines(0)] self.body.append(''.join(lines)) def visit_Text(self, node): self.append(self.encode(node.astext())) def depart_Text(self, node): pass def before_title(self, node): self.append(r'\renewcommand{\Dtitleastext}{%s}' % self.encode(node.astext())) self.append(r'\renewcommand{\Dhassubtitle}{%s}' % ((len(node.parent) > 2 and isinstance(node.parent[1], nodes.subtitle)) and 'true' or 'false')) literal_block = 0 def visit_literal_block(self, node): self.literal_block = 1 def depart_literal_block(self, node): self.literal_block = 0 visit_doctest_block = visit_literal_block depart_doctest_block = depart_literal_block inline_literal = 0 def visit_literal(self, node): self.inline_literal += 1 def depart_literal(self, node): self.inline_literal -= 1 def visit_comment(self, node): self.append('\n'.join(['% ' + line for line in node.astext().splitlines(0)]), newline='\n') raise nodes.SkipChildren bullet_list_level = 0 def visit_bullet_list(self, node): self.append(r'\Dsetbullet{\labelitem%s}' % ['i', 'ii', 'iii', 'iv'][min(self.bullet_list_level, 3)]) self.bullet_list_level += 1 def depart_bullet_list(self, node): self.bullet_list_level -= 1 enum_styles = {'arabic': 'arabic', 'loweralpha': 'alph', 'upperalpha': 'Alph', 'lowerroman': 'roman', 'upperroman': 'Roman'} enum_counter = 0 def visit_enumerated_list(self, node): # We create our own enumeration list environment. This allows # to set the style and starting value and unlimited nesting. # Maybe this can be moved to the stylesheet? self.enum_counter += 1 enum_prefix = self.encode(node['prefix']) enum_suffix = self.encode(node['suffix']) enum_type = '\\' + self.enum_styles.get(node['enumtype'], r'arabic') start = node.get('start', 1) - 1 counter = 'Denumcounter%d' % self.enum_counter self.append(r'\Dmakeenumeratedlist{%s}{%s}{%s}{%s}{%s}{' % (enum_prefix, enum_type, enum_suffix, counter, start)) # for Emacs: } def depart_enumerated_list(self, node): self.append('}') # for Emacs: { def before_list_item(self, node): # XXX needs cleanup. if (len(node) and (isinstance(node[-1], nodes.TextElement) or isinstance(node[-1], nodes.Text)) and node.parent.index(node) == len(node.parent) - 1): node['lastitem'] = 'true' before_line = before_list_item def before_raw(self, node): if 'latex' in node.get('format', '').split(): # We're inserting the text in before_raw and thus outside # of \DN... and \Dattr in order to make grouping with # curly brackets work. self.append(node.astext()) raise nodes.SkipChildren def process_backlinks(self, node, type): self.append(r'\renewcommand{\Dsinglebackref}{}') self.append(r'\renewcommand{\Dmultiplebackrefs}{}') if len(node['backrefs']) > 1: refs = [] for i in range(len(node['backrefs'])): refs.append(r'\Dmulti%sbacklink{%s}{%s}' % (type, node['backrefs'][i], i + 1)) self.append(r'\renewcommand{\Dmultiplebackrefs}{(%s){ }}' % ', '.join(refs)) elif len(node['backrefs']) == 1: self.append(r'\renewcommand{\Dsinglebackref}{%s}' % node['backrefs'][0]) def visit_footnote(self, node): self.process_backlinks(node, 'footnote') def visit_citation(self, node): self.process_backlinks(node, 'citation') def before_table(self, node): # A tables contains exactly one tgroup. See before_tgroup. pass def before_tgroup(self, node): widths = [] total_width = 0 for i in range(int(node['cols'])): assert isinstance(node[i], nodes.colspec) widths.append(int(node[i]['colwidth']) + 1) total_width += widths[-1] del node[:len(widths)] tablespec = '|' for w in widths: # 0.93 is probably wrong in many cases. XXX Find a # solution which works *always*. tablespec += r'p{%s\linewidth}|' % (0.93 * w / max(total_width, 60)) self.append(r'\Dmaketable{%s}{' % tablespec) self.context.append('}') raise SkipAttrParentLaTeX def depart_tgroup(self, node): self.append(self.context.pop()) def before_row(self, node): raise SkipAttrParentLaTeX def before_thead(self, node): raise SkipAttrParentLaTeX def before_tbody(self, node): raise SkipAttrParentLaTeX def is_simply_entry(self, node): return (len(node) == 1 and isinstance(node[0], nodes.paragraph) or len(node) == 0) def before_entry(self, node): is_leftmost = 0 if node.hasattr('morerows'): self.document.reporter.severe('Rowspans are not supported.') # Todo: Add empty cells below rowspanning cell and issue # warning instead of severe. if node.hasattr('morecols'): # The author got a headache trying to implement # multicolumn support. if not self.is_simply_entry(node): self.document.reporter.severe( 'Colspanning table cells may only contain one paragraph.') # Todo: Same as above. # The number of columns this entry spans (as a string). colspan = int(node['morecols']) + 1 del node['morecols'] else: colspan = 1 # Macro to call. macro_name = r'\Dcolspan' if node.parent.index(node) == 0: # Leftmost column. macro_name += 'left' is_leftmost = 1 if colspan > 1: self.append('%s{%s}{' % (macro_name, colspan)) self.context.append('}') else: # Do not add a multicolumn with colspan 1 beacuse we need # at least one non-multicolumn cell per column to get the # desired column widths, and we can only do colspans with # cells consisting of only one paragraph. if not is_leftmost: self.append(r'\Dsubsequententry{') self.context.append('}') else: self.context.append('') if isinstance(node.parent.parent, nodes.thead): node['tableheaderentry'] = 'true' # Don't add \renewcommand{\Dparent}{...} because there must # not be any non-expandable commands in front of \multicolumn. raise SkipParentLaTeX def depart_entry(self, node): self.append(self.context.pop()) def before_substitution_definition(self, node): raise nodes.SkipNode indentation_level = 0 def node_name(self, node): return node.__class__.__name__.replace('_', '') def propagate_attributes(self, node): # Propagate attributes using \Dattr macros. node_name = self.node_name(node) attlist = [] if isinstance(node, nodes.Element): attlist = node.attlist() numatts = 0 pass_contents = self.pass_contents(node) for key, value in attlist: if isinstance(value, ListType): self.append(r'\renewcommand{\Dattrlen}{%s}' % len(value)) for i in range(len(value)): self.append(r'\Dattr{%s}{%s}{%s}{%s}{' % (i+1, key, self.encode(value[i], attval=1), node_name)) if not pass_contents: self.append('}') numatts += len(value) else: self.append(r'\Dattr{}{%s}{%s}{%s}{' % (key, self.encode(unicode(value), attval=1), node_name)) if not pass_contents: self.append('}') numatts += 1 if pass_contents: self.context.append('}' * numatts) # for Emacs: { else: self.context.append('') def visit_docinfo(self, node): raise NotImplementedError('Docinfo not yet implemented.') def visit_document(self, node): document = node # Move IDs into TextElements. This won't work for images. # Need to review this. for node in document.traverse(lambda n: isinstance(n, nodes.Element)): if node.has_key('ids') and not isinstance(node, nodes.TextElement): next_text_element = node.next_node( lambda n: isinstance(n, nodes.TextElement)) if next_text_element: next_text_element['ids'].extend(node['ids']) node['ids'] = [] def pass_contents(self, node): r""" Return true if the node contents should be passed in parameters of \DN... and \Dattr. """ return not isinstance(node, (nodes.document, nodes.section)) def dispatch_visit(self, node): skip_attr = skip_parent = 0 # TreePruningException to be propagated. tree_pruning_exception = None if hasattr(self, 'before_' + node.__class__.__name__): try: getattr(self, 'before_' + node.__class__.__name__)(node) except SkipParentLaTeX: skip_parent = 1 except SkipAttrParentLaTeX: skip_attr = 1 skip_parent = 1 except nodes.SkipNode: raise except (nodes.SkipChildren, nodes.SkipSiblings), instance: tree_pruning_exception = instance except nodes.SkipDeparture: raise NotImplementedError( 'SkipDeparture not usable in LaTeX writer') if not isinstance(node, nodes.Text): node_name = self.node_name(node) # attribute_deleters will be appended to self.context. attribute_deleters = [] if not skip_parent and not isinstance(node, nodes.document): self.append(r'\renewcommand{\Dparent}{%s}' % self.node_name(node.parent)) for name, value in node.attlist(): # @@@ Evaluate if this is really needed and refactor. if not isinstance(value, ListType) and not ':' in name: macro = r'\DcurrentN%sA%s' % (node_name, name) self.append(r'\def%s{%s}' % ( macro, self.encode(unicode(value), attval=1))) attribute_deleters.append(r'\let%s=\relax' % macro) self.context.append('\n'.join(attribute_deleters)) if self.pass_contents(node): self.append(r'\DN%s{' % node_name) self.context.append('}') else: self.append(r'\Dvisit%s' % node_name) self.context.append(r'\Ddepart%s' % node_name) self.indentation_level += 1 if not skip_attr: self.propagate_attributes(node) else: self.context.append('') if (isinstance(node, nodes.TextElement) and not isinstance(node.parent, nodes.TextElement)): # Reset current quote to left. self.left_quote = 1 # Call visit_... method. try: nodes.SparseNodeVisitor.dispatch_visit(self, node) except LaTeXException: raise NotImplementedError( 'visit_... methods must not raise LaTeXExceptions') if tree_pruning_exception: # Propagate TreePruningException raised in before_... method. raise tree_pruning_exception def is_invisible(self, node): # Return true if node is invisible or moved away in the LaTeX # rendering. return (isinstance(node, nodes.Invisible) or isinstance(node, nodes.footnote) or isinstance(node, nodes.citation) or # We never know what's inside raw nodes, and often # they *are* invisible. So let's have the user take # care of them. isinstance(node, nodes.raw) or # Horizontally aligned image or figure. node.get('align', None) in ('left', 'center', 'right')) def needs_space(self, node): # Return true if node is a visible block-level element. return ((isinstance(node, nodes.Body) or isinstance(node, nodes.topic) or #isinstance(node, nodes.rubric) or isinstance(node, nodes.transition) or isinstance(node, nodes.caption) or isinstance(node, nodes.legend)) and not (self.is_invisible(node) or isinstance(node.parent, nodes.TextElement))) def dispatch_departure(self, node): # Call departure method. nodes.SparseNodeVisitor.dispatch_departure(self, node) if not isinstance(node, nodes.Text): # Close attribute and node handler call (\DN...{...}). self.indentation_level -= 1 self.append(self.context.pop() + self.context.pop()) # Delete \Dcurrent... attribute macros. self.append(self.context.pop()) # Insert space. if self.needs_space(node): # Next sibling. next_node = node.next_node( ascend=0, siblings=1, descend=0, condition=lambda n: not self.is_invisible(n)) if self.needs_space(next_node): # Insert space. if isinstance(next_node, nodes.paragraph): if isinstance(node, nodes.paragraph): # Space between paragraphs. self.append(r'\Dparagraphspace') else: # Space in front of a paragraph. self.append(r'\Dauxiliaryparspace') else: # Space in front of something else than a paragraph. self.append(r'\Dauxiliaryspace') _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins