3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Jürgen Spitzmüller
9 * Full author contact details are available in file CREDITS.
13 #include "InsetIndex.h"
16 #include "BufferParams.h"
17 #include "BufferView.h"
20 #include "DispatchResult.h"
22 #include "FuncRequest.h"
23 #include "FuncStatus.h"
24 #include "IndicesList.h"
26 #include "LaTeXFeatures.h"
28 #include "output_latex.h"
29 #include "output_xhtml.h"
31 #include "texstream.h"
32 #include "TextClass.h"
33 #include "TocBackend.h"
35 #include "support/debug.h"
36 #include "support/docstream.h"
37 #include "support/FileName.h"
38 #include "support/gettext.h"
39 #include "support/lstrings.h"
41 #include "frontends/alert.h"
47 #include <QThreadStorage>
50 using namespace lyx::support;
54 /////////////////////////////////////////////////////////////////////
58 ///////////////////////////////////////////////////////////////////////
61 InsetIndex::InsetIndex(Buffer * buf, InsetIndexParams const & params)
62 : InsetCollapsible(buf), params_(params)
66 void InsetIndex::latex(otexstream & ios, OutputParams const & runparams_in) const
68 OutputParams runparams(runparams_in);
69 runparams.inIndexEntry = true;
73 if (buffer().masterBuffer()->params().use_indices && !params_.index.empty()
74 && params_.index != "idx") {
76 os << escape(params_.index);
83 odocstringstream ourlatex;
84 otexstream ots(ourlatex);
85 InsetText::latex(ots, runparams);
86 if (runparams.for_searchAdv != OutputParams::NoSearch) {
87 // No need for special handling, if we are only searching for some patterns
88 os << ourlatex.str() << "}";
91 // get contents of InsetText as LaTeX and plaintext
92 odocstringstream ourplain;
93 InsetText::plaintext(ourplain, runparams);
94 // FIXME: do Tex/Row correspondence (I don't currently understand what is
95 // being generated from latexstr below)
96 docstring latexstr = ourlatex.str();
97 docstring plainstr = ourplain.str();
99 // this will get what follows | if anything does
102 // check for the | separator
103 // FIXME This would go wrong on an escaped "|", but
104 // how far do we want to go here?
105 size_t pos = latexstr.find(from_ascii("|"));
106 if (pos != docstring::npos) {
107 // put the bit after "|" into cmd...
108 cmd = latexstr.substr(pos + 1);
109 // ...and erase that stuff from latexstr
110 latexstr = latexstr.erase(pos);
111 // ...and similarly from plainstr
112 size_t ppos = plainstr.find(from_ascii("|"));
113 if (ppos < plainstr.size())
114 plainstr.erase(ppos);
116 LYXERR0("The `|' separator was not found in the plaintext version!");
119 // Separate the entries and subentries, i.e., split on "!"
120 // FIXME This would do the wrong thing with escaped ! characters
121 std::vector<docstring> const levels =
122 getVectorFromString(latexstr, from_ascii("!"), true);
123 std::vector<docstring> const levels_plain =
124 getVectorFromString(plainstr, from_ascii("!"), true);
126 vector<docstring>::const_iterator it = levels.begin();
127 vector<docstring>::const_iterator end = levels.end();
128 vector<docstring>::const_iterator it2 = levels_plain.begin();
130 for (; it != end; ++it) {
131 // write the separator except the first time
137 // correctly sort macros and formatted strings
138 // if we do find a command, prepend a plain text
139 // version of the content to get sorting right,
140 // e.g. \index{LyX@\LyX}, \index{text@\textbf{text}}
141 // Don't do that if the user entered '@' himself, though.
142 if (contains(*it, '\\') && !contains(*it, '@')) {
143 // Plaintext might return nothing (e.g. for ERTs)
144 docstring const spart =
145 (it2 < levels_plain.end() && !(*it2).empty())
147 // Now we need to validate that all characters in
148 // the sorting part are representable in the current
149 // encoding. If not try the LaTeX macro which might
150 // or might not be a good choice, and issue a warning.
151 pair<docstring, docstring> spart_latexed =
152 runparams.encoding->latexString(spart, runparams.dryrun);
153 if (!spart_latexed.second.empty())
154 LYXERR0("Uncodable character in index entry. Sorting might be wrong!");
155 if (spart != spart_latexed.first && !runparams.dryrun) {
156 // FIXME: warning should be passed to the error dialog
157 frontend::Alert::warning(_("Index sorting failed"),
158 bformat(_("LyX's automatic index sorting algorithm faced\n"
159 "problems with the entry '%1$s'.\n"
160 "Please specify the sorting of this entry manually, as\n"
161 "explained in the User Guide."), spart));
163 // remove remaining \'s for the sorting part
164 docstring const ppart =
165 subst(spart_latexed.first, from_ascii("\\"), docstring());
169 docstring const tpart = *it;
171 if (it2 < levels_plain.end())
174 // write the bit that followed "|"
180 // In macros with moving arguments, such as \section,
181 // we store the index and output it after the macro (#2154)
182 if (runparams_in.postpone_fragile_stuff)
183 runparams_in.post_macro += os.str();
189 void InsetIndex::docbook(XMLStream & xs, OutputParams const & runparams) const
191 // Get the content of the inset as LaTeX, as some things may be encoded as ERT (like {}).
192 // TODO: if there is an ERT within the index term, its conversion should be tried, in case it becomes useful;
193 // otherwise, ERTs should become comments. For now, they are just copied as-is, which is barely satisfactory.
194 odocstringstream odss;
195 otexstream ots(odss);
196 InsetText::latex(ots, runparams);
197 docstring latexString = trim(odss.str());
199 // Check whether there are unsupported things. @ is supported, but only for sorting, without specific formatting.
200 if (latexString.find(from_utf8("@\\")) != lyx::docstring::npos) {
201 docstring error = from_utf8("Unsupported feature: an index entry contains an @\\. "
202 "Complete entry: \"") + latexString + from_utf8("\"");
204 xs << XMLStream::ESCAPE_NONE << (from_utf8("<!-- Output Error: ") + error + from_utf8(" -->\n"));
207 // Handle several indices (indicated in the inset instead of the raw latexString).
208 docstring indexType = from_utf8("");
209 if (buffer().masterBuffer()->params().use_indices) {
210 indexType += " type=\"" + params_.index + "\"";
213 // Split the string into its main constituents: terms, and command (see, see also, range).
214 size_t positionVerticalBar = latexString.find(from_ascii("|")); // What comes before | is (sub)(sub)entries.
215 docstring indexTerms = latexString.substr(0, positionVerticalBar);
217 if (positionVerticalBar != lyx::docstring::npos) {
218 command = latexString.substr(positionVerticalBar + 1);
221 // Handle sorting issues, with @.
222 vector<docstring> sortingElements = getVectorFromString(indexTerms, from_ascii("@"), false);
224 if (sortingElements.size() == 2) {
225 sortAs = sortingElements[0];
226 indexTerms = sortingElements[1];
229 // Handle primary, secondary, and tertiary terms (entries, subentries, and subsubentries, for LaTeX).
230 vector<docstring> terms = getVectorFromString(indexTerms, from_ascii("!"), false);
232 // Handle ranges. Happily, (| and |) can only be at the end of the string!
233 bool hasStartRange = latexString.find(from_ascii("|(")) != lyx::docstring::npos;
234 bool hasEndRange = latexString.find(from_ascii("|)")) != lyx::docstring::npos;
235 if (hasStartRange || hasEndRange) {
236 // Remove the ranges from the command if they do not appear at the beginning.
238 while ((index = command.find(from_utf8("|("), index)) != std::string::npos)
239 command.erase(index, 1);
241 while ((index = command.find(from_utf8("|)"), index)) != std::string::npos)
242 command.erase(index, 1);
244 // Remove the ranges when they are the only vertical bar in the complete string.
245 if (command[0] == '(' || command[0] == ')')
249 // Handle see and seealso. As "see" is a prefix of "seealso", the order of the comparisons is important.
250 // Both commands are mutually exclusive!
251 docstring see = from_utf8("");
252 vector<docstring> seeAlsoes;
253 if (command.substr(0, 3) == "see") {
254 // Unescape brackets.
256 while ((index = command.find(from_utf8("\\{"), index)) != std::string::npos)
257 command.erase(index, 1);
259 while ((index = command.find(from_utf8("\\}"), index)) != std::string::npos)
260 command.erase(index, 1);
262 // Retrieve the part between brackets, and remove the complete seealso.
263 size_t positionOpeningBracket = command.find(from_ascii("{"));
264 size_t positionClosingBracket = command.find(from_ascii("}"));
265 docstring list = command.substr(positionOpeningBracket + 1, positionClosingBracket - positionOpeningBracket - 1);
267 // Parse the list of referenced entries (or a single one for see).
268 if (command.substr(0, 7) == "seealso") {
269 seeAlsoes = getVectorFromString(list, from_ascii(","), false);
273 if (see.find(from_ascii(",")) != std::string::npos) {
274 docstring error = from_utf8("Several index terms found as \"see\"! Only one is acceptable. "
275 "Complete entry: \"") + latexString + from_utf8("\"");
277 xs << XMLStream::ESCAPE_NONE << (from_utf8("<!-- Output Error: ") + error + from_utf8(" -->\n"));
281 // Remove the complete see/seealso from the commands, in case there is something else to parse.
282 command = command.substr(positionClosingBracket + 1);
285 // Some parts of the strings are not parsed, as they do not have anything matching in DocBook: things like
286 // formatting the entry or the page number, other strings for sorting. https://wiki.lyx.org/Tips/Indexing
287 // If there are such things in the index entry, then this code may miserably fail. For example, for "Peter|(textbf",
288 // no range will be detected.
289 // TODO: Could handle formatting as significance="preferred"?
290 if (!command.empty()) {
291 docstring error = from_utf8("Unsupported feature: an index entry contains a | with an unsupported command, ")
292 + command + from_utf8(". ") + from_utf8("Complete entry: \"") + latexString + from_utf8("\"");
294 xs << XMLStream::ESCAPE_NONE << (from_utf8("<!-- Output Error: ") + error + from_utf8(" -->\n"));
297 // Write all of this down.
298 if (terms.empty() && !hasEndRange) {
299 docstring error = from_utf8("No index term found! Complete entry: \"") + latexString + from_utf8("\"");
301 xs << XMLStream::ESCAPE_NONE << (from_utf8("<!-- Output Error: ") + error + from_utf8(" -->\n"));
303 // Generate the attributes for ranges. It is based on the terms that are indexed, but the ID must be unique
304 // to this indexing area (xml::cleanID does not guarantee this: for each call with the same arguments,
305 // the same legal ID is produced; here, as the input would be the same, the output must be, by design).
306 // Hence the thread-local storage, as the numbers must strictly be unique, and thus cannot be shared across
307 // a paragraph (making the solution used for HTML worthless). This solution is very similar to the one used in
309 // indexType can only be used for singular and startofrange types!
311 if (!hasStartRange && !hasEndRange) {
314 // Append an ID if uniqueness is not guaranteed across the document.
315 static QThreadStorage<set<docstring>> tKnownTermLists;
316 static QThreadStorage<int> tID;
318 set<docstring> &knownTermLists = tKnownTermLists.localData();
319 int &ID = tID.localData();
321 if (!tID.hasLocalData()) {
325 // Modify the index terms to add the unique ID if needed.
326 docstring newIndexTerms = indexTerms;
327 if (knownTermLists.find(indexTerms) != knownTermLists.end()) {
328 newIndexTerms += from_ascii(string("-") + to_string(ID));
330 // Only increment for the end of range, so that the same number is used for the start of range.
336 // Term list not yet known: add it to the set AFTER the end of range. After
337 if (knownTermLists.find(indexTerms) == knownTermLists.end() && hasEndRange) {
338 knownTermLists.insert(indexTerms);
341 // Generate the attributes.
342 docstring id = xml::cleanID(newIndexTerms);
344 attrs = indexType + " class=\"startofrange\" xml:id=\"" + id + "\"";
346 attrs = " class=\"endofrange\" startref=\"" + id + "\"";
350 // Handle the index terms (including the specific index for this entry).
352 xs << xml::CompTag("indexterm", attrs);
354 xs << xml::StartTag("indexterm", attrs);
355 if (!terms.empty()) { // hasEndRange has no content.
357 if (!sortAs.empty()) {
358 attr = from_utf8("sortas='") + sortAs + from_utf8("'");
361 xs << xml::StartTag("primary", attr);
363 xs << xml::EndTag("primary");
365 if (terms.size() > 1) {
366 xs << xml::StartTag("secondary");
368 xs << xml::EndTag("secondary");
370 if (terms.size() > 2) {
371 xs << xml::StartTag("tertiary");
373 xs << xml::EndTag("tertiary");
376 // Handle see and see also.
378 xs << xml::StartTag("see");
380 xs << xml::EndTag("see");
383 if (!seeAlsoes.empty()) {
384 for (auto &entry : seeAlsoes) {
385 xs << xml::StartTag("seealso");
387 xs << xml::EndTag("seealso");
392 xs << xml::EndTag("indexterm");
398 docstring InsetIndex::xhtml(XMLStream & xs, OutputParams const &) const
400 // we just print an anchor, taking the paragraph ID from
401 // our own interior paragraph, which doesn't get printed
402 std::string const magic = paragraphs().front().magicLabel();
403 std::string const attr = "id='" + magic + "'";
404 xs << xml::CompTag("a", attr);
409 bool InsetIndex::showInsetDialog(BufferView * bv) const
411 bv->showDialog("index", params2string(params_),
412 const_cast<InsetIndex *>(this));
417 void InsetIndex::doDispatch(Cursor & cur, FuncRequest & cmd)
419 switch (cmd.action()) {
421 case LFUN_INSET_MODIFY: {
422 if (cmd.getArg(0) == "changetype") {
423 cur.recordUndoInset(this);
424 params_.index = from_utf8(cmd.getArg(1));
427 InsetIndexParams params;
428 InsetIndex::string2params(to_utf8(cmd.argument()), params);
429 cur.recordUndoInset(this);
430 params_.index = params.index;
431 // what we really want here is a TOC update, but that means
432 // a full buffer update
433 cur.forceBufferUpdate();
437 case LFUN_INSET_DIALOG_UPDATE:
438 cur.bv().updateDialog("index", params2string(params_));
442 InsetCollapsible::doDispatch(cur, cmd);
448 bool InsetIndex::getStatus(Cursor & cur, FuncRequest const & cmd,
449 FuncStatus & flag) const
451 switch (cmd.action()) {
453 case LFUN_INSET_MODIFY:
454 if (cmd.getArg(0) == "changetype") {
455 docstring const newtype = from_utf8(cmd.getArg(1));
456 Buffer const & realbuffer = *buffer().masterBuffer();
457 IndicesList const & indiceslist = realbuffer.params().indiceslist();
458 Index const * index = indiceslist.findShortcut(newtype);
459 flag.setEnabled(index != 0);
461 from_utf8(cmd.getArg(1)) == params_.index);
464 return InsetCollapsible::getStatus(cur, cmd, flag);
466 case LFUN_INSET_DIALOG_UPDATE: {
467 Buffer const & realbuffer = *buffer().masterBuffer();
468 flag.setEnabled(realbuffer.params().use_indices);
473 return InsetCollapsible::getStatus(cur, cmd, flag);
478 ColorCode InsetIndex::labelColor() const
480 if (params_.index.empty() || params_.index == from_ascii("idx"))
481 return InsetCollapsible::labelColor();
483 ColorCode c = lcolor.getFromLyXName(to_utf8(params_.index)
484 + "@" + buffer().fileName().absFileName());
486 c = InsetCollapsible::labelColor();
491 docstring InsetIndex::toolTip(BufferView const &, int, int) const
493 docstring tip = _("Index Entry");
494 if (buffer().params().use_indices && !params_.index.empty()) {
495 Buffer const & realbuffer = *buffer().masterBuffer();
496 IndicesList const & indiceslist = realbuffer.params().indiceslist();
498 Index const * index = indiceslist.findShortcut(params_.index);
500 tip += _("unknown type!");
502 tip += index->index();
506 return toolTipText(tip);
510 docstring const InsetIndex::buttonLabel(BufferView const & bv) const
512 InsetLayout const & il = getLayout();
513 docstring label = translateIfPossible(il.labelstring());
515 if (buffer().params().use_indices && !params_.index.empty()) {
516 Buffer const & realbuffer = *buffer().masterBuffer();
517 IndicesList const & indiceslist = realbuffer.params().indiceslist();
519 Index const * index = indiceslist.findShortcut(params_.index);
521 label += _("unknown type!");
523 label += index->index();
527 if (!il.contentaslabel() || geometry(bv) != ButtonOnly)
529 return getNewLabel(label);
533 void InsetIndex::write(ostream & os) const
535 os << to_utf8(layoutName());
537 InsetCollapsible::write(os);
541 void InsetIndex::read(Lexer & lex)
544 InsetCollapsible::read(lex);
548 string InsetIndex::params2string(InsetIndexParams const & params)
557 void InsetIndex::string2params(string const & in, InsetIndexParams & params)
559 params = InsetIndexParams();
563 istringstream data(in);
566 lex.setContext("InsetIndex::string2params");
572 void InsetIndex::addToToc(DocIterator const & cpit, bool output_active,
573 UpdateType utype, TocBackend & backend) const
575 DocIterator pit = cpit;
576 pit.push_back(CursorSlice(const_cast<InsetIndex &>(*this)));
578 string type = "index";
579 if (buffer().masterBuffer()->params().use_indices)
580 type += ":" + to_utf8(params_.index);
581 // this is unlikely to be terribly long
582 text().forOutliner(str, INT_MAX);
583 TocBuilder & b = backend.builder(type);
584 b.pushItem(pit, str, output_active);
585 // Proceed with the rest of the inset.
586 InsetCollapsible::addToToc(cpit, output_active, utype, backend);
591 void InsetIndex::validate(LaTeXFeatures & features) const
593 if (buffer().masterBuffer()->params().use_indices
594 && !params_.index.empty()
595 && params_.index != "idx")
596 features.require("splitidx");
597 InsetCollapsible::validate(features);
601 string InsetIndex::contextMenuName() const
603 return "context-index";
607 bool InsetIndex::hasSettings() const
609 return buffer().masterBuffer()->params().use_indices;
615 /////////////////////////////////////////////////////////////////////
619 ///////////////////////////////////////////////////////////////////////
622 void InsetIndexParams::write(ostream & os) const
626 os << to_utf8(index);
633 void InsetIndexParams::read(Lexer & lex)
636 index = lex.getDocString();
638 index = from_ascii("idx");
642 /////////////////////////////////////////////////////////////////////
646 ///////////////////////////////////////////////////////////////////////
648 InsetPrintIndex::InsetPrintIndex(Buffer * buf, InsetCommandParams const & p)
649 : InsetCommand(buf, p)
653 ParamInfo const & InsetPrintIndex::findInfo(string const & /* cmdName */)
655 static ParamInfo param_info_;
656 if (param_info_.empty()) {
657 param_info_.add("type", ParamInfo::LATEX_OPTIONAL,
658 ParamInfo::HANDLING_ESCAPE);
659 param_info_.add("name", ParamInfo::LATEX_OPTIONAL,
660 ParamInfo::HANDLING_LATEXIFY);
661 param_info_.add("literal", ParamInfo::LYX_INTERNAL);
667 docstring InsetPrintIndex::screenLabel() const
669 bool const printall = suffixIs(getCmdName(), '*');
670 bool const multind = buffer().masterBuffer()->params().use_indices;
672 && getParam("type") == from_ascii("idx"))
673 || (getParam("type").empty() && !printall))
675 Buffer const & realbuffer = *buffer().masterBuffer();
676 IndicesList const & indiceslist = realbuffer.params().indiceslist();
677 Index const * index = indiceslist.findShortcut(getParam("type"));
678 if (!index && !printall)
679 return _("Unknown index type!");
680 docstring res = printall ? _("All indexes") : index->index();
682 res += " (" + _("non-active") + ")";
683 else if (contains(getCmdName(), "printsubindex"))
684 res += " (" + _("subindex") + ")";
689 bool InsetPrintIndex::isCompatibleCommand(string const & s)
691 return s == "printindex" || s == "printsubindex"
692 || s == "printindex*" || s == "printsubindex*";
696 void InsetPrintIndex::doDispatch(Cursor & cur, FuncRequest & cmd)
698 switch (cmd.action()) {
700 case LFUN_INSET_MODIFY: {
701 if (cmd.argument() == from_ascii("toggle-subindex")) {
702 string scmd = getCmdName();
703 if (contains(scmd, "printindex"))
704 scmd = subst(scmd, "printindex", "printsubindex");
706 scmd = subst(scmd, "printsubindex", "printindex");
710 } else if (cmd.argument() == from_ascii("check-printindex*")) {
711 string scmd = getCmdName();
712 if (suffixIs(scmd, '*'))
716 setParam("type", docstring());
720 InsetCommandParams p(INDEX_PRINT_CODE);
722 InsetCommand::string2params(to_utf8(cmd.argument()), p);
723 if (p.getCmdName().empty()) {
724 cur.noScreenUpdate();
733 InsetCommand::doDispatch(cur, cmd);
739 bool InsetPrintIndex::getStatus(Cursor & cur, FuncRequest const & cmd,
740 FuncStatus & status) const
742 switch (cmd.action()) {
744 case LFUN_INSET_MODIFY: {
745 if (cmd.argument() == from_ascii("toggle-subindex")) {
746 status.setEnabled(buffer().masterBuffer()->params().use_indices);
747 status.setOnOff(contains(getCmdName(), "printsubindex"));
749 } else if (cmd.argument() == from_ascii("check-printindex*")) {
750 status.setEnabled(buffer().masterBuffer()->params().use_indices);
751 status.setOnOff(suffixIs(getCmdName(), '*'));
753 } if (cmd.getArg(0) == "index_print"
754 && cmd.getArg(1) == "CommandInset") {
755 InsetCommandParams p(INDEX_PRINT_CODE);
756 InsetCommand::string2params(to_utf8(cmd.argument()), p);
757 if (suffixIs(p.getCmdName(), '*')) {
758 status.setEnabled(true);
759 status.setOnOff(false);
762 Buffer const & realbuffer = *buffer().masterBuffer();
763 IndicesList const & indiceslist =
764 realbuffer.params().indiceslist();
765 Index const * index = indiceslist.findShortcut(p["type"]);
766 status.setEnabled(index != 0);
767 status.setOnOff(p["type"] == getParam("type"));
770 return InsetCommand::getStatus(cur, cmd, status);
773 case LFUN_INSET_DIALOG_UPDATE: {
774 status.setEnabled(buffer().masterBuffer()->params().use_indices);
779 return InsetCommand::getStatus(cur, cmd, status);
784 void InsetPrintIndex::updateBuffer(ParIterator const &, UpdateType, bool const /*deleted*/)
786 Index const * index =
787 buffer().masterParams().indiceslist().findShortcut(getParam("type"));
789 setParam("name", index->index());
793 void InsetPrintIndex::latex(otexstream & os, OutputParams const & runparams_in) const
795 if (!buffer().masterBuffer()->params().use_indices) {
796 if (getParam("type") == from_ascii("idx"))
797 os << "\\printindex" << termcmd;
800 OutputParams runparams = runparams_in;
801 os << getCommand(runparams);
805 void InsetPrintIndex::validate(LaTeXFeatures & features) const
807 features.require("makeidx");
808 if (buffer().masterBuffer()->params().use_indices)
809 features.require("splitidx");
810 InsetCommand::validate(features);
814 string InsetPrintIndex::contextMenuName() const
816 return buffer().masterBuffer()->params().use_indices ?
817 "context-indexprint" : string();
821 bool InsetPrintIndex::hasSettings() const
823 return buffer().masterBuffer()->params().use_indices;
829 void parseItem(docstring & s, bool for_output)
831 // this does not yet check for escaped things
832 size_type loc = s.find(from_ascii("@"));
833 if (loc != string::npos) {
839 loc = s.find(from_ascii("|"));
840 if (loc != string::npos)
845 void extractSubentries(docstring const & entry, docstring & main,
846 docstring & sub1, docstring & sub2)
850 size_type const loc = entry.find(from_ascii(" ! "));
851 if (loc == string::npos)
854 main = trim(entry.substr(0, loc));
855 size_t const locend = loc + 3;
856 size_type const loc2 = entry.find(from_ascii(" ! "), locend);
857 if (loc2 == string::npos) {
858 sub1 = trim(entry.substr(locend));
860 sub1 = trim(entry.substr(locend, loc2 - locend));
861 sub2 = trim(entry.substr(loc2 + 3));
872 IndexEntry(docstring const & s, DocIterator const & d)
875 extractSubentries(s, main, sub, subsub);
876 parseItem(main, false);
877 parseItem(sub, false);
878 parseItem(subsub, false);
881 bool equal(IndexEntry const & rhs) const
883 return main == rhs.main && sub == rhs.sub && subsub == rhs.subsub;
886 bool same_sub(IndexEntry const & rhs) const
888 return main == rhs.main && sub == rhs.sub;
891 bool same_main(IndexEntry const & rhs) const
893 return main == rhs.main;
902 bool operator<(IndexEntry const & lhs, IndexEntry const & rhs)
904 int comp = compare_no_case(lhs.main, rhs.main);
906 comp = compare_no_case(lhs.sub, rhs.sub);
908 comp = compare_no_case(lhs.subsub, rhs.subsub);
915 docstring InsetPrintIndex::xhtml(XMLStream &, OutputParams const & op) const
917 BufferParams const & bp = buffer().masterBuffer()->params();
919 // we do not presently support multiple indices, so we refuse to print
920 // anything but the main index, so as not to generate multiple indices.
921 // NOTE Multiple index support would require some work. The reason
922 // is that the TOC does not know about multiple indices. Either it would
923 // need to be told about them (not a bad idea), or else the index entries
924 // would need to be collected differently, say, during validation.
925 if (bp.use_indices && getParam("type") != from_ascii("idx"))
928 shared_ptr<Toc const> toc = buffer().tocBackend().toc("index");
932 // Collect the index entries in a form we can use them.
933 Toc::const_iterator it = toc->begin();
934 Toc::const_iterator const en = toc->end();
935 vector<IndexEntry> entries;
936 for (; it != en; ++it)
938 entries.push_back(IndexEntry(it->str(), it->dit()));
941 // not very likely that all the index entries are in notes or
945 stable_sort(entries.begin(), entries.end());
947 Layout const & lay = bp.documentClass().htmlTOCLayout();
948 string const & tocclass = lay.defaultCSSClass();
949 string const tocattr = "class='index " + tocclass + "'";
951 // we'll use our own stream, because we are going to defer everything.
952 // that's how we deal with the fact that we're probably inside a standard
953 // paragraph, and we don't want to be.
954 odocstringstream ods;
957 xs << xml::StartTag("div", tocattr);
958 xs << xml::StartTag(lay.htmltag(), lay.htmlattr())
959 << translateIfPossible(from_ascii("Index"),
960 op.local_font->language()->lang())
961 << xml::EndTag(lay.htmltag());
962 xs << xml::StartTag("ul", "class='main'");
965 vector<IndexEntry>::const_iterator eit = entries.begin();
966 vector<IndexEntry>::const_iterator const een = entries.end();
967 // tracks whether we are already inside a main entry (1),
968 // a sub-entry (2), or a sub-sub-entry (3). see below for the
971 // the last one we saw
973 int entry_number = -1;
974 for (; eit != een; ++eit) {
975 Paragraph const & par = eit->dit.innerParagraph();
976 if (entry_number == -1 || !eit->equal(last)) {
977 if (entry_number != -1) {
978 // not the first time through the loop, so
979 // close last entry or entries, depending.
981 // close this sub-sub-entry
982 xs << xml::EndTag("li") << xml::CR();
983 // is this another sub-sub-entry within the same sub-entry?
984 if (!eit->same_sub(last)) {
986 xs << xml::EndTag("ul") << xml::CR();
990 // the point of the second test here is that we might get
991 // here two ways: (i) by falling through from above; (ii) because,
992 // though the sub-entry hasn't changed, the sub-sub-entry has,
993 // which means that it is the first sub-sub-entry within this
994 // sub-entry. In that case, we do not want to close anything.
995 if (level == 2 && !eit->same_sub(last)) {
997 xs << xml::EndTag("li") << xml::CR();
998 // is this another sub-entry with the same main entry?
999 if (!eit->same_main(last)) {
1001 xs << xml::EndTag("ul") << xml::CR();
1005 // again, we can get here two ways: from above, or because we have
1006 // found the first sub-entry. in the latter case, we do not want to
1008 if (level == 1 && !eit->same_main(last)) {
1010 xs << xml::EndTag("li") << xml::CR();
1014 // we'll be starting new entries
1017 // We need to use our own stream, since we will have to
1018 // modify what we get back.
1019 odocstringstream ent;
1020 XMLStream entstream(ent);
1021 OutputParams ours = op;
1022 ours.for_toc = true;
1023 par.simpleLyXHTMLOnePar(buffer(), entstream, ours, dummy);
1025 // these will contain XHTML versions of the main entry, etc
1026 // remember that everything will already have been escaped,
1027 // so we'll need to use NextRaw() during output.
1031 extractSubentries(ent.str(), main, sub, subsub);
1032 parseItem(main, true);
1033 parseItem(sub, true);
1034 parseItem(subsub, true);
1037 // another subsubentry
1038 xs << xml::StartTag("li", "class='subsubentry'")
1039 << XMLStream::ESCAPE_NONE << subsub;
1040 } else if (level == 2) {
1041 // there are two ways we can be here:
1042 // (i) we can actually be inside a sub-entry already and be about
1043 // to output the first sub-sub-entry. in this case, our sub
1044 // and the last sub will be the same.
1045 // (ii) we can just have closed a sub-entry, possibly after also
1046 // closing a list of sub-sub-entries. here our sub and the last
1047 // sub are different.
1048 // only in the latter case do we need to output the new sub-entry.
1049 // note that in this case, too, though, the sub-entry might already
1050 // have a sub-sub-entry.
1051 if (eit->sub != last.sub)
1052 xs << xml::StartTag("li", "class='subentry'")
1053 << XMLStream::ESCAPE_NONE << sub;
1054 if (!subsub.empty()) {
1055 // it's actually a subsubentry, so we need to start that list
1057 << xml::StartTag("ul", "class='subsubentry'")
1058 << xml::StartTag("li", "class='subsubentry'")
1059 << XMLStream::ESCAPE_NONE << subsub;
1063 // there are also two ways we can be here:
1064 // (i) we can actually be inside an entry already and be about
1065 // to output the first sub-entry. in this case, our main
1066 // and the last main will be the same.
1067 // (ii) we can just have closed an entry, possibly after also
1068 // closing a list of sub-entries. here our main and the last
1069 // main are different.
1070 // only in the latter case do we need to output the new main entry.
1071 // note that in this case, too, though, the main entry might already
1072 // have a sub-entry, or even a sub-sub-entry.
1073 if (eit->main != last.main)
1074 xs << xml::StartTag("li", "class='main'") << main;
1076 // there's a sub-entry, too
1078 << xml::StartTag("ul", "class='subentry'")
1079 << xml::StartTag("li", "class='subentry'")
1080 << XMLStream::ESCAPE_NONE << sub;
1082 if (!subsub.empty()) {
1083 // and a sub-sub-entry
1085 << xml::StartTag("ul", "class='subsubentry'")
1086 << xml::StartTag("li", "class='subsubentry'")
1087 << XMLStream::ESCAPE_NONE << subsub;
1093 // finally, then, we can output the index link itself
1094 string const parattr = "href='#" + par.magicLabel() + "'";
1095 xs << (entry_number == 0 ? ":" : ",");
1096 xs << " " << xml::StartTag("a", parattr)
1097 << ++entry_number << xml::EndTag("a");
1100 // now we have to close all the open levels
1102 xs << xml::EndTag("li") << xml::EndTag("ul") << xml::CR();
1105 xs << xml::EndTag("div") << xml::CR();