]> git.lyx.org Git - lyx.git/blob - lib/scripts/layout2layout.py
unicodesymbols: Add force flags for Arabic and Hebrew input encodings.
[lyx.git] / lib / scripts / layout2layout.py
1 # -*- coding: utf-8 -*-
2
3 # file layout2layout.py
4 # This file is part of LyX, the document processor.
5 # Licence details can be found in the file COPYING.
6
7 # author Georg Baum
8
9 # Full author contact details are available in file CREDITS
10
11 # This script will update a .layout file to current format
12
13 # The latest layout format is also defined in src/TextClass.cpp
14 currentFormat = 71
15
16
17 # Incremented to format 4, 6 April 2007, lasgouttes
18 # Introduction of generic "Provides" declaration
19
20 # Incremented to format 5, 22 August 2007 by vermeer
21 # InsetLayout material
22
23 # Incremented to format 6, 7 January 2008 by spitz
24 # Requires tag added to layout files
25
26 # Incremented to format 7, 24 March 2008 by rgh
27 # AddToPreamble tag added to layout files
28
29 # Incremented to format 8, 25 July 2008 by rgh
30 # UseModule tag added to layout files
31 # CopyStyle added to InsetLayout
32
33 # Incremented to format 9, 5 October 2008 by rgh
34 # ForcePlain and CustomPars tags added to InsetLayout
35
36 # Incremented to format 10, 6 October 2008 by rgh
37 # Change format of counters
38
39 # Incremented to format 11, 14 October 2008 by rgh
40 # Add ProvidesModule, ExcludesModule tags
41
42 # Incremented to format 12, 10 January 2009 by gb
43 # Add I18NPreamble tag
44
45 # Incremented to format 13, 5 February 2009 by rgh
46 # Add InToc tag for InsetLayout
47
48 # Incremented to format 14, 14 February 2009 by gb
49 # Rename I18NPreamble to BabelPreamble and add LangPreamble
50
51 # Incremented to format 15, 28 May 2009 by lasgouttes
52 # Add new tag OutputFormat; modules can be conditioned on feature
53 # "from->to".
54
55 # Incremented to format 16, 5 June 2009 by rgh
56 # Add new tags for Text Class:
57 #   HTMLPreamble, HTMLAddToPreamble
58 # For Layout:
59 #   HTMLTag, HTMLAttr, HTMLLabel, HTMLLabelAttr, HTMLItem, HTMLItemAttr
60 #   HTMLStyle, and HTMLPreamble
61 # For InsetLayout:
62 #   HTMLTag, HTMLAttr, HTMLStyle, and HTMLPreamble
63 # For Floats:
64 #   HTMLType, HTMLClass, HTMLStyle
65
66 # Incremented to format 17, 12 August 2009 by rgh
67 # Add IfStyle and IfCounter tags for layout.
68
69 # Incremented to format 18, 27 October 2009 by rgh
70 # Added some new tags for HTML output.
71
72 # Incremented to format 19, 17 November 2009 by rgh
73 # Added InPreamble tag.
74
75 # Incremented to format 20, 17 December 2009 by rgh
76 # Added ContentAsLabel tag.
77
78 # Incremented to format 21, 12 January 2010 by rgh
79 # Added HTMLTocLayout and HTMLTitle tags.
80
81 # Incremented to format 22, 20 January 2010 by rgh
82 # Added HTMLFormat tag to Counters.
83
84 # Incremented to format 23, 13 February 2010 by spitz
85 # Added Spellcheck tag.
86
87 # Incremented to format 24, 5 March 2010 by rgh
88 # Changed LaTeXBuiltin tag to NeedsFloatPkg and
89 # added new tag ListCommand.
90
91 # Incremented to format 25, 12 March 2010 by rgh
92 # Added RefPrefix tag for layouts and floats.
93
94 # Incremented to format 26, 29 March 2010 by rgh
95 # Added CiteFormat.
96
97 # Incremented to format 27, 4 June 2010 by rgh
98 # Added RequiredArgs tag.
99
100 # Incremented to format 28, 6 August 2010 by lasgouttes
101 # Added ParbreakIsNewline tag for Layout and InsetLayout.
102
103 # Incremented to format 29, 10 August 2010 by rgh
104 # Changed Custom:Style, CharStyle:Style, and Element:Style
105 # uniformly to Flex:Style.
106
107 # Incremented to format 30, 13 August 2010 by rgh
108 # Introduced ResetsFont tag for InsetLayout.
109
110 # Incremented to format 31, 12 January 2011 by rgh
111 # Introducted NoCounter tag.
112
113 # Incremented to format 32, 30 January 2011 by forenr
114 # Added Display tag for InsetLayout
115
116 # Incremented to format 33, 2 February 2011 by rgh
117 # Changed NeedsFloatPkg to UsesFloatPkg
118
119 # Incremented to format 34, 28 March 2011 by rgh
120 # Remove obsolete Fill_(Top|Bottom) tags
121
122 # Incremented to format 35, 28 March 2011 by rgh
123 # Try to add "Flex:" to any flex insets that don't have it.
124
125 # Incremented to format 36, 7 December 2011, by rgh
126 # Added HTMLStyles and AddToHTMLStyles tags.
127
128 # Incremented to format 37, 29 February 2012 by jrioux
129 # Implement the citation engine machinery in layouts.
130 # Change CiteFormat to CiteFormat (default|authoryear|numerical).
131
132 # Incremented to format 38, 08 April 2012 by gb
133 # Introduce LangPreamble and BabelPreamble for InsetLayout.
134
135 # Incremented to format 39, 15 April 2012 by sanda
136 # Introduce styling of branches via "InsetLayout Branch:".
137
138 # Incremented to format 40, 10 October 2012 by rgh
139 # Re-do layout names for layout categories
140
141 # Incremented to format 41, 20 November 2012 by spitz
142 # New Argument syntax
143
144 # Incremented to format 42, 22 December 2012 by spitz
145 # New Style tag "ItemCommand"
146
147 # Incremented to format 43, 30 December 2012 by spitz
148 # Extended InsetCaption format
149
150 # Incremented to format 44, 9 February 2013 by rgh
151 # Remove COUNTER label style; rename as STATIC
152 # Rename TOP_ENVIRONMENT to ABOVE and CENTERED_TOP_ENVIRONMENT to CENTERED
153
154 # Incremented to format 45, 12 February 2013 by rgh
155 # New Tag "NoInsetLayout"
156
157 # Incremented to format 46, 15 May 2013 by gb
158 # New Tag "ForceLocal"
159
160 # Incremented to format 47, 23 May 2013 by rgh
161 # Add PackageOptions tag
162
163 # Incremented to format 48, 31 May 2013 by rgh
164 # Add InitialValue tag for counters
165
166 # Incremented to format 49, 10 Feb 2014 by gb
167 # Change default of "ResetsFont" tag to false
168
169 # Incremented to format 50, 9 May 2014 by forenr
170 # Removal of "Separator" layouts
171
172 # Incremented to format 51, 29 May 2014 by spitz
173 # New Style tag "ToggleIndent"
174
175 # Incremented to format 52, 1 December 2014 by spitz
176 # New InsetLayout tag "ForceOwnlines"
177
178 # Incremented to format 53, 7 December 2014 by spitz
179 # New InsetLayout tag "ObsoletedBy"
180
181 # Incremented to format 54, 11 Jan 2014 by gb
182 # New InsetLayout tag "FixedWidthPreambleEncoding"
183
184 # Incremented to format 55, 20 April 2015 by spitz
185 # New InsetLayout and Layout tags "PassThruChars"
186
187 # Incremented to format 56, 20 May 2015 by spitz
188 # New Float tags "AllowedPlacement", "AllowsWide", "AllowsSideways"
189
190 # Incremented to format 57, 30 May 2015 by spitz
191 # New Layout tag "ParagraphGroup"
192
193 # Incremented to format 58, 5 December 2015, by rgh
194 # New Layout tag "ProvideStyle"
195 # Change "IfStyle" to "ModifyStyle"
196
197 # Incremented to format 59, 22 November 2015 by gm
198 # New Tag "OutlinerName"
199 # New Layout tags "AddToToc", "IsTocCaption"
200 # New Layout argument tag "IsTocCaption"
201
202 # Incremented to format 60, 25 March 2016 by lasgouttes
203 # Rename caption subtype LongTableNoNumber to Unnumbered
204
205 # Incremented to format 61, 14 October 2016 by spitz
206 # New Layout tags "ResumeCounter", "StepMasterCounter"
207
208 # Incremented to format 62, 21 October 2016 by spitz
209 # New Layout argument tag "PassThru"
210
211 # Incremented to format 63, 7 January 2017 by spitz
212 # - New textclass tags CiteFramework, MaxCiteNames (for cite engines)
213 # - Extended InsetCite syntax.
214
215 # Incremented to format 64, 30 August 2017 by rgh
216 # Strip leading and trailing spaces from LabelString,
217 # LabelStringAppendix, and EndLabelString, and LabelCounter,
218 # to conform to what we used to do.
219
220 # Incremented to format 65, 16 October 2017 by spitz
221 # Color collapsable -> collapsible
222
223 # Incremented to format 66, 28 December 2017 by spitz
224 # New Layout tags "AutoNests ... EndAutoNests" and
225 # "IsAutoNestedBy ... EndIsAutoNestedBy"
226
227 # Incremented to format 67, 14 April 2018 by spitz
228 # New Layout tag "NeedsCProtect"
229
230 # Incremented to format 68, 21 May 2018 by spitz
231 # New Layout tag "AddToCiteEngine"
232
233 # Incremented to format 69, 16 August 2018 by spitz
234 # New argument type "listpreamble"
235
236 # Incremented to format 70, 5 June 2018 by rkh
237 # New InsetLayout tag EditExternal
238
239 # Incremented to format 71, 12 March 2019 by spitz
240 # New [Inset]Layout tag NeedMBoxProtect
241
242 # Do not forget to document format change in Customization
243 # Manual (section "Declaring a new text class").
244
245 # You might also want to consider running the
246 # development/tools/updatelayouts.py script to update all
247 # layout files to the new format.
248
249
250 import os, re, sys
251 import argparse
252
253 # Provide support for both python 2 and 3
254 # (copied from lyx2lyx)
255 PY2 = sys.version_info[0] == 2
256 if PY2:
257     # argparse returns strings in the commandline encoding, we need to convert.
258     # sys.getdefaultencoding() would not always be correct, see
259     # http://legacy.python.org/dev/peps/pep-0383/
260     def cmd_arg(arg):
261         return arg.decode(sys.getfilesystemencoding())
262 else:
263     cmd_arg = str
264 # End of code to support for both python 2 and 3
265
266
267 def error(message):
268     sys.stderr.write(message + '\n')
269     sys.exit(1)
270
271
272 def trim_bom(line):
273     " Remove byte order mark."
274     if line[0:3] == "\357\273\277":
275         return line[3:]
276     else:
277         return line
278
279
280 def read(source):
281     " Read input file and strip lineendings."
282     lines = source.read().splitlines() or ['']
283     lines[0] = trim_bom(lines[0])
284     return lines
285
286
287 def write(output, lines):
288     " Write output file with native lineendings."
289     output.write(os.linesep.encode('ascii').join(lines)
290                  + os.linesep.encode('ascii'))
291
292
293 # Concatenates old and new in an intelligent way:
294 # If old is wrapped in ", they are stripped. The result is wrapped in ".
295 def concatenate_label(old, new):
296     # Don't use strip as long as we support python 1.5.2
297     if old[0] == b'"':
298         return old[0:-1] + new + b'"'
299     else:
300         return b'"' + old + new + b'"'
301
302 # appends a string to a list unless it's already there
303 def addstring(s, l):
304     if l.count(s) > 0:
305         return
306     l.append(s)
307
308
309 def convert(lines, end_format):
310     " Convert to new format."
311     re_Comment = re.compile(b'^(\\s*)#')
312     re_Counter = re.compile(b'\\s*Counter\\s*', re.IGNORECASE)
313     re_Name = re.compile(b'\\s*Name\\s+(\\S+)\\s*', re.IGNORECASE)
314     re_UseMod = re.compile(b'^\\s*UseModule\\s+(.*)', re.IGNORECASE)
315     re_Empty = re.compile(b'^(\\s*)$')
316     re_Format = re.compile(b'^(\\s*)(Format)(\\s+)(\\S+)', re.IGNORECASE)
317     re_Preamble = re.compile(b'^(\\s*)Preamble', re.IGNORECASE)
318     re_EndPreamble = re.compile(b'^(\\s*)EndPreamble', re.IGNORECASE)
319     re_LangPreamble = re.compile(b'^(\\s*)LangPreamble', re.IGNORECASE)
320     re_EndLangPreamble = re.compile(b'^(\\s*)EndLangPreamble', re.IGNORECASE)
321     re_BabelPreamble = re.compile(b'^(\\s*)BabelPreamble', re.IGNORECASE)
322     re_EndBabelPreamble = re.compile(b'^(\\s*)EndBabelPreamble', re.IGNORECASE)
323     re_MaxCounter = re.compile(b'^(\\s*)(MaxCounter)(\\s+)(\\S+)', re.IGNORECASE)
324     re_LabelType = re.compile(b'^(\\s*)(LabelType)(\\s+)(\\S+)', re.IGNORECASE)
325     re_LabelString = re.compile(b'^(\\s*)(LabelString)(\\s+)(("[^"]+")|(\\S+))', re.IGNORECASE)
326     re_LabelStringAppendix = re.compile(b'^(\\s*)(LabelStringAppendix)(\\s+)(("[^"]+")|(\\S+))', re.IGNORECASE)
327     re_LatexType = re.compile(b'^(\\s*)(LatexType)(\\s+)(\\S+)', re.IGNORECASE)
328     re_Style = re.compile(b'^(\\s*)(Style)(\\s+)(\\S+)', re.IGNORECASE)
329     re_IfStyle = re.compile(b'^(\\s*)IfStyle(\\s+\\S+)', re.IGNORECASE)
330     re_CopyStyle = re.compile(b'^(\\s*)(CopyStyle)(\\s+)(\\S+)', re.IGNORECASE)
331     re_NoStyle = re.compile(b'^(\\s*)(NoStyle)(\\s+)(\\S+)', re.IGNORECASE)
332     re_End = re.compile(b'^(\\s*)(End)(\\s*)$', re.IGNORECASE)
333     re_Provides = re.compile(b'^(\\s*)Provides(\\S+)(\\s+)(\\S+)', re.IGNORECASE)
334     re_CharStyle = re.compile(b'^(\\s*)CharStyle(\\s+)(\\S+)$', re.IGNORECASE)
335     re_CiteFormat = re.compile(b'^(\\s*)(CiteFormat)(?:(\\s*)()|(\\s+)(default|authoryear|numerical))', re.IGNORECASE)
336     re_AMSMaths = re.compile(b'^\\s*Input ams(?:math|def)s.inc\\s*')
337     re_AMSMathsPlain = re.compile(b'^\\s*Input amsmaths-plain.inc\\s*')
338     re_AMSMathsSeq = re.compile(b'^\\s*Input amsmaths-seq.inc\\s*')
339     re_TocLevel = re.compile(b'^(\\s*)(TocLevel)(\\s+)(\\S+)', re.IGNORECASE)
340     re_I18nPreamble = re.compile(b'^(\\s*)I18nPreamble', re.IGNORECASE)
341     re_EndI18nPreamble = re.compile(b'^(\\s*)EndI18nPreamble', re.IGNORECASE)
342     re_Float = re.compile(b'^\\s*Float\\s*$', re.IGNORECASE)
343     re_Type = re.compile(b'\\s*Type\\s+(\\w+)', re.IGNORECASE)
344     re_Builtin = re.compile(b'^(\\s*)LaTeXBuiltin\\s+(\\w*)', re.IGNORECASE)
345     re_True = re.compile(b'^\\s*(?:true|1)\\s*$', re.IGNORECASE)
346     re_InsetLayout = re.compile(b'^\\s*InsetLayout\\s+(?:Custom|CharStyle|Element):(\\S+)\\s*$', re.IGNORECASE)
347     re_ResetsFont = re.compile(b'^(\\s*)ResetsFont(\\s+)(\\S+)$', re.IGNORECASE)
348     # with quotes
349     re_QInsetLayout = re.compile(b'^\\s*InsetLayout\\s+"(?:Custom|CharStyle|Element):([^"]+)"\\s*$', re.IGNORECASE)
350     re_InsetLayout_CopyStyle = re.compile(b'^\\s*CopyStyle\\s+(?:Custom|CharStyle|Element):(\\S+)\\s*$', re.IGNORECASE)
351     re_QInsetLayout_CopyStyle = re.compile(b'^\\s*CopyStyle\\s+"(?:Custom|CharStyle|Element):([^"]+)"\\s*$', re.IGNORECASE)
352     re_NeedsFloatPkg = re.compile(b'^(\\s*)NeedsFloatPkg\\s+(\\w+)\\s*$', re.IGNORECASE)
353     re_Fill = re.compile(b'^\\s*Fill_(?:Top|Bottom).*$', re.IGNORECASE)
354     re_InsetLayout2 = re.compile(b'^\\s*InsetLayout\\s+(\\S+)\\s*$', re.IGNORECASE)
355     # with quotes
356     re_QInsetLayout2 = re.compile(b'^\\s*InsetLayout\\s+"([^"]+)"\\s*$', re.IGNORECASE)
357     re_IsFlex = re.compile(b'\\s*LyXType.*$', re.IGNORECASE)
358     re_CopyStyle2 = re.compile(b'(\\s*CopyStyle\\s+)"?([^"]+)"?\\s*$')
359     re_Separator = re.compile(b'^(?:(-*)|(\\s*))(Separator|EndOfSlide)(?:(-*)|(\\s*))$', re.IGNORECASE)
360     # for categories
361     re_Declaration = re.compile(b'^#\\s*\\Declare\\w+Class.*$')
362     re_ExtractCategory = re.compile(b'^(#\\s*\\Declare\\w+Class(?:\\[[^]]*?\\])?){([^(]+?)\\s+\\(([^)]+?)\\)\\s*}\\s*$')
363     ConvDict = {"article": "Articles", "book" : "Books", "letter" : "Letters", "report": "Reports", \
364                 "presentation" : "Presentations", "curriculum vitae" : "Curricula Vitae", "handout" : "Handouts"}
365     # Arguments
366     re_OptArgs = re.compile(b'^(\\s*)OptionalArgs(\\s+)(\\d+)\\D*$', re.IGNORECASE)
367     re_ReqArgs = re.compile(b'^(\\s*)RequiredArgs(\\s+)(\\d+)\\D*$', re.IGNORECASE)
368
369     # various changes associated with changing how chapters are handled
370     re_LabelTypeIsCounter = re.compile(b'^(\\s*)LabelType(\\s*)Counter\\s*$', re.IGNORECASE)
371     re_TopEnvironment = re.compile(b'^(\\s*)LabelType(\\s+)Top_Environment\\s*$', re.IGNORECASE)
372     re_CenteredEnvironment = re.compile(b'^(\\s*)LabelType(\\s+)Centered_Top_Environment\\s*$', re.IGNORECASE)
373     re_ChapterStyle = re.compile(b'^\\s*Style\\s+Chapter\\s*$', re.IGNORECASE)
374     re_InsetLayout_CaptionLTNN = re.compile(b'^(\\s*InsetLayout\\s+)(Caption:LongTableNonumber)', re.IGNORECASE)
375     # for format 64
376     re_trimLabelString = re.compile(b'^(\\s*LabelString\s+)"\\s*(.*?)\\s*"\\s*$')
377     re_trimLabelStringAppendix  = re.compile(b'^(\\s*LabelStringAppendix\s+)"\\s*(.*?)\\s*"\\s*$')
378     re_trimEndLabelString = re.compile(b'^(\\s*EndLabelString\s+)"\\s*(.*?)\\s*"\\s*$')
379     re_trimLabelCounter = re.compile(b'^(\\s*LabelCounter\s+)"\\s*(.*?)\\s*"\\s*$')
380
381
382     # counters for sectioning styles (hardcoded in 1.3)
383     counters = {b"part"          : b"\\Roman{part}",
384                 b"chapter"       : b"\\arabic{chapter}",
385                 b"section"       : b"\\arabic{section}",
386                 b"subsection"    : b"\\arabic{section}.\\arabic{subsection}",
387                 b"subsubsection" : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}",
388                 b"paragraph"     : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}",
389                 b"subparagraph"  : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}.\\arabic{subparagraph}"}
390
391     # counters for sectioning styles in appendix (hardcoded in 1.3)
392     appendixcounters = {b"chapter"       : b"\\Alph{chapter}",
393                         b"section"       : b"\\Alph{section}",
394                         b"subsection"    : b"\\arabic{section}.\\arabic{subsection}",
395                         b"subsubsection" : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}",
396                         b"paragraph"     : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}",
397                         b"subparagraph"  : b"\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}.\\arabic{subparagraph}"}
398
399     # Value of TocLevel for sectioning styles
400     toclevels = {b"part"          : -1,
401                  b"chapter"       : 0,
402                  b"section"       : 1,
403                  b"subsection"    : 2,
404                  b"subsubsection" : 3,
405                  b"paragraph"     : 4,
406                  b"subparagraph"  : 5}
407
408     i = 0
409     only_comment = 1
410     counter = b""
411     toclevel = b""
412     label = b""
413     labelstring = b""
414     labelstringappendix = b""
415     space1 = b""
416     labelstring_line = -1
417     labelstringappendix_line = -1
418     labeltype_line = -1
419     latextype = b""
420     latextype_line = -1
421     style = b""
422     maxcounter = 0
423     format = 1
424     formatline = 0
425     usemodules = []
426     flexstyles = []
427     opts = 0
428     reqs = 0
429     inchapter = False
430     isflexlayout = False         # only used for 48 -> 49
431     # Whether a style is inherited (works only for CopyStyle currently,
432     # not for true inherited styles, see bug 8920
433     inherited = False        # only used for 48 -> 49
434     resetsfont_found = False # only used for 48 -> 49
435
436     while i < len(lines):
437         # Skip comments and empty lines
438         if (re_Comment.match(lines[i]) or re_Empty.match(lines[i])):
439           # We need to deal with this conversion here, because it happens
440           # inside the initial comment block.
441           if only_comment and format == 39:
442               match = re_ExtractCategory.match(lines[i])
443               if match:
444                   lpre = match.group(1)
445                   lcat = match.group(2)
446                   lnam = match.group(3)
447                   if lcat in ConvDict:
448                       lcat = ConvDict[lcat]
449                   lines[i] = lpre + b"{" + lnam + b"}"
450                   lines.insert(i+1, b"#  \\DeclareCategory{" + lcat + b"}")
451                   i += 1
452           i += 1
453           continue
454
455         # insert file format if not already there
456         if (only_comment):
457             match = re_Format.match(lines[i])
458             if match:
459                 formatline = i
460                 format = int(match.group(4))
461                 if format > 1 and format < end_format:
462                     lines[i] = b"Format %d" % (format + 1)
463                     only_comment = 0
464                 elif format == end_format:
465                     # nothing to do
466                     return format
467                 else:
468                     error('Cannot convert file format %s to %s' % (format, end_format))
469             else:
470                 lines.insert(i, b"Format 2")
471                 only_comment = 0
472                 continue
473
474         # Don't get confused by LaTeX code
475         if re_Preamble.match(lines[i]):
476             i += 1
477             while i < len(lines) and not re_EndPreamble.match(lines[i]):
478                 i += 1
479             continue
480         if re_LangPreamble.match(lines[i]):
481             i += 1
482             while i < len(lines) and not re_EndLangPreamble.match(lines[i]):
483                 i += 1
484             continue
485         if re_BabelPreamble.match(lines[i]):
486             i += 1
487             while i < len(lines) and not re_EndBabelPreamble.match(lines[i]):
488                 i += 1
489             continue
490
491         if format >= 65 and format <= 70:
492             # nothing to do.
493             i += 1
494             continue
495
496         if format == 64:
497             match = re.compile(b'(\\s*Color\\s+)(\\w+)', re.IGNORECASE).match(lines[i])
498             if not match:
499                 i += 1
500                 continue
501             col  = match.group(2)
502             if col == "collapsable":
503                 lines[i] = match.group(1) + "collapsible"
504             i += 1
505             continue
506
507         if format == 63:
508             for r in (re_trimLabelString, re_trimLabelStringAppendix,\
509               re_trimEndLabelString, re_trimLabelCounter):
510                 m = r.match(lines[i])
511                 if m:
512                     lines[i] = m.group(1) + b'"' + m.group(2) + b'"'
513             i += 1
514             continue
515
516         if format >= 60 and format <= 62:
517             # nothing to do.
518             i += 1
519             continue
520
521         if format == 59:
522             match = re_InsetLayout_CaptionLTNN.match(lines[i])
523             if not match:
524                 i += 1
525                 continue
526             # '^(\s*InsetLayout\s+)(Caption:LongTableNonumber)'
527             lead  = match.group(1)
528             lines[i] = lead + b"Caption:Unnumbered"
529             i += 1
530             continue
531
532         if format == 58:
533             # nothing to do.
534             i += 1
535             continue
536
537         if format == 57:
538             match = re_IfStyle.match(lines[i])
539             if not match:
540                 i += 1
541                 continue
542             # b'^(\\s*)IfStyle(\\s+\\S+)
543             lead  = match.group(1)
544             trail = match.group(2)
545             lines[i] = lead + b"ModifyStyle" + trail
546             i += 1
547             continue
548
549         if format >= 50 and format <= 56:
550             # nothing to do.
551             i += 1
552             continue
553
554         if format == 49:
555             separator = []
556
557             # delete separator styles
558             match = re_Style.match(lines[i])
559             if match:
560                 style = match.group(4).lower()
561                 if re_Separator.match(style):
562                     del lines[i]
563                     while i < len(lines) and not re_End.match(lines[i]):
564                         separator.append(lines[i])
565                         del lines[i]
566                     if i == len(lines):
567                         error('Incomplete separator style.')
568                     else:
569                         del lines[i]
570                         continue
571
572             # delete undefinition of separator styles
573             match = re_NoStyle.match(lines[i])
574             if match:
575                 style = match.group(4).lower()
576                 if re_Separator.match(style):
577                     del lines[i]
578                     continue
579
580             # replace the CopyStyle statement with the definition of the real
581             # style. This may result in duplicate statements, but that is OK
582             # since the second one will overwrite the first one.
583             match = re_CopyStyle.match(lines[i])
584             if match:
585                 style = match.group(4).lower()
586                 if re_Separator.match(style):
587                     if len(separator) > 0:
588                         lines[i:i+1] = separator
589                     else:
590                         # FIXME: If this style was redefined in an include file,
591                         # we should replace the real style and not this default.
592                         lines[i:i+1] = [b'      Category              MainText',
593                                         b'      KeepEmpty             1',
594                                         b'      Margin                Dynamic',
595                                         b'      LatexType             Paragraph',
596                                         b'      LatexName             dummy',
597                                         b'      ParIndent             MM',
598                                         b'      Align                 Block',
599                                         b'      LabelType             Static',
600                                         b'      LabelString           "--- Separate Environment ---"',
601                                         b'      LabelFont',
602                                         b'        Family              Roman',
603                                         b'        Series              Medium',
604                                         b'        Size                Normal',
605                                         b'        Color               Blue',
606                                         b'      EndFont',
607                                         b'      HTMLLabel             NONE']
608             i += 1
609             continue
610
611         if format == 48:
612             # The default of ResetsFont in LyX changed from true to false,
613             # because it is now used for all InsetLayouts, not only flex ones.
614             # Therefore we need to set it to true for all flex insets which do
615             # do not already have a ResetsFont.
616             match = re_InsetLayout2.match(lines[i])
617             if not match:
618                 i += 1
619                 continue
620
621             name = match.group(1).lower()
622             if name != b"flex" and name != b"\"flex\"" and name[0:5] != b"flex:" and name [0:6] != b"\"flex:":
623                 i += 1
624                 continue
625
626             resetsfont_found = False
627             inherited = False
628             notdone = True
629             while i < len(lines):
630               match = re_ResetsFont.match(lines[i])
631               if match:
632                   resetsfont_found = True
633               else:
634                 match = re_CopyStyle.match(lines[i])
635                 if match:
636                   inherited = True
637                 else:
638                   match = re_End.match(lines[i])
639                   if match:
640                     break
641               i += 1
642             if not resetsfont_found and not inherited:
643               lines.insert(i, b"\tResetsFont true")
644
645             continue
646
647         if format >= 44 and format <= 47:
648             # nothing to do.
649             i += 1
650             continue
651
652         if format == 43:
653           match = re_LabelTypeIsCounter.match(lines[i])
654           if match:
655             if inchapter:
656              lines[i] = match.group(1) + b"LabelType" + match.group(2) + b"Above"
657             else:
658               lines[i] = match.group(1) + b"LabelType" + match.group(2) + b"Static"
659
660           match = re_TopEnvironment.match(lines[i])
661           if match:
662             lines[i] = match.group(1) + b"LabelType" + match.group(2) + b"Above"
663
664           match = re_CenteredEnvironment.match(lines[i])
665           if match:
666             lines[i] = match.group(1) + b"LabelType" + match.group(2) + b"Centered"
667
668           if inchapter:
669             match = re_Style.match(lines[i])
670             if match:
671               inchapter = False
672           else:
673             match = re_ChapterStyle.match(lines[i])
674             if match:
675               inchapter = True
676
677           i += 1
678           continue
679
680         if format == 42:
681           if lines[i] == b"InsetLayout Caption":
682             lines[i] = b"InsetLayout Caption:Standard"
683           i += 1
684           continue
685
686         if format == 41:
687             # nothing to do.
688             i += 1
689             continue
690
691         if format == 40:
692             # reset counters on Style beginning
693             match = re_Style.match(lines[i])
694             if match:
695                 opts = 0
696                 reqs = 0
697                 i += 1
698                 continue
699             match = re_OptArgs.match(lines[i])
700             if match:
701                 # Save number of optional arguments
702                 space1 = match.group(1)
703                 opts = int(match.group(3))
704                 # OptionalArgs 0 > ResetArgs 1
705                 if opts == 0:
706                     lines[i] = space1 + b"ResetArgs\t1"
707                     i += 1
708                 else:
709                     del lines[i]
710                 continue
711             match = re_ReqArgs.match(lines[i])
712             if match:
713                 # Save number of required arguments
714                 space1 = match.group(1)
715                 reqs = int(match.group(3))
716                 del lines[i]
717                 continue
718             # Insert the required number of arguments at the end of the style definition
719             match = re_End.match(lines[i])
720             if match:
721                 newarg = ['']
722                 # First the optionals (this is the required order pre 2.1)
723                 if opts > 0:
724                     if opts == 1:
725                         newarg = [ b'%sArgument 1' % (space1),
726                                    b'%s\tLabelString\t\"Optional Layout Argument\"' % (space1),
727                                    b'%sEndArgument' % (space1)]
728                     elif opts > 1:
729                         actopt = 1
730                         while actopt < (opts + 1):
731                             newarg += [ b'%sArgument %d' % (space1, actopt),
732                                b'%s\tLabelString\t\"Optional Layout Argument %d\"' % (space1, actopt),
733                                b'%sEndArgument' % (space1)]
734                             actopt += 1
735                 # Now the mandatories
736                 if reqs > 0:
737                     actopt = opts + 1
738                     while actopt < (opts +  reqs + 1):
739                         newarg += [ b'%sArgument %d' % (space1, actopt),
740                            b'%s\tLabelString\t"Required Layout Argument %d"' % (space1, actopt - opts),
741                            b'%s\tMandatory\t1' % (space1),
742                            b'%sEndArgument' % (space1)]
743                         actopt += 1
744                 # Since we replace the "End" line, re-add this line
745                 if len(newarg) > 1:
746                     newarg += [b'End']
747                     lines[i:i+1] = newarg
748                     i += len(newarg)
749                 # Reset the counters
750                 opts = 0
751                 reqs = 0
752             i += 1
753             continue
754
755         if format == 39:
756             # There is a conversion with format 40, but it is done within the
757             # initial comment block and so is above.
758             i += 1
759             continue
760
761         if format == 37 or format == 38:
762             i += 1
763             continue
764
765         if format == 36:
766             match = re_CiteFormat.match(lines[i]);
767             if match and match.group(4) == b"":
768                 lines[i] = match.group(0) + b" default"
769             i += 1
770             continue
771
772         if format == 35:
773           i += 1
774           continue
775
776         if format == 34:
777           match = re_QInsetLayout2.match(lines[i])
778           if not match:
779             match = re_InsetLayout2.match(lines[i])
780           if not match:
781             match = re_CopyStyle2.match(lines[i])
782             if not match:
783               i += 1
784               continue
785             style = match.group(2)
786
787             if flexstyles.count(style):
788               lines[i] = match.group(1) + b"\"Flex:" + style + b"\""
789             i += 1
790             continue
791
792           name = match.group(1)
793           names = name.split(b":", 1)
794           if len(names) > 1 and names[0] == b"Flex":
795             i += 1
796             continue
797
798           isflex = False
799           for j in range(i + 1, len(lines)):
800             if re_IsFlex.match(lines[j]):
801               isflex = True
802               break
803             if re_End.match(lines[j]):
804               break
805
806           if isflex:
807             flexstyles.append(name)
808             lines[i] = b"InsetLayout \"Flex:" + name + b"\""
809
810           i += 1
811           continue
812
813         if format == 33:
814           m = re_Fill.match(lines[i])
815           if m:
816             lines[i] = b""
817           i += 1
818           continue
819
820         if format == 32:
821           match = re_NeedsFloatPkg.match(lines[i])
822           if match:
823             space = match.group(1)
824             val = match.group(2)
825             lines[i] = space + b"UsesFloatPkg " + val
826             newval = b'true'
827             if val == b'1' or val.lower() == b'true':
828               newval = b'false'
829             lines.insert(i, space + b"IsPredefined " + newval)
830             i += 1
831           i += 1
832           continue
833
834         # Only new features
835         if format >= 29 and format <= 31:
836           i += 1
837           continue
838
839         if format == 28:
840           match = re_InsetLayout.match(lines[i])
841           if match:
842             lines[i] = b"InsetLayout Flex:" + match.group(1)
843           else:
844             match = re_QInsetLayout.match(lines[i])
845             if match:
846               lines[i] = b"InsetLayout \"Flex:" + match.group(1) + b"\""
847             else:
848               match = re_InsetLayout_CopyStyle.match(lines[i])
849               if match:
850                 lines[i] = b"\tCopyStyle Flex:" + match.group(1)
851               else:
852                 match = re_QInsetLayout_CopyStyle.match(lines[i])
853                 if match:
854                   lines[i] = b"\tCopyStyle \"Flex:" + match.group(1) + b"\""
855           i += 1
856           continue
857
858         # Only new features
859         if format >= 24 and format <= 27:
860           i += 1
861           continue
862
863         if format == 23:
864           match = re_Float.match(lines[i])
865           i += 1
866           if not match:
867             continue
868           # we need to do two things:
869           # (i)  Convert Builtin to NeedsFloatPkg
870           # (ii) Write ListCommand lines for the builtin floats table and figure
871           builtin = False
872           cmd = b""
873           while True and i < len(lines):
874             m1 = re_End.match(lines[i])
875             if m1:
876               if builtin and cmd:
877                 line = b"    ListCommand " + cmd
878                 lines.insert(i, line)
879                 i += 1
880               break
881             m2 = re_Builtin.match(lines[i])
882             if m2:
883               builtin = True
884               ws1 = m2.group(1)
885               arg = m2.group(2)
886               newarg = b""
887               if re_True.match(arg):
888                 newarg = b"false"
889               else:
890                 newarg = b"true"
891               lines[i] = ws1 + b"NeedsFloatPkg " + newarg
892             m3 = re_Type.match(lines[i])
893             if m3:
894               fltype = m3.group(1)
895               fltype = fltype.lower()
896               if fltype == b"table":
897                 cmd = b"listoftables"
898               elif fltype == b"figure":
899                 cmd = b"listoffigures"
900               # else unknown, which is why we're doing this
901             i += 1
902           continue
903
904         # This just involved new features, not any changes to old ones
905         if format >= 14 and format <= 22:
906           i += 1
907           continue
908
909         # Rename I18NPreamble to BabelPreamble
910         if format == 13:
911             match = re_I18nPreamble.match(lines[i])
912             if match:
913                 lines[i] = match.group(1) + b"BabelPreamble"
914                 i += 1
915                 match = re_EndI18nPreamble.match(lines[i])
916                 while i < len(lines) and not match:
917                     i += 1
918                     match = re_EndI18nPreamble.match(lines[i])
919                 lines[i] = match.group(1) + b"EndBabelPreamble"
920                 i += 1
921                 continue
922
923         # These just involved new features, not any changes to old ones
924         if format == 11 or format == 12:
925           i += 1
926           continue
927
928         if format == 10:
929             match = re_UseMod.match(lines[i])
930             if match:
931                 module = match.group(1)
932                 lines[i] = b"DefaultModule " + module
933             i += 1
934             continue
935
936         if format == 9:
937             match = re_Counter.match(lines[i])
938             if match:
939                 counterline = i
940                 i += 1
941                 while i < len(lines):
942                     namem = re_Name.match(lines[i])
943                     if namem:
944                         name = namem.group(1)
945                         lines.pop(i)
946                         lines[counterline] = b"Counter %s" % name
947                         # we don't need to increment i
948                         continue
949                     endem = re_End.match(lines[i])
950                     if endem:
951                         i += 1
952                         break
953                     i += 1
954             i += 1
955             continue
956
957         if format == 8:
958             # We want to scan for ams-type includes and, if we find them,
959             # add corresponding UseModule tags to the layout.
960             match = re_AMSMaths.match(lines[i])
961             if match:
962                 addstring(b"theorems-ams", usemodules)
963                 addstring(b"theorems-ams-extended", usemodules)
964                 addstring(b"theorems-sec", usemodules)
965                 lines.pop(i)
966                 continue
967             match = re_AMSMathsPlain.match(lines[i])
968             if match:
969                 addstring(b"theorems-starred", usemodules)
970                 lines.pop(i)
971                 continue
972             match = re_AMSMathsSeq.match(lines[i])
973             if match:
974                 addstring(b"theorems-ams", usemodules)
975                 addstring(b"theorems-ams-extended", usemodules)
976                 lines.pop(i)
977                 continue
978             i += 1
979             continue
980
981         # These just involved new features, not any changes to old ones
982         if format >= 5 and format <= 7:
983           i += 1
984           continue
985
986         if format == 4:
987             # Handle conversion to long CharStyle names
988             match = re_CharStyle.match(lines[i])
989             if match:
990                 lines[i] = b"InsetLayout CharStyle:%s" % (match.group(3))
991                 i += 1
992                 lines.insert(i, b"\tLyXType charstyle")
993                 i += 1
994                 lines.insert(i, b"")
995                 lines[i] = b"\tLabelString %s" % (match.group(3))
996             i += 1
997             continue
998
999         if format == 3:
1000             # convert 'providesamsmath x',  'providesmakeidx x',  'providesnatbib x',  'providesurl x' to
1001             #         'provides amsmath x', 'provides makeidx x', 'provides natbib x', 'provides url x'
1002             # x is either 0 or 1
1003             match = re_Provides.match(lines[i])
1004             if match:
1005                 lines[i] = b"%sProvides %s%s%s" % (match.group(1), match.group(2).lower(),
1006                                                   match.group(3), match.group(4))
1007             i += 1
1008             continue
1009
1010         if format == 2:
1011             caption = []
1012
1013             # delete caption styles
1014             match = re_Style.match(lines[i])
1015             if match:
1016                 style = match.group(4).lower()
1017                 if style == b"caption":
1018                     del lines[i]
1019                     while i < len(lines) and not re_End.match(lines[i]):
1020                         caption.append(lines[i])
1021                         del lines[i]
1022                     if i == len(lines):
1023                         error('Incomplete caption style.')
1024                     else:
1025                         del lines[i]
1026                         continue
1027
1028             # delete undefinition of caption styles
1029             match = re_NoStyle.match(lines[i])
1030             if match:
1031                 style = match.group(4).lower()
1032                 if style == b"caption":
1033                     del lines[i]
1034                     continue
1035
1036             # replace the CopyStyle statement with the definition of the real
1037             # style. This may result in duplicate statements, but that is OK
1038             # since the second one will overwrite the first one.
1039             match = re_CopyStyle.match(lines[i])
1040             if match:
1041                 style = match.group(4).lower()
1042                 if style == b"caption":
1043                     if len(caption) > 0:
1044                         lines[i:i+1] = caption
1045                     else:
1046                         # FIXME: This style comes from an include file, we
1047                         # should replace the real style and not this default.
1048                         lines[i:i+1] = [b'      Margin                First_Dynamic',
1049                                         b'      LatexType             Command',
1050                                         b'      LatexName             caption',
1051                                         b'      NeedProtect           1',
1052                                         b'      LabelSep              xx',
1053                                         b'      ParSkip               0.4',
1054                                         b'      TopSep                0.5',
1055                                         b'      Align                 Center',
1056                                         b'      AlignPossible         Center',
1057                                         b'      LabelType             Sensitive',
1058                                         b'      LabelString           "Senseless!"',
1059                                         b'      OptionalArgs          1',
1060                                         b'      LabelFont',
1061                                         b'        Series              Bold',
1062                                         b'      EndFont']
1063
1064             i += 1
1065             continue
1066
1067         # Delete MaxCounter and remember the value of it
1068         match = re_MaxCounter.match(lines[i])
1069         if match:
1070             level = match.group(4).lower()
1071             if level == b"counter_chapter":
1072                 maxcounter = 0
1073             elif level == b"counter_section":
1074                 maxcounter = 1
1075             elif level == b"counter_subsection":
1076                 maxcounter = 2
1077             elif level == b"counter_subsubsection":
1078                 maxcounter = 3
1079             elif level == b"counter_paragraph":
1080                 maxcounter = 4
1081             elif level == b"counter_subparagraph":
1082                 maxcounter = 5
1083             elif level == b"counter_enumi":
1084                 maxcounter = 6
1085             elif level == b"counter_enumii":
1086                 maxcounter = 7
1087             elif level == b"counter_enumiii":
1088                 maxcounter = 8
1089             del lines[i]
1090             continue
1091
1092         # Replace line
1093         #
1094         # LabelType Counter_EnumI
1095         #
1096         # with two lines
1097         #
1098         # LabelType Counter
1099         # LabelCounter EnumI
1100         #
1101         match = re_LabelType.match(lines[i])
1102         if match:
1103             label = match.group(4)
1104             # Remember indenting space for later reuse in added lines
1105             space1 = match.group(1)
1106             # Remember the line for adding the LabelCounter later.
1107             # We can't do it here because it could shift latextype_line etc.
1108             labeltype_line = i
1109             if label[:8].lower() == b"counter_":
1110                 counter = label[8:].lower()
1111                 lines[i] = re_LabelType.sub(b'\\1\\2\\3Counter', lines[i])
1112
1113         # Remember the LabelString line
1114         match = re_LabelString.match(lines[i])
1115         if match:
1116             labelstring = match.group(4)
1117             labelstring_line = i
1118
1119         # Remember the LabelStringAppendix line
1120         match = re_LabelStringAppendix.match(lines[i])
1121         if match:
1122             labelstringappendix = match.group(4)
1123             labelstringappendix_line = i
1124
1125         # Remember the LatexType line
1126         match = re_LatexType.match(lines[i])
1127         if match:
1128             latextype = match.group(4).lower()
1129             latextype_line = i
1130
1131         # Remember the TocLevel line
1132         match = re_TocLevel.match(lines[i])
1133         if match:
1134             toclevel = match.group(4).lower()
1135
1136         # Reset variables at the beginning of a style definition
1137         match = re_Style.match(lines[i])
1138         if match:
1139             style = match.group(4).lower()
1140             counter = b""
1141             toclevel = b""
1142             label = b""
1143             space1 = b""
1144             labelstring = b""
1145             labelstringappendix = b""
1146             labelstring_line = -1
1147             labelstringappendix_line = -1
1148             labeltype_line = -1
1149             latextype = b""
1150             latextype_line = -1
1151
1152         if re_End.match(lines[i]):
1153
1154             # Add a line "LatexType Bib_Environment" if LabelType is Bibliography
1155             # (or change the existing LatexType)
1156             if label.lower() == b"bibliography":
1157                 if (latextype_line < 0):
1158                     lines.insert(i, b"%sLatexType Bib_Environment" % space1)
1159                     i += 1
1160                 else:
1161                     lines[latextype_line] = re_LatexType.sub(b'\\1\\2\\3Bib_Environment', lines[latextype_line])
1162
1163             # Change "LabelType Static" to "LabelType Itemize" for itemize environments
1164             if latextype == b"item_environment" and label.lower() == b"static":
1165                 lines[labeltype_line] = re_LabelType.sub(b'\\1\\2\\3Itemize', lines[labeltype_line])
1166
1167             # Change "LabelType Counter_EnumI" to "LabelType Enumerate" for enumerate environments
1168             if latextype == b"item_environment" and label.lower() == b"counter_enumi":
1169                 lines[labeltype_line] = re_LabelType.sub(b'\\1\\2\\3Enumerate', lines[labeltype_line])
1170                 # Don't add the LabelCounter line later
1171                 counter = ""
1172
1173             # Replace
1174             #
1175             # LabelString "Chapter"
1176             #
1177             # with
1178             #
1179             # LabelString "Chapter \arabic{chapter}"
1180             #
1181             # if this style has a counter. Ditto for LabelStringAppendix.
1182             # This emulates the hardcoded article style numbering of 1.3
1183             #
1184             if counter != b"":
1185                 if style in counters:
1186                     if labelstring_line < 0:
1187                         lines.insert(i, b'%sLabelString "%s"' % (space1, counters[style]))
1188                         i += 1
1189                     else:
1190                         new_labelstring = concatenate_label(labelstring, counters[style])
1191                         lines[labelstring_line] = re_LabelString.sub(
1192                                 b'\\1\\2\\3%s' % new_labelstring.replace(b"\\", b"\\\\"),
1193                                 lines[labelstring_line])
1194                 if style in appendixcounters:
1195                     if labelstringappendix_line < 0:
1196                         lines.insert(i, b'%sLabelStringAppendix "%s"' % (space1, appendixcounters[style]))
1197                         i += 1
1198                     else:
1199                         new_labelstring = concatenate_label(labelstring, appendixcounters[style])
1200                         lines[labelstringappendix_line] = re_LabelStringAppendix.sub(
1201                                 b'\\1\\2\\3%s' % new_labelstring.replace(b"\\", b"\\\\"),
1202                                 lines[labelstringappendix_line])
1203
1204                 # Now we can safely add the LabelCounter line
1205                 lines.insert(labeltype_line + 1, b"%sLabelCounter %s" % (space1, counter))
1206                 i += 1
1207
1208             # Add the TocLevel setting for sectioning styles
1209             if toclevel == b"" and style in toclevels and maxcounter <= toclevels[style]:
1210                 lines.insert(i, b'%s\tTocLevel %d' % (space1, toclevels[style]))
1211                 i += 1
1212
1213         i += 1
1214
1215     if only_comment:
1216         lines.insert(i, b"Format 2")
1217     if usemodules:
1218         i = formatline + 1
1219         for mod in usemodules:
1220             lines.insert(i, b"UseModule " + mod)
1221             i += 1
1222
1223     return format + 1
1224
1225
1226 def main(argv):
1227     args = {}
1228     args["description"] = "Convert layout file <inputfile> to a newer format."
1229
1230     parser = argparse.ArgumentParser(**args)
1231
1232     parser.add_argument("-t", "--to", type=int, dest="format", default= currentFormat,
1233                         help=("destination layout format, default %i (latest)") % currentFormat)
1234     parser.add_argument("input_file", nargs='?', type=cmd_arg, default=None,
1235                         help="input file (default stdin)")
1236     parser.add_argument("output_file", nargs='?', type=cmd_arg, default=None,
1237                         help="output file (default stdout)")
1238
1239     options = parser.parse_args(argv[1:])
1240
1241     # Open files
1242     if options.input_file:
1243         source = open(options.input_file, 'rb')
1244     else:
1245         source = sys.stdin
1246
1247     if options.output_file:
1248         output = open(options.output_file, 'wb')
1249     else:
1250         output = sys.stdout
1251
1252     if options.format > currentFormat:
1253         error("Format %i does not exist" % options.format);
1254
1255     # Do the real work
1256     lines = read(source)
1257     format = 1
1258     while (format < options.format):
1259         format = convert(lines, options.format)
1260     write(output, lines)
1261
1262     # Close files
1263     if options.input_file:
1264         source.close()
1265     if options.output_file:
1266         output.close()
1267
1268     return 0
1269
1270
1271 if __name__ == "__main__":
1272     main(sys.argv)