]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
Initial Qt4 support for view-source feature (r13610), from Bo Peng (ben.bob@gmail...
[lyx.git] / src / paragraph_pimpl.C
1 /**
2  * \file paragraph_pimpl.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author John Levon
9  * \author André Pönitz
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "paragraph_pimpl.h"
17 #include "paragraph.h"
18
19 #include "bufferparams.h"
20 #include "debug.h"
21 #include "encoding.h"
22 #include "language.h"
23 #include "LaTeXFeatures.h"
24 #include "LColor.h"
25 #include "lyxlength.h"
26 #include "lyxrc.h"
27 #include "outputparams.h"
28 #include "texrow.h"
29
30 #include <boost/next_prior.hpp>
31
32 using lyx::pos_type;
33
34 using std::endl;
35 using std::upper_bound;
36 using std::lower_bound;
37 using std::string;
38 using std::ostream;
39
40
41 // Initialization of the counter for the paragraph id's,
42 unsigned int Paragraph::Pimpl::paragraph_id = 0;
43
44 namespace {
45
46 struct special_phrase {
47         string phrase;
48         string macro;
49         bool builtin;
50 };
51
52 special_phrase special_phrases[] = {
53         { "LyX", "\\LyX{}", false },
54         { "TeX", "\\TeX{}", true },
55         { "LaTeX2e", "\\LaTeXe{}", true },
56         { "LaTeX", "\\LaTeX{}", true },
57 };
58
59 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
60
61 } // namespace anon
62
63
64 Paragraph::Pimpl::Pimpl(Paragraph * owner)
65         : owner_(owner)
66 {
67         inset_owner = 0;
68         id_ = paragraph_id++;
69 }
70
71
72 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
73         : params(p.params), owner_(owner)
74 {
75         inset_owner = p.inset_owner;
76         fontlist = p.fontlist;
77         id_ = paragraph_id++;
78
79         if (p.tracking())
80                 changes_.reset(new Changes(*p.changes_.get()));
81 }
82
83
84 void Paragraph::Pimpl::setContentsFromPar(Paragraph const & par)
85 {
86         owner_->text_ = par.text_;
87         if (par.pimpl_->tracking()) {
88                 changes_.reset(new Changes(*(par.pimpl_->changes_.get())));
89         }
90 }
91
92
93 void Paragraph::Pimpl::trackChanges(Change::Type type)
94 {
95         if (tracking()) {
96                 lyxerr[Debug::CHANGES] << "already tracking for par " << id_ << endl;
97                 return;
98         }
99
100         lyxerr[Debug::CHANGES] << "track changes for par "
101                 << id_ << " type " << type << endl;
102         changes_.reset(new Changes(type));
103         changes_->set(type, 0, size() + 1);
104 }
105
106
107 void Paragraph::Pimpl::untrackChanges()
108 {
109         changes_.reset(0);
110 }
111
112
113 void Paragraph::Pimpl::cleanChanges(Paragraph::ChangeTracking ct)
114 {
115         // if the paragraph was not tracked and we don't know the buffer's
116         // change tracking state, we do nothing
117         if ((ct == Paragraph::trackingUnknown) && !tracking())
118                 return;
119
120         // untrack everything if we are in a buffer where ct is disabled
121         else if (ct == Paragraph::trackingOff) {
122                 untrackChanges();
123                 return;
124         }
125         
126         // in a buffer where ct is enabled, set everything to INSERTED
127         changes_.reset(new Changes(Change::INSERTED));
128         changes_->set(Change::INSERTED, 0, size() + 1);
129 }
130
131
132 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
133 {
134         if (!tracking())
135                 return false;
136
137         return changes_->isChange(start, end);
138 }
139
140
141 bool Paragraph::Pimpl::isChangeEdited(pos_type start, pos_type end) const
142 {
143         if (!tracking())
144                 return false;
145
146         return changes_->isChangeEdited(start, end);
147 }
148
149
150 void Paragraph::Pimpl::setChange(pos_type pos, Change::Type type)
151 {
152         if (!tracking())
153                 return;
154
155         changes_->set(type, pos);
156 }
157
158
159 void Paragraph::Pimpl::setChangeFull(pos_type pos, Change change)
160 {
161         if (!tracking())
162                 return;
163
164         changes_->set(change, pos);
165 }
166
167 Change::Type Paragraph::Pimpl::lookupChange(pos_type pos) const
168 {
169         if (!tracking())
170                 return Change::UNCHANGED;
171
172         return changes_->lookup(pos);
173 }
174
175
176 Change const Paragraph::Pimpl::lookupChangeFull(pos_type pos) const
177 {
178         if (!tracking())
179                 return Change(Change::UNCHANGED);
180
181         return changes_->lookupFull(pos);
182 }
183
184
185 void Paragraph::Pimpl::markErased(bool erased)
186 {
187         BOOST_ASSERT(tracking());
188
189         if (erased) {
190                 erase(0, size());
191         } else {
192                 for (pos_type i = 0; i < size(); ++i) {
193                         changes_->set(Change::UNCHANGED, i);
194                         if (owner_->isInset(i))
195                                 owner_->getInset(i)->markErased(false);
196                 }
197         }
198 }
199
200
201 void Paragraph::Pimpl::acceptChange(pos_type start, pos_type end)
202 {
203         if (!tracking())
204                 return;
205
206         if (!size()) {
207                 changes_.reset(new Changes(Change::UNCHANGED));
208                 return;
209         }
210
211         lyxerr[Debug::CHANGES] << "acceptchange" << endl;
212         pos_type i = start;
213
214         for (; i < end; ++i) {
215                 switch (lookupChange(i)) {
216                         case Change::UNCHANGED:
217                                 break;
218
219                         case Change::INSERTED:
220                                 changes_->set(Change::UNCHANGED, i);
221                                 break;
222
223                         case Change::DELETED:
224                                 // Suppress access to nonexistent
225                                 // "end-of-paragraph char":
226                                 if (i < size()) {
227                                         eraseIntern(i);
228                                         changes_->erase(i);
229                                         --end;
230                                         --i;
231                                 }
232                                 break;
233                 }
234         }
235
236         lyxerr[Debug::CHANGES] << "endacceptchange" << endl;
237         changes_->reset(Change::UNCHANGED);
238 }
239
240
241 void Paragraph::Pimpl::rejectChange(pos_type start, pos_type end)
242 {
243         if (!tracking())
244                 return;
245
246         if (!size()) {
247                 changes_.reset(new Changes(Change::UNCHANGED));
248                 return;
249         }
250
251         pos_type i = start;
252
253         for (; i < end; ++i) {
254                 switch (lookupChange(i)) {
255                         case Change::UNCHANGED:
256                                 break;
257
258                         case Change::INSERTED:
259                                 if (i < size()) {
260                                         eraseIntern(i);
261                                         changes_->erase(i);
262                                         --end;
263                                         --i;
264                                 }
265                                 break;
266
267                         case Change::DELETED:
268                                 changes_->set(Change::UNCHANGED, i);
269                                 // No real char at position size():
270                                 if (i < size() && owner_->isInset(i))
271                                         owner_->getInset(i)->markErased(false);
272                                 break;
273                 }
274         }
275         changes_->reset(Change::UNCHANGED);
276 }
277
278
279 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
280 {
281         return owner_->getChar(pos);
282 }
283
284
285 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change change)
286 {
287         BOOST_ASSERT(pos <= size());
288
289         if (tracking()) {
290                 changes_->record(change, pos);
291         }
292
293         // This is actually very common when parsing buffers (and
294         // maybe inserting ascii text)
295         if (pos == size()) {
296                 // when appending characters, no need to update tables
297                 owner_->text_.push_back(c);
298                 return;
299         }
300
301         owner_->text_.insert(owner_->text_.begin() + pos, c);
302
303         // Update the font table.
304         FontTable search_font(pos, LyXFont());
305         for (FontList::iterator it = lower_bound(fontlist.begin(),
306                                                       fontlist.end(),
307                                                       search_font, matchFT());
308              it != fontlist.end(); ++it)
309         {
310                 it->pos(it->pos() + 1);
311         }
312
313         // Update the insets
314         owner_->insetlist.increasePosAfterPos(pos);
315 }
316
317
318 void Paragraph::Pimpl::insertInset(pos_type pos,
319                                    InsetBase * inset, Change change)
320 {
321         BOOST_ASSERT(inset);
322         BOOST_ASSERT(pos <= size());
323
324         insertChar(pos, META_INSET, change);
325         BOOST_ASSERT(owner_->text_[pos] == META_INSET);
326
327         // Add a new entry in the insetlist.
328         owner_->insetlist.insert(inset, pos);
329 }
330
331
332 void Paragraph::Pimpl::eraseIntern(pos_type pos)
333 {
334         // if it is an inset, delete the inset entry
335         if (owner_->text_[pos] == Paragraph::META_INSET) {
336                 owner_->insetlist.erase(pos);
337         }
338
339         owner_->text_.erase(owner_->text_.begin() + pos);
340
341         // Erase entries in the tables.
342         FontTable search_font(pos, LyXFont());
343
344         FontList::iterator it =
345                 lower_bound(fontlist.begin(),
346                             fontlist.end(),
347                             search_font, matchFT());
348         if (it != fontlist.end() && it->pos() == pos &&
349             (pos == 0 ||
350              (it != fontlist.begin()
351               && boost::prior(it)->pos() == pos - 1))) {
352                 // If it is a multi-character font
353                 // entry, we just make it smaller
354                 // (see update below), otherwise we
355                 // should delete it.
356                 unsigned int const i = it - fontlist.begin();
357                 fontlist.erase(fontlist.begin() + i);
358                 it = fontlist.begin() + i;
359                 if (i > 0 && i < fontlist.size() &&
360                     fontlist[i - 1].font() == fontlist[i].font()) {
361                         fontlist.erase(fontlist.begin() + i - 1);
362                         it = fontlist.begin() + i - 1;
363                 }
364         }
365
366         // Update all other entries.
367         FontList::iterator fend = fontlist.end();
368         for (; it != fend; ++it)
369                 it->pos(it->pos() - 1);
370
371         // Update the insetlist.
372         owner_->insetlist.decreasePosAfterPos(pos);
373 }
374
375
376 bool Paragraph::Pimpl::erase(pos_type pos)
377 {
378         BOOST_ASSERT(pos <= size());
379
380         if (tracking()) {
381                 Change::Type changetype(changes_->lookup(pos));
382                 changes_->record(Change(Change::DELETED), pos);
383
384                 // only allow the actual removal if it was /new/ text
385                 if (changetype != Change::INSERTED) {
386                         if (pos < size() && owner_->isInset(pos))
387                                 owner_->getInset(pos)->markErased(true);
388                         return false;
389                 }
390         }
391
392         // Don't physically access nonexistent end-of-paragraph char
393         if (pos < size()) {
394                 eraseIntern(pos);
395                 return true;
396         }
397
398         return false;
399 }
400
401
402 int Paragraph::Pimpl::erase(pos_type start, pos_type end)
403 {
404         pos_type i = start;
405         for (pos_type count = end - start; count; --count) {
406                 if (!erase(i))
407                         ++i;
408         }
409         return end - i;
410 }
411
412
413 void Paragraph::Pimpl::simpleTeXBlanks(ostream & os, TexRow & texrow,
414                                        pos_type const i,
415                                        unsigned int & column,
416                                        LyXFont const & font,
417                                        LyXLayout const & style)
418 {
419         if (style.pass_thru)
420                 return;
421
422         if (column > lyxrc.ascii_linelen
423             && i
424             && getChar(i - 1) != ' '
425             && (i < size() - 1)
426             // same in FreeSpacing mode
427             && !owner_->isFreeSpacing()
428             // In typewriter mode, we want to avoid
429             // ! . ? : at the end of a line
430             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
431                  && (getChar(i - 1) == '.'
432                      || getChar(i - 1) == '?'
433                      || getChar(i - 1) == ':'
434                      || getChar(i - 1) == '!'))) {
435                 os << '\n';
436                 texrow.newline();
437                 texrow.start(owner_->id(), i + 1);
438                 column = 0;
439         } else if (style.free_spacing) {
440                 os << '~';
441         } else {
442                 os << ' ';
443         }
444 }
445
446
447 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
448 {
449         pos_type const len = str.length();
450
451         // is the paragraph large enough?
452         if (pos + len > size())
453                 return false;
454
455         // does the wanted text start at point?
456         for (string::size_type i = 0; i < str.length(); ++i) {
457                 if (str[i] != owner_->text_[pos + i])
458                         return false;
459         }
460
461         // is there a font change in middle of the word?
462         FontList::const_iterator cit = fontlist.begin();
463         FontList::const_iterator end = fontlist.end();
464         for (; cit != end; ++cit) {
465                 if (cit->pos() >= pos)
466                         break;
467         }
468         if (cit != end && pos + len - 1 > cit->pos())
469                 return false;
470
471         return true;
472 }
473
474
475 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
476                                              BufferParams const & bparams,
477                                              ostream & os,
478                                              TexRow & texrow,
479                                              OutputParams const & runparams,
480                                              LyXFont & font,
481                                              LyXFont & running_font,
482                                              LyXFont & basefont,
483                                              LyXFont const & outerfont,
484                                              bool & open_font,
485                                              Change::Type & running_change,
486                                              LyXLayout const & style,
487                                              pos_type & i,
488                                              unsigned int & column,
489                                              value_type const c)
490 {
491         if (style.pass_thru) {
492                 if (c != Paragraph::META_INSET) {
493                         if (c != '\0')
494                                 os << c;
495                 } else {
496                         owner_->getInset(i)->plaintext(buf, os, runparams);
497                 }
498                 return;
499         }
500
501         // Two major modes:  LaTeX or plain
502         // Handle here those cases common to both modes
503         // and then split to handle the two modes separately.
504         switch (c) {
505         case Paragraph::META_INSET: {
506                 InsetBase * inset = owner_->getInset(i);
507
508                 // FIXME: remove this check
509                 if (!inset)
510                         break;
511
512                 // FIXME: move this to InsetNewline::latex
513                 if (inset->lyxCode() == InsetBase::NEWLINE_CODE) {
514                         // newlines are handled differently here than
515                         // the default in simpleTeXSpecialChars().
516                         if (!style.newline_allowed) {
517                                 os << '\n';
518                         } else {
519                                 if (open_font) {
520                                         column += running_font.latexWriteEndChanges(os, basefont, basefont);
521                                         open_font = false;
522                                 }
523                                 basefont = owner_->getLayoutFont(bparams, outerfont);
524                                 running_font = basefont;
525
526                                 if (font.family() == LyXFont::TYPEWRITER_FAMILY)
527                                         os << '~';
528
529                                 if (runparams.moving_arg)
530                                         os << "\\protect ";
531
532                                 os << "\\\\\n";
533                         }
534                         texrow.newline();
535                         texrow.start(owner_->id(), i + 1);
536                         column = 0;
537                         break;
538                 }
539
540                 // output change tracking marks only if desired,
541                 // if dvipost is installed,
542                 // and with dvi/ps (other formats don't work)
543                 LaTeXFeatures features(buf, bparams, runparams);
544                 bool const output = bparams.output_changes
545                         && runparams.flavor == OutputParams::LATEX
546                         && features.isAvailable("dvipost");
547
548                 if (inset->canTrackChanges()) {
549                         column += Changes::latexMarkChange(os, running_change,
550                                 Change::UNCHANGED, output);
551                         running_change = Change::UNCHANGED;
552                 }
553
554                 bool close = false;
555                 ostream::pos_type const len = os.tellp();
556
557                 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
558                      || inset->lyxCode() == InsetBase::MATH_CODE
559                      || inset->lyxCode() == InsetBase::URL_CODE)
560                     && running_font.isRightToLeft()) {
561                         os << "\\L{";
562                         close = true;
563                 }
564
565 #ifdef WITH_WARNINGS
566 #warning Bug: we can have an empty font change here!
567 // if there has just been a font change, we are going to close it
568 // right now, which means stupid latex code like \textsf{}. AFAIK,
569 // this does not harm dvi output. A minor bug, thus (JMarc)
570 #endif
571                 // some insets cannot be inside a font change command
572                 if (open_font && inset->noFontChange()) {
573                         column +=running_font.
574                                 latexWriteEndChanges(os,
575                                                      basefont,
576                                                      basefont);
577                         open_font = false;
578                         basefont = owner_->getLayoutFont(bparams, outerfont);
579                         running_font = basefont;
580                 }
581
582                 int tmp = inset->latex(buf, os, runparams);
583
584                 if (close)
585                         os << '}';
586
587                 if (tmp) {
588                         for (int j = 0; j < tmp; ++j) {
589                                 texrow.newline();
590                         }
591                         texrow.start(owner_->id(), i + 1);
592                         column = 0;
593                 } else {
594                         column += os.tellp() - len;
595                 }
596         }
597         break;
598
599         default:
600                 // And now for the special cases within each mode
601
602                 switch (c) {
603                 case '\\':
604                         os << "\\textbackslash{}";
605                         column += 15;
606                         break;
607
608                 case '±': case '²': case '³':
609                 case '×': case '÷': case '¹':
610                 case '¬': case 'µ':
611                         if ((bparams.inputenc == "latin1" ||
612                              bparams.inputenc == "latin9") ||
613                             (bparams.inputenc == "auto" &&
614                              (font.language()->encoding()->latexName()
615                               == "latin1" ||
616                               font.language()->encoding()->latexName()
617                               == "latin9"))) {
618                                 os << "\\ensuremath{"
619                                    << c
620                                    << '}';
621                                 column += 13;
622                         } else {
623                                 os << c;
624                         }
625                         break;
626
627                 case '|': case '<': case '>':
628                         // In T1 encoding, these characters exist
629                         if (lyxrc.fontenc == "T1") {
630                                 os << c;
631                                 //... but we should avoid ligatures
632                                 if ((c == '>' || c == '<')
633                                     && i <= size() - 2
634                                     && getChar(i + 1) == c) {
635                                         //os << "\\textcompwordmark{}";
636                                         // Jean-Marc, have a look at
637                                         // this. I think this works
638                                         // equally well:
639                                         os << "\\,{}";
640                                         // Lgb
641                                         column += 19;
642                                 }
643                                 break;
644                         }
645                         // Typewriter font also has them
646                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
647                                 os << c;
648                                 break;
649                         }
650                         // Otherwise, we use what LaTeX
651                         // provides us.
652                         switch (c) {
653                         case '<':
654                                 os << "\\textless{}";
655                                 column += 10;
656                                 break;
657                         case '>':
658                                 os << "\\textgreater{}";
659                                 column += 13;
660                                 break;
661                         case '|':
662                                 os << "\\textbar{}";
663                                 column += 9;
664                                 break;
665                         }
666                         break;
667
668                 case '-': // "--" in Typewriter mode -> "-{}-"
669                         if (i <= size() - 2
670                             && getChar(i + 1) == '-'
671                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
672                                 os << "-{}";
673                                 column += 2;
674                         } else {
675                                 os << '-';
676                         }
677                         break;
678
679                 case '\"':
680                         os << "\\char`\\\"{}";
681                         column += 9;
682                         break;
683
684                 case '£':
685                         if (bparams.inputenc == "default") {
686                                 os << "\\pounds{}";
687                                 column += 8;
688                         } else {
689                                 os << c;
690                         }
691                         break;
692
693                 case '$': case '&':
694                 case '%': case '#': case '{':
695                 case '}': case '_':
696                         os << '\\' << c;
697                         column += 1;
698                         break;
699
700                 case '~':
701                         os << "\\textasciitilde{}";
702                         column += 16;
703                         break;
704
705                 case '^':
706                         os << "\\textasciicircum{}";
707                         column += 17;
708                         break;
709
710                 case '*': case '[':
711                         // avoid being mistaken for optional arguments
712                         os << '{' << c << '}';
713                         column += 2;
714                         break;
715
716                 case ' ':
717                         // Blanks are printed before font switching.
718                         // Sure? I am not! (try nice-latex)
719                         // I am sure it's correct. LyX might be smarter
720                         // in the future, but for now, nothing wrong is
721                         // written. (Asger)
722                         break;
723
724                 default:
725
726                         // I assume this is hack treating typewriter as verbatim
727                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
728                                 if (c != '\0') {
729                                         os << c;
730                                 }
731                                 break;
732                         }
733
734                         // LyX, LaTeX etc.
735
736                         // FIXME: if we have "LaTeX" with a font
737                         // change in the middle (before the 'T', then
738                         // the "TeX" part is still special cased.
739                         // Really we should only operate this on
740                         // "words" for some definition of word
741
742                         size_t pnr = 0;
743
744                         for (; pnr < phrases_nr; ++pnr) {
745                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
746                                         os << special_phrases[pnr].macro;
747                                         i += special_phrases[pnr].phrase.length() - 1;
748                                         column += special_phrases[pnr].macro.length() - 1;
749                                         break;
750                                 }
751                         }
752
753                         if (pnr == phrases_nr && c != '\0') {
754                                 os << c;
755                         }
756                         break;
757                 }
758         }
759 }
760
761
762 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
763                                 LyXLayout const & layout) const
764 {
765         BufferParams const & bparams = features.bufferParams();
766
767         // check the params.
768         if (!params.spacing().isDefault())
769                 features.require("setspace");
770
771         // then the layouts
772         features.useLayout(layout.name());
773
774         // then the fonts
775         Language const * doc_language = bparams.language;
776
777         FontList::const_iterator fcit = fontlist.begin();
778         FontList::const_iterator fend = fontlist.end();
779         for (; fcit != fend; ++fcit) {
780                 if (fcit->font().noun() == LyXFont::ON) {
781                         lyxerr[Debug::LATEX] << "font.noun: "
782                                              << fcit->font().noun()
783                                              << endl;
784                         features.require("noun");
785                         lyxerr[Debug::LATEX] << "Noun enabled. Font: "
786                                              << fcit->font().stateText(0)
787                                              << endl;
788                 }
789                 switch (fcit->font().color()) {
790                 case LColor::none:
791                 case LColor::inherit:
792                 case LColor::ignore:
793                         // probably we should put here all interface colors used for
794                         // font displaying! For now I just add this ones I know of (Jug)
795                 case LColor::latex:
796                 case LColor::note:
797                         break;
798                 default:
799                         features.require("color");
800                         lyxerr[Debug::LATEX] << "Color enabled. Font: "
801                                              << fcit->font().stateText(0)
802                                              << endl;
803                 }
804
805                 Language const * language = fcit->font().language();
806                 if (language->babel() != doc_language->babel() &&
807                     language != ignore_language &&
808                     language != latex_language)
809                 {
810                         features.useLanguage(language);
811                         lyxerr[Debug::LATEX] << "Found language "
812                                              << language->babel() << endl;
813                 }
814         }
815
816         if (!params.leftIndent().zero())
817                 features.require("ParagraphLeftIndent");
818
819         // then the insets
820         InsetList::const_iterator icit = owner_->insetlist.begin();
821         InsetList::const_iterator iend = owner_->insetlist.end();
822         for (; icit != iend; ++icit) {
823                 if (icit->inset) {
824                         icit->inset->validate(features);
825                         if (layout.needprotect &&
826                             icit->inset->lyxCode() == InsetBase::FOOT_CODE)
827                                 features.require("NeedLyXFootnoteCode");
828                 }
829         }
830
831         // then the contents
832         for (pos_type i = 0; i < size() ; ++i) {
833                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
834                         if (!special_phrases[pnr].builtin
835                             && isTextAt(special_phrases[pnr].phrase, i)) {
836                                 features.require(special_phrases[pnr].phrase);
837                                 break;
838                         }
839                 }
840         }
841 }