]> git.lyx.org Git - lyx.git/blob - src/insets/InsetIndex.cpp
Properly terminate command
[lyx.git] / src / insets / InsetIndex.cpp
1 /**
2  * \file InsetIndex.cpp
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 Jürgen Spitzmüller
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11 #include <config.h>
12
13 #include "InsetIndex.h"
14
15 #include "Buffer.h"
16 #include "BufferParams.h"
17 #include "BufferView.h"
18 #include "ColorSet.h"
19 #include "Cursor.h"
20 #include "DispatchResult.h"
21 #include "Encoding.h"
22 #include "FuncRequest.h"
23 #include "FuncStatus.h"
24 #include "IndicesList.h"
25 #include "Language.h"
26 #include "LaTeXFeatures.h"
27 #include "Lexer.h"
28 #include "output_latex.h"
29 #include "output_xhtml.h"
30 #include "sgml.h"
31 #include "texstream.h"
32 #include "TextClass.h"
33 #include "TocBackend.h"
34
35 #include "support/debug.h"
36 #include "support/docstream.h"
37 #include "support/gettext.h"
38 #include "support/lstrings.h"
39
40 #include "frontends/alert.h"
41
42 #include <algorithm>
43 #include <ostream>
44
45 using namespace std;
46 using namespace lyx::support;
47
48 namespace lyx {
49
50 /////////////////////////////////////////////////////////////////////
51 //
52 // InsetIndex
53 //
54 ///////////////////////////////////////////////////////////////////////
55
56
57 InsetIndex::InsetIndex(Buffer * buf, InsetIndexParams const & params)
58         : InsetCollapsible(buf), params_(params)
59 {}
60
61
62 void InsetIndex::latex(otexstream & ios, OutputParams const & runparams_in) const
63 {
64         OutputParams runparams(runparams_in);
65         runparams.inIndexEntry = true;
66
67         otexstringstream os;
68
69         if (buffer().masterBuffer()->params().use_indices && !params_.index.empty()
70             && params_.index != "idx") {
71                 os << "\\sindex[";
72                 os << escape(params_.index);
73                 os << "]{";
74         } else {
75                 os << "\\index";
76                 os << '{';
77         }
78
79         odocstringstream ourlatex;
80         otexstream ots(ourlatex);
81         InsetText::latex(ots, runparams);
82         if (runparams.for_search) {
83                 // No need for special handling, if we are only searching for some patterns
84                 os << ourlatex.str() << "}";
85                 return;
86         }
87         // get contents of InsetText as LaTeX and plaintext
88         odocstringstream ourplain;
89         InsetText::plaintext(ourplain, runparams);
90         // FIXME: do Tex/Row correspondence (I don't currently understand what is
91         // being generated from latexstr below)
92         docstring latexstr = ourlatex.str();
93         docstring plainstr = ourplain.str();
94
95         // this will get what follows | if anything does
96         docstring cmd;
97
98         // check for the | separator
99         // FIXME This would go wrong on an escaped "|", but
100         // how far do we want to go here?
101         size_t pos = latexstr.find(from_ascii("|"));
102         if (pos != docstring::npos) {
103                 // put the bit after "|" into cmd...
104                 cmd = latexstr.substr(pos + 1);
105                 // ...and erase that stuff from latexstr
106                 latexstr = latexstr.erase(pos);
107                 // ...and similarly from plainstr
108                 size_t ppos = plainstr.find(from_ascii("|"));
109                 if (ppos < plainstr.size())
110                         plainstr.erase(ppos);
111                 else
112                         LYXERR0("The `|' separator was not found in the plaintext version!");
113         }
114
115         // Separate the entires and subentries, i.e., split on "!"
116         // FIXME This would do the wrong thing with escaped ! characters
117         std::vector<docstring> const levels =
118                 getVectorFromString(latexstr, from_ascii("!"), true);
119         std::vector<docstring> const levels_plain =
120                 getVectorFromString(plainstr, from_ascii("!"), true);
121
122         vector<docstring>::const_iterator it = levels.begin();
123         vector<docstring>::const_iterator end = levels.end();
124         vector<docstring>::const_iterator it2 = levels_plain.begin();
125         bool first = true;
126         for (; it != end; ++it) {
127                 // write the separator except the first time
128                 if (!first)
129                         os << '!';
130                 else
131                         first = false;
132
133                 // correctly sort macros and formatted strings
134                 // if we do find a command, prepend a plain text
135                 // version of the content to get sorting right,
136                 // e.g. \index{LyX@\LyX}, \index{text@\textbf{text}}
137                 // Don't do that if the user entered '@' himself, though.
138                 if (contains(*it, '\\') && !contains(*it, '@')) {
139                         // Plaintext might return nothing (e.g. for ERTs)
140                         docstring const spart =
141                                 (it2 < levels_plain.end() && !(*it2).empty())
142                                 ? *it2 : *it;
143                         // Now we need to validate that all characters in
144                         // the sorting part are representable in the current
145                         // encoding. If not try the LaTeX macro which might
146                         // or might not be a good choice, and issue a warning.
147                         pair<docstring, docstring> spart_latexed =
148                                 runparams.encoding->latexString(spart, runparams.dryrun);
149                         if (!spart_latexed.second.empty())
150                                         LYXERR0("Uncodable character in index entry. Sorting might be wrong!");
151                         if (spart != spart_latexed.first && !runparams.dryrun) {
152                                 // FIXME: warning should be passed to the error dialog
153                                 frontend::Alert::warning(_("Index sorting failed"),
154                                 bformat(_("LyX's automatic index sorting algorithm faced\n"
155                                   "problems with the entry '%1$s'.\n"
156                                   "Please specify the sorting of this entry manually, as\n"
157                                   "explained in the User Guide."), spart));
158                         }
159                         // remove remaining \'s for the sorting part
160                         docstring const ppart =
161                                 subst(spart_latexed.first, from_ascii("\\"), docstring());
162                         os << ppart;
163                         os << '@';
164                 }
165                 docstring const tpart = *it;
166                 os << tpart;
167                 if (it2 < levels_plain.end())
168                         ++it2;
169         }
170         // write the bit that followed "|"
171         if (!cmd.empty()) {
172                 os << "|" << cmd;
173         }
174         os << '}';
175
176         // In macros with moving arguments, such as \section,
177         // we store the index and output it after the macro (#2154)
178         if (runparams_in.postpone_fragile_stuff)
179                 runparams_in.post_macro += os.str();
180         else
181                 ios << os.release();
182 }
183
184
185 int InsetIndex::docbook(odocstream & os, OutputParams const & runparams) const
186 {
187         os << "<indexterm><primary>";
188         int const i = InsetText::docbook(os, runparams);
189         os << "</primary></indexterm>";
190         return i;
191 }
192
193
194 docstring InsetIndex::xhtml(XHTMLStream & xs, OutputParams const &) const
195 {
196         // we just print an anchor, taking the paragraph ID from
197         // our own interior paragraph, which doesn't get printed
198         std::string const magic = paragraphs().front().magicLabel();
199         std::string const attr = "id='" + magic + "'";
200         xs << html::CompTag("a", attr);
201         return docstring();
202 }
203
204
205 bool InsetIndex::showInsetDialog(BufferView * bv) const
206 {
207         bv->showDialog("index", params2string(params_),
208                         const_cast<InsetIndex *>(this));
209         return true;
210 }
211
212
213 void InsetIndex::doDispatch(Cursor & cur, FuncRequest & cmd)
214 {
215         switch (cmd.action()) {
216
217         case LFUN_INSET_MODIFY: {
218                 if (cmd.getArg(0) == "changetype") {
219                         cur.recordUndoInset(this);
220                         params_.index = from_utf8(cmd.getArg(1));
221                         break;
222                 }
223                 InsetIndexParams params;
224                 InsetIndex::string2params(to_utf8(cmd.argument()), params);
225                 cur.recordUndoInset(this);
226                 params_.index = params.index;
227                 // what we really want here is a TOC update, but that means
228                 // a full buffer update
229                 cur.forceBufferUpdate();
230                 break;
231         }
232
233         case LFUN_INSET_DIALOG_UPDATE:
234                 cur.bv().updateDialog("index", params2string(params_));
235                 break;
236
237         default:
238                 InsetCollapsible::doDispatch(cur, cmd);
239                 break;
240         }
241 }
242
243
244 bool InsetIndex::getStatus(Cursor & cur, FuncRequest const & cmd,
245                 FuncStatus & flag) const
246 {
247         switch (cmd.action()) {
248
249         case LFUN_INSET_MODIFY:
250                 if (cmd.getArg(0) == "changetype") {
251                         docstring const newtype = from_utf8(cmd.getArg(1));
252                         Buffer const & realbuffer = *buffer().masterBuffer();
253                         IndicesList const & indiceslist = realbuffer.params().indiceslist();
254                         Index const * index = indiceslist.findShortcut(newtype);
255                         flag.setEnabled(index != 0);
256                         flag.setOnOff(
257                                 from_utf8(cmd.getArg(1)) == params_.index);
258                         return true;
259                 }
260                 return InsetCollapsible::getStatus(cur, cmd, flag);
261
262         case LFUN_INSET_DIALOG_UPDATE: {
263                 Buffer const & realbuffer = *buffer().masterBuffer();
264                 flag.setEnabled(realbuffer.params().use_indices);
265                 return true;
266         }
267
268         default:
269                 return InsetCollapsible::getStatus(cur, cmd, flag);
270         }
271 }
272
273
274 ColorCode InsetIndex::labelColor() const
275 {
276         if (params_.index.empty() || params_.index == from_ascii("idx"))
277                 return InsetCollapsible::labelColor();
278         // FIXME UNICODE
279         ColorCode c = lcolor.getFromLyXName(to_utf8(params_.index));
280         if (c == Color_none)
281                 c = InsetCollapsible::labelColor();
282         return c;
283 }
284
285
286 docstring InsetIndex::toolTip(BufferView const &, int, int) const
287 {
288         docstring tip = _("Index Entry");
289         if (buffer().params().use_indices && !params_.index.empty()) {
290                 Buffer const & realbuffer = *buffer().masterBuffer();
291                 IndicesList const & indiceslist = realbuffer.params().indiceslist();
292                 tip += " (";
293                 Index const * index = indiceslist.findShortcut(params_.index);
294                 if (!index)
295                         tip += _("unknown type!");
296                 else
297                         tip += index->index();
298                 tip += ")";
299         }
300         tip += ": ";
301         return toolTipText(tip);
302 }
303
304
305 docstring const InsetIndex::buttonLabel(BufferView const & bv) const
306 {
307         InsetLayout const & il = getLayout();
308         docstring label = translateIfPossible(il.labelstring());
309
310         if (buffer().params().use_indices && !params_.index.empty()) {
311                 Buffer const & realbuffer = *buffer().masterBuffer();
312                 IndicesList const & indiceslist = realbuffer.params().indiceslist();
313                 label += " (";
314                 Index const * index = indiceslist.findShortcut(params_.index);
315                 if (!index)
316                         label += _("unknown type!");
317                 else
318                         label += index->index();
319                 label += ")";
320         }
321
322         if (!il.contentaslabel() || geometry(bv) != ButtonOnly)
323                 return label;
324         return getNewLabel(label);
325 }
326
327
328 void InsetIndex::write(ostream & os) const
329 {
330         os << to_utf8(layoutName());
331         params_.write(os);
332         InsetCollapsible::write(os);
333 }
334
335
336 void InsetIndex::read(Lexer & lex)
337 {
338         params_.read(lex);
339         InsetCollapsible::read(lex);
340 }
341
342
343 string InsetIndex::params2string(InsetIndexParams const & params)
344 {
345         ostringstream data;
346         data << "index";
347         params.write(data);
348         return data.str();
349 }
350
351
352 void InsetIndex::string2params(string const & in, InsetIndexParams & params)
353 {
354         params = InsetIndexParams();
355         if (in.empty())
356                 return;
357
358         istringstream data(in);
359         Lexer lex;
360         lex.setStream(data);
361         lex.setContext("InsetIndex::string2params");
362         lex >> "index";
363         params.read(lex);
364 }
365
366
367 void InsetIndex::addToToc(DocIterator const & cpit, bool output_active,
368                                                   UpdateType utype, TocBackend & backend) const
369 {
370         DocIterator pit = cpit;
371         pit.push_back(CursorSlice(const_cast<InsetIndex &>(*this)));
372         docstring str;
373         string type = "index";
374         if (buffer().masterBuffer()->params().use_indices)
375                 type += ":" + to_utf8(params_.index);
376         // this is unlikely to be terribly long
377         text().forOutliner(str, INT_MAX);
378         TocBuilder & b = backend.builder(type);
379         b.pushItem(pit, str, output_active);
380         // Proceed with the rest of the inset.
381         InsetCollapsible::addToToc(cpit, output_active, utype, backend);
382         b.pop();
383 }
384
385
386 void InsetIndex::validate(LaTeXFeatures & features) const
387 {
388         if (buffer().masterBuffer()->params().use_indices
389             && !params_.index.empty()
390             && params_.index != "idx")
391                 features.require("splitidx");
392         InsetCollapsible::validate(features);
393 }
394
395
396 string InsetIndex::contextMenuName() const
397 {
398         return "context-index";
399 }
400
401
402 bool InsetIndex::hasSettings() const
403 {
404         return buffer().masterBuffer()->params().use_indices;
405 }
406
407
408
409
410 /////////////////////////////////////////////////////////////////////
411 //
412 // InsetIndexParams
413 //
414 ///////////////////////////////////////////////////////////////////////
415
416
417 void InsetIndexParams::write(ostream & os) const
418 {
419         os << ' ';
420         if (!index.empty())
421                 os << to_utf8(index);
422         else
423                 os << "idx";
424         os << '\n';
425 }
426
427
428 void InsetIndexParams::read(Lexer & lex)
429 {
430         if (lex.eatLine())
431                 index = lex.getDocString();
432         else
433                 index = from_ascii("idx");
434 }
435
436
437 /////////////////////////////////////////////////////////////////////
438 //
439 // InsetPrintIndex
440 //
441 ///////////////////////////////////////////////////////////////////////
442
443 InsetPrintIndex::InsetPrintIndex(Buffer * buf, InsetCommandParams const & p)
444         : InsetCommand(buf, p)
445 {}
446
447
448 ParamInfo const & InsetPrintIndex::findInfo(string const & /* cmdName */)
449 {
450         static ParamInfo param_info_;
451         if (param_info_.empty()) {
452                 param_info_.add("type", ParamInfo::LATEX_OPTIONAL,
453                                 ParamInfo::HANDLING_ESCAPE);
454                 param_info_.add("name", ParamInfo::LATEX_OPTIONAL,
455                                 ParamInfo::HANDLING_LATEXIFY);
456                 param_info_.add("literal", ParamInfo::LYX_INTERNAL);
457         }
458         return param_info_;
459 }
460
461
462 docstring InsetPrintIndex::screenLabel() const
463 {
464         bool const printall = suffixIs(getCmdName(), '*');
465         bool const multind = buffer().masterBuffer()->params().use_indices;
466         if ((!multind
467              && getParam("type") == from_ascii("idx"))
468             || (getParam("type").empty() && !printall))
469                 return _("Index");
470         Buffer const & realbuffer = *buffer().masterBuffer();
471         IndicesList const & indiceslist = realbuffer.params().indiceslist();
472         Index const * index = indiceslist.findShortcut(getParam("type"));
473         if (!index && !printall)
474                 return _("Unknown index type!");
475         docstring res = printall ? _("All indexes") : index->index();
476         if (!multind)
477                 res += " (" + _("non-active") + ")";
478         else if (contains(getCmdName(), "printsubindex"))
479                 res += " (" + _("subindex") + ")";
480         return res;
481 }
482
483
484 bool InsetPrintIndex::isCompatibleCommand(string const & s)
485 {
486         return s == "printindex" || s == "printsubindex"
487                 || s == "printindex*" || s == "printsubindex*";
488 }
489
490
491 void InsetPrintIndex::doDispatch(Cursor & cur, FuncRequest & cmd)
492 {
493         switch (cmd.action()) {
494
495         case LFUN_INSET_MODIFY: {
496                 if (cmd.argument() == from_ascii("toggle-subindex")) {
497                         string scmd = getCmdName();
498                         if (contains(scmd, "printindex"))
499                                 scmd = subst(scmd, "printindex", "printsubindex");
500                         else
501                                 scmd = subst(scmd, "printsubindex", "printindex");
502                         cur.recordUndo();
503                         setCmdName(scmd);
504                         break;
505                 } else if (cmd.argument() == from_ascii("check-printindex*")) {
506                         string scmd = getCmdName();
507                         if (suffixIs(scmd, '*'))
508                                 break;
509                         scmd += '*';
510                         cur.recordUndo();
511                         setParam("type", docstring());
512                         setCmdName(scmd);
513                         break;
514                 }
515                 InsetCommandParams p(INDEX_PRINT_CODE);
516                 // FIXME UNICODE
517                 InsetCommand::string2params(to_utf8(cmd.argument()), p);
518                 if (p.getCmdName().empty()) {
519                         cur.noScreenUpdate();
520                         break;
521                 }
522                 cur.recordUndo();
523                 setParams(p);
524                 break;
525         }
526
527         default:
528                 InsetCommand::doDispatch(cur, cmd);
529                 break;
530         }
531 }
532
533
534 bool InsetPrintIndex::getStatus(Cursor & cur, FuncRequest const & cmd,
535         FuncStatus & status) const
536 {
537         switch (cmd.action()) {
538
539         case LFUN_INSET_MODIFY: {
540                 if (cmd.argument() == from_ascii("toggle-subindex")) {
541                         status.setEnabled(buffer().masterBuffer()->params().use_indices);
542                         status.setOnOff(contains(getCmdName(), "printsubindex"));
543                         return true;
544                 } else if (cmd.argument() == from_ascii("check-printindex*")) {
545                         status.setEnabled(buffer().masterBuffer()->params().use_indices);
546                         status.setOnOff(suffixIs(getCmdName(), '*'));
547                         return true;
548                 } if (cmd.getArg(0) == "index_print"
549                     && cmd.getArg(1) == "CommandInset") {
550                         InsetCommandParams p(INDEX_PRINT_CODE);
551                         InsetCommand::string2params(to_utf8(cmd.argument()), p);
552                         if (suffixIs(p.getCmdName(), '*')) {
553                                 status.setEnabled(true);
554                                 status.setOnOff(false);
555                                 return true;
556                         }
557                         Buffer const & realbuffer = *buffer().masterBuffer();
558                         IndicesList const & indiceslist =
559                                 realbuffer.params().indiceslist();
560                         Index const * index = indiceslist.findShortcut(p["type"]);
561                         status.setEnabled(index != 0);
562                         status.setOnOff(p["type"] == getParam("type"));
563                         return true;
564                 } else
565                         return InsetCommand::getStatus(cur, cmd, status);
566         }
567
568         case LFUN_INSET_DIALOG_UPDATE: {
569                 status.setEnabled(buffer().masterBuffer()->params().use_indices);
570                 return true;
571         }
572
573         default:
574                 return InsetCommand::getStatus(cur, cmd, status);
575         }
576 }
577
578
579 void InsetPrintIndex::updateBuffer(ParIterator const &, UpdateType)
580 {
581         Index const * index =
582                 buffer().masterParams().indiceslist().findShortcut(getParam("type"));
583         if (index)
584                 setParam("name", index->index());
585 }
586
587
588 void InsetPrintIndex::latex(otexstream & os, OutputParams const & runparams_in) const
589 {
590         if (!buffer().masterBuffer()->params().use_indices) {
591                 if (getParam("type") == from_ascii("idx"))
592                         os << "\\printindex" << termcmd;
593                 return;
594         }
595         OutputParams runparams = runparams_in;
596         os << getCommand(runparams);
597 }
598
599
600 void InsetPrintIndex::validate(LaTeXFeatures & features) const
601 {
602         features.require("makeidx");
603         if (buffer().masterBuffer()->params().use_indices)
604                 features.require("splitidx");
605         InsetCommand::validate(features);
606 }
607
608
609 string InsetPrintIndex::contextMenuName() const
610 {
611         return buffer().masterBuffer()->params().use_indices ?
612                 "context-indexprint" : string();
613 }
614
615
616 bool InsetPrintIndex::hasSettings() const
617 {
618         return buffer().masterBuffer()->params().use_indices;
619 }
620
621
622 namespace {
623
624 void parseItem(docstring & s, bool for_output)
625 {
626         // this does not yet check for escaped things
627         size_type loc = s.find(from_ascii("@"));
628         if (loc != string::npos) {
629                 if (for_output)
630                         s.erase(0, loc + 1);
631                 else
632                         s.erase(loc);
633         }
634         loc = s.find(from_ascii("|"));
635         if (loc != string::npos)
636                 s.erase(loc);
637 }
638
639
640 void extractSubentries(docstring const & entry, docstring & main,
641                 docstring & sub1, docstring & sub2)
642 {
643         if (entry.empty())
644                 return;
645         size_type const loc = entry.find(from_ascii(" ! "));
646         if (loc == string::npos)
647                 main = entry;
648         else {
649                 main = trim(entry.substr(0, loc));
650                 size_t const locend = loc + 3;
651                 size_type const loc2 = entry.find(from_ascii(" ! "), locend);
652                 if (loc2 == string::npos) {
653                         sub1 = trim(entry.substr(locend));
654                 } else {
655                         sub1 = trim(entry.substr(locend, loc2 - locend));
656                         sub2 = trim(entry.substr(loc2 + 3));
657                 }
658         }
659 }
660
661
662 struct IndexEntry
663 {
664         IndexEntry()
665         {}
666
667         IndexEntry(docstring const & s, DocIterator const & d)
668                         : dit(d)
669         {
670                 extractSubentries(s, main, sub, subsub);
671                 parseItem(main, false);
672                 parseItem(sub, false);
673                 parseItem(subsub, false);
674         }
675
676         bool equal(IndexEntry const & rhs) const
677         {
678                 return main == rhs.main && sub == rhs.sub && subsub == rhs.subsub;
679         }
680
681         bool same_sub(IndexEntry const & rhs) const
682         {
683                 return main == rhs.main && sub == rhs.sub;
684         }
685
686         bool same_main(IndexEntry const & rhs) const
687         {
688                 return main == rhs.main;
689         }
690
691         docstring main;
692         docstring sub;
693         docstring subsub;
694         DocIterator dit;
695 };
696
697 bool operator<(IndexEntry const & lhs, IndexEntry const & rhs)
698 {
699         int comp = compare_no_case(lhs.main, rhs.main);
700         if (comp == 0)
701                 comp = compare_no_case(lhs.sub, rhs.sub);
702         if (comp == 0)
703                 comp = compare_no_case(lhs.subsub, rhs.subsub);
704         return (comp < 0);
705 }
706
707 } // namespace
708
709
710 docstring InsetPrintIndex::xhtml(XHTMLStream &, OutputParams const & op) const
711 {
712         BufferParams const & bp = buffer().masterBuffer()->params();
713
714         // we do not presently support multiple indices, so we refuse to print
715         // anything but the main index, so as not to generate multiple indices.
716         // NOTE Multiple index support would require some work. The reason
717         // is that the TOC does not know about multiple indices. Either it would
718         // need to be told about them (not a bad idea), or else the index entries
719         // would need to be collected differently, say, during validation.
720         if (bp.use_indices && getParam("type") != from_ascii("idx"))
721                 return docstring();
722
723         shared_ptr<Toc const> toc = buffer().tocBackend().toc("index");
724         if (toc->empty())
725                 return docstring();
726
727         // Collect the index entries in a form we can use them.
728         Toc::const_iterator it = toc->begin();
729         Toc::const_iterator const en = toc->end();
730         vector<IndexEntry> entries;
731         for (; it != en; ++it)
732                 if (it->isOutput())
733                         entries.push_back(IndexEntry(it->str(), it->dit()));
734
735         if (entries.empty())
736                 // not very likely that all the index entries are in notes or
737                 // whatever, but....
738                 return docstring();
739
740         stable_sort(entries.begin(), entries.end());
741
742         Layout const & lay = bp.documentClass().htmlTOCLayout();
743         string const & tocclass = lay.defaultCSSClass();
744         string const tocattr = "class='index " + tocclass + "'";
745
746         // we'll use our own stream, because we are going to defer everything.
747         // that's how we deal with the fact that we're probably inside a standard
748         // paragraph, and we don't want to be.
749         odocstringstream ods;
750         XHTMLStream xs(ods);
751
752         xs << html::StartTag("div", tocattr);
753         xs << html::StartTag(lay.htmltag(), lay.htmlattr())
754                  << translateIfPossible(from_ascii("Index"),
755                                   op.local_font->language()->lang())
756                  << html::EndTag(lay.htmltag());
757         xs << html::StartTag("ul", "class='main'");
758         Font const dummy;
759
760         vector<IndexEntry>::const_iterator eit = entries.begin();
761         vector<IndexEntry>::const_iterator const een = entries.end();
762         // tracks whether we are already inside a main entry (1),
763         // a sub-entry (2), or a sub-sub-entry (3). see below for the
764         // details.
765         int level = 1;
766         // the last one we saw
767         IndexEntry last;
768         int entry_number = -1;
769         for (; eit != een; ++eit) {
770                 Paragraph const & par = eit->dit.innerParagraph();
771                 if (entry_number == -1 || !eit->equal(last)) {
772                         if (entry_number != -1) {
773                                 // not the first time through the loop, so
774                                 // close last entry or entries, depending.
775                                 if (level == 3) {
776                                         // close this sub-sub-entry
777                                         xs << html::EndTag("li") << html::CR();
778                                         // is this another sub-sub-entry within the same sub-entry?
779                                         if (!eit->same_sub(last)) {
780                                                 // close this level
781                                                 xs << html::EndTag("ul") << html::CR();
782                                                 level = 2;
783                                         }
784                                 }
785                                 // the point of the second test here is that we might get
786                                 // here two ways: (i) by falling through from above; (ii) because,
787                                 // though the sub-entry hasn't changed, the sub-sub-entry has,
788                                 // which means that it is the first sub-sub-entry within this
789                                 // sub-entry. In that case, we do not want to close anything.
790                                 if (level == 2 && !eit->same_sub(last)) {
791                                         // close sub-entry
792                                         xs << html::EndTag("li") << html::CR();
793                                         // is this another sub-entry with the same main entry?
794                                         if (!eit->same_main(last)) {
795                                                 // close this level
796                                                 xs << html::EndTag("ul") << html::CR();
797                                                 level = 1;
798                                         }
799                                 }
800                                 // again, we can get here two ways: from above, or because we have
801                                 // found the first sub-entry. in the latter case, we do not want to
802                                 // close the entry.
803                                 if (level == 1 && !eit->same_main(last)) {
804                                         // close entry
805                                         xs << html::EndTag("li") << html::CR();
806                                 }
807                         }
808
809                         // we'll be starting new entries
810                         entry_number = 0;
811
812                         // We need to use our own stream, since we will have to
813                         // modify what we get back.
814                         odocstringstream ent;
815                         XHTMLStream entstream(ent);
816                         OutputParams ours = op;
817                         ours.for_toc = true;
818                         par.simpleLyXHTMLOnePar(buffer(), entstream, ours, dummy);
819
820                         // these will contain XHTML versions of the main entry, etc
821                         // remember that everything will already have been escaped,
822                         // so we'll need to use NextRaw() during output.
823                         docstring main;
824                         docstring sub;
825                         docstring subsub;
826                         extractSubentries(ent.str(), main, sub, subsub);
827                         parseItem(main, true);
828                         parseItem(sub, true);
829                         parseItem(subsub, true);
830
831                         if (level == 3) {
832                                 // another subsubentry
833                                 xs << html::StartTag("li", "class='subsubentry'")
834                                    << XHTMLStream::ESCAPE_NONE << subsub;
835                         } else if (level == 2) {
836                                 // there are two ways we can be here:
837                                 // (i) we can actually be inside a sub-entry already and be about
838                                 //     to output the first sub-sub-entry. in this case, our sub
839                                 //     and the last sub will be the same.
840                                 // (ii) we can just have closed a sub-entry, possibly after also
841                                 //     closing a list of sub-sub-entries. here our sub and the last
842                                 //     sub are different.
843                                 // only in the latter case do we need to output the new sub-entry.
844                                 // note that in this case, too, though, the sub-entry might already
845                                 // have a sub-sub-entry.
846                                 if (eit->sub != last.sub)
847                                         xs << html::StartTag("li", "class='subentry'")
848                                            << XHTMLStream::ESCAPE_NONE << sub;
849                                 if (!subsub.empty()) {
850                                         // it's actually a subsubentry, so we need to start that list
851                                         xs << html::CR()
852                                            << html::StartTag("ul", "class='subsubentry'")
853                                            << html::StartTag("li", "class='subsubentry'")
854                                            << XHTMLStream::ESCAPE_NONE << subsub;
855                                         level = 3;
856                                 }
857                         } else {
858                                 // there are also two ways we can be here:
859                                 // (i) we can actually be inside an entry already and be about
860                                 //     to output the first sub-entry. in this case, our main
861                                 //     and the last main will be the same.
862                                 // (ii) we can just have closed an entry, possibly after also
863                                 //     closing a list of sub-entries. here our main and the last
864                                 //     main are different.
865                                 // only in the latter case do we need to output the new main entry.
866                                 // note that in this case, too, though, the main entry might already
867                                 // have a sub-entry, or even a sub-sub-entry.
868                                 if (eit->main != last.main)
869                                         xs << html::StartTag("li", "class='main'") << main;
870                                 if (!sub.empty()) {
871                                         // there's a sub-entry, too
872                                         xs << html::CR()
873                                            << html::StartTag("ul", "class='subentry'")
874                                            << html::StartTag("li", "class='subentry'")
875                                            << XHTMLStream::ESCAPE_NONE << sub;
876                                         level = 2;
877                                         if (!subsub.empty()) {
878                                                 // and a sub-sub-entry
879                                                 xs << html::CR()
880                                                    << html::StartTag("ul", "class='subsubentry'")
881                                                    << html::StartTag("li", "class='subsubentry'")
882                                                    << XHTMLStream::ESCAPE_NONE << subsub;
883                                                 level = 3;
884                                         }
885                                 }
886                         }
887                 }
888                 // finally, then, we can output the index link itself
889                 string const parattr = "href='#" + par.magicLabel() + "'";
890                 xs << (entry_number == 0 ? ":" : ",");
891                 xs << " " << html::StartTag("a", parattr)
892                    << ++entry_number << html::EndTag("a");
893                 last = *eit;
894         }
895         // now we have to close all the open levels
896         while (level > 0) {
897                 xs << html::EndTag("li") << html::EndTag("ul") << html::CR();
898                 --level;
899         }
900         xs << html::EndTag("div") << html::CR();
901         return ods.str();
902 }
903
904 } // namespace lyx