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