Brian Silverman | 70325d6 | 2015-09-20 17:00:43 -0400 | [diff] [blame] | 1 | // Copyright (c) 2006, Google Inc. |
| 2 | // All rights reserved. |
| 3 | // |
| 4 | // Redistribution and use in source and binary forms, with or without |
| 5 | // modification, are permitted provided that the following conditions are |
| 6 | // met: |
| 7 | // |
| 8 | // * Redistributions of source code must retain the above copyright |
| 9 | // notice, this list of conditions and the following disclaimer. |
| 10 | // * Redistributions in binary form must reproduce the above |
| 11 | // copyright notice, this list of conditions and the following disclaimer |
| 12 | // in the documentation and/or other materials provided with the |
| 13 | // distribution. |
| 14 | // * Neither the name of Google Inc. nor the names of its |
| 15 | // contributors may be used to endorse or promote products derived from |
| 16 | // this software without specific prior written permission. |
| 17 | // |
| 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 | |
| 30 | // --- |
| 31 | // Author: csilvers@google.com (Craig Silverstein) |
| 32 | // |
| 33 | // Based on the 'old' TemplateDictionary by Frank Jernigan. |
| 34 | // |
| 35 | // A template dictionary maps names (as found in template files) |
| 36 | // to their values. There are three types of names: |
| 37 | // variables: value is a string. |
| 38 | // sections: value is a list of sub-dicts to use when expanding the section; |
| 39 | // the section is expanded once per sub-dict. |
| 40 | // template-include: value is a list of pairs: name of the template file |
| 41 | // to include, and the sub-dict to use when expanding it. |
| 42 | // TemplateDictionary has routines for setting these values. |
| 43 | // |
| 44 | // For (many) more details, see the doc/ directory. |
| 45 | |
| 46 | #ifndef TEMPLATE_TEMPLATE_DICTIONARY_H_ |
| 47 | #define TEMPLATE_TEMPLATE_DICTIONARY_H_ |
| 48 | |
| 49 | #include <stdarg.h> // for StringAppendV() |
| 50 | #include <stddef.h> // for size_t and ptrdiff_t |
| 51 | #include <stdlib.h> // for NULL |
| 52 | #include <sys/types.h> |
| 53 | #include <functional> // for less<> |
| 54 | #include <map> |
| 55 | #include <string> |
| 56 | #include <vector> |
| 57 | |
| 58 | #include <ctemplate/str_ref.h> |
| 59 | #include <ctemplate/template_dictionary_interface.h> |
| 60 | #include <ctemplate/template_modifiers.h> |
| 61 | #include <ctemplate/template_string.h> |
| 62 | |
| 63 | // NOTE: if you are statically linking the template library into your binary |
| 64 | // (rather than using the template .dll), set '/D CTEMPLATE_DLL_DECL=' |
| 65 | // as a compiler flag in your project file to turn off the dllimports. |
| 66 | #ifndef CTEMPLATE_DLL_DECL |
| 67 | # define CTEMPLATE_DLL_DECL __declspec(dllimport) |
| 68 | #endif |
| 69 | |
| 70 | namespace ctemplate { |
| 71 | template <class T, class C> class ArenaAllocator; |
| 72 | class UnsafeArena; |
| 73 | template<typename A, int B, typename C, typename D> class small_map; |
| 74 | template<typename NormalMap> class small_map_default_init; // in small_map.h |
| 75 | } |
| 76 | |
| 77 | namespace ctemplate { |
| 78 | |
| 79 | |
| 80 | class CTEMPLATE_DLL_DECL TemplateDictionary : public TemplateDictionaryInterface { |
| 81 | public: |
| 82 | // name is used only for debugging. |
| 83 | // arena is used to store all names and values. It can be NULL (the |
| 84 | // default), in which case we create own own arena. |
| 85 | explicit TemplateDictionary(const TemplateString& name, |
| 86 | UnsafeArena* arena=NULL); |
| 87 | ~TemplateDictionary(); |
| 88 | |
| 89 | // If you want to be explicit, you can use NO_ARENA as a synonym to NULL. |
| 90 | static UnsafeArena* const NO_ARENA; |
| 91 | |
| 92 | std::string name() const { |
| 93 | return std::string(name_.data(), name_.size()); |
| 94 | } |
| 95 | |
| 96 | // Returns a recursive copy of this dictionary. This dictionary |
| 97 | // *must* be a "top-level" dictionary (that is, not created via |
| 98 | // AddSectionDictionary() or AddIncludeDictionary()). Caller owns |
| 99 | // the resulting dict, and must delete it. If arena is NULL, we |
| 100 | // create our own. Returns NULL if the copy fails (probably because |
| 101 | // the "top-level" rule was violated). |
| 102 | TemplateDictionary* MakeCopy(const TemplateString& name_of_copy, |
| 103 | UnsafeArena* arena=NULL); |
| 104 | |
| 105 | // --- Routines for VARIABLES |
| 106 | // These are the five main routines used to set the value of a variable. |
| 107 | // As always, wherever you see TemplateString, you can also pass in |
| 108 | // either a char* or a C++ string, or a TemplateString(s, slen). |
| 109 | |
| 110 | void SetValue(const TemplateString variable, const TemplateString value); |
| 111 | void SetIntValue(const TemplateString variable, long value); |
| 112 | void SetFormattedValue(const TemplateString variable, const char* format, ...) |
| 113 | #if 0 |
| 114 | __attribute__((__format__ (__printf__, 3, 4))) |
| 115 | #endif |
| 116 | ; // starts at 3 because of implicit 1st arg 'this' |
| 117 | |
| 118 | class SetProxy { |
| 119 | public: |
| 120 | SetProxy(TemplateDictionary& dict, const TemplateString& variable) : |
| 121 | dict_(dict), |
| 122 | variable_(variable) { |
| 123 | } |
| 124 | |
| 125 | void operator=(str_ref value) { |
| 126 | dict_.SetValue(variable_, TemplateString(value.data(), value.size())); |
| 127 | } |
| 128 | |
| 129 | void operator=(long value) { |
| 130 | dict_.SetIntValue(variable_, value); |
| 131 | } |
| 132 | |
| 133 | private: |
| 134 | TemplateDictionary& dict_; |
| 135 | const TemplateString& variable_; |
| 136 | }; |
| 137 | |
| 138 | SetProxy operator[](const TemplateString& variable) { |
| 139 | return SetProxy(*this, variable); |
| 140 | } |
| 141 | |
| 142 | // We also let you set values in the 'global' dictionary which is |
| 143 | // referenced when all other dictionaries fail. Note this is a |
| 144 | // static method: no TemplateDictionary instance needed. Since |
| 145 | // this routine is rarely used, we don't provide variants. |
| 146 | static void SetGlobalValue(const TemplateString variable, |
| 147 | const TemplateString value); |
| 148 | |
| 149 | // This is used for a value that you want to be 'global', but only |
| 150 | // in the scope of a given template, including all its sections and |
| 151 | // all its sub-included dictionaries. The main difference between |
| 152 | // SetTemplateGlobalValue() and SetValue(), is that |
| 153 | // SetTemplateGlobalValue() values persist across template-includes. |
| 154 | // This is intended for session-global data; since that should be |
| 155 | // fairly rare, we don't provide variants. |
| 156 | void SetTemplateGlobalValue(const TemplateString variable, |
| 157 | const TemplateString value); |
| 158 | |
| 159 | // Similar SetTemplateGlobalValue above, this method shows a section in this |
| 160 | // template, all its sections, and all its template-includes. This is intended |
| 161 | // for session-global data, for example allowing you to show variant portions |
| 162 | // of your template for certain browsers/languages without having to call |
| 163 | // ShowSection on each template you use. |
| 164 | void ShowTemplateGlobalSection(const TemplateString variable); |
| 165 | |
| 166 | // These routines are like SetValue and SetTemplateGlobalValue, but |
| 167 | // they do not make a copy of the input data. THE CALLER IS |
| 168 | // RESPONSIBLE FOR ENSURING THE PASSED-IN STRINGS LIVE FOR AT LEAST |
| 169 | // AS LONG AS THIS DICTIONARY! In general, they yield a quite minor |
| 170 | // performance increase for significant increased code fragility, |
| 171 | // so do not use them unless you really need the speed improvements. |
| 172 | void SetValueWithoutCopy(const TemplateString variable, |
| 173 | const TemplateString value); |
| 174 | void SetTemplateGlobalValueWithoutCopy(const TemplateString variable, |
| 175 | const TemplateString value); |
| 176 | |
| 177 | |
| 178 | // --- Routines for SECTIONS |
| 179 | // We show a section once per dictionary that is added with its name. |
| 180 | // Recall that lookups are hierarchical: if a section tried to look |
| 181 | // up a variable in its sub-dictionary and fails, it will look next |
| 182 | // in its parent dictionary (us). So it's perfectly appropriate to |
| 183 | // keep the sub-dictionary empty: that will show the section once, |
| 184 | // and take all var definitions from us. ShowSection() is a |
| 185 | // convenience routine that does exactly that. |
| 186 | |
| 187 | // Creates an empty dictionary whose parent is us, and returns it. |
| 188 | // As always, wherever you see TemplateString, you can also pass in |
| 189 | // either a char* or a C++ string, or a TemplateString(s, slen). |
| 190 | TemplateDictionary* AddSectionDictionary(const TemplateString section_name); |
| 191 | void ShowSection(const TemplateString section_name); |
| 192 | |
| 193 | // A convenience method. Often a single variable is surrounded by |
| 194 | // some HTML that should not be printed if the variable has no |
| 195 | // value. The way to do this is to put that html in a section. |
| 196 | // This method makes it so the section is shown exactly once, with a |
| 197 | // dictionary that maps the variable to the proper value. If the |
| 198 | // value is "", on the other hand, this method does nothing, so the |
| 199 | // section remains hidden. |
| 200 | void SetValueAndShowSection(const TemplateString variable, |
| 201 | const TemplateString value, |
| 202 | const TemplateString section_name); |
| 203 | |
| 204 | |
| 205 | // --- Routines for TEMPLATE-INCLUDES |
| 206 | // Included templates are treated like sections, but they require |
| 207 | // the name of the include-file to go along with each dictionary. |
| 208 | |
| 209 | TemplateDictionary* AddIncludeDictionary(const TemplateString variable); |
| 210 | |
| 211 | // This is required for include-templates; it specifies what template |
| 212 | // to include. But feel free to call this on any dictionary, to |
| 213 | // document what template-file the dictionary is intended to go with. |
| 214 | void SetFilename(const TemplateString filename); |
| 215 | |
| 216 | // --- DEBUGGING TOOLS |
| 217 | |
| 218 | // Logs the contents of a dictionary and its sub-dictionaries. |
| 219 | // Dump goes to stdout/stderr, while DumpToString goes to the given string. |
| 220 | // 'indent' is how much to indent each line of the output. |
| 221 | void Dump(int indent=0) const; |
| 222 | virtual void DumpToString(std::string* out, int indent=0) const; |
| 223 | |
| 224 | |
| 225 | // --- DEPRECATED ESCAPING FUNCTIONALITY |
| 226 | |
| 227 | // Escaping in the binary has been deprecated in favor of using modifiers |
| 228 | // to do the escaping in the template: |
| 229 | // "...{{MYVAR:html_escape}}..." |
| 230 | void SetEscapedValue(const TemplateString variable, const TemplateString value, |
| 231 | const TemplateModifier& escfn); |
| 232 | void SetEscapedFormattedValue(const TemplateString variable, |
| 233 | const TemplateModifier& escfn, |
| 234 | const char* format, ...) |
| 235 | #if 0 |
| 236 | __attribute__((__format__ (__printf__, 4, 5))) |
| 237 | #endif |
| 238 | ; // starts at 4 because of implicit 1st arg 'this' |
| 239 | void SetEscapedValueAndShowSection(const TemplateString variable, |
| 240 | const TemplateString value, |
| 241 | const TemplateModifier& escfn, |
| 242 | const TemplateString section_name); |
| 243 | |
| 244 | |
| 245 | private: |
| 246 | friend class SectionTemplateNode; // for access to GetSectionValue(), etc. |
| 247 | friend class TemplateTemplateNode; // for access to GetSectionValue(), etc. |
| 248 | friend class VariableTemplateNode; // for access to GetSectionValue(), etc. |
| 249 | // For unittesting code using a TemplateDictionary. |
| 250 | friend class TemplateDictionaryPeer; |
| 251 | |
| 252 | class DictionaryPrinter; // nested class |
| 253 | friend class DictionaryPrinter; |
| 254 | |
| 255 | // We need this functor to tell small_map how to create a map<> when |
| 256 | // it decides to do so: we want it to create that map on the arena. |
| 257 | class map_arena_init; |
| 258 | |
| 259 | typedef std::vector<TemplateDictionary*, |
| 260 | ArenaAllocator<TemplateDictionary*, UnsafeArena> > |
| 261 | DictVector; |
| 262 | // The '4' here is the size where small_map switches from vector<> to map<>. |
| 263 | typedef small_map<std::map<TemplateId, TemplateString, std::less<TemplateId>, |
| 264 | ArenaAllocator<std::pair<const TemplateId, TemplateString>, |
| 265 | UnsafeArena> >, |
| 266 | 4, std::equal_to<TemplateId>, map_arena_init> |
| 267 | VariableDict; |
| 268 | typedef small_map<std::map<TemplateId, DictVector*, std::less<TemplateId>, |
| 269 | ArenaAllocator<std::pair<const TemplateId, DictVector*>, |
| 270 | UnsafeArena> >, |
| 271 | 4, std::equal_to<TemplateId>, map_arena_init> |
| 272 | SectionDict; |
| 273 | typedef small_map<std::map<TemplateId, DictVector*, std::less<TemplateId>, |
| 274 | ArenaAllocator<std::pair<const TemplateId, DictVector*>, |
| 275 | UnsafeArena> >, |
| 276 | 4, std::equal_to<TemplateId>, map_arena_init> |
| 277 | IncludeDict; |
| 278 | // This is used only for global_dict_, which is just like a VariableDict |
| 279 | // but does not bother with an arena (since this memory lives forever). |
| 280 | typedef small_map<std::map<TemplateId, TemplateString, std::less<TemplateId> >, |
| 281 | 4, std::equal_to<TemplateId>, |
| 282 | small_map_default_init< |
| 283 | std::map<TemplateId, TemplateString, |
| 284 | std::less<TemplateId> > > > |
| 285 | GlobalDict; |
| 286 | |
| 287 | |
| 288 | // These are helper functions to allocate the parts of the dictionary |
| 289 | // on the arena. |
| 290 | template<typename T> inline void LazilyCreateDict(T** dict); |
| 291 | inline void LazyCreateTemplateGlobalDict(); |
| 292 | inline DictVector* CreateDictVector(); |
| 293 | inline TemplateDictionary* CreateTemplateSubdict( |
| 294 | const TemplateString& name, |
| 295 | UnsafeArena* arena, |
| 296 | TemplateDictionary* parent_dict, |
| 297 | TemplateDictionary* template_global_dict_owner); |
| 298 | |
| 299 | // This is a helper function to insert <key,value> into m. |
| 300 | // Normally, we'd just use m[key] = value, but map rules |
| 301 | // require default constructor to be public for that to compile, and |
| 302 | // for some types we'd rather not allow that. HashInsert also inserts |
| 303 | // the key into an id(key)->key map, to allow for id-lookups later. |
| 304 | template<typename MapType, typename ValueType> |
| 305 | static void HashInsert(MapType* m, TemplateString key, ValueType value); |
| 306 | |
| 307 | // Constructor created for all children dictionaries. This includes |
| 308 | // both a pointer to the parent dictionary and also the the |
| 309 | // template-global dictionary from which all children (both |
| 310 | // IncludeDictionary and SectionDictionary) inherit. Values are |
| 311 | // filled into global_template_dict via SetTemplateGlobalValue. |
| 312 | explicit TemplateDictionary(const TemplateString& name, |
| 313 | class UnsafeArena* arena, |
| 314 | TemplateDictionary* parent_dict, |
| 315 | TemplateDictionary* template_global_dict_owner); |
| 316 | |
| 317 | // Helps set up the static stuff. Must be called exactly once before |
| 318 | // accessing global_dict_. GoogleOnceInit() is used to manage that |
| 319 | // initialization in a thread-safe way. |
| 320 | static void SetupGlobalDict(); |
| 321 | |
| 322 | // Utility functions for copying a string into the arena. |
| 323 | // Memdup also copies in a trailing NUL, which is why we have the |
| 324 | // trailing-NUL check in the TemplateString version of Memdup. |
| 325 | TemplateString Memdup(const char* s, size_t slen); |
| 326 | TemplateString Memdup(const TemplateString& s) { |
| 327 | if (s.is_immutable() && s.data()[s.size()] == '\0') { |
| 328 | return s; |
| 329 | } |
| 330 | return Memdup(s.data(), s.size()); |
| 331 | } |
| 332 | |
| 333 | // Used for recursive MakeCopy calls. |
| 334 | TemplateDictionary* InternalMakeCopy( |
| 335 | const TemplateString& name_of_copy, |
| 336 | UnsafeArena* arena, |
| 337 | TemplateDictionary* parent_dict, |
| 338 | TemplateDictionary* template_global_dict_owner); |
| 339 | |
| 340 | // A helper for creating section and include dicts. |
| 341 | static std::string CreateSubdictName( |
| 342 | const TemplateString& dict_name, const TemplateString& sub_name, |
| 343 | size_t index, const char* suffix); |
| 344 | |
| 345 | // Must be called whenever we add a value to one of the dictionaries above, |
| 346 | // to ensure that we can reconstruct the id -> string mapping. |
| 347 | static void AddToIdToNameMap(TemplateId id, const TemplateString& str); |
| 348 | |
| 349 | // Used to do the formatting for the SetFormatted*() functions |
| 350 | static int StringAppendV(char* space, char** out, |
| 351 | const char* format, va_list ap); |
| 352 | |
| 353 | // How Template::Expand() and its children access the template-dictionary. |
| 354 | // These fill the API required by TemplateDictionaryInterface. |
| 355 | virtual TemplateString GetValue(const TemplateString& variable) const; |
| 356 | virtual bool IsHiddenSection(const TemplateString& name) const; |
| 357 | virtual bool IsUnhiddenSection(const TemplateString& name) const { |
| 358 | return !IsHiddenSection(name); |
| 359 | } |
| 360 | virtual bool IsHiddenTemplate(const TemplateString& name) const; |
| 361 | virtual const char* GetIncludeTemplateName( |
| 362 | const TemplateString& variable, int dictnum) const; |
| 363 | |
| 364 | // Determine whether there's anything set in this dictionary |
| 365 | bool Empty() const; |
| 366 | |
| 367 | // This is needed by DictionaryPrinter because it's not a friend |
| 368 | // of TemplateString, but we are |
| 369 | static std::string PrintableTemplateString( |
| 370 | const TemplateString& ts) { |
| 371 | return std::string(ts.data(), ts.size()); |
| 372 | } |
| 373 | static bool InvalidTemplateString(const TemplateString& ts) { |
| 374 | return ts.data() == NULL; |
| 375 | } |
| 376 | // Compilers differ about whether nested classes inherit our friendship. |
| 377 | // The only thing DictionaryPrinter needs is IdToString, so just re-export. |
| 378 | static TemplateString IdToString(TemplateId id) { // for DictionaryPrinter |
| 379 | return TemplateString::IdToString(id); |
| 380 | } |
| 381 | |
| 382 | // CreateTemplateIterator |
| 383 | // This is SectionIterator exactly, just with a different name to |
| 384 | // self-document the fact the value applies to a template include. |
| 385 | // Caller frees return value. |
| 386 | virtual TemplateDictionaryInterface::Iterator* CreateTemplateIterator( |
| 387 | const TemplateString& section_name) const; |
| 388 | |
| 389 | // CreateSectionIterator |
| 390 | // Factory method implementation that constructs a iterator representing the |
| 391 | // set of dictionaries associated with a section name, if any. This |
| 392 | // implementation checks the local dictionary itself, not the template-wide |
| 393 | // dictionary or the global dictionary. |
| 394 | // Caller frees return value. |
| 395 | virtual TemplateDictionaryInterface::Iterator* CreateSectionIterator( |
| 396 | const TemplateString& section_name) const; |
| 397 | |
| 398 | // TemplateDictionary-specific implementation of dictionary iterators. |
| 399 | template <typename T> // T is *TemplateDictionary::const_iterator |
| 400 | class Iterator : public TemplateDictionaryInterface::Iterator { |
| 401 | protected: |
| 402 | friend class TemplateDictionary; |
| 403 | Iterator(T begin, T end) : begin_(begin), end_(end) { } |
| 404 | public: |
| 405 | virtual ~Iterator() { } |
| 406 | virtual bool HasNext() const; |
| 407 | virtual const TemplateDictionaryInterface& Next(); |
| 408 | private: |
| 409 | T begin_; |
| 410 | const T end_; |
| 411 | }; |
| 412 | |
| 413 | // A small helper factory function for Iterator |
| 414 | template <typename T> |
| 415 | static Iterator<typename T::const_iterator>* MakeIterator(const T& dv) { |
| 416 | return new Iterator<typename T::const_iterator>(dv.begin(), dv.end()); |
| 417 | } |
| 418 | |
| 419 | |
| 420 | // The "name" of the dictionary for debugging output (Dump, etc.) |
| 421 | // The arena, also set at construction time. |
| 422 | class UnsafeArena* const arena_; |
| 423 | bool should_delete_arena_; // only true if we 'new arena' in constructor |
| 424 | TemplateString name_; // points into the arena, or to static memory |
| 425 | |
| 426 | // The three dictionaries that I own -- for vars, sections, and template-incs |
| 427 | VariableDict* variable_dict_; |
| 428 | SectionDict* section_dict_; |
| 429 | IncludeDict* include_dict_; |
| 430 | |
| 431 | |
| 432 | // The template_global_dict is consulted if a lookup in the variable, section, |
| 433 | // or include dicts named above fails. It forms a convenient place to store |
| 434 | // session-specific data that's applicable to all templates in the dictionary |
| 435 | // tree. |
| 436 | // For the parent-template, template_global_dict_ is not NULL, and |
| 437 | // template_global_dict_owner_ is this. For all of its children, |
| 438 | // template_global_dict_ is NULL, and template_global_dict_owner_ points to |
| 439 | // the root parent-template (the one with the non-NULL template_global_dict_). |
| 440 | TemplateDictionary* template_global_dict_; |
| 441 | TemplateDictionary* template_global_dict_owner_; |
| 442 | |
| 443 | // My parent dictionary, used when variable lookups at this level fail. |
| 444 | // Note this is only for *variables* and *sections*, not templates. |
| 445 | TemplateDictionary* parent_dict_; |
| 446 | // The static, global dictionary, at the top of the parent-dictionary chain |
| 447 | static GlobalDict* global_dict_; |
| 448 | static TemplateString* empty_string_; // what is returned on lookup misses |
| 449 | |
| 450 | // The filename associated with this dictionary. If set, this declares |
| 451 | // what template the dictionary is supposed to be expanded with. Required |
| 452 | // for template-includes, optional (but useful) for 'normal' dicts. |
| 453 | const char* filename_; |
| 454 | |
| 455 | private: |
| 456 | // Can't invoke copy constructor or assignment operator |
| 457 | TemplateDictionary(const TemplateDictionary&); |
| 458 | void operator=(const TemplateDictionary&); |
| 459 | }; |
| 460 | |
| 461 | } |
| 462 | |
| 463 | |
| 464 | #endif // TEMPLATE_TEMPLATE_DICTIONARY_H_ |