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