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