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