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