1 // Copyright 2008 John Maddock
2 //
3 // Use, modification and distribution are subject to the
4 // Boost Software License, Version 1.0.
5 // (See accompanying file LICENSE_1_0.txt
6 // or copy at http://www.boost.org/LICENSE_1_0.txt)
7
8 #include "auto_index.hpp"
9 #include <boost/range.hpp>
10 #include <boost/format.hpp>
11
12 //
13 // Get a numerical ID for the next item:
14 //
get_next_index_id()15 std::string get_next_index_id()
16 {
17 static int index_id_count = 0;
18 std::stringstream s;
19 s << "idx_id_" << index_id_count;
20 ++index_id_count;
21 return s.str();
22 }
23
raise_invalid_xml(const std::string & parent,const std::string & child)24 void raise_invalid_xml(const std::string& parent, const std::string& child)
25 {
26 throw std::runtime_error("Error: element " + child + " can not appear inside the container " + parent + ": try using a different value for property \"auto-index-type\".");
27 }
28 //
29 // Validate that the container for the Index is in a valid place:
30 //
check_index_type_and_placement(const std::string & parent,const std::string & container)31 void check_index_type_and_placement(const std::string& parent, const std::string& container)
32 {
33 if(container == "section")
34 {
35 if((parent != "appendix")
36 && (parent != "article")
37 && (parent != "chapter")
38 && (parent != "partintro")
39 && (parent != "preface")
40 && (parent != "section"))
41 raise_invalid_xml(parent, container);
42 }
43 else if(container == "appendix")
44 {
45 if((parent != "article")
46 && (parent != "book")
47 && (parent != "part"))
48 raise_invalid_xml(parent, container);
49 }
50 else if(container == "index")
51 {
52 if((parent != "appendix")
53 && (parent != "article")
54 && (parent != "book")
55 && (parent != "chapter")
56 && (parent != "part")
57 && (parent != "preface")
58 && (parent != "sect1")
59 && (parent != "sect2")
60 && (parent != "sect3")
61 && (parent != "sect4")
62 && (parent != "sect5")
63 && (parent != "section")
64 )
65 raise_invalid_xml(parent, container);
66 }
67 else if((container == "article") || (container == "chapter") || (container == "reference"))
68 {
69 if((parent != "book")
70 && (parent != "part"))
71 raise_invalid_xml(parent, container);
72 }
73 else if(container == "part")
74 {
75 if(parent != "book")
76 raise_invalid_xml(parent, container);
77 }
78 else if(container == "refsect1")
79 {
80 if(parent != "refentry")
81 raise_invalid_xml(parent, container);
82 }
83 else if(container == "refsect2")
84 {
85 if(parent != "refsect1")
86 raise_invalid_xml(parent, container);
87 }
88 else if(container == "refsect3")
89 {
90 if(parent != "refsect2")
91 raise_invalid_xml(parent, container);
92 }
93 else if(container == "refsection")
94 {
95 if((parent != "refsection") && (parent != "refentry"))
96 raise_invalid_xml(parent, container);
97 }
98 else if(container == "sect1")
99 {
100 if((parent != "appendix")
101 && (parent != "article")
102 && (parent != "chapter")
103 && (parent != "partintro")
104 && (parent != "preface")
105 )
106 raise_invalid_xml(parent, container);
107 }
108 else if(container == "sect2")
109 {
110 if(parent != "sect1")
111 raise_invalid_xml(parent, container);
112 }
113 else if(container == "sect3")
114 {
115 if(parent != "sect2")
116 raise_invalid_xml(parent, container);
117 }
118 else if(container == "sect4")
119 {
120 if(parent != "sect3")
121 raise_invalid_xml(parent, container);
122 }
123 else if(container == "sect5")
124 {
125 if(parent != "sect4")
126 raise_invalid_xml(parent, container);
127 }
128 else
129 {
130 throw std::runtime_error("Error: element " + container + " is unknown, and can not be used as a container for an index: try using a different value for property \"auto-index-type\".");
131 }
132 }
133
make_element(const std::string & name)134 boost::tiny_xml::element_ptr make_element(const std::string& name)
135 {
136 boost::tiny_xml::element_ptr result(new boost::tiny_xml::element);
137 result->name = name;
138 return result;
139 }
140
add_attribute(boost::tiny_xml::element_ptr ptr,const std::string & name,const std::string & value)141 boost::tiny_xml::element_ptr add_attribute(boost::tiny_xml::element_ptr ptr, const std::string& name, const std::string& value)
142 {
143 boost::tiny_xml::attribute attr;
144 attr.name = name;
145 attr.value = value;
146 ptr->attributes.push_back(attr);
147 return ptr;
148 }
149
make_primary_key_matcher(const std::string & s)150 boost::regex make_primary_key_matcher(const std::string& s)
151 {
152 static const boost::regex e("[-_[:space:]]+|([.\\[{}()\\*+?|^$])");
153 static const char* format = "(?1\\\\$1:[-_[:space:]]+)";
154 return boost::regex(regex_replace(s, e, format, boost::regex_constants::format_all), boost::regex::icase|boost::regex::perl);
155 }
156
157 //
158 // Generate an index entry using our own internal method:
159 //
160 template <class Range>
generate_entry(const Range & range,const std::string * pcategory,int level=0,const boost::regex * primary_key=0)161 boost::tiny_xml::element_ptr generate_entry(const Range& range, const std::string* pcategory, int level = 0, const boost::regex* primary_key = 0)
162 {
163 boost::tiny_xml::element_ptr list = add_attribute(add_attribute(::add_attribute(make_element("itemizedlist"), "mark", "none"), "spacing", "compact"), "role", "index");
164
165 for(typename boost::range_iterator<Range>::type i = boost::begin(range); i != boost::end(range);)
166 {
167 std::string key = (*i)->key;
168 index_entry_set entries;
169 bool preferred = false;
170 std::string id;
171 bool collapse = false;
172
173 //
174 // Create a regular expression for comparing key to other key's:
175 //
176 boost::regex key_regex;
177 if(level == 0)
178 {
179 key_regex = make_primary_key_matcher(key);
180 primary_key = &key_regex;
181 }
182 //
183 // Begin by consolidating entries with identical keys but possibly different categories:
184 //
185 while((i != boost::end(range)) && ((*i)->key == key))
186 {
187 if((0 == pcategory) || (pcategory->size() == 0) || (pcategory && (**i).category == *pcategory))
188 {
189 entries.insert((*i)->sub_keys.begin(), (*i)->sub_keys.end());
190 if((*i)->preferred)
191 preferred = true;
192 if((*i)->id.size())
193 {
194 if(id.size())
195 {
196 std::cerr << "WARNING: two identical index terms have different link destinations!!" << std::endl;
197 }
198 id = (*i)->id;
199 }
200 }
201 ++i;
202 }
203 //
204 // Only actually generate content if we have anything in the entries set:
205 //
206 if(entries.size() || id.size())
207 {
208 //
209 // See if we can collapse any sub-entries into this one:
210 //
211 if(entries.size() == 1)
212 {
213 if((regex_match((*entries.begin())->key, *primary_key) || ((*entries.begin())->key == key))
214 && ((*entries.begin())->id.size())
215 && ((*entries.begin())->id != id))
216 {
217 collapse = true;
218 id = (*entries.begin())->id;
219 }
220 }
221 //
222 // See if this key is the same as the primary key, if it is then make it prefered:
223 //
224 if(level && regex_match(key, *primary_key))
225 {
226 preferred = true;
227 }
228 boost::tiny_xml::element_ptr item = make_element("listitem");
229 boost::tiny_xml::element_ptr para = make_element("para");
230 item->elements.push_back(para);
231 list->elements.push_back(item);
232 if(preferred)
233 {
234 para->elements.push_back(add_attribute(make_element("emphasis"), "role", "bold"));
235 para = para->elements.back();
236 }
237 if(id.size())
238 {
239 boost::tiny_xml::element_ptr link = add_attribute(make_element("link"), "linkend", id);
240 para->elements.push_back(link);
241 para = link;
242 }
243 std::string classname = (boost::format("index-entry-level-%1%") % level).str();
244 para->elements.push_back(add_attribute(make_element("phrase"), "role", classname));
245 para = para->elements.back();
246 para->content = key;
247 if(!collapse && entries.size())
248 {
249 std::pair<index_entry_set::const_iterator, index_entry_set::const_iterator> subrange(entries.begin(), entries.end());
250 item->elements.push_back(generate_entry(subrange, 0, level+1, primary_key));
251 }
252 }
253 }
254 return list;
255 }
256 //
257 // Generate indexes using our own internal method:
258 //
generate_indexes()259 void generate_indexes()
260 {
261 for(boost::tiny_xml::element_list::const_iterator i = indexes.begin(); i != indexes.end(); ++i)
262 {
263 boost::tiny_xml::element_ptr node = *i;
264 const std::string* category = find_attr(node, "type");
265 bool has_title = false;
266
267 for(boost::tiny_xml::element_list::const_iterator k = (*i)->elements.begin(); k != (*i)->elements.end(); ++k)
268 {
269 if((**k).name == "title")
270 {
271 has_title = true;
272 break;
273 }
274 }
275
276 boost::tiny_xml::element_ptr navbar = make_element("para");
277 node->elements.push_back(navbar);
278
279 index_entry_set::const_iterator m = index_entries.begin();
280 index_entry_set::const_iterator n = m;
281 boost::tiny_xml::element_ptr vlist = make_element("variablelist");
282 node->elements.push_back(vlist);
283 while(n != index_entries.end())
284 {
285 char current_letter = std::toupper((*n)->key[0]);
286 std::string id_name = get_next_index_id();
287 boost::tiny_xml::element_ptr entry = add_attribute(make_element("varlistentry"), "id", id_name);
288 boost::tiny_xml::element_ptr term = make_element("term");
289 term->content = std::string(1, current_letter);
290 entry->elements.push_back(term);
291 boost::tiny_xml::element_ptr item = make_element("listitem");
292 entry->elements.push_back(item);
293 while((n != index_entries.end()) && (std::toupper((*n)->key[0]) == current_letter))
294 ++n;
295 std::pair<index_entry_set::const_iterator, index_entry_set::const_iterator> range(m, n);
296 item->elements.push_back(generate_entry(range, category));
297 if(item->elements.size() && (*item->elements.begin())->elements.size())
298 {
299 vlist->elements.push_back(entry);
300 boost::tiny_xml::element_ptr p = make_element("");
301 p->content = " ";
302 if(navbar->elements.size())
303 {
304 navbar->elements.push_back(p);
305 }
306 p = add_attribute(make_element("link"), "linkend", id_name);
307 p->content = current_letter;
308 navbar->elements.push_back(p);
309 }
310 m = n;
311 }
312
313 node->name = internal_index_type;
314 boost::tiny_xml::element_ptr p(node->parent);
315 while(p->name.empty())
316 p = boost::tiny_xml::element_ptr(p->parent);
317 check_index_type_and_placement(p->name, node->name);
318 node->attributes.clear();
319 if(!has_title)
320 {
321 boost::tiny_xml::element_ptr t = make_element("title");
322 t->content = "Index";
323 node->elements.push_front(t);
324 }
325 }
326 }
327
328