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