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