00001 #ifndef DV_PROPS_H 00002 #define DV_PROPS_H 00003 // $Id: props.h,v 1.33 2003/10/07 12:06:45 dvermeir Exp $ 00004 00005 /** \file 00006 * A \ref Dv::Util::Props class object represents a set of key-value 00007 * pairs (aka property list). Prop object can be written to and read 00008 * from a stream, making them suitable to implement e.g. configuration 00009 * files. 00010 * 00011 * The Dv::Util::env2props(Props&) function loads the shell environment 00012 * (man getenv) into a Dv::Util::Props object. 00013 */ 00014 00015 00016 #include <string> 00017 #include <iterator> 00018 #include <iostream> 00019 #include <map> 00020 #include <stdexcept> // runtime_error 00021 00022 namespace Dv { 00023 namespace Util { 00024 00025 /** 00026 * Exception class associated with Props. 00027 * 00028 * Derived from runtime_error, i.e. the only useful member function 00029 * is PropsException::what() which returns constant C string 00030 * describing the error. 00031 */ 00032 class PropsException: public std::runtime_error { 00033 public: 00034 /** 00035 * Name of class, is prepended to each message argument of the 00036 * constructor. 00037 */ 00038 static const std::string NAME; 00039 /** 00040 * Constructor, prepends NAME to message to obtain runtime_error::what(). 00041 */ 00042 PropsException(const std::string& message): std::runtime_error(NAME+": "+message) {} 00043 }; 00044 00045 /** 00046 * A propert list class. 00047 * 00048 * A Props object represents a mapping from key strings to value strings. 00049 * Values can be accessed with Props::operator[], using both 00050 * strings or integers as a key (the latter will first be converted 00051 * to strings); the resulting value will be converted to string or 00052 * int, as required. The result of Props::operator[]} can 00053 * be assigned to, thus giving a simple way to update. 00054 * 00055 * A Props objects can be read from and written to a stream, in 00056 * a format that is illustrated below (| indicates the beginning of a line): 00057 * 00058 * \verbatim 00059 |database=my-database 00060 |query=select name, semester 00061 |>from course 00062 |host=mainserver 00063 |# a comment 00064 |age=99 00065 \endverbatim 00066 * 00067 * Note that 00068 * <ul> 00069 * <li> Empty lines (with only white space chars) are ignored. 00070 * <li>Lines starting with '#' are ignored (useful for comments). 00071 * <li>A property key has the format \a X \a y where \a X is any character 00072 * except white space, \a ``#'' and \a ``='' and \a y is a possibly 00073 * empty string of characters not containing \a ``=''. 00074 * <li>A property key may also be empty. 00075 * <li>White space is copied literally, in keys as well as in values. 00076 * <li> A new line in a value will be represented by a \a ``>'' in the 00077 * first position of the following line. 00078 * </ul> 00079 * 00080 */ 00081 class Props { 00082 public: 00083 /** 00084 * A class representing a reference to a key-value pair in a Props object. 00085 * A non-const access with Props::operator[] returns an object 00086 * of this type. It supports several convertors and assignment operations 00087 * to support code like 00088 * \code 00089 * props["abc"] = 32; 00090 * \endcode 00091 * which will use Props::Value::operator=(int) do perform the 00092 * assignment. 00093 */ 00094 class Value { 00095 public: 00096 /** 00097 * Friend class. 00098 */ 00099 friend class Props; 00100 00101 //@{ 00102 /** 00103 * Props::Value::operator>>() can be used to set its reference parameter 00104 * with the value associated with the Props::Value object. 00105 * It will throw an exception, if there is no such value. 00106 */ 00107 Props& operator>>(std::string&) const throw (PropsException); 00108 Props& operator>>(int&) const throw (PropsException); 00109 Props& operator>>(unsigned int&) const throw (PropsException); 00110 Props& operator>>(long&) const throw (PropsException); 00111 Props& operator>>(unsigned long&) const throw (PropsException); 00112 Props& operator>>(short&) const throw (PropsException); 00113 Props& operator>>(unsigned short&) const throw (PropsException); 00114 Props& operator>>(double&) const throw (PropsException); 00115 //@} 00116 00117 /** 00118 * Output operator needed to avoid ambiguity 00119 * E.g. 00120 * \code 00121 * cout << props["key"]; 00122 * \endcode 00123 * leaves too many conversions for the compiler. 00124 */ 00125 friend std::ostream& operator<<(std::ostream&,const Props::Value&); 00126 00127 //@{ 00128 /** 00129 * The conversion operators have a similar functionality as the 00130 * Props::Value::operator>>() function members. 00131 * Note how the string& conversion returns a reference, making 00132 * examples such as the following efficient. 00133 * \code 00134 * int i(props["f"]); 00135 * std::string& huge_string(props["text"]); 00136 * \endcode 00137 */ 00138 operator int() const throw (PropsException); 00139 operator long() const throw (PropsException); 00140 operator short() const throw (PropsException); 00141 operator unsigned int() const throw (PropsException); 00142 operator unsigned long() const throw (PropsException); 00143 operator unsigned short() const throw (PropsException); 00144 operator double() const throw (PropsException); 00145 operator std::string&() throw (PropsException); 00146 /** Explicit "conversion" to std::string. 00147 * This function is handy in cases such as the following: 00148 * @code 00149 * std::string s; 00150 * s = props[key]; 00151 * @endcode 00152 * which, under gcc-3.3.1 yields a compiler error due to an ambiguity between 00153 * @code 00154 * std::string::operator=(const char); 00155 * std::string::operator=(const std::string); 00156 * @endcode 00157 * and note that the conversion of Value to int provides also a conversion 00158 * to char (apparently of the same "quality"). 00159 * @return reference to static_cast<std::string&>(*this) 00160 * @sa Dv::Util::Props::ConstValue::str 00161 */ 00162 std::string& str() throw (PropsException) { return static_cast<std::string&>(*this); } 00163 //@} 00164 00165 //@{ 00166 /** 00167 * Props::Value::operator=() sets the value associated with 00168 * the key corresponding to Props::Value. Non-string 00169 * arguments are first converted to strings. 00170 */ 00171 Props& operator=(int); 00172 Props& operator=(long); 00173 Props& operator=(short); 00174 Props& operator=(unsigned int); 00175 Props& operator=(unsigned long); 00176 Props& operator=(unsigned short); 00177 Props& operator=(const double&); 00178 Props& operator=(const std::string&); 00179 //@} 00180 00181 private: 00182 Value(Props& props,const std::string& key,std::string* value): 00183 props_(props), key_(key), value_(value) {} 00184 Value(const Value&); // forbidden cctor 00185 Value& operator=(const Value&); // forbidden assignment 00186 00187 Props& props_; 00188 const std::string key_; 00189 std::string* value_; 00190 }; 00191 00192 /** 00193 * A class representing a reference to a key-value pair in a Props object. 00194 * 00195 * The difference with Props::Value is that there are no assignment 00196 * operators (because the underlying Props object is constant. 00197 */ 00198 class ConstValue { 00199 public: 00200 /** 00201 * Friend class. 00202 */ 00203 friend class Props; 00204 00205 //@{ 00206 /** 00207 * Props::Value::operator>>() can be used to set its reference parameter 00208 * with the value associated with the Props::Value object. 00209 * It will throw an exception, if there is no such value. 00210 */ 00211 const Props& operator>>(std::string&) const throw (PropsException); 00212 const Props& operator>>(int&) const throw (PropsException); 00213 const Props& operator>>(long&) const throw (PropsException); 00214 const Props& operator>>(short&) const throw (PropsException); 00215 const Props& operator>>(unsigned int&) const throw (PropsException); 00216 const Props& operator>>(unsigned long&) const throw (PropsException); 00217 const Props& operator>>(unsigned short&) const throw (PropsException); 00218 const Props& operator>>(double&) const throw (PropsException); 00219 //@} 00220 00221 //@{ 00222 /** 00223 * The conversion operators have a similar functionality as the 00224 * Props::Value::operator>>() function members. 00225 * Note how the std::string& conversion returns a reference, making 00226 * examples such as the following efficient. 00227 * \code 00228 * int i(props["f"]); 00229 * const std::string& huge_string(props["text"]); 00230 * \endcode 00231 */ 00232 operator int() const throw (PropsException); 00233 operator long() const throw (PropsException); 00234 operator short() const throw (PropsException); 00235 operator unsigned int() const throw (PropsException); 00236 operator unsigned long() const throw (PropsException); 00237 operator unsigned short() const throw (PropsException); 00238 operator double() const throw (PropsException); 00239 operator const std::string&() const throw (PropsException); 00240 /** Explicit "conversion" to std::string. 00241 * This function is handy in cases such as the following: 00242 * @code 00243 * std::string s; 00244 * s = props[key]; 00245 * @endcode 00246 * which, under gcc-3.3.1 yields a compiler error due to an ambiguity between 00247 * @code 00248 * std::string::operator=(const char); 00249 * std::string::operator=(const std::string); 00250 * @endcode 00251 * and note that the conversion of Value to int provides also a conversion 00252 * to char (apparently of the same "quality"). 00253 * @return @a static_cast<const std::string&>(*this) 00254 * @sa Dv::Util::Props::Value::str 00255 */ 00256 const std::string& str() const throw (PropsException) { return static_cast<const std::string&>(*this); } 00257 //@} 00258 /** 00259 * Output operator needed to avoid ambiguity 00260 * E.g. 00261 * \code 00262 * cout << props["key"]; 00263 * \endcode 00264 * leaves too many conversions for the compiler. 00265 */ 00266 friend std::ostream& operator<<(std::ostream&,const Props::ConstValue&); 00267 00268 private: 00269 ConstValue(const Props& props,const std::string& key,const std::string* value): 00270 props_(props), key_(key), value_(value) {} 00271 ConstValue(const ConstValue&); // forbidden cctor 00272 ConstValue& operator=(const ConstValue&); // forbidden assignment 00273 00274 const Props& props_; 00275 const std::string key_; 00276 const std::string* value_; 00277 }; 00278 00279 /** 00280 * The underlying implementation type. 00281 */ 00282 typedef std::map<std::string,std::string*> propmap; 00283 00284 /** 00285 * Default constructor. 00286 */ 00287 Props() {} 00288 /** 00289 * Reads value=key pairs from istream. 00290 * 00291 * \param is istream from which \a key=value pairs are are read. 00292 * 00293 * This constructor uses Props::read(\a istream&, \a -1) to read 00294 * the rest of the open istream into a Props object. 00295 * This constructor is convenient to read in configuration files 00296 * in a format as described above. 00297 * \code 00298 * ifstream ifs("/usr/local/share/z.config"); 00299 * Props config(ifs); 00300 * try { 00301 * int max = config["max"]; 00302 * } 00303 * catch (PropsException& e) { 00304 * cerr << "Configuration file does not define 'max'" << endl; 00305 * exit(1); 00306 * } 00307 * \endcode 00308 * \warning 00309 * Note that this constructor does not work properly with streams 00310 * that have no clear ``\a eof'' indication, e.g. simple network connections. 00311 * \see Props 00312 */ 00313 Props(std::istream& is); 00314 /** 00315 * Virtual destructor. 00316 */ 00317 virtual ~Props(); 00318 //@{ 00319 /** 00320 * Key-based ``r-value'' access to properties. 00321 * \param key value, can be of a variety of types, it will be 00322 * converted to std::string. 00323 * \return a Props::ConstValue which can be converted to 00324 * a variety of types. 00325 * \exception PropsException if no value for \a key can be found. 00326 * \see Props::ConstValue::operator int 00327 * 00328 * This overloaded member functions supports, together with the 00329 * conversion functions for Props::ConstValue, simple retrieval 00330 * a value corresponding to a given key, as illustrated in the 00331 * following example. 00332 * \code 00333 * props["name"] = "lisa"; 00334 * props[0] = "zero"; 00335 * props[10] = 11; 00336 * props["age"] = 21; 00337 * 00338 * int age(props["age"]); 00339 * std::string name(props["name"]); 00340 * int n(props[10]); 00341 * \endcode 00342 */ 00343 ConstValue operator[](const std::string& key) const throw (PropsException); 00344 ConstValue operator[](int key) const throw (PropsException); 00345 ConstValue operator[](unsigned int key) const throw (PropsException); 00346 ConstValue operator[](long key) const throw (PropsException); 00347 ConstValue operator[](unsigned long key) const throw (PropsException); 00348 ConstValue operator[](short key) const throw (PropsException); 00349 ConstValue operator[](unsigned short key) const throw (PropsException); 00350 ConstValue operator[](const double& key) const throw (PropsException); 00351 //@} 00352 //@{ 00353 /** 00354 * Key-based l-value access to properties. 00355 * \param k key value, can be of a variety of types, it will be 00356 * converted to std::string. 00357 * \return a Props::Value object which can be assigned to. 00358 * 00359 * These member functions support, together with the conversion and assignment 00360 * functions for Props::Value simple retrieval and assignment of a value 00361 * corresponding to a given \a key, as illustrated in the following example. 00362 * 00363 * \code 00364 * props["name"] = "lisa"; 00365 * props[0] = "zero"; 00366 * props[10] = 11; 00367 * props["age"] = 21; 00368 * 00369 * int age(props["age"]); 00370 * std::string name(props["name"]); 00371 * int n(props[10]); 00372 * \endcode 00373 */ 00374 Value operator[](const std::string& k); 00375 Value operator[](int k); 00376 Value operator[](unsigned int k); 00377 Value operator[](long k); 00378 Value operator[](unsigned long k); 00379 Value operator[](short k); 00380 Value operator[](unsigned short k); 00381 Value operator[](const double& k); 00382 //@} 00383 00384 /** 00385 * Erase any value that is associated with key. 00386 * \param key for which the associated \a key=\a value pair will be 00387 * removed. 00388 */ 00389 Props& erase(const std::string& key); 00390 /** 00391 * Erase all \a key = \a value pairs from the property list. 00392 * \see Props::erase 00393 */ 00394 Props& clear() { props_.clear(); return *this; } 00395 00396 /** 00397 * Write the Props object to the stream, in the format described above. 00398 */ 00399 friend std::ostream& operator<<(std::ostream&,const Props &); 00400 00401 /** 00402 * Add a \a key = \a value pair. 00403 * Use Props::operator[] for automatic type conversion to std::string. 00404 * 00405 * \param key std::string. 00406 * \param value std::string. 00407 * \return reference to \a *this. 00408 * \warning 00409 * The value added overrides any existing \a value for \a key. 00410 * \see Props::operator[] 00411 * 00412 */ 00413 Props& add(const std::string& key,const std::string& value); 00414 /** 00415 * Append to a value associated with a \a key. 00416 * The \a value, preceded by the \a separator, is appended to the current 00417 * value associated with \a key. 00418 * If there is no current value, Props::append behaves like Props::add. 00419 * 00420 * \param key for which the value is to be extended 00421 * \param value to append to the current value of \a key 00422 * \param separator is appended to \a (*this)[key] before \a value, 00423 * if \a (*this)[key] was not yet defined. 00424 * \see Props::add 00425 */ 00426 Props& append(const std::string& key, const std::string& value, 00427 const std::string& separator=" "); 00428 00429 /** 00430 * Try to find value for key, return null if not found. 00431 * \param key to search for. 00432 * \return pointer to \a value associated with \a key or 0 if none 00433 * found. 00434 */ 00435 const std::string* find(const std::string& key) const; 00436 /** 00437 * Try to find value for key, return null if not found. 00438 * \param key to search for. 00439 * \see Props::find 00440 */ 00441 const std::string* operator()(const std::string& key) const { return find(key); } 00442 00443 /** 00444 * Map iterator, *iterator is a pair<std::string,*std::string>. 00445 */ 00446 typedef propmap::const_iterator iterator; 00447 /** 00448 * \return iterator referring to the first \a key=value pair. 00449 */ 00450 iterator begin() const { return props_.begin(); } 00451 /** 00452 * \return iterator referring past the last \a key=value pair. 00453 */ 00454 iterator end() const { return props_.end(); } 00455 /** 00456 * \return number of \a key=value pairs in the property list. 00457 */ 00458 unsigned size() const { return props_.size(); } 00459 00460 /** 00461 * Read \a key-value pairs from istream until the end of the stream 00462 * is reached or a syntax error occurs. 00463 * \param is stream to read from. 00464 * \param props property object to update. 00465 * \return reference to \a is. 00466 * \warning 00467 * Existing key values will be overriden. 00468 * \see Props::read 00469 */ 00470 friend std::istream& operator>>(std::istream& is, Props& props); 00471 00472 /** 00473 * Read \a n \a key=value pairs from istream, or until \a eof 00474 * if \a n <\a 0. 00475 * Note that the Props object is not cleared before reading 00476 * the \a key=value pairs, they are simply added or override 00477 * existing values. 00478 * \param is stream to read from. 00479 * \param n number of \a key=value pairs to read. If \a n<0, reading 00480 * will continue until \a eof. 00481 * \return reference to is. 00482 */ 00483 std::istream& read(std::istream& is, int n=-1); 00484 00485 /** 00486 * Return a reference to the \a key=value pair with the given key. 00487 * This is an auxiliary function. It supports expressions such as 00488 * the following. 00489 * 00490 * \code 00491 * int value; 00492 * Props props; 00493 * props >> "key" >> value; 00494 * \endcode 00495 * 00496 * \param key to search for. 00497 * \return a ConstValue which can be converted to any type using 00498 * ConstValue::operator>>. 00499 * \warning The conversion from the Props::ConstValue will throw 00500 * an exception if there was no \a value corresponding to \a key. 00501 * \see ConstValue::operator>> 00502 */ 00503 ConstValue operator>>(const char* key) const { 00504 return ConstValue(*this,key,find(key)); } 00505 00506 /** 00507 * Recursively substitute variable occurrences in sin to yield sout. 00508 * \param sin stream that is appended to \a sout. 00509 * \param sout stream that is appended to. 00510 * \param f function that transforms strings. ${name} 00511 * in \a sin is replaced by \a f((*this)[name]). 00512 * \exception invalid_argument is thrown upon a syntax error. 00513 * 00514 * Sin is a stream, the contents which is appended to sout except for variable 00515 * occurrences of the form ${key} or %{key} which 00516 * are replaced by \a f((*this)[key]) and \a (*this)[key], 00517 * respectively (the ``$'' sign can be escaped using ``\''). 00518 * 00519 * Undefined (in *this) variables are replaced by the empty std::string. 00520 * 00521 * Example: 00522 * \code 00523 * std::string query("select * from"); 00524 * props.substitute("person where name = '${name}' and age = %{age}", 00525 * query,MySql::escape); 00526 * \endcode 00527 * 00528 * Syntax: 00529 * <ul> 00530 * <li> Variables start with ``${'' or ``%{'' except if the 00531 * ``$'' or ``%'' is preceeded by a backslash (``\''). 00532 * <li> A backslash is a normal character, except if it 00533 * is immediately followed by a ``$'' or a ``%'', in which 00534 * case it is not copied. 00535 * </ul> 00536 */ 00537 void substitute(std::istream& sin,std::ostream& sout, 00538 std::string (*f)(const std::string&) = 0) const throw (std::invalid_argument); 00539 /** 00540 * Recursively substitute variable occurrences in sin to yield sout. 00541 * 00542 * \param sin std::string that is appended to \a sout. 00543 * \param sout std::string that is appended to. 00544 * \param f function that transforms strings. ${name} 00545 * in \a sin is replaced by \a f((*this)[name]). 00546 * \exception invalid_argument is thrown upon a syntax error. 00547 * 00548 * Sin is a std::string which is appended to sout except for variable 00549 * occurrences of the form ${key} or %{key} which 00550 * are replaced by \a f((*this)[key]) and \a (*this)[key], 00551 * respectively (the ``$'' sign can be escaped using ``\''). 00552 * 00553 * Undefined (in \a *this) variables are replaced by the empty std::string. 00554 * 00555 * Syntax: 00556 * <ul> 00557 * <li> Variables start with ``${'' or ``%{'' except if the 00558 * ``$'' or ``%'' is preceeded by a backslash (``\''). 00559 * <li> A backslash is a normal character, except if it 00560 * is immediately followed by a ``$'' or a ``%'', in which 00561 * case it is not copied. 00562 * </ul> 00563 */ 00564 void substitute(const std::string& sin, std::string& sout, 00565 std::string (*f)(const std::string&) = 0) const throw (std::invalid_argument); 00566 /** 00567 * Same as above but returns result std::string. 00568 * \param sin std::string that is appended to return value. 00569 * \param f function that transforms strings. ${name} 00570 * in \a sin is replaced by \a f((*this)[name]). 00571 * \return std::string with subsitutions performed. 00572 * \exception invalid_argument is thrown upon a syntax error. 00573 * \see Props::substitute 00574 */ 00575 std::string substitute(const std::string& sin, std::string (*f)(const std::string&) = 0) 00576 const throw (std::invalid_argument); 00577 00578 /** 00579 * Insert selected \a subkey=value pairs of this object into the target. 00580 * The selected pairs are those for which the \a key starts with 00581 * the \a scope std::string followed by '\a ::'. The pairs will be inserted 00582 * with a \a key consisting of the remainder of the original key, 00583 * after removing the \a scope std::string and '::'. 00584 * 00585 * Example. 00586 * \code 00587 * Props config; 00588 * Props dbconfig; 00589 * config.select("database",dbconfig); 00590 * \endcode 00591 * 00592 * In the example, if <tt>config["database::name"] == "admindb"</tt> 00593 * then <tt>dbconfig["name"] == "admindb"</tt> after the call to 00594 * <tt>select</tt>. 00595 * 00596 * The function returns a reference to its second first argument. 00597 * 00598 * \param target the Props object the selected pairs will be added to. 00599 * \param scope the selected pairs have ``<tt>scope::</tt>'' as a key. 00600 * \return a reference to \a target. 00601 */ 00602 Props& select(const std::string& scope,Props& target) const; 00603 00604 /** 00605 * Load all \a key-value pairs from another props object under a certain 00606 * scope. 00607 * All key-value pairs from \a source will be added to \a *this, 00608 * where the key will be prefixed with <tt>scope::</tt> if the size 00609 * of \a scope is not zero. 00610 * 00611 * Example usage: 00612 * \code 00613 * Props dbconfig; 00614 * Props config; 00615 * config.merge(dbconfig,"database"); 00616 * \endcode 00617 * 00618 * If <tt>dbconfig["name"]=="admindb"</tt> then 00619 * <tt>config["database::name"]=="admindb"</tt> after the merge. 00620 * 00621 * \param source the Props object from which \a key=value pairs will 00622 * be copied. 00623 * \param scope only \a key=value pairs where \a key starts with \a scope:: 00624 * will be copied. 00625 * \return reference to \a *this. 00626 * \warning \a &source must be different from \a this. 00627 * \see Dv::Util::Props::select 00628 */ 00629 Props& merge(const Props& source,const std::string& scope="") throw (PropsException); 00630 /** 00631 * Compare two Props objects. 00632 */ 00633 bool operator==(const Props&) const; 00634 bool operator!=(const Props& p) const { return !(*this == p); } 00635 00636 /** 00637 * Send a props object over a stream. 00638 * The difference with e.g. 00639 * \code 00640 * ostream& operator<<(ostream&,const Props::Value&); 00641 * \endcode 00642 * is that Props::send also works over network connections where 00643 * the end of the input is not indicated by \a EOF. 00644 * An object sent by Props::send() should be received 00645 * by Props::receive(). 00646 * \param os stream to write Props object to. 00647 * \see Dv::Util::Props::receive 00648 */ 00649 std::ostream& send(std::ostream& os) const; 00650 /** 00651 * Receive a props object from a stream. 00652 * The difference with e.g. 00653 * \code 00654 * Props(istream&); 00655 * \endcode 00656 * is that Props::receive also works over network connections where 00657 * the end of the input is not indicated by \a EOF. 00658 * An object received by Props::receive() should have been sent 00659 * by Props::send(). 00660 * 00661 * \param is stream from which to read the Props object. 00662 * \warning 00663 * Note that the object is not cleared before receiving new 00664 * \a key=value pairs. 00665 * \see Dv::Util::Props::send 00666 */ 00667 std::istream& receive(std::istream& is); 00668 /** 00669 * Copy construction is allowed. 00670 */ 00671 Props(const Props& props) throw (PropsException); 00672 /** 00673 * Assignment allowed. 00674 */ 00675 Props& operator=(const Props& props) throw (PropsException); 00676 private: 00677 // Try to find value for key, return null if not found 00678 std::string* found(const std::string& key); 00679 00680 // Using a value type of std::string* allows us to return a reference 00681 // to a value (operator[]) [values don't move], which is good 00682 // for efficiency when retrieving large values. 00683 propmap props_; 00684 }; 00685 00686 /** 00687 * This function adds its environment to its props parameter. 00688 * It returns a reference to its parameter. 00689 * See man (5) environ for information about the environment. 00690 * \param props to add environment variables to. 00691 * \return reference to \a props. 00692 */ 00693 Props& env2props(Props& props); 00694 00695 }} 00696 #endif
dvutil-0.13.15 | [30 December, 2004] |