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