]> git.lyx.org Git - lyx.git/blob - development/lyx2lyx.lyx
More lyx2lyx documentation.
[lyx.git] / development / lyx2lyx.lyx
1 #LyX 2.0.0svn created this file. For more info see http://www.lyx.org/
2 \lyxformat 405
3 \begin_document
4 \begin_header
5 \textclass article
6 \use_default_options true
7 \begin_modules
8 logicalmkup
9 \end_modules
10 \maintain_unincluded_children false
11 \language english
12 \inputencoding auto
13 \fontencoding global
14 \font_roman default
15 \font_sans default
16 \font_typewriter default
17 \font_default_family default
18 \use_xetex false
19 \font_sc false
20 \font_osf false
21 \font_sf_scale 100
22 \font_tt_scale 100
23
24 \graphics default
25 \default_output_format default
26 \output_sync 0
27 \bibtex_command default
28 \index_command default
29 \paperfontsize default
30 \spacing single
31 \use_hyperref false
32 \papersize default
33 \use_geometry false
34 \use_amsmath 1
35 \use_esint 1
36 \use_mhchem 1
37 \use_mathdots 1
38 \cite_engine basic
39 \use_bibtopic false
40 \use_indices false
41 \paperorientation portrait
42 \suppress_date false
43 \use_refstyle 1
44 \index Index
45 \shortcut idx
46 \color #008000
47 \end_index
48 \secnumdepth 3
49 \tocdepth 3
50 \paragraph_separation indent
51 \paragraph_indentation default
52 \quotes_language english
53 \papercolumns 1
54 \papersides 1
55 \paperpagestyle default
56 \tracking_changes false
57 \output_changes false
58 \html_math_output 0
59 \html_be_strict false
60 \end_header
61
62 \begin_body
63
64 \begin_layout Title
65 Programming lyx2lyx
66 \end_layout
67
68 \begin_layout Author
69 Richard Heck
70 \end_layout
71
72 \begin_layout Standard
73 Contained herein are some observations and suggestions about how to write
74  
75 \begin_inset Flex Code
76 status collapsed
77
78 \begin_layout Plain Layout
79 lyx2lyx
80 \end_layout
81
82 \end_inset
83
84  routines, including some thoughts about common pitfalls.
85 \end_layout
86
87 \begin_layout Section
88 The LyX_base Class
89 \end_layout
90
91 \begin_layout Standard
92 Conversion and reversion routines will always be defined as functions that
93  take an object of type 
94 \begin_inset Flex Code
95 status collapsed
96
97 \begin_layout Plain Layout
98 LyX_base
99 \end_layout
100
101 \end_inset
102
103  as argument.
104  This argument, conventionally called 
105 \begin_inset Flex Code
106 status collapsed
107
108 \begin_layout Plain Layout
109 document
110 \end_layout
111
112 \end_inset
113
114 , represents the LyX document being converted.
115  The 
116 \begin_inset Flex Code
117 status collapsed
118
119 \begin_layout Plain Layout
120 LyX_base
121 \end_layout
122
123 \end_inset
124
125  class is defined in the file 
126 \begin_inset Flex Code
127 status collapsed
128
129 \begin_layout Plain Layout
130 LyX.py
131 \end_layout
132
133 \end_inset
134
135 , and it has several properties and a number of methods.
136  
137 \end_layout
138
139 \begin_layout Standard
140 Some of the most important properties are:
141 \end_layout
142
143 \begin_layout Description
144 backend Either 
145 \begin_inset Flex Code
146 status collapsed
147
148 \begin_layout Plain Layout
149 linuxdoc
150 \end_layout
151
152 \end_inset
153
154
155 \begin_inset Flex Code
156 status collapsed
157
158 \begin_layout Plain Layout
159 docbook
160 \end_layout
161
162 \end_inset
163
164 , or 
165 \begin_inset Flex Code
166 status collapsed
167
168 \begin_layout Plain Layout
169 latex
170 \end_layout
171
172 \end_inset
173
174 , depending upon the document class
175 \end_layout
176
177 \begin_layout Description
178 textclass The layout file for this document, e.g., 
179 \begin_inset Flex Code
180 status collapsed
181
182 \begin_layout Plain Layout
183 article
184 \end_layout
185
186 \end_inset
187
188 .
189 \end_layout
190
191 \begin_layout Description
192 default_layout The default layout style for the class.
193 \begin_inset Newline newline
194 \end_inset
195
196 Note that this is all 
197 \begin_inset Flex Code
198 status collapsed
199
200 \begin_layout Plain Layout
201 lyx2lyx
202 \end_layout
203
204 \end_inset
205
206  knows about the layout.
207  It does not know what paragraph styles are available, for example, let
208  alone what their properties might be.
209  
210 \end_layout
211
212 \begin_layout Description
213 encoding The document encoding.
214 \end_layout
215
216 \begin_layout Description
217 language The document language.
218 \end_layout
219
220 \begin_layout Standard
221 These three represent the content of the document.
222  
223 \end_layout
224
225 \begin_layout Description
226 header The document header, meaning the lines that come before 
227 \begin_inset Flex Code
228 status collapsed
229
230 \begin_layout Plain Layout
231
232 \backslash
233 begin_body
234 \end_layout
235
236 \end_inset
237
238
239 \emph on
240 except
241 \emph default
242  for the LaTeX preamble.
243 \end_layout
244
245 \begin_layout Description
246 preamble The LaTeX preamble.
247 \end_layout
248
249 \begin_layout Description
250 body The document body.
251 \end_layout
252
253 \begin_layout Standard
254 All three of these are lists of strings.
255  The importance of this point will be discussed later.
256 \end_layout
257
258 \begin_layout Standard
259 Important methods include:
260 \end_layout
261
262 \begin_layout Description
263 warning Writes its argument to the console as a warning.
264  (Also takes an optional argument, the debug level, which can be used to
265  suppress output below a certain debug level, but this is rarely used.)
266 \end_layout
267
268 \begin_layout Description
269 error Writes the warning and exits, unless we are in try_hard mode, which
270  is set with a command-line option.
271  Rarely used in converter code, but I shall mention times it might be used
272  below.
273 \end_layout
274
275 \begin_layout Description
276 set_parameter Sets the value of a header parameter.
277  This needs to be a parameter already present in the header or nothing will
278  happen.
279 \end_layout
280
281 \begin_layout Description
282 set_textclass This writes the value of the 
283 \begin_inset Flex Code
284 status collapsed
285
286 \begin_layout Plain Layout
287 textclass
288 \end_layout
289
290 \end_inset
291
292  member variable to the header.
293  So, for example, one might have something like this in a reversion routine:
294 \end_layout
295
296 \begin_layout LyX-Code
297 if document.textclass = 'fancy_new_class':
298 \end_layout
299
300 \begin_layout LyX-Code
301   document.textclass = 'old_class'
302 \end_layout
303
304 \begin_layout LyX-Code
305   document.setclass()
306 \end_layout
307
308 \begin_layout Description
309 add_module Adds a LyX module to the list of modules to be loaded with the
310  document.
311 \end_layout
312
313 \begin_layout Description
314 get_module_list Returns the list of modules to be loaded.
315 \end_layout
316
317 \begin_layout Description
318 set_module_list Takes a list as argument and replaces the existing list
319  of modules.
320 \end_layout
321
322 \begin_layout Standard
323 There are some other methods, too, such as 
324 \begin_inset Flex Code
325 status collapsed
326
327 \begin_layout Plain Layout
328 read()
329 \end_layout
330
331 \end_inset
332
333 , but those are more for `internal' use.
334 \end_layout
335
336 \begin_layout Standard
337 It is extremely important to understand that 
338 \begin_inset Flex Code
339 status collapsed
340
341 \begin_layout Plain Layout
342 lyx2lyx
343 \end_layout
344
345 \end_inset
346
347  is 
348 \emph on
349 line-oriented
350 \emph default
351 .
352  That is, 
353 \begin_inset Flex Code
354 status collapsed
355
356 \begin_layout Plain Layout
357 lyx2lyx
358 \end_layout
359
360 \end_inset
361
362  represents the content of a LyX file---the header, preamble, and body---as
363  lists of lines.
364  It is critical that one maintain this structure when modifying the document.
365  Since Python is not type-safe, one can easily fail to do so if one is not
366  careful, and this will cause problems.
367 \end_layout
368
369 \begin_layout Standard
370 For example, one must absolutely never do anything like this:
371 \end_layout
372
373 \begin_layout LyX-Code
374 newstuff = '
375 \backslash
376
377 \backslash
378 begin_inset ERT
379 \backslash
380 n, status collapsed
381 \backslash
382 n
383 \backslash
384
385 \end_layout
386
387 \begin_layout LyX-Code
388   
389 \backslash
390
391 \backslash
392 begin_layout Plain Layout
393 \backslash
394 n
395 \backslash
396 nI am in ERT
397 \backslash
398 n
399 \backslash
400
401 \end_layout
402
403 \begin_layout LyX-Code
404   
405 \backslash
406
407 \backslash
408 end_layout
409 \backslash
410 n
411 \backslash
412 n
413 \backslash
414
415 \backslash
416 end_inset
417 \backslash
418 n
419 \backslash
420 n'
421 \end_layout
422
423 \begin_layout LyX-Code
424 document.body[i:i] = newstuff
425 \end_layout
426
427 \begin_layout Standard
428 This is supposed to insert an InsetERT at line i of the document, and in
429  a sense it will.
430  But it has the potential to confuse 
431 \begin_inset Flex Code
432 status collapsed
433
434 \begin_layout Plain Layout
435 lyx2lyx
436 \end_layout
437
438 \end_inset
439
440  very badly.
441  Suppose at some later point in the conversion we want to change 
442 \begin_inset Flex Code
443 status collapsed
444
445 \begin_layout Plain Layout
446
447 \backslash
448 begin_layout Plain Layout
449 \end_layout
450
451 \end_inset
452
453  to 
454 \begin_inset Flex Code
455 status collapsed
456
457 \begin_layout Plain Layout
458
459 \backslash
460 begin_layout PlainLayout
461 \end_layout
462
463 \end_inset
464
465 .
466  (In fact, this is actually done.) Then we are going to have code that looks
467  like:
468 \end_layout
469
470 \begin_layout LyX-Code
471 i = find_token(document.body, '
472 \backslash
473
474 \backslash
475 begin_layout Plain Layout', i)
476 \end_layout
477
478 \begin_layout Standard
479 This will not find the occurence of 
480 \begin_inset Flex Code
481 status collapsed
482
483 \begin_layout Plain Layout
484
485 \backslash
486 begin_layout Plain Layout
487 \end_layout
488
489 \end_inset
490
491  that we just inserted.
492  This is because 
493 \begin_inset Flex Code
494 status collapsed
495
496 \begin_layout Plain Layout
497 find_token
498 \end_layout
499
500 \end_inset
501
502  looks for things at the beginning of lines, and 
503 \begin_inset Flex Code
504 status collapsed
505
506 \begin_layout Plain Layout
507
508 \backslash
509 begin_layout Plain Layout
510 \end_layout
511
512 \end_inset
513
514  is not at the beginning of the long string 
515 \begin_inset Flex Code
516 status collapsed
517
518 \begin_layout Plain Layout
519 newstuff
520 \end_layout
521
522 \end_inset
523
524 .
525  It follows a newline, to be sure, but that is different.
526  So what one should do instead is:
527 \end_layout
528
529 \begin_layout LyX-Code
530 newstuff = ['
531 \backslash
532
533 \backslash
534 begin_inset ERT', 'status collapsed',
535 \end_layout
536
537 \begin_layout LyX-Code
538   '
539 \backslash
540
541 \backslash
542 begin_layout Plain Layout', '', 'I am in ERT',
543 \end_layout
544
545 \begin_layout LyX-Code
546   '
547 \backslash
548
549 \backslash
550 end_layout', '', '
551 \backslash
552
553 \backslash
554 end_inset', '']
555 \end_layout
556
557 \begin_layout LyX-Code
558 document.body[i:i] = newstuff
559 \end_layout
560
561 \begin_layout Standard
562 That inserts a bunch of lines.
563 \end_layout
564
565 \begin_layout Section
566 Utility Functions
567 \end_layout
568
569 \begin_layout Standard
570 There are two Python modules that provide commonly used functions for parsing
571  the file and for modifying it.
572  The parsing functions are in 
573 \begin_inset Flex Code
574 status collapsed
575
576 \begin_layout Plain Layout
577 parser_tools
578 \end_layout
579
580 \end_inset
581
582  and the modifying functions are in 
583 \begin_inset Flex Code
584 status collapsed
585
586 \begin_layout Plain Layout
587 lyx2lyx_tools
588 \end_layout
589
590 \end_inset
591
592 .
593  Both of these files have extensive documentation at the beginning that
594  lists the functions that are available and explains what they do.
595  Those writing 
596 \begin_inset Flex Code
597 status collapsed
598
599 \begin_layout Plain Layout
600 lyx2lyx
601 \end_layout
602
603 \end_inset
604
605  code should familiarize themselves with these functions.
606 \end_layout
607
608 \begin_layout Section
609 Common Code Structures and Pitfalls
610 \end_layout
611
612 \begin_layout Standard
613 As said, reversion routines receive an argument of type 
614 \begin_inset Flex Code
615 status collapsed
616
617 \begin_layout Plain Layout
618 LyX_base
619 \end_layout
620
621 \end_inset
622
623 , and they almost always have one of two sorts of structure, depending upon
624  whether it is the header or the body that one is modifying.
625  
626 \end_layout
627
628 \begin_layout Standard
629 If it is the header, then the routine can be quite simple, because items
630  usually occur in the header only once.
631  So the structure will typically be:
632 \end_layout
633
634 \begin_layout LyX-Code
635 def revert_header_stuff(document):
636 \end_layout
637
638 \begin_layout LyX-Code
639   i = find_token(document.header, '
640 \backslash
641 use_xetex', 0)
642 \end_layout
643
644 \begin_layout LyX-Code
645   if i == -1:
646 \end_layout
647
648 \begin_layout LyX-Code
649     # not found
650 \end_layout
651
652 \begin_layout LyX-Code
653     document.warning('Hmm')
654 \end_layout
655
656 \begin_layout LyX-Code
657   else:
658 \end_layout
659
660 \begin_layout LyX-Code
661     # do something with line i
662 \end_layout
663
664 \begin_layout Standard
665 How complex such routines become depends of course on the case.
666 \end_layout
667
668 \begin_layout Standard
669 If the changes will be made to the body, then the routine usually has this
670  sort of structure:
671 \end_layout
672
673 \begin_layout LyX-Code
674 def revert_something(document):
675 \end_layout
676
677 \begin_layout LyX-Code
678   i = 0 
679 \end_layout
680
681 \begin_layout LyX-Code
682   while True:
683 \end_layout
684
685 \begin_layout LyX-Code
686     i = find_token(document.body, '
687 \backslash
688 begin_inset Funky', i)
689 \end_layout
690
691 \begin_layout LyX-Code
692     if i == -1:
693 \end_layout
694
695 \begin_layout LyX-Code
696       break
697 \end_layout
698
699 \begin_layout LyX-Code
700     # do something ...
701 \end_layout
702
703 \begin_layout LyX-Code
704     i += 1 # or other appropriate reset
705 \end_layout
706
707 \begin_layout Standard
708 In some cases, one may need both sorts of routines together.
709 \end_layout
710
711 \begin_layout Subsection
712 Where Am I?
713 \end_layout
714
715 \begin_layout Standard
716 In the course of doing something in this last case, one will often want
717  to look for content in the inset or layout (or whatever) that one has found.
718  Suppose, for example, that one is trying to remove the option 
719 \begin_inset Flex Code
720 status collapsed
721
722 \begin_layout Plain Layout
723 newoption
724 \end_layout
725
726 \end_inset
727
728  from Funky insets.
729  Then one might think to use code like this in place of the comment.
730 \end_layout
731
732 \begin_layout LyX-Code
733     j = find_token(document.body, 'newoption', i)
734 \end_layout
735
736 \begin_layout LyX-Code
737     if j == -1:
738 \end_layout
739
740 \begin_layout LyX-Code
741       document.warning('UnFunky inset!')
742 \end_layout
743
744 \begin_layout LyX-Code
745       break
746 \end_layout
747
748 \begin_layout LyX-Code
749     del document.body[j]
750 \end_layout
751
752 \begin_layout Standard
753 This is terrible code, for several reasons.
754 \end_layout
755
756 \begin_layout Standard
757 First, it is wrong to break on the error here.
758  The LyX file is corrupted, yes.
759  But that does not necessarily mean that it is unusable---LyX is pretty
760  forgiving---and just because we have failed to find this one option does
761  not mean we should give up.
762  We at least need to try to remove the option from other Funky insets.
763  So the right think to do here is instead:
764 \end_layout
765
766 \begin_layout LyX-Code
767     j = find_token(document.body, 'newoption', i)
768 \end_layout
769
770 \begin_layout LyX-Code
771     if j == -1:
772 \end_layout
773
774 \begin_layout LyX-Code
775       document.warning('UnFunky inset!')
776 \end_layout
777
778 \begin_layout LyX-Code
779       i += 1
780 \end_layout
781
782 \begin_layout LyX-Code
783       continue
784 \end_layout
785
786 \begin_layout LyX-Code
787     del document.body[j]
788 \end_layout
789
790 \begin_layout --Separator--
791
792 \end_layout
793
794 \begin_layout Standard
795 The second problem is that we have no way of knowing that the line we find
796  here is actually a line containing an option for the Funky inset on line
797  i.
798  Suppose this inset is missing its 
799 \begin_inset Flex Code
800 status collapsed
801
802 \begin_layout Plain Layout
803 newoption
804 \end_layout
805
806 \end_inset
807
808 .
809  There might be a later one that has a 
810 \begin_inset Flex Code
811 status collapsed
812
813 \begin_layout Plain Layout
814 newoption
815 \end_layout
816
817 \end_inset
818
819 .
820  Then 
821 \begin_inset Flex Code
822 status collapsed
823
824 \begin_layout Plain Layout
825 find_token
826 \end_layout
827
828 \end_inset
829
830  will find the 
831 \begin_inset Flex Code
832 status collapsed
833
834 \begin_layout Plain Layout
835 newoption
836 \end_layout
837
838 \end_inset
839
840  for the later one.
841  If we're just removing it, that might not be so bad.
842  But if we were doing something more extensive, it could be.
843  So, at the very least, we need to find the end of this inset and make sure
844  the option comes before that:
845 \end_layout
846
847 \begin_layout LyX-Code
848     k = find_end_of_inset(document.body, i)
849 \end_layout
850
851 \begin_layout LyX-Code
852     if k == -1:
853 \end_layout
854
855 \begin_layout LyX-Code
856       document.warning('No end to Funky inset!')
857 \end_layout
858
859 \begin_layout LyX-Code
860       i += 1
861 \end_layout
862
863 \begin_layout LyX-Code
864       continue
865 \end_layout
866
867 \begin_layout LyX-Code
868     j = find_token(document.body, 'newoption', i, k)
869 \end_layout
870
871 \begin_layout LyX-Code
872     if j == -1:
873 \end_layout
874
875 \begin_layout LyX-Code
876       document.warning('UnFunky inset!')
877 \end_layout
878
879 \begin_layout LyX-Code
880       i = k
881 \end_layout
882
883 \begin_layout LyX-Code
884       continue
885 \end_layout
886
887 \begin_layout LyX-Code
888     del document.body[j]
889 \end_layout
890
891 \begin_layout Standard
892 Note that we can reset 
893 \begin_inset Flex Code
894 status collapsed
895
896 \begin_layout Plain Layout
897 i
898 \end_layout
899
900 \end_inset
901
902  to 
903 \begin_inset Flex Code
904 status collapsed
905
906 \begin_layout Plain Layout
907 k
908 \end_layout
909
910 \end_inset
911
912  here only if we know that no Funky inset can occur inside a Funky inset.
913  Otherwise, it should have been 
914 \begin_inset Flex Code
915 status collapsed
916
917 \begin_layout Plain Layout
918 i += 1
919 \end_layout
920
921 \end_inset
922
923 , again.
924 \end_layout
925
926 \begin_layout Standard
927 By the way, although it is not often done, there are definitely cases where
928  we should use 
929 \begin_inset Flex Code
930 status collapsed
931
932 \begin_layout Plain Layout
933 document.error()
934 \end_layout
935
936 \end_inset
937
938  rather than 
939 \begin_inset Flex Code
940 status collapsed
941
942 \begin_layout Plain Layout
943 document.warning()
944 \end_layout
945
946 \end_inset
947
948 .
949  In particular, suppose that we are actually planning to remove Funky insets
950  altogether, or to replace them with ERT.
951  Then, if the file is so corrupt that we cannot find the end of the inset,
952  we cannot do this work, so we 
953 \emph on
954 know
955 \emph default
956  we cannot produce a LyX file an older version will be able to load.
957  In that case, it seems right just to abort, and if the user wants to 
958 \begin_inset Quotes eld
959 \end_inset
960
961 try hard
962 \begin_inset Quotes erd
963 \end_inset
964
965 , she can run 
966 \begin_inset Flex Code
967 status collapsed
968
969 \begin_layout Plain Layout
970 lyx2lyx
971 \end_layout
972
973 \end_inset
974
975  from the command line and pass the appropriate opttion.
976 \end_layout
977
978 \begin_layout Standard
979 The routine above may still fail to do the right thing, however.
980  Suppose again that 
981 \begin_inset Flex Code
982 status collapsed
983
984 \begin_layout Plain Layout
985 newoption
986 \end_layout
987
988 \end_inset
989
990  is missing, but, due to a strange typo, one of the lines of text in the
991  inset happens to begin with 
992 \begin_inset Quotes eld
993 \end_inset
994
995 newoption
996 \begin_inset Quotes erd
997 \end_inset
998
999 .
1000  Then 
1001 \begin_inset Flex Code
1002 status collapsed
1003
1004 \begin_layout Plain Layout
1005 find_token
1006 \end_layout
1007
1008 \end_inset
1009
1010  will find that line and we will remove text from the document! This will
1011  not generally happen with command insets, but it can easily happen with
1012  text insets.
1013  In that case, one has to make sure the option comes before the content
1014  of the inset, and to do that, we must find the first layout in the inset,
1015  thus:
1016 \end_layout
1017
1018 \begin_layout LyX-Code
1019     k = find_end_of_inset(document.body, i)
1020 \end_layout
1021
1022 \begin_layout LyX-Code
1023     if k == -1:
1024 \end_layout
1025
1026 \begin_layout LyX-Code
1027       document.warning('No end to Funky inset!')
1028 \end_layout
1029
1030 \begin_layout LyX-Code
1031       i += 1
1032 \end_layout
1033
1034 \begin_layout LyX-Code
1035       continue
1036 \end_layout
1037
1038 \begin_layout LyX-Code
1039     m = find_token(document.body, '
1040 \backslash
1041
1042 \backslash
1043 begin_layout', i, k)
1044 \end_layout
1045
1046 \begin_layout LyX-Code
1047     if m == -1:
1048 \end_layout
1049
1050 \begin_layout LyX-Code
1051       document.warning('No layout! Hope for the best!')
1052 \end_layout
1053
1054 \begin_layout LyX-Code
1055       m = k
1056 \end_layout
1057
1058 \begin_layout LyX-Code
1059     j = find_token(document.body, 'newoption', i, m)
1060 \end_layout
1061
1062 \begin_layout LyX-Code
1063     if j == -1:
1064 \end_layout
1065
1066 \begin_layout LyX-Code
1067       document.warning('UnFunky inset!')
1068 \end_layout
1069
1070 \begin_layout LyX-Code
1071       i = k
1072 \end_layout
1073
1074 \begin_layout LyX-Code
1075       continue
1076 \end_layout
1077
1078 \begin_layout LyX-Code
1079     del document.body[j]
1080 \end_layout
1081
1082 \begin_layout Standard
1083 Note the response here to 
1084 \begin_inset Flex Code
1085 status collapsed
1086
1087 \begin_layout Plain Layout
1088 m != 1
1089 \end_layout
1090
1091 \end_inset
1092
1093 .
1094  There is not necessarily a need to give up trying to remove the option.
1095  What the right response is will depend upon the specific case.
1096 \end_layout
1097
1098 \begin_layout Standard
1099 The last problem, though it would be unlikely in this case, is that we might
1100  find not 
1101 \begin_inset Flex Code
1102 status collapsed
1103
1104 \begin_layout Plain Layout
1105 newoption
1106 \end_layout
1107
1108 \end_inset
1109
1110  but 
1111 \begin_inset Flex Code
1112 status collapsed
1113
1114 \begin_layout Plain Layout
1115 newoptions
1116 \end_layout
1117
1118 \end_inset
1119
1120 , because 
1121 \begin_inset Flex Code
1122 status collapsed
1123
1124 \begin_layout Plain Layout
1125 find_token
1126 \end_layout
1127
1128 \end_inset
1129
1130  only looks to see if the beginning of the line matches.
1131  Typically, then, what one really wants is 
1132 \begin_inset Flex Code
1133 status collapsed
1134
1135 \begin_layout Plain Layout
1136 find_token_exact
1137 \end_layout
1138
1139 \end_inset
1140
1141 , which makes sure that we are finding a complete token.
1142 \begin_inset Foot
1143 status collapsed
1144
1145 \begin_layout Plain Layout
1146 In the implementation in LyX 2.0svn and earlier, this function also ignores
1147  other differences in whitespace.
1148  This needs to be fixed and will be once 2.0 is out.
1149 \end_layout
1150
1151 \end_inset
1152
1153  So what we really want, for the entire function, is:
1154 \end_layout
1155
1156 \begin_layout LyX-Code
1157  def revert_something(document):
1158 \end_layout
1159
1160 \begin_layout LyX-Code
1161   i = 0 
1162 \end_layout
1163
1164 \begin_layout LyX-Code
1165   while True:
1166 \end_layout
1167
1168 \begin_layout LyX-Code
1169     i = find_token(document.body, '
1170 \backslash
1171 begin_inset Funky', i)
1172 \end_layout
1173
1174 \begin_layout LyX-Code
1175     if i == -1:
1176 \end_layout
1177
1178 \begin_layout LyX-Code
1179       break
1180 \end_layout
1181
1182 \begin_layout LyX-Code
1183     k = find_end_of_inset(document.body, i)
1184 \end_layout
1185
1186 \begin_layout LyX-Code
1187     if k == -1:
1188 \end_layout
1189
1190 \begin_layout LyX-Code
1191       document.warning('No end to Funky inset!')
1192 \end_layout
1193
1194 \begin_layout LyX-Code
1195       i += 1
1196 \end_layout
1197
1198 \begin_layout LyX-Code
1199       continue
1200 \end_layout
1201
1202 \begin_layout LyX-Code
1203     m = find_token(document.body, '
1204 \backslash
1205
1206 \backslash
1207 begin_layout', i, k)
1208 \end_layout
1209
1210 \begin_layout LyX-Code
1211     if m == -1:
1212 \end_layout
1213
1214 \begin_layout LyX-Code
1215       document.warning('No layout! Hope for the best!')
1216 \end_layout
1217
1218 \begin_layout LyX-Code
1219       m = k
1220 \end_layout
1221
1222 \begin_layout LyX-Code
1223     j = find_token(document.body, 'newoption', i, m)
1224 \end_layout
1225
1226 \begin_layout LyX-Code
1227     if j == -1:
1228 \end_layout
1229
1230 \begin_layout LyX-Code
1231       document.warning('UnFunky inset!')
1232 \end_layout
1233
1234 \begin_layout LyX-Code
1235       i = k
1236 \end_layout
1237
1238 \begin_layout LyX-Code
1239       continue
1240 \end_layout
1241
1242 \begin_layout LyX-Code
1243     del document.body[j]
1244 \end_layout
1245
1246 \begin_layout LyX-Code
1247     i += 1
1248 \end_layout
1249
1250 \begin_layout Standard
1251 This is much more complicated than what we had before, but it is much more
1252  reliable.
1253  (Probably, much of this logic should be wrapped in a function.)
1254 \end_layout
1255
1256 \begin_layout Subsection
1257 Comments and Coding Style
1258 \end_layout
1259
1260 \begin_layout Standard
1261 I've written the previous routine in the style in which most 
1262 \begin_inset Flex Code
1263 status collapsed
1264
1265 \begin_layout Plain Layout
1266 lyx2lyx
1267 \end_layout
1268
1269 \end_inset
1270
1271  routines have generally been written: There are no comments, and all variable
1272  names are completely uninformative.
1273  For all the usual reasons, this is bad.
1274  It will take us a bit of effort to change this practice, but it is worth
1275  doing.
1276  The people who have to fix 
1277 \begin_inset Flex Code
1278 status collapsed
1279
1280 \begin_layout Plain Layout
1281 lyx2lyx
1282 \end_layout
1283
1284 \end_inset
1285
1286  bugs are not always the ones who wrote the code, and even the ones who
1287  did may not remember what it was supposed to do.
1288  So let's write something like this:
1289 \end_layout
1290
1291 \begin_layout LyX-Code
1292  def revert_something(document):
1293 \end_layout
1294
1295 \begin_layout LyX-Code
1296   i = 0 
1297 \end_layout
1298
1299 \begin_layout LyX-Code
1300   while True:
1301 \end_layout
1302
1303 \begin_layout LyX-Code
1304     i = find_token(document.body, '
1305 \backslash
1306 begin_inset Funky', i)
1307 \end_layout
1308
1309 \begin_layout LyX-Code
1310     if i == -1:
1311 \end_layout
1312
1313 \begin_layout LyX-Code
1314       break
1315 \end_layout
1316
1317 \begin_layout LyX-Code
1318     endins = find_end_of_inset(document.body, i)
1319 \end_layout
1320
1321 \begin_layout LyX-Code
1322     if endins == -1:
1323 \end_layout
1324
1325 \begin_layout LyX-Code
1326       document.warning('No end to Funky inset!')
1327 \end_layout
1328
1329 \begin_layout LyX-Code
1330       i += 1
1331 \end_layout
1332
1333 \begin_layout LyX-Code
1334       continue
1335 \end_layout
1336
1337 \begin_layout LyX-Code
1338     blay = find_token(document.body, '
1339 \backslash
1340
1341 \backslash
1342 begin_layout', i, endins)
1343 \end_layout
1344
1345 \begin_layout LyX-Code
1346     if blay == -1:
1347 \end_layout
1348
1349 \begin_layout LyX-Code
1350       document.warning('No layout! Hope for the best!')
1351 \end_layout
1352
1353 \begin_layout LyX-Code
1354       blay = endins
1355 \end_layout
1356
1357 \begin_layout LyX-Code
1358     optline = find_token(document.body, 'newoption', i, blay)
1359 \end_layout
1360
1361 \begin_layout LyX-Code
1362     if optline == -1:
1363 \end_layout
1364
1365 \begin_layout LyX-Code
1366       document.warning('UnFunky inset!')
1367 \end_layout
1368
1369 \begin_layout LyX-Code
1370       i = endins
1371 \end_layout
1372
1373 \begin_layout LyX-Code
1374       continue
1375 \end_layout
1376
1377 \begin_layout LyX-Code
1378     del document.body[optline]
1379 \end_layout
1380
1381 \begin_layout LyX-Code
1382     i += 1
1383 \end_layout
1384
1385 \begin_layout Standard
1386 No comments really needed in that one, I suppose.
1387 \end_layout
1388
1389 \begin_layout Subsection
1390 Magic Numbers
1391 \end_layout
1392
1393 \begin_layout Standard
1394 Another common error is relying too much on assumptions about the structure
1395  of a valid LyX file.
1396  Here is an example.
1397  Suppose we want to add a 
1398 \begin_inset Flex Code
1399 status collapsed
1400
1401 \begin_layout Plain Layout
1402
1403 \backslash
1404 noindent
1405 \end_layout
1406
1407 \end_inset
1408
1409  flag to the first paragraph of any Funky inset.
1410  Then it is tempting to do something like this:
1411 \end_layout
1412
1413 \begin_layout LyX-Code
1414 def add_noindent(document):
1415 \end_layout
1416
1417 \begin_layout LyX-Code
1418   i = 0 
1419 \end_layout
1420
1421 \begin_layout LyX-Code
1422   while True:
1423 \end_layout
1424
1425 \begin_layout LyX-Code
1426     i = find_token(document.body, '
1427 \backslash
1428 begin_inset Funky', i)
1429 \end_layout
1430
1431 \begin_layout LyX-Code
1432     if i == -1:
1433 \end_layout
1434
1435 \begin_layout LyX-Code
1436       break
1437 \end_layout
1438
1439 \begin_layout LyX-Code
1440     document.body.insert(i+4, '
1441 \backslash
1442
1443 \backslash
1444 noindent')
1445 \end_layout
1446
1447 \begin_layout LyX-Code
1448     i += 4
1449 \end_layout
1450
1451 \begin_layout Standard
1452 Experienced programmers will know that this is bad.
1453  Where does the magic number 4 come from? The answer is that it comes from
1454  examining the LyX file.
1455  One looks at a typical file containing a Funky inset and sees:
1456 \end_layout
1457
1458 \begin_layout LyX-Code
1459
1460 \backslash
1461 begin_inset Funky
1462 \end_layout
1463
1464 \begin_layout LyX-Code
1465 status collapsed
1466 \end_layout
1467
1468 \begin_layout LyX-Code
1469  
1470 \end_layout
1471
1472 \begin_layout LyX-Code
1473
1474 \backslash
1475 begin_layout Standard
1476 \end_layout
1477
1478 \begin_layout LyX-Code
1479 here is some content
1480 \end_layout
1481
1482 \begin_layout LyX-Code
1483
1484 \backslash
1485 end_layout
1486 \end_layout
1487
1488 \begin_layout LyX-Code
1489  
1490 \end_layout
1491
1492 \begin_layout LyX-Code
1493
1494 \backslash
1495 end_inset
1496 \end_layout
1497
1498 \begin_layout Standard
1499 So 
1500 \begin_inset Flex Code
1501 status collapsed
1502
1503 \begin_layout Plain Layout
1504
1505 \backslash
1506 noindent
1507 \end_layout
1508
1509 \end_inset
1510
1511  goes four lines after the inset, as we might confirm by adding it in LyX
1512  and looking at that file.
1513 \end_layout
1514
1515 \begin_layout Standard
1516 Much of the time, this will work, but there is no guarantee that it will
1517  be correct, and the same goes for any assumption of this sort.
1518  It is not enough even to study the LyX source code and make very sure that
1519  the output routine produces what one thinks it does.
1520  The problem is that the empty line before 
1521 \begin_inset Flex Code
1522 status collapsed
1523
1524 \begin_layout Plain Layout
1525
1526 \backslash
1527 begin_layout
1528 \end_layout
1529
1530 \end_inset
1531
1532  could easily disappear, without any change to the semantics.
1533  Or another line could appear.
1534  There are several reasons for this.
1535 \end_layout
1536
1537 \begin_layout Standard
1538 First, looking at the source code of the current version of LyX tells you
1539  nothing about how the file might have been created by some other version.
1540  Maybe we get tired of blank lines and decide to remove them.
1541  This is not going to be accounted for in some reversion routine in 
1542 \begin_inset Flex Code
1543 status collapsed
1544
1545 \begin_layout Plain Layout
1546 lyx2lyx
1547 \end_layout
1548
1549 \end_inset
1550
1551 .
1552  The semantics of the file matters, and LyX's ability to read it matters.
1553  Blank lines here and there do not matter.
1554 \end_layout
1555
1556 \begin_layout Standard
1557 Second, LyX files are not always produced by LyX.
1558  Some of them are produced by external scripts (sed, perl, etc) that people
1559  write to do search and replace operations that are not possible inside
1560  LyX (and this will still be true once advanced search and replace is available).
1561  Such files may end up having slightly different structures than are usual,
1562  and yet be perfectly good files.
1563 \end_layout
1564
1565 \begin_layout Standard
1566 Third, and most importantly, the file you are modifying has almost certainly
1567  been through several other conversion routines before it gets to yours.
1568  A quick look at some of these routines will make it very clear how difficult
1569  it is to get all the blank lines in the right places, and people rarely
1570  check for this: They check to make sure the file opens correctly and that
1571  its output is right, but who cares how many blank lines there are? Again,
1572  it is the semantics that matters, not the fine details of file structure.
1573 \end_layout
1574
1575 \begin_layout Standard
1576 Or consider this possibility: Someone else wrote a routine to remove 
1577 \begin_inset Flex Code
1578 status collapsed
1579
1580 \begin_layout Plain Layout
1581 newoption
1582 \end_layout
1583
1584 \end_inset
1585
1586 , but, since they failed to read this document, their routine has all the
1587  bugs we discussed before.
1588  As a result, 
1589 \begin_inset Flex Code
1590 status collapsed
1591
1592 \begin_layout Plain Layout
1593 newoption
1594 \end_layout
1595
1596 \end_inset
1597
1598  is still there in one of the Funky insets in the document, now that it
1599  has gotten to your routine.
1600  So what you actually have is:
1601 \end_layout
1602
1603 \begin_layout LyX-Code
1604
1605 \backslash
1606 begin_inset Funky
1607 \end_layout
1608
1609 \begin_layout LyX-Code
1610 status collapsed
1611 \end_layout
1612
1613 \begin_layout LyX-Code
1614 newoption false
1615 \end_layout
1616
1617 \begin_layout LyX-Code
1618  
1619 \end_layout
1620
1621 \begin_layout LyX-Code
1622
1623 \backslash
1624 begin_layout Standard
1625 \end_layout
1626
1627 \begin_layout LyX-Code
1628 here is some content
1629 \end_layout
1630
1631 \begin_layout LyX-Code
1632
1633 \backslash
1634 end_layout
1635 \end_layout
1636
1637 \begin_layout LyX-Code
1638  
1639 \end_layout
1640
1641 \begin_layout LyX-Code
1642
1643 \backslash
1644 end_inset
1645 \end_layout
1646
1647 \begin_layout Standard
1648 This is not a valid LyX document of the format on which you are operating.
1649  But surely you do not really want to produce this:
1650 \end_layout
1651
1652 \begin_layout LyX-Code
1653
1654 \backslash
1655 begin_inset Funky
1656 \end_layout
1657
1658 \begin_layout LyX-Code
1659 status collapsed
1660 \end_layout
1661
1662 \begin_layout LyX-Code
1663 newoption false
1664 \end_layout
1665
1666 \begin_layout LyX-Code
1667  
1668 \end_layout
1669
1670 \begin_layout LyX-Code
1671
1672 \backslash
1673 noindent
1674 \end_layout
1675
1676 \begin_layout LyX-Code
1677
1678 \backslash
1679 begin_layout Standard
1680 \end_layout
1681
1682 \begin_layout LyX-Code
1683 here is some content
1684 \end_layout
1685
1686 \begin_layout LyX-Code
1687
1688 \backslash
1689 end_layout
1690 \end_layout
1691
1692 \begin_layout LyX-Code
1693  
1694 \end_layout
1695
1696 \begin_layout LyX-Code
1697
1698 \backslash
1699 end_inset
1700 \end_layout
1701
1702 \begin_layout Standard
1703 If you do, you will have made matters worse, and also failed to unindent
1704  the paragraph.
1705  The file will still open, probably, though with warnings.
1706  
1707 \end_layout
1708
1709 \begin_layout Standard
1710 But things can (and do) get much worse.
1711  Suppose you had meant, for some reason, to change the layout, whatever
1712  it was, to 
1713 \begin_inset Flex Code
1714 status collapsed
1715
1716 \begin_layout Plain Layout
1717 Plain Layout
1718 \end_layout
1719
1720 \end_inset
1721
1722 .
1723  So you do:
1724 \end_layout
1725
1726 \begin_layout LyX-Code
1727 def make_funky_plain(document):
1728 \end_layout
1729
1730 \begin_layout LyX-Code
1731   i = 0 
1732 \end_layout
1733
1734 \begin_layout LyX-Code
1735   while True:
1736 \end_layout
1737
1738 \begin_layout LyX-Code
1739     i = find_token(document.body, '
1740 \backslash
1741 begin_inset Funky', i)
1742 \end_layout
1743
1744 \begin_layout LyX-Code
1745     if i == -1:
1746 \end_layout
1747
1748 \begin_layout LyX-Code
1749       break
1750 \end_layout
1751
1752 \begin_layout LyX-Code
1753     document.body[i+4] = '
1754 \backslash
1755 begin_layout Plain Layout'
1756 \end_layout
1757
1758 \begin_layout LyX-Code
1759     i += 4
1760 \end_layout
1761
1762 \begin_layout Standard
1763 Now you've produced this:
1764 \end_layout
1765
1766 \begin_layout LyX-Code
1767
1768 \backslash
1769 begin_inset Funky
1770 \end_layout
1771
1772 \begin_layout LyX-Code
1773 status collapsed
1774 \end_layout
1775
1776 \begin_layout LyX-Code
1777 newoption false
1778 \end_layout
1779
1780 \begin_layout LyX-Code
1781
1782 \backslash
1783 begin_layout Plain Layout
1784 \end_layout
1785
1786 \begin_layout LyX-Code
1787
1788 \backslash
1789 begin_layout Standard
1790 \end_layout
1791
1792 \begin_layout LyX-Code
1793 here is some content
1794 \end_layout
1795
1796 \begin_layout LyX-Code
1797
1798 \backslash
1799 end_layout
1800 \end_layout
1801
1802 \begin_layout LyX-Code
1803  
1804 \end_layout
1805
1806 \begin_layout LyX-Code
1807
1808 \backslash
1809 end_inset
1810 \end_layout
1811
1812 \begin_layout Standard
1813 LyX will abort the parse when it hits 
1814 \begin_inset Flex Code
1815 status collapsed
1816
1817 \begin_layout Plain Layout
1818
1819 \backslash
1820 end_inset
1821 \end_layout
1822
1823 \end_inset
1824
1825 , complaining about a missing 
1826 \begin_inset Flex Code
1827 status collapsed
1828
1829 \begin_layout Plain Layout
1830
1831 \backslash
1832 end_layout
1833 \end_layout
1834
1835 \end_inset
1836
1837 .
1838 \end_layout
1839
1840 \begin_layout Standard
1841 The solution is very simple:
1842 \end_layout
1843
1844 \begin_layout LyX-Code
1845 def make_funky_plain(document):
1846 \end_layout
1847
1848 \begin_layout LyX-Code
1849   i = 0 
1850 \end_layout
1851
1852 \begin_layout LyX-Code
1853   while True:
1854 \end_layout
1855
1856 \begin_layout LyX-Code
1857     i = find_token(document.body, '
1858 \backslash
1859 begin_inset Funky', i)
1860 \end_layout
1861
1862 \begin_layout LyX-Code
1863     if i == -1:
1864 \end_layout
1865
1866 \begin_layout LyX-Code
1867       break
1868 \end_layout
1869
1870 \begin_layout LyX-Code
1871     endins = find_end_of_inset(document.body, i)
1872 \end_layout
1873
1874 \begin_layout LyX-Code
1875     if endins == -1:
1876 \end_layout
1877
1878 \begin_layout LyX-Code
1879       ...
1880 \end_layout
1881
1882 \begin_layout LyX-Code
1883     lay = find_token(document.body, '
1884 \backslash
1885 begin_layout', i, endins)
1886 \end_layout
1887
1888 \begin_layout LyX-Code
1889     if lay == -1:
1890 \end_layout
1891
1892 \begin_layout LyX-Code
1893       ...
1894 \end_layout
1895
1896 \begin_layout LyX-Code
1897     document.body[lay] = '
1898 \backslash
1899 begin_layout Plain Layout'
1900 \end_layout
1901
1902 \begin_layout LyX-Code
1903     i = endins
1904 \end_layout
1905
1906 \begin_layout Standard
1907 Again, a bit more complex, but reliable.
1908 \end_layout
1909
1910 \end_body
1911 \end_document