1 # -*- coding: utf-8 -*-
2 # This file is part of lyx2lyx
3 # Copyright (C) 2018 The LyX team
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.
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.
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.
19 """ Convert files to the file format generated by lyx 2.4"""
25 # Uncomment only what you need to import, please.
27 from parser_tools import (count_pars_in_inset, find_end_of_inset, find_end_of_layout,
28 find_token, get_bool_value, get_option_value, get_value, get_quoted_value)
29 # del_token, del_value, del_complete_lines,
30 # find_complete_lines, find_end_of,
31 # find_re, find_substring, find_token_backwards,
32 # get_containing_inset, get_containing_layout,
33 # is_in_inset, set_bool_value
34 # find_tokens, find_token_exact, check_token
36 from lyx2lyx_tools import (put_cmd_in_ert, add_to_preamble)
37 # revert_font_attrs, insert_to_preamble, latex_length
38 # get_ert, lyx2latex, lyx2verbatim, length_in_bp, convert_info_insets
39 # revert_flex_inset, hex2ratio, str2bool
41 ####################################################################
42 # Private helper functions
46 ###############################################################################
48 ### Conversion and reversion routines
50 ###############################################################################
52 def removeFrontMatterStyles(document):
53 " Remove styles Begin/EndFromatter"
55 layouts = ['BeginFrontmatter', 'EndFrontmatter']
56 for layout in layouts:
59 i = find_token(document.body, '\\begin_layout ' + layout, i)
62 j = find_end_of_layout(document.body, i)
64 document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
67 while i > 0 and document.body[i-1].strip() == '':
69 while document.body[j+1].strip() == '':
71 document.body[i:j+1] = ['']
73 def addFrontMatterStyles(document):
74 " Use styles Begin/EndFrontmatter for elsarticle"
76 def insertFrontmatter(prefix, line):
78 while above > 0 and document.body[above-1].strip() == '':
81 while document.body[below].strip() == '':
83 document.body[above:below] = ['', '\\begin_layout ' + prefix + 'Frontmatter',
84 '\\begin_inset Note Note',
86 '\\begin_layout Plain Layout',
89 '\\end_inset', '', '',
92 if document.textclass == "elsarticle":
93 layouts = ['Title', 'Title footnote', 'Author', 'Author footnote',
94 'Corresponding author', 'Address', 'Email', 'Abstract', 'Keywords']
97 for layout in layouts:
100 i = find_token(document.body, '\\begin_layout ' + layout, i)
103 k = find_end_of_layout(document.body, i)
105 document.warning("Malformed LyX document: Can't find end of layout at line %d" % i)
108 if first == -1 or i < first:
110 if last == -1 or last <= k:
115 insertFrontmatter('End', last)
116 insertFrontmatter('Begin', first)
118 def convert_lst_literalparam(document):
119 " Add param literal to include inset "
123 i = find_token(document.body, '\\begin_inset CommandInset include', i)
126 j = find_end_of_inset(document.body, i)
128 document.warning("Malformed LyX document: Can't find end of command inset at line %d" % i)
131 while i < j and document.body[i].strip() != '':
133 document.body.insert(i, "literal \"true\"")
136 def revert_lst_literalparam(document):
137 " Remove param literal from include inset "
141 i = find_token(document.body, '\\begin_inset CommandInset include', i)
144 j = find_end_of_inset(document.body, i)
146 document.warning("Malformed LyX document: Can't find end of include inset at line %d" % i)
149 k = find_token(document.body, 'literal', i, j)
156 def revert_paratype(document):
157 " Revert ParaType font definitions to LaTeX "
159 if find_token(document.header, "\\use_non_tex_fonts false", 0) != -1:
161 i1 = find_token(document.header, "\\font_roman \"PTSerif-TLF\"", 0)
162 i2 = find_token(document.header, "\\font_sans \"default\"", 0)
163 i3 = find_token(document.header, "\\font_typewriter \"default\"", 0)
164 j = find_token(document.header, "\\font_sans \"PTSans-TLF\"", 0)
165 sfval = get_value(document.header, "\\font_sf_scale", 0)
170 sfoption = "scaled=" + format(float(sfval) / 100, '.2f')
171 k = find_token(document.header, "\\font_typewriter \"PTMono-TLF\"", 0)
172 ttval = get_value(document.header, "\\font_tt_scale", 0)
177 ttoption = "scaled=" + format(float(ttval) / 100, '.2f')
178 if i1 != -1 and i2 != -1 and i3!= -1:
179 add_to_preamble(document, ["\\usepackage{paratype}"])
182 add_to_preamble(document, ["\\usepackage{PTSerif}"])
183 document.header[i1] = document.header[i1].replace("PTSerif-TLF", "default")
186 add_to_preamble(document, ["\\usepackage[" + sfoption + "]{PTSans}"])
188 add_to_preamble(document, ["\\usepackage{PTSans}"])
189 document.header[j] = document.header[j].replace("PTSans-TLF", "default")
192 add_to_preamble(document, ["\\usepackage[" + ttoption + "]{PTMono}"])
194 add_to_preamble(document, ["\\usepackage{PTMono}"])
195 document.header[k] = document.header[k].replace("PTMono-TLF", "default")
198 def revert_xcharter(document):
199 " Revert XCharter font definitions to LaTeX "
201 i = find_token(document.header, "\\font_roman \"xcharter\"", 0)
205 # replace unsupported font setting
206 document.header[i] = document.header[i].replace("xcharter", "default")
207 # no need for preamble code with system fonts
208 if get_bool_value(document.header, "\\use_non_tex_fonts"):
211 # transfer old style figures setting to package options
212 j = find_token(document.header, "\\font_osf true")
215 document.header[j] = "\\font_osf false"
219 add_to_preamble(document, ["\\usepackage%s{XCharter}"%options])
222 def revert_lscape(document):
223 " Reverts the landscape environment (Landscape module) to TeX-code "
225 if not "landscape" in document.get_module_list():
230 i = find_token(document.body, "\\begin_inset Flex Landscape", i)
233 j = find_end_of_inset(document.body, i)
235 document.warning("Malformed LyX document: Can't find end of Landscape inset")
239 if document.body[i] == "\\begin_inset Flex Landscape (Floating)":
240 document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}}")
241 document.body[i : i + 4] = put_cmd_in_ert("\\afterpage{\\begin{landscape}")
242 add_to_preamble(document, ["\\usepackage{afterpage}"])
244 document.body[j - 2 : j + 1] = put_cmd_in_ert("\\end{landscape}")
245 document.body[i : i + 4] = put_cmd_in_ert("\\begin{landscape}")
247 add_to_preamble(document, ["\\usepackage{pdflscape}"])
251 def convert_fontenc(document):
252 " Convert default fontenc setting "
254 i = find_token(document.header, "\\fontencoding global", 0)
258 document.header[i] = document.header[i].replace("global", "auto")
261 def revert_fontenc(document):
262 " Revert default fontenc setting "
264 i = find_token(document.header, "\\fontencoding auto", 0)
268 document.header[i] = document.header[i].replace("auto", "global")
271 def revert_nospellcheck(document):
272 " Remove nospellcheck font info param "
276 i = find_token(document.body, '\\nospellcheck', i)
282 def revert_floatpclass(document):
283 " Remove float placement params 'document' and 'class' "
286 i = find_token(document.header, "\\float_placement class", 0)
288 del document.header[i]
292 i = find_token(document.body, '\\begin_inset Float', i)
295 j = find_end_of_inset(document.body, i)
296 k = find_token(document.body, 'placement class', i, i + 2)
298 k = find_token(document.body, 'placement document', i, i + 2)
306 def revert_floatalignment(document):
307 " Remove float alignment params "
310 i = find_token(document.header, "\\float_alignment", 0)
313 galignment = get_value(document.header, "\\float_alignment", i)
314 del document.header[i]
318 i = find_token(document.body, '\\begin_inset Float', i)
321 j = find_end_of_inset(document.body, i)
323 document.warning("Malformed LyX document: Can't find end of inset at line " + str(i))
325 k = find_token(document.body, 'alignment', i, i + 4)
329 alignment = get_value(document.body, "alignment", k)
330 if alignment == "document":
331 alignment = galignment
333 l = find_token(document.body, "\\begin_layout Plain Layout", i, j)
335 document.warning("Can't find float layout!")
339 if alignment == "left":
340 alcmd = put_cmd_in_ert("\\raggedright{}")
341 elif alignment == "center":
342 alcmd = put_cmd_in_ert("\\centering{}")
343 elif alignment == "right":
344 alcmd = put_cmd_in_ert("\\raggedleft{}")
346 document.body[l+1:l+1] = alcmd
350 def revert_tuftecite(document):
351 " Revert \cite commands in tufte classes "
353 tufte = ["tufte-book", "tufte-handout"]
354 if document.textclass not in tufte:
359 i = find_token(document.body, "\\begin_inset CommandInset citation", i)
362 j = find_end_of_inset(document.body, i)
364 document.warning("Can't find end of citation inset at line %d!!" %(i))
367 k = find_token(document.body, "LatexCommand", i, j)
369 document.warning("Can't find LatexCommand for citation inset at line %d!" %(i))
372 cmd = get_value(document.body, "LatexCommand", k)
376 pre = get_quoted_value(document.body, "before", i, j)
377 post = get_quoted_value(document.body, "after", i, j)
378 key = get_quoted_value(document.body, "key", i, j)
380 document.warning("Citation inset at line %d does not have a key!" %(i))
382 # Replace command with ERT
385 res += "[" + pre + "]"
387 res += "[" + post + "]"
390 res += "{" + key + "}"
391 document.body[i:j+1] = put_cmd_in_ert([res])
395 def revert_stretchcolumn(document):
396 " We remove the column varwidth flags or everything else will become a mess. "
399 i = find_token(document.body, "\\begin_inset Tabular", i)
402 j = find_end_of_inset(document.body, i + 1)
404 document.warning("Malformed LyX document: Could not find end of tabular.")
406 for k in range(i, j):
407 if re.search('^<column.*varwidth="[^"]+".*>$', document.body[k]):
408 document.warning("Converting 'tabularx'/'xltabular' table to normal table.")
409 document.body[k] = document.body[k].replace(' varwidth="true"', '')
413 def revert_vcolumns(document):
414 " Revert standard columns with line breaks etc. "
420 i = find_token(document.body, "\\begin_inset Tabular", i)
423 j = find_end_of_inset(document.body, i)
425 document.warning("Malformed LyX document: Could not find end of tabular.")
429 # Collect necessary column information
431 nrows = int(document.body[i+1].split('"')[3])
432 ncols = int(document.body[i+1].split('"')[5])
434 for k in range(ncols):
435 m = find_token(document.body, "<column", m)
436 width = get_option_value(document.body[m], 'width')
437 varwidth = get_option_value(document.body[m], 'varwidth')
438 alignment = get_option_value(document.body[m], 'alignment')
439 special = get_option_value(document.body[m], 'special')
440 col_info.append([width, varwidth, alignment, special, m])
445 for row in range(nrows):
446 for col in range(ncols):
447 m = find_token(document.body, "<cell", m)
448 multicolumn = get_option_value(document.body[m], 'multicolumn')
449 multirow = get_option_value(document.body[m], 'multirow')
450 width = get_option_value(document.body[m], 'width')
451 rotate = get_option_value(document.body[m], 'rotate')
452 # Check for: linebreaks, multipars, non-standard environments
454 endcell = find_token(document.body, "</cell>", begcell)
456 if find_token(document.body, "\\begin_inset Newline", begcell, endcell) != -1:
458 elif count_pars_in_inset(document.body, begcell + 2) > 1:
460 elif get_value(document.body, "\\begin_layout", begcell) != "Plain Layout":
462 if vcand and rotate == "" and ((multicolumn == "" and multirow == "") or width == ""):
463 if col_info[col][0] == "" and col_info[col][1] == "" and col_info[col][3] == "":
465 alignment = col_info[col][2]
466 col_line = col_info[col][4]
468 if alignment == "center":
469 vval = ">{\\centering}"
470 elif alignment == "left":
471 vval = ">{\\raggedright}"
472 elif alignment == "right":
473 vval = ">{\\raggedleft}"
476 vval += "V{\\linewidth}"
478 document.body[col_line] = document.body[col_line][:-1] + " special=\"" + vval + "\">"
479 # ERT newlines and linebreaks (since LyX < 2.4 automatically inserts parboxes
480 # with newlines, and we do not want that)
482 endcell = find_token(document.body, "</cell>", begcell)
484 nl = find_token(document.body, "\\begin_inset Newline newline", begcell, endcell)
486 nl = find_token(document.body, "\\begin_inset Newline linebreak", begcell, endcell)
490 nle = find_end_of_inset(document.body, nl)
491 del(document.body[nle:nle+1])
493 document.body[nl:nl+1] = put_cmd_in_ert("\\linebreak{}")
495 document.body[nl:nl+1] = put_cmd_in_ert("\\\\")
501 if needarray == True:
502 add_to_preamble(document, ["\\usepackage{array}"])
503 if needvarwidth == True:
504 add_to_preamble(document, ["\\usepackage{varwidth}"])
507 def revert_bibencoding(document):
508 " Revert bibliography encoding "
512 i = find_token(document.header, "\\cite_engine", 0)
514 document.warning("Malformed document! Missing \\cite_engine")
516 engine = get_value(document.header, "\\cite_engine", i)
520 if engine in ["biblatex", "biblatex-natbib"]:
523 # Map lyx to latex encoding names
527 "armscii8" : "armscii8",
528 "iso8859-1" : "latin1",
529 "iso8859-2" : "latin2",
530 "iso8859-3" : "latin3",
531 "iso8859-4" : "latin4",
532 "iso8859-5" : "iso88595",
533 "iso8859-6" : "8859-6",
534 "iso8859-7" : "iso-8859-7",
535 "iso8859-8" : "8859-8",
536 "iso8859-9" : "latin5",
537 "iso8859-13" : "latin7",
538 "iso8859-15" : "latin9",
539 "iso8859-16" : "latin10",
540 "applemac" : "applemac",
542 "cp437de" : "cp437de",
559 "utf8-platex" : "utf8",
566 i = find_token(document.body, "\\begin_inset CommandInset bibtex", i)
569 j = find_end_of_inset(document.body, i)
571 document.warning("Can't find end of bibtex inset at line %d!!" %(i))
574 encoding = get_quoted_value(document.body, "encoding", i, j)
578 # remove encoding line
579 k = find_token(document.body, "encoding", i, j)
582 # Re-find inset end line
583 j = find_end_of_inset(document.body, i)
586 h = find_token(document.header, "\\biblio_options", 0)
588 biblio_options = get_value(document.header, "\\biblio_options", h)
589 if not "bibencoding" in biblio_options:
590 document.header[h] += ",bibencoding=%s" % encodings[encoding]
592 bs = find_token(document.header, "\\biblatex_bibstyle", 0)
594 # this should not happen
595 document.warning("Malformed LyX document! No \\biblatex_bibstyle header found!")
597 document.header[bs-1 : bs-1] = ["\\biblio_options bibencoding=" + encodings[encoding]]
599 document.body[j+1:j+1] = put_cmd_in_ert("\\egroup")
600 document.body[i:i] = put_cmd_in_ert("\\bgroup\\inputencoding{" + encodings[encoding] + "}")
606 def convert_vcsinfo(document):
607 " Separate vcs Info inset from buffer Info inset. "
610 "vcs-revision" : "revision",
611 "vcs-tree-revision" : "tree-revision",
612 "vcs-author" : "author",
618 i = find_token(document.body, "\\begin_inset Info", i)
621 j = find_end_of_inset(document.body, i + 1)
623 document.warning("Malformed LyX document: Could not find end of Info inset.")
626 tp = find_token(document.body, 'type', i, j)
627 tpv = get_quoted_value(document.body, "type", tp)
631 arg = find_token(document.body, 'arg', i, j)
632 argv = get_quoted_value(document.body, "arg", arg)
633 if argv not in list(types.keys()):
636 document.body[tp] = "type \"vcs\""
637 document.body[arg] = "arg \"" + types[argv] + "\""
641 def revert_vcsinfo(document):
642 " Merge vcs Info inset to buffer Info inset. "
644 args = ["revision", "tree-revision", "author", "time", "date" ]
647 i = find_token(document.body, "\\begin_inset Info", i)
650 j = find_end_of_inset(document.body, i + 1)
652 document.warning("Malformed LyX document: Could not find end of Info inset.")
655 tp = find_token(document.body, 'type', i, j)
656 tpv = get_quoted_value(document.body, "type", tp)
660 arg = find_token(document.body, 'arg', i, j)
661 argv = get_quoted_value(document.body, "arg", arg)
663 document.warning("Malformed Info inset. Invalid vcs arg.")
666 document.body[tp] = "type \"buffer\""
667 document.body[arg] = "arg \"vcs-" + argv + "\""
675 supported_versions = ["2.4.0", "2.4"]
677 [545, [convert_lst_literalparam]],
682 [550, [convert_fontenc]],
689 [557, [convert_vcsinfo]],
690 [558, [removeFrontMatterStyles]]
694 [557, [addFrontMatterStyles]],
695 [556, [revert_vcsinfo]],
696 [555, [revert_bibencoding]],
697 [554, [revert_vcolumns]],
698 [553, [revert_stretchcolumn]],
699 [552, [revert_tuftecite]],
700 [551, [revert_floatpclass, revert_floatalignment]],
701 [550, [revert_nospellcheck]],
702 [549, [revert_fontenc]],
703 [548, []],# dummy format change
704 [547, [revert_lscape]],
705 [546, [revert_xcharter]],
706 [545, [revert_paratype]],
707 [544, [revert_lst_literalparam]]
711 if __name__ == "__main__":