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