• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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