]> git.lyx.org Git - features.git/blob - lib/scripts/layout2layout.py
Allow literate documents other than noweb to work out of the box. Currently
[features.git] / lib / scripts / layout2layout.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # file layout2layout.py
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7
8 # author Georg Baum
9
10 # Full author contact details are available in file CREDITS
11
12 # This script will update a .layout file to current format
13
14
15 import os, re, string, sys
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 conditionned on feature 
53 # "from->to".
54
55 # Do not forget to document format change in Customization
56 # Manual (section "Declaring a new text class").
57
58 currentFormat = 15
59
60
61 def usage(prog_name):
62     return ("Usage: %s inputfile outputfile\n" % prog_name +
63             "or     %s <inputfile >outputfile" % prog_name)
64
65
66 def error(message):
67     sys.stderr.write(message + '\n')
68     sys.exit(1)
69
70
71 def trim_eol(line):
72     " Remove end of line char(s)."
73     if line[-2:-1] == '\r':
74         return line[:-2]
75     elif line[-1:] == '\r' or line[-1:] == '\n':
76         return line[:-1]
77     else:
78         # file with no EOL in last line
79         return line
80
81
82 def read(input):
83     " Read input file and strip lineendings."
84     lines = list()
85     while 1:
86         line = input.readline()
87         if not line:
88             break
89         lines.append(trim_eol(line))
90     return lines
91
92
93 def write(output, lines):
94     " Write output file with native lineendings."
95     for line in lines:
96         output.write(line + os.linesep)
97
98
99 # Concatenates old and new in an intelligent way:
100 # If old is wrapped in ", they are stripped. The result is wrapped in ".
101 def concatenate_label(old, new):
102     # Don't use strip as long as we support python 1.5.2
103     if old[0] == '"':
104         return old[0:-1] + new + '"'
105     else:
106         return '"' + old + new + '"'
107
108 # appends a string to a list unless it's already there
109 def addstring(s, l):
110     if l.count(s) > 0:
111         return
112     l.append(s)
113
114
115 def convert(lines):
116     " Convert to new format."
117     re_Comment = re.compile(r'^(\s*)#')
118     re_Counter = re.compile(r'\s*Counter\s*', re.IGNORECASE)
119     re_Name = re.compile(r'\s*Name\s+(\S+)\s*', re.IGNORECASE)
120     re_UseMod = re.compile(r'^\s*UseModule\s+(.*)', re.IGNORECASE)
121     re_Empty = re.compile(r'^(\s*)$')
122     re_Format = re.compile(r'^(\s*)(Format)(\s+)(\S+)', re.IGNORECASE)
123     re_Preamble = re.compile(r'^(\s*)Preamble', re.IGNORECASE)
124     re_EndPreamble = re.compile(r'^(\s*)EndPreamble', re.IGNORECASE)
125     re_LangPreamble = re.compile(r'^(\s*)LangPreamble', re.IGNORECASE)
126     re_EndLangPreamble = re.compile(r'^(\s*)EndLangPreamble', re.IGNORECASE)
127     re_BabelPreamble = re.compile(r'^(\s*)BabelPreamble', re.IGNORECASE)
128     re_EndBabelPreamble = re.compile(r'^(\s*)EndBabelPreamble', re.IGNORECASE)
129     re_MaxCounter = re.compile(r'^(\s*)(MaxCounter)(\s+)(\S+)', re.IGNORECASE)
130     re_LabelType = re.compile(r'^(\s*)(LabelType)(\s+)(\S+)', re.IGNORECASE)
131     re_LabelString = re.compile(r'^(\s*)(LabelString)(\s+)(("[^"]+")|(\S+))', re.IGNORECASE)
132     re_LabelStringAppendix = re.compile(r'^(\s*)(LabelStringAppendix)(\s+)(("[^"]+")|(\S+))', re.IGNORECASE)
133     re_LatexType = re.compile(r'^(\s*)(LatexType)(\s+)(\S+)', re.IGNORECASE)
134     re_Style = re.compile(r'^(\s*)(Style)(\s+)(\S+)', re.IGNORECASE)
135     re_CopyStyle = re.compile(r'^(\s*)(CopyStyle)(\s+)(\S+)', re.IGNORECASE)
136     re_NoStyle = re.compile(r'^(\s*)(NoStyle)(\s+)(\S+)', re.IGNORECASE)
137     re_End = re.compile(r'^(\s*)(End)(\s*)$', re.IGNORECASE)
138     re_Provides = re.compile(r'^(\s*)Provides(\S+)(\s+)(\S+)', re.IGNORECASE)
139     re_CharStyle = re.compile(r'^(\s*)CharStyle(\s+)(\S+)$', re.IGNORECASE)
140     re_AMSMaths = re.compile(r'^\s*Input amsmaths.inc\s*')
141     re_AMSMathsPlain = re.compile(r'^\s*Input amsmaths-plain.inc\s*')
142     re_AMSMathsSeq = re.compile(r'^\s*Input amsmaths-seq.inc\s*')
143     re_TocLevel = re.compile(r'^(\s*)(TocLevel)(\s+)(\S+)', re.IGNORECASE)
144     re_I18nPreamble = re.compile(r'^(\s*)I18nPreamble', re.IGNORECASE)
145     re_EndI18nPreamble = re.compile(r'^(\s*)EndI18nPreamble', re.IGNORECASE)
146
147     # counters for sectioning styles (hardcoded in 1.3)
148     counters = {"part"          : "\\Roman{part}",
149                 "chapter"       : "\\arabic{chapter}",
150                 "section"       : "\\arabic{section}",
151                 "subsection"    : "\\arabic{section}.\\arabic{subsection}",
152                 "subsubsection" : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}",
153                 "paragraph"     : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}",
154                 "subparagraph"  : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}.\\arabic{subparagraph}"}
155
156     # counters for sectioning styles in appendix (hardcoded in 1.3)
157     appendixcounters = {"chapter"       : "\\Alph{chapter}",
158                         "section"       : "\\Alph{section}",
159                         "subsection"    : "\\arabic{section}.\\arabic{subsection}",
160                         "subsubsection" : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}",
161                         "paragraph"     : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}",
162                         "subparagraph"  : "\\arabic{section}.\\arabic{subsection}.\\arabic{subsubsection}.\\arabic{paragraph}.\\arabic{subparagraph}"}
163
164     # Value of TocLevel for sectioning styles
165     toclevels = {"part"          : 0,
166                  "chapter"       : 0,
167                  "section"       : 1,
168                  "subsection"    : 2,
169                  "subsubsection" : 3,
170                  "paragraph"     : 4,
171                  "subparagraph"  : 5}
172
173     i = 0
174     only_comment = 1
175     counter = ""
176     toclevel = ""
177     label = ""
178     labelstring = ""
179     labelstringappendix = ""
180     space1 = ""
181     labelstring_line = -1
182     labelstringappendix_line = -1
183     labeltype_line = -1
184     latextype = ""
185     latextype_line = -1
186     style = ""
187     maxcounter = 0
188     format = 1
189     formatline = 0
190     usemodules = []
191
192     while i < len(lines):
193         # Skip comments and empty lines
194         if re_Comment.match(lines[i]) or re_Empty.match(lines[i]):
195             i += 1
196             continue
197
198         # insert file format if not already there
199         if (only_comment):
200             match = re_Format.match(lines[i])
201             if match:
202                 formatline = i
203                 format = int(match.group(4))
204                 if format > 1 and format < currentFormat:
205                     lines[i] = "Format %d" % (format + 1)
206                     only_comment = 0
207                 elif format == currentFormat:
208                     # nothing to do
209                     return format
210                 else:
211                     error('Cannot convert file format %s' % format)
212             else:
213                 lines.insert(i, "Format 2")
214                 only_comment = 0
215                 continue
216
217         # Don't get confused by LaTeX code
218         if re_Preamble.match(lines[i]):
219             i += 1
220             while i < len(lines) and not re_EndPreamble.match(lines[i]):
221                 i += 1
222             continue
223         if re_LangPreamble.match(lines[i]):
224             i += 1
225             while i < len(lines) and not re_EndLangPreamble.match(lines[i]):
226                 i += 1
227             continue
228         if re_BabelPreamble.match(lines[i]):
229             i += 1
230             while i < len(lines) and not re_EndBabelPreamble.match(lines[i]):
231                 i += 1
232             continue
233
234         # This just involved new features, not any changes to old ones
235         if format == 14:
236           i += 1
237           continue
238
239         # Rename I18NPreamble to BabelPreamble
240         if format == 13:
241             match = re_I18nPreamble.match(lines[i])
242             if match:
243                 lines[i] = match.group(1) + "BabelPreamble"
244                 i += 1
245                 match = re_EndI18nPreamble.match(lines[i])
246                 while i < len(lines) and not match:
247                     i += 1
248                     match = re_EndI18nPreamble.match(lines[i])
249                 lines[i] = match.group(1) + "EndBabelPreamble"
250                 i += 1
251                 continue
252
253         # These just involved new features, not any changes to old ones
254         if format == 11 or format == 12:
255           i += 1
256           continue
257
258         if format == 10:
259             match = re_UseMod.match(lines[i])
260             if match:
261                 module = match.group(1)
262                 lines[i] = "DefaultModule " + module
263             i += 1
264             continue
265
266         if format == 9:
267             match = re_Counter.match(lines[i])
268             if match:
269                 counterline = i
270                 i += 1
271                 while i < len(lines):
272                     namem = re_Name.match(lines[i])
273                     if namem:
274                         name = namem.group(1)
275                         lines.pop(i)
276                         lines[counterline] = "Counter %s" % name
277                         # we don't need to increment i
278                         continue
279                     endem = re_End.match(lines[i])
280                     if endem:
281                         i += 1
282                         break
283                     i += 1
284             i += 1
285             continue
286
287         if format == 8:
288             # We want to scan for ams-type includes and, if we find them,
289             # add corresponding UseModule tags to the layout.
290             match = re_AMSMaths.match(lines[i])
291             if match:
292                 addstring("theorems-ams", usemodules)
293                 addstring("theorems-ams-extended", usemodules)
294                 addstring("theorems-sec", usemodules)
295                 lines.pop(i)
296                 continue
297             match = re_AMSMathsPlain.match(lines[i])
298             if match:
299                 addstring("theorems-starred", usemodules)
300                 lines.pop(i)
301                 continue
302             match = re_AMSMathsSeq.match(lines[i])
303             if match:
304                 addstring("theorems-ams", usemodules)
305                 addstring("theorems-ams-extended", usemodules)
306                 lines.pop(i)
307                 continue
308             i += 1
309             continue
310
311         # These just involved new features, not any changes to old ones
312         if format >= 5 and format <= 7:
313           i += 1
314           continue
315
316         if format == 4:
317             # Handle conversion to long CharStyle names
318             match = re_CharStyle.match(lines[i])
319             if match:
320                 lines[i] = "InsetLayout CharStyle:%s" % (match.group(3))
321                 i += 1
322                 lines.insert(i, "\tLyXType charstyle")
323                 i += 1
324                 lines.insert(i, "")
325                 lines[i] = "\tLabelString %s" % (match.group(3))
326             i += 1
327             continue
328
329         if format == 3:
330             # convert 'providesamsmath x',  'providesmakeidx x',  'providesnatbib x',  'providesurl x' to
331             #         'provides amsmath x', 'provides makeidx x', 'provides natbib x', 'provides url x'
332             # x is either 0 or 1
333             match = re_Provides.match(lines[i])
334             if match:
335                 lines[i] = "%sProvides %s%s%s" % (match.group(1), match.group(2).lower(),
336                                                   match.group(3), match.group(4))
337             i += 1
338             continue
339
340         if format == 2:
341             caption = []
342
343             # delete caption styles
344             match = re_Style.match(lines[i])
345             if match:
346                 style = string.lower(match.group(4))
347                 if style == "caption":
348                     del lines[i]
349                     while i < len(lines) and not re_End.match(lines[i]):
350                         caption.append(lines[i])
351                         del lines[i]
352                     if i == len(lines):
353                         error('Incomplete caption style.')
354                     else:
355                         del lines[i]
356                         continue
357
358             # delete undefinition of caption styles
359             match = re_NoStyle.match(lines[i])
360             if match:
361                 style = string.lower(match.group(4))
362                 if style == "caption":
363                     del lines[i]
364                     continue
365
366             # replace the CopyStyle statement with the definition of the real
367             # style. This may result in duplicate statements, but that is OK
368             # since the second one will overwrite the first one.
369             match = re_CopyStyle.match(lines[i])
370             if match:
371                 style = string.lower(match.group(4))
372                 if style == "caption":
373                     if len(caption) > 0:
374                         lines[i:i+1] = caption
375                     else:
376                         # FIXME: This style comes from an include file, we
377                         # should replace the real style and not this default.
378                         lines[i:i+1] = ['       Margin                First_Dynamic',
379                                         '       LatexType             Command',
380                                         '       LatexName             caption',
381                                         '       NeedProtect           1',
382                                         '       LabelSep              xx',
383                                         '       ParSkip               0.4',
384                                         '       TopSep                0.5',
385                                         '       Align                 Center',
386                                         '       AlignPossible         Center',
387                                         '       LabelType             Sensitive',
388                                         '       LabelString           "Senseless!"',
389                                         '       OptionalArgs          1',
390                                         '       LabelFont',
391                                         '         Series              Bold',
392                                         '       EndFont']
393
394             i += 1
395             continue
396
397         # Delete MaxCounter and remember the value of it
398         match = re_MaxCounter.match(lines[i])
399         if match:
400             level = match.group(4)
401             if string.lower(level) == "counter_chapter":
402                 maxcounter = 0
403             elif string.lower(level) == "counter_section":
404                 maxcounter = 1
405             elif string.lower(level) == "counter_subsection":
406                 maxcounter = 2
407             elif string.lower(level) == "counter_subsubsection":
408                 maxcounter = 3
409             elif string.lower(level) == "counter_paragraph":
410                 maxcounter = 4
411             elif string.lower(level) == "counter_subparagraph":
412                 maxcounter = 5
413             elif string.lower(level) == "counter_enumi":
414                 maxcounter = 6
415             elif string.lower(level) == "counter_enumii":
416                 maxcounter = 7
417             elif string.lower(level) == "counter_enumiii":
418                 maxcounter = 8
419             del lines[i]
420             continue
421
422         # Replace line
423         #
424         # LabelType Counter_EnumI
425         #
426         # with two lines
427         #
428         # LabelType Counter
429         # LabelCounter EnumI
430         #
431         match = re_LabelType.match(lines[i])
432         if match:
433             label = match.group(4)
434             # Remember indenting space for later reuse in added lines
435             space1 = match.group(1)
436             # Remember the line for adding the LabelCounter later.
437             # We can't do it here because it could shift latextype_line etc.
438             labeltype_line = i
439             if string.lower(label[:8]) == "counter_":
440                 counter = string.lower(label[8:])
441                 lines[i] = re_LabelType.sub(r'\1\2\3Counter', lines[i])
442
443         # Remember the LabelString line
444         match = re_LabelString.match(lines[i])
445         if match:
446             labelstring = match.group(4)
447             labelstring_line = i
448
449         # Remember the LabelStringAppendix line
450         match = re_LabelStringAppendix.match(lines[i])
451         if match:
452             labelstringappendix = match.group(4)
453             labelstringappendix_line = i
454
455         # Remember the LatexType line
456         match = re_LatexType.match(lines[i])
457         if match:
458             latextype = string.lower(match.group(4))
459             latextype_line = i
460
461         # Remember the TocLevel line
462         match = re_TocLevel.match(lines[i])
463         if match:
464             toclevel = string.lower(match.group(4))
465
466         # Reset variables at the beginning of a style definition
467         match = re_Style.match(lines[i])
468         if match:
469             style = string.lower(match.group(4))
470             counter = ""
471             toclevel = ""
472             label = ""
473             space1 = ""
474             labelstring = ""
475             labelstringappendix = ""
476             labelstring_line = -1
477             labelstringappendix_line = -1
478             labeltype_line = -1
479             latextype = ""
480             latextype_line = -1
481
482         if re_End.match(lines[i]):
483
484             # Add a line "LatexType Bib_Environment" if LabelType is Bibliography
485             # (or change the existing LatexType)
486             if string.lower(label) == "bibliography":
487                 if (latextype_line < 0):
488                     lines.insert(i, "%sLatexType Bib_Environment" % space1)
489                     i += 1
490                 else:
491                     lines[latextype_line] = re_LatexType.sub(r'\1\2\3Bib_Environment', lines[latextype_line])
492
493             # Change "LabelType Static" to "LabelType Itemize" for itemize environments
494             if latextype == "item_environment" and string.lower(label) == "static":
495                 lines[labeltype_line] = re_LabelType.sub(r'\1\2\3Itemize', lines[labeltype_line])
496
497             # Change "LabelType Counter_EnumI" to "LabelType Enumerate" for enumerate environments
498             if latextype == "item_environment" and string.lower(label) == "counter_enumi":
499                 lines[labeltype_line] = re_LabelType.sub(r'\1\2\3Enumerate', lines[labeltype_line])
500                 # Don't add the LabelCounter line later
501                 counter = ""
502
503             # Replace
504             #
505             # LabelString "Chapter"
506             #
507             # with
508             #
509             # LabelString "Chapter \arabic{chapter}"
510             #
511             # if this style has a counter. Ditto for LabelStringAppendix.
512             # This emulates the hardcoded article style numbering of 1.3
513             #
514             if counter != "":
515                 if counters.has_key(style):
516                     if labelstring_line < 0:
517                         lines.insert(i, '%sLabelString "%s"' % (space1, counters[style]))
518                         i += 1
519                     else:
520                         new_labelstring = concatenate_label(labelstring, counters[style])
521                         lines[labelstring_line] = re_LabelString.sub(
522                                 r'\1\2\3%s' % new_labelstring.replace("\\", "\\\\"),
523                                 lines[labelstring_line])
524                 if appendixcounters.has_key(style):
525                     if labelstringappendix_line < 0:
526                         lines.insert(i, '%sLabelStringAppendix "%s"' % (space1, appendixcounters[style]))
527                         i += 1
528                     else:
529                         new_labelstring = concatenate_label(labelstring, appendixcounters[style])
530                         lines[labelstringappendix_line] = re_LabelStringAppendix.sub(
531                                 r'\1\2\3%s' % new_labelstring.replace("\\", "\\\\"),
532                                 lines[labelstringappendix_line])
533
534                 # Now we can safely add the LabelCounter line
535                 lines.insert(labeltype_line + 1, "%sLabelCounter %s" % (space1, counter))
536                 i += 1
537
538             # Add the TocLevel setting for sectioning styles
539             if toclevel == "" and toclevels.has_key(style) and maxcounter <= toclevels[style]:
540                 lines.insert(i, '%sTocLevel %d' % (space1, toclevels[style]))
541                 i += 1
542
543         i += 1
544
545     if usemodules:
546         i = formatline + 1
547         for mod in usemodules:
548             lines.insert(i, "UseModule " + mod)
549             i += 1
550
551     return format + 1
552
553
554 def main(argv):
555
556     # Open files
557     if len(argv) == 1:
558         input = sys.stdin
559         output = sys.stdout
560     elif len(argv) == 3:
561         input = open(argv[1], 'rb')
562         output = open(argv[2], 'wb')
563     else:
564         error(usage(argv[0]))
565
566     # Do the real work
567     lines = read(input)
568     format = 1
569     while (format < currentFormat):
570         format = convert(lines)
571     write(output, lines)
572
573     # Close files
574     if len(argv) == 3:
575         input.close()
576         output.close()
577
578     return 0
579
580
581 if __name__ == "__main__":
582     main(sys.argv)