3 # Copyright (C) 2011-2012 W. Trevor King <wking@drexel.edu>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this program. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """View Maple worksheets (.mw) from the command line (without X).
21 ./mw2txt.py -c input.mw | less
22 ./mw2txt.py -m input.mw | /opt/maple15/bin/maple | less
27 import lxml.etree as _lxml_etree
30 from pygments.console import colorize as _colorize
31 except ImportError, e:
32 _sys.stderr.write(str(e) + '\n')
33 def _write_color(string, color=None, stream=None):
38 def _write_color(string, color=None, stream=None):
43 stream.write(_colorize(color_key=color, text=string))
49 class Writer (object):
50 def __init__(self, color=None, stream=None, use_color=False):
53 self.use_color = use_color
56 def __call__(self, text, color=None):
57 if not self.use_color:
61 if text == '\n' and self.last_char == '\n':
62 return # don't add lots of blank lines
63 _write_color(string=text, color=color, stream=self.stream)
64 self.last_char = text[-1]
67 def mw2txt(path, writer, filter_math=False):
68 xml = _lxml_etree.parse(path)
71 match=lambda node: node.tag == 'Text-field',
72 match_action=lambda node: top_text_node2txt(
73 node=node, writer=writer, filter_math=filter_math),
74 match_tail=lambda node:writer(text='\n'))
76 def top_text_node2txt(node, writer, filter_math=False):
80 match_action = lambda node: other_in_text_node2txt(
81 node=node, writer=writer)
84 match=lambda node: node.tag not in ['Text-field', 'Font', 'Hyperlink'],
85 match_action=match_action,
86 other_action=lambda node: text_node2txt(
87 node=node, writer=writer, filter_math=filter_math),
88 match_tail=lambda node:tail_node2txt(
89 node=node, writer=writer, filter_math=filter_math),
90 other_tail=lambda node:tail_node2txt(
91 node=node, writer=writer, filter_math=filter_math))
93 def other_in_text_node2txt(node, writer):
94 if node.tag in ['Drawing-Root']:
95 # ignore missing content
97 elif node.tag in ['Equation', 'Image', 'Plot']:
98 # warn about missing content
99 writer(text=node.tag, color='yellow')
101 # warn about wierd tag
102 writer(text=node.tag, color='magenta')
104 def text_node2txt(node, writer, filter_math=False):
105 if node.tag not in ['Text-field', 'Font', 'Hyperlink'] and not filter_math:
106 # warn about wierd tag
107 writer(text=node.tag, color='magenta')
109 node=node, text=node.text, writer=writer, filter_math=filter_math)
111 def tail_node2txt(node, writer, filter_math=False):
112 if node.tag != 'Text-field':
114 node=node.getparent(), text=node.tail, writer=writer,
115 filter_math=filter_math)
117 def write_text(node, text, writer, filter_math=False):
120 style = node_style(node)
122 if style == 'Maple Input':
125 prompt = node.get('prompt', None)
127 t = '\n'.join(prompt+line for line in text.splitlines())
128 if text.endswith('\n'):
129 t += '\n' # '\n'.join('a\nb\n'.splitlines()) == 'a\nb'
130 if writer.last_char not in [None, '\n']:
131 t = t[len(prompt):] # no initial prompt
133 if style == 'Maple Input':
137 writer(text=text, color=color)
139 def node_style(node):
142 style = p.get('style', None)
148 def pruned_iteration(root, match, match_action=None, match_tail=None,
149 other_action=None, other_tail=None, debug=False):
151 _write_color('start pruned iteration from %s\n' % root, color='blue')
154 while len(stack) > 0:
161 _pruned_iteration_handle_tail(
162 node=n, match=match, match_tail=match_tail,
163 other_tail=other_tail, debug=debug)
166 color_node(node, color='cyan')
173 stack = list(node.getchildren()) + stack
178 _pruned_iteration_handle_tail(
179 node=n, match=match, match_tail=match_tail, other_tail=other_tail,
182 _write_color('end pruned iteration from %s\n' % root, color='blue')
184 def _pruned_iteration_handle_tail(node, match, match_tail, other_tail,
187 color_node(node, color='magenta', tail=True)
195 def node_depth(node):
203 def color_node(node, color=None, tail=False):
204 depth = node_depth(node)
205 string = ' '*depth + node.tag
208 _write_color(string + '\n', color)
211 if __name__ == '__main__':
212 from optparse import OptionParser as _OptionParser
214 # don't wrap epilog paragraphs
215 class OptionParser (_OptionParser):
216 def format_epilog(self, formatter):
219 parser = OptionParser(
220 usage='%prog [options] input.mw', epilog='\n'+__doc__)
222 '-c', '--color', dest='color', action='store_true',
223 help='Use ANSI escape sequences to color output')
225 '-m', '--maple', dest='maple', action='store_true',
226 help='output text suitable for piping into `maple`')
228 options,args = parser.parse_args()
231 filter_math = options.maple
232 writer = Writer(use_color=options.color)
235 raise ValueError("maple doesn't understand ANSI color")
236 mw2txt(path=path, writer=writer, filter_math=filter_math)