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