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