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