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