]> git.lyx.org Git - lyx.git/blob - lib/lyx2lyx/lyx2lyx_tools.py
Reminders.
[lyx.git] / lib / lyx2lyx / lyx2lyx_tools.py
1 # This file is part of lyx2lyx
2 # -*- coding: utf-8 -*-
3 # Copyright (C) 2010 The LyX team
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 " This modules offer several free functions to help with lyx2lyx'ing. "
20
21 import string
22 from parser_tools import find_token
23 from unicode_symbols import unicode_reps
24
25 # Note that text can be either a list of lines or a single line.
26 def add_to_preamble(document, text):
27     """ Add text to the preamble if it is not already there.
28     Only the first line is checked!"""
29
30     if not type(text) is list:
31       document.warning("You should pass a list to add_to_preamble!")
32       # split on \n just in case
33       # it'll give us the one element list we want
34       # if there's no \n, too
35       text = text.split('\n')
36
37     if find_token(document.preamble, text[0], 0) != -1:
38         return
39
40     document.preamble.extend(text)
41
42
43 # Note that text can be either a list of lines or a single line.
44 # It should really be a list.
45 def insert_to_preamble(index, document, text):
46     """ Insert text to the preamble at a given line"""
47     
48     if not type(text) is list:
49       document.warning("You should pass a list to insert_to_preamble!")
50       # split on \n just in case
51       # it'll give us the one element list we want
52       # if there's no \n, too
53       text = text.split('\n')
54
55     document.preamble[index:index] = text
56
57
58 # This routine wraps some content in an ERT inset. 
59 #
60 # NOTE: The function accepts either a single string or a LIST of strings as
61 # argument. But it returns a LIST of strings, split on \n, so that it does 
62 # not have embedded newlines.
63
64 # This is how lyx2lyx represents a LyX document: as a list of strings, 
65 # each representing a line of a LyX file. Embedded newlines confuse 
66 # lyx2lyx very much.
67 #
68 # A call to this routine will often go something like this:
69 #   i = find_token('\\begin_inset FunkyInset', ...)
70 #   ...
71 #   j = find_end_of_inset(document.body, i)
72 #   content = ...extract content from insets
73 #   # that could be as simple as: 
74 #   # content = lyx2latex(document[i:j + 1])
75 #   ert = put_cmd_in_ert(content)
76 #   document.body[i:j] = ert
77 # Now, before we continue, we need to reset i appropriately. Normally,
78 # this would be: 
79 #   i += len(ert)
80 # That puts us right after the ERT we just inserted.
81 #
82 def put_cmd_in_ert(arg):
83     ret = ["\\begin_inset ERT", "status collapsed", "\\begin_layout Plain Layout", ""]
84     # Despite the warnings just given, it will be faster for us to work
85     # with a single string internally. That way, we only go through the
86     # unicode_reps loop once.
87     if type(arg) is list:
88       s = "\n".join(arg)
89     else:
90       s = arg
91     for rep in unicode_reps:
92       s = s.replace(rep[1], rep[0].replace('\\\\', '\\'))
93     s = s.replace('\\', "\\backslash\n")
94     ret += s.splitlines()
95     ret += ["\\end_layout", "\\end_inset"]
96     return ret
97
98             
99 def lyx2latex(document, lines):
100     'Convert some LyX stuff into corresponding LaTeX stuff, as best we can.'
101     # clean up multiline stuff
102     content = ""
103     ert_end = 0
104     note_end = 0
105     hspace = ""
106
107     for curline in range(len(lines)):
108       line = lines[curline]
109       if line.startswith("\\begin_inset Note Note"):
110           # We want to skip LyX notes, so remember where the inset ends
111           note_end = find_end_of_inset(lines, curline + 1)
112           continue
113       elif note_end >= curline:
114           # Skip LyX notes
115           continue
116       elif line.startswith("\\begin_inset ERT"):
117           # We don't want to replace things inside ERT, so figure out
118           # where the end of the inset is.
119           ert_end = find_end_of_inset(lines, curline + 1)
120           continue
121       elif line.startswith("\\begin_inset Formula"):
122           line = line[20:]
123       elif line.startswith("\\begin_inset Quotes"):
124           # For now, we do a very basic reversion. Someone who understands
125           # quotes is welcome to fix it up.
126           qtype = line[20:].strip()
127           # lang = qtype[0]
128           side = qtype[1]
129           dbls = qtype[2]
130           if side == "l":
131               if dbls == "d":
132                   line = "``"
133               else:
134                   line = "`"
135           else:
136               if dbls == "d":
137                   line = "''"
138               else:
139                   line = "'"
140       elif line.startswith("\\begin_inset space"):
141           line = line[18:].strip()
142           if line.startswith("\\hspace"):
143               # Account for both \hspace and \hspace*
144               hspace = line[:-2]
145               continue
146           elif line == "\\space{}":
147               line = "\\ "
148           elif line == "\\thinspace{}":
149               line = "\\,"
150       elif hspace != "":
151           # The LyX length is in line[8:], after the \length keyword
152           length = latex_length(line[8:])[1]
153           line = hspace + "{" + length + "}"
154           hspace = ""
155       elif line.isspace() or \
156             line.startswith("\\begin_layout") or \
157             line.startswith("\\end_layout") or \
158             line.startswith("\\begin_inset") or \
159             line.startswith("\\end_inset") or \
160             line.startswith("\\lang") or \
161             line.strip() == "status collapsed" or \
162             line.strip() == "status open":
163           #skip all that stuff
164           continue
165
166       # this needs to be added to the preamble because of cases like
167       # \textmu, \textbackslash, etc.
168       add_to_preamble(document, ['% added by lyx2lyx for converted index entries',
169                                  '\\@ifundefined{textmu}',
170                                  ' {\\usepackage{textcomp}}{}'])
171       # a lossless reversion is not possible
172       # try at least to handle some common insets and settings
173       if ert_end >= curline:
174           line = line.replace(r'\backslash', '\\')
175       else:
176           # No need to add "{}" after single-nonletter macros
177           line = line.replace('&', '\\&')
178           line = line.replace('#', '\\#')
179           line = line.replace('^', '\\textasciicircum{}')
180           line = line.replace('%', '\\%')
181           line = line.replace('_', '\\_')
182           line = line.replace('$', '\\$')
183
184           # Do the LyX text --> LaTeX conversion
185           for rep in unicode_reps:
186             line = line.replace(rep[1], rep[0] + "{}")
187           line = line.replace(r'\backslash', r'\textbackslash{}')
188           line = line.replace(r'\series bold', r'\bfseries{}').replace(r'\series default', r'\mdseries{}')
189           line = line.replace(r'\shape italic', r'\itshape{}').replace(r'\shape smallcaps', r'\scshape{}')
190           line = line.replace(r'\shape slanted', r'\slshape{}').replace(r'\shape default', r'\upshape{}')
191           line = line.replace(r'\emph on', r'\em{}').replace(r'\emph default', r'\em{}')
192           line = line.replace(r'\noun on', r'\scshape{}').replace(r'\noun default', r'\upshape{}')
193           line = line.replace(r'\bar under', r'\underbar{').replace(r'\bar default', r'}')
194           line = line.replace(r'\family sans', r'\sffamily{}').replace(r'\family default', r'\normalfont{}')
195           line = line.replace(r'\family typewriter', r'\ttfamily{}').replace(r'\family roman', r'\rmfamily{}')
196           line = line.replace(r'\InsetSpace ', r'').replace(r'\SpecialChar ', r'')
197       content += line
198     return content
199
200
201 def latex_length(slen):
202     ''' 
203     Convert lengths to their LaTeX representation. Returns (bool, length),
204     where the bool tells us if it was a percentage, and the length is the
205     LaTeX representation.
206     '''
207     i = 0
208     percent = False
209     # the slen has the form
210     # ValueUnit+ValueUnit-ValueUnit or
211     # ValueUnit+-ValueUnit
212     # the + and - (glue lengths) are optional
213     # the + always precedes the -
214
215     # Convert relative lengths to LaTeX units
216     units = {"text%":"\\textwidth", "col%":"\\columnwidth",
217              "page%":"\\paperwidth", "line%":"\\linewidth",
218              "theight%":"\\textheight", "pheight%":"\\paperheight"}
219     for unit in units.keys():
220         i = slen.find(unit)
221         if i == -1:
222             continue
223         percent = True
224         minus = slen.rfind("-", 1, i)
225         plus = slen.rfind("+", 0, i)
226         latex_unit = units[unit]
227         if plus == -1 and minus == -1:
228             value = slen[:i]
229             value = str(float(value)/100)
230             end = slen[i + len(unit):]
231             slen = value + latex_unit + end
232         if plus > minus:
233             value = slen[plus + 1:i]
234             value = str(float(value)/100)
235             begin = slen[:plus + 1]
236             end = slen[i+len(unit):]
237             slen = begin + value + latex_unit + end
238         if plus < minus:
239             value = slen[minus + 1:i]
240             value = str(float(value)/100)
241             begin = slen[:minus + 1]
242             slen = begin + value + latex_unit
243
244     # replace + and -, but only if the - is not the first character
245     slen = slen[0] + slen[1:].replace("+", " plus ").replace("-", " minus ")
246     # handle the case where "+-1mm" was used, because LaTeX only understands
247     # "plus 1mm minus 1mm"
248     if slen.find("plus  minus"):
249         lastvaluepos = slen.rfind(" ")
250         lastvalue = slen[lastvaluepos:]
251         slen = slen.replace("  ", lastvalue + " ")
252     return (percent, slen)
253
254
255 def revert_flex_inset(document, name, LaTeXname, position):
256   " Convert flex insets to TeX code "
257   i = position
258   while True:
259     i = find_token(document.body, '\\begin_inset Flex ' + name, i)
260     if i == -1:
261       return
262     z = find_end_of_inset(document.body, i)
263     if z == -1:
264       document.warning("Malformed LyX document: Can't find end of Flex " + name + " inset.")
265       return
266     # remove the \end_inset
267     document.body[z - 2:z + 1] = put_cmd_in_ert("}")
268     # we need to reset character layouts if necessary
269     j = find_token(document.body, '\\emph on', i, z)
270     k = find_token(document.body, '\\noun on', i, z)
271     l = find_token(document.body, '\\series', i, z)
272     m = find_token(document.body, '\\family', i, z)
273     n = find_token(document.body, '\\shape', i, z)
274     o = find_token(document.body, '\\color', i, z)
275     p = find_token(document.body, '\\size', i, z)
276     q = find_token(document.body, '\\bar under', i, z)
277     r = find_token(document.body, '\\uuline on', i, z)
278     s = find_token(document.body, '\\uwave on', i, z)
279     t = find_token(document.body, '\\strikeout on', i, z)
280     if j != -1:
281       document.body.insert(z - 2, "\\emph default")
282     if k != -1:
283       document.body.insert(z - 2, "\\noun default")
284     if l != -1:
285       document.body.insert(z - 2, "\\series default")
286     if m != -1:
287       document.body.insert(z - 2, "\\family default")
288     if n != -1:
289       document.body.insert(z - 2, "\\shape default")
290     if o != -1:
291       document.body.insert(z - 2, "\\color inherit")
292     if p != -1:
293       document.body.insert(z - 2, "\\size default")
294     if q != -1:
295       document.body.insert(z - 2, "\\bar default")
296     if r != -1:
297       document.body.insert(z - 2, "\\uuline default")
298     if s != -1:
299       document.body.insert(z - 2, "\\uwave default")
300     if t != -1:
301       document.body.insert(z - 2, "\\strikeout default")
302     document.body[i:i + 4] = put_cmd_in_ert(LaTeXname + "{")
303     i += 1
304
305
306 def revert_font_attrs(document, name, LaTeXname):
307   " Reverts font changes to TeX code "
308   i = 0
309   changed = False
310   while True:
311     i = find_token(document.body, name + ' on', i)
312     if i == -1:
313       return changed
314     j = find_token(document.body, name + ' default', i)
315     k = find_token(document.body, name + ' on', i + 1)
316     # if there is no default set, the style ends with the layout
317     # assure hereby that we found the correct layout end
318     if j != -1 and (j < k or k == -1):
319       document.body[j:j + 1] = put_cmd_in_ert("}")
320     else:
321       j = find_token(document.body, '\\end_layout', i)
322       document.body[j:j] = put_cmd_in_ert("}")
323     document.body[i:i + 1] = put_cmd_in_ert(LaTeXname + "{")
324     changed = True
325     i += 1
326
327
328 def revert_layout_command(document, name, LaTeXname, position):
329   " Reverts a command from a layout to TeX code "
330   i = position
331   while True:
332     i = find_token(document.body, '\\begin_layout ' + name, i)
333     if i == -1:
334       return
335     k = -1
336     # find the next layout
337     j = i + 1
338     while k == -1:
339       j = find_token(document.body, '\\begin_layout', j)
340       l = len(document.body)
341       # if nothing was found it was the last layout of the document
342       if j == -1:
343         document.body[l - 4:l - 4] = put_cmd_in_ert("}")
344         k = 0
345       # exclude plain layout because this can be TeX code or another inset
346       elif document.body[j] != '\\begin_layout Plain Layout':
347         document.body[j - 2:j - 2] = put_cmd_in_ert("}")
348         k = 0
349       else:
350         j += 1
351     document.body[i] = '\\begin_layout Standard'
352     document.body[i + 1:i + 1] = put_cmd_in_ert(LaTeXname + "{")
353     i += 1
354
355
356 def hex2ratio(s):
357   " Converts an RRGGBB-type hexadecimal string to a float in [0.0,1.0] "
358   try:
359     val = int(s, 16)
360   except:
361     val = 0
362   if val != 0:
363     val += 1
364   return str(val / 256.0)
365
366
367 def str2bool(s):
368   "'true' goes to True, case-insensitively, and we strip whitespace."
369   s = s.strip().lower()
370   return s == "true"