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