Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/util/Convert.h @ 1057

Last change on this file since 1057 was 1056, checked in by landauf, 18 years ago

don't panic, no codechanges!
added a link to www.orxonox.net

File size: 23.2 KB
RevLine 
[1052]1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
[1056]3 *                    > www.orxonox.net <
[1052]4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Benjamin Grauer
24 *      Fabian 'x3n' Landau
[726]25 *   Co-authors:
[1052]26 *      ...
27 */
28
29/*!
30    @file Convert.h
31    @brief Definition and Implementation of the Convert class.
32*/
33
34#ifndef _Convert_H__
35#define _Convert_H__
36
37#include <string>
38#include <sstream>
39
40#include "UtilPrereqs.h"
41#include "Math.h"
42#include "SubString.h"
43#include "MultiTypeMath.h"
[721]44
45
[1052]46//////////
47// MAIN //
48//////////
[721]49
[1052]50// Enum to declare the wanted conversion preference in case of equal type-levels
51enum ConversionPreference
52{
53    CP_PreferToType,
54    CP_PreferFromType,
55};
[721]56
[1052]57// Helper classes to determine the preferred partial template specialization
58class _ToType_   {};
59class _FromType_ {};
60class _Explicit_ {};
[726]61
[871]62
[1052]63// The default convert functions
64template <class FromType, class ToType, class Type>
65struct ConverterSpecialized
[721]66{
[1052]67    enum { specialized = false };
68    static bool convert(ToType* output, const FromType& input)
69    { return false; }
70};
71
72
73// The default convert function if both types are the same
74template <class BothTypes>
75struct ConverterSpecialized<BothTypes, BothTypes, _Explicit_>
76{
77    enum { specialized = true };
78    static bool convert(BothTypes* output, const BothTypes& input)
79    { (*output) = input; return true; }
80};
81
82
83// The possible levels
84#define __low__  0 // Everything that is or behaves like a primitive type (an can be converted with a typecast to every other low-level type)
85#define __mid__  1 // Everything that has overloaded << and >> operators to operate on a std::stream
86#define __high__ 2 // Everything that doesn't fullfill the lowerlevel-requirements and therefore needs specialized conversions
87
88// Defines the levels of all types: Default is __high__ so you don't have to define every high-level type
89template <class T> struct ConverterLevel           { enum { level = __high__ }; };
90template <> struct ConverterLevel<std::string>     { enum { level = __mid__ }; };
91template <> struct ConverterLevel<orxonox::Radian> { enum { level = __mid__ }; };
92template <> struct ConverterLevel<orxonox::Degree> { enum { level = __mid__ }; };
93template <> struct ConverterLevel<int>             { enum { level = __low__ }; };
94template <> struct ConverterLevel<unsigned int>    { enum { level = __low__ }; };
95template <> struct ConverterLevel<char>            { enum { level = __low__ }; };
96template <> struct ConverterLevel<unsigned char>   { enum { level = __low__ }; };
97template <> struct ConverterLevel<short>           { enum { level = __low__ }; };
98template <> struct ConverterLevel<unsigned short>  { enum { level = __low__ }; };
99template <> struct ConverterLevel<long>            { enum { level = __low__ }; };
100template <> struct ConverterLevel<unsigned long>   { enum { level = __low__ }; };
101template <> struct ConverterLevel<float>           { enum { level = __low__ }; };
102template <> struct ConverterLevel<double>          { enum { level = __low__ }; };
103template <> struct ConverterLevel<long double>     { enum { level = __low__ }; };
104template <> struct ConverterLevel<bool>            { enum { level = __low__ }; };
105
106
107// Calculates the preference based on the levels of FromType and ToType
108template <int from, int to>
109struct ConverterPreference
110{
111    enum
[871]112    {
[1052]113        // The maximum of both levels: element of {0, 1, 2}
114        // max 0: Both types are primitives or have a similar behaviour
115        // max 1: At least one type is not a primitive, but both can be put on a std::stream
116        // max 2: There is at least one generic type that needs specialized conversions
117        max = (from > to) ? from : to,
118
119        // The difference between both levels limited to +-1: element of {-1, 0, 1}
120        // diff -1: The FromType has higher level than the ToType
121        // diff  0: Both types have the same level
122        // diff  1: The ToType has higher level than the FromType
123        diff = ((to - from) > 1) ? 1 : (((to - from) < -1) ? -1 : to - from)
124    };
[726]125};
[721]126
[1052]127
128// The default conversion: This usually does nothing
129template <int max, class FromType, class ToType>
130struct ConverterDefault
[726]131{
[1052]132    static bool convert(ToType* output, const FromType& input)
[721]133    {
[871]134        return false;
[721]135    }
[726]136};
[1052]137// The default conversion for primitives: A typecast (defined over two partial specialized templates to exclude all non-primitive types and classes)    template <int max, class FromType, class ToType>
138template <class FromType, class ToType>
139struct ConverterDefault<0, FromType, ToType>
140{
141    static bool convert(ToType* output, const FromType& input)
142    {
143        (*output) = (ToType)input;
144        return true;
145    }
146};
[721]147
[1052]148
149// Converter: Converts input of FromType into output of ToType
150template <int diff, int max, class FromType, class ToType, ConversionPreference pref>
151struct Converter
[726]152{
[1052]153    static bool convert(ToType* output, const FromType& input)
[871]154    {
155        return false;
156    }
[726]157};
[1052]158// Converter: level{FromType} > level{ToType}
159template <int max, class FromType, class ToType, ConversionPreference pref>
160struct Converter<-1, max, FromType, ToType, pref>
161{   static bool convert(ToType* output, const FromType& input)
162    { return (ConverterSpecialized<FromType, ToType, _Explicit_>::specialized) ? (ConverterSpecialized<FromType, ToType, _Explicit_>::convert(output, input)) : (ConverterSpecialized<FromType, ToType, _FromType_>::specialized) ? (ConverterSpecialized<FromType, ToType, _FromType_>::convert(output, input)) : (ConverterDefault<max, FromType, ToType>::convert(output, input)); } };
163// Converter: level{FromType} < level{ToType}
164template <int max, class FromType, class ToType, ConversionPreference pref>
165struct Converter<1, max, FromType, ToType, pref>
166{   static bool convert(ToType* output, const FromType& input)
167    { return (ConverterSpecialized<FromType, ToType, _Explicit_>::specialized) ? (ConverterSpecialized<FromType, ToType, _Explicit_>::convert(output, input)) : (ConverterSpecialized<FromType, ToType, _ToType_>::specialized) ? (ConverterSpecialized<FromType, ToType, _ToType_>::convert(output, input)) : (ConverterDefault<max, FromType, ToType>::convert(output, input)); } };
168// Converter: level{FromType} = level{ToType}
169// CP_PreferToType
170template <int max, class FromType, class ToType>
171struct Converter<0, max, FromType, ToType, CP_PreferToType>
172{   static bool convert(ToType* output, const FromType& input)
173    { return (ConverterSpecialized<FromType, ToType, _Explicit_>::specialized) ? (ConverterSpecialized<FromType, ToType, _Explicit_>::convert(output, input)) : (ConverterSpecialized<FromType, ToType, _ToType_>::specialized) ? (ConverterSpecialized<FromType, ToType, _ToType_>::convert(output, input)) : (ConverterSpecialized<FromType, ToType, _FromType_>::specialized) ? (ConverterSpecialized<FromType, ToType, _FromType_>::convert(output, input)) : (ConverterDefault<max, FromType, ToType>::convert(output, input)); } };
174// CP_PreferFromType
175template <int max, class FromType, class ToType>
176struct Converter<0, max, FromType, ToType, CP_PreferFromType>
177{   static bool convert(ToType* output, const FromType& input)
178    { return (ConverterSpecialized<FromType, ToType, _Explicit_>::specialized) ? (ConverterSpecialized<FromType, ToType, _Explicit_>::convert(output, input)) : (ConverterSpecialized<FromType, ToType, _FromType_>::specialized) ? (ConverterSpecialized<FromType, ToType, _FromType_>::convert(output, input)) : (ConverterSpecialized<FromType, ToType, _ToType_>::specialized) ? (ConverterSpecialized<FromType, ToType, _ToType_>::convert(output, input)) : (ConverterDefault<max, FromType, ToType>::convert(output, input)); } };
[721]179
[1052]180
181// Calls the Converter::convertValue function with the correct template type parameters calculated by ConverterPreference
182template <class FromType, class ToType>
183static bool convertValue(ToType* output, const FromType& input, ConversionPreference preference = CP_PreferToType)
[726]184{
[1052]185    return (preference == CP_PreferToType) ?
186           Converter<ConverterPreference<ConverterLevel<FromType>::level, ConverterLevel<ToType>::level>::diff,
187                     ConverterPreference<ConverterLevel<FromType>::level, ConverterLevel<ToType>::level>::max,
188                     FromType,
189                     ToType,
190                     CP_PreferToType>::convert(output, input)
191         : Converter<ConverterPreference<ConverterLevel<FromType>::level, ConverterLevel<ToType>::level>::diff,
192                     ConverterPreference<ConverterLevel<FromType>::level, ConverterLevel<ToType>::level>::max,
193                     FromType,
194                     ToType,
195                     CP_PreferFromType>::convert(output, input);
[726]196}
[721]197
[1052]198
199//////////////////////
200// HELPER FUNCTIONS //
201//////////////////////
202
203// Helper function: Calls convertValue with and without default value and returns true if the conversion was successful
204template<class FromType, class ToType>
205static bool ConvertValue(ToType* output, const FromType& input, ConversionPreference preference = CP_PreferToType)
[726]206{
[1052]207    return convertValue(output, input, preference);
208}
209template<class FromType, class ToType>
210static bool ConvertValue(ToType* output, const FromType& input, const ToType& fallback, ConversionPreference preference = CP_PreferToType)
211{
212    if (convertValue(output, input, preference))
213        return true;
[721]214
[1052]215    (*output) = fallback;
216    return false;
[726]217}
[721]218
[1052]219// Helper function: Calls convertValue with and without default value and returns the converted value
220template<class FromType, class ToType>
221static ToType getConvertedValue(const FromType& input, ConversionPreference preference = CP_PreferToType)
[871]222{
[1052]223    ToType output = ToType();
224    ConvertValue(&output, input, preference);
225    return output;
[871]226}
[1052]227template<class FromType, class ToType>
228static ToType getConvertedValue(const FromType& input, const ToType& fallback, ConversionPreference preference = CP_PreferToType)
[871]229{
[1052]230    ToType output = fallback;
231    ConvertValue(&output, input, fallback, preference);
232    return output;
[871]233}
234
[1052]235
236/////////////////////
237// SPECIALIZATIONS //
238/////////////////////
[871]239
[1052]240/////////////
241// SAMPLES //
242/////////////
243/*
244// convert everything to xyz
245template <class FromType>
246struct ConverterSpecialized<FromType, xyz, _ToType_>
[871]247{
[1052]248    enum { specialized = true };
249    static bool convert(xyz* output, const FromType& input)
250    { return ...; }
[871]251};
252
[1052]253// convert xyz to everything
254template <class ToType>
255struct ConverterSpecialized<xyz, ToType, _FromType_>
256{
257    enum { specialized = true };
258    static bool convert(ToType* output, const xyz& input)
259    { return ...; }
260};
[871]261
[1052]262// convert abc to xyz
263template <>
264struct ConverterSpecialized<abc, xyz, _Explicit_>
[871]265{
[1052]266    enum { specialized = true };
267    static bool convert(xyz* output, const abc& input)
268    { return ...; }
[871]269};
[1052]270*/
271
272////////////
273// STRING //
274////////////
275
276// convert to string
277template <class FromType>
278struct ConverterSpecialized<FromType, std::string, _ToType_>
[871]279{
[1052]280    enum { specialized = true };
281    static bool convert(std::string* output, const FromType& input)
[871]282    {
[1052]283        std::ostringstream oss;
284        if (oss << input)
285        {
286            (*output) = oss.str();
287            return true;
288        }
289        else
290            return false;
[871]291    }
292};
293
[1052]294// convert from string
295template <class ToType>
296struct ConverterSpecialized<std::string, ToType, _FromType_>
[871]297{
[1052]298    enum { specialized = true };
299    static bool convert(ToType* output, const std::string& input)
[871]300    {
[1052]301        std::istringstream iss(input);
302        if (iss >> (*output))
303            return true;
304        else
305            return false;
[871]306    }
307};
[1052]308
309
310////////////////
311// MULTITYPES //
312////////////////
313
314// convert from MultiTypePrimitive
315template <class ToType>
316struct ConverterSpecialized<MultiTypePrimitive, ToType, _FromType_>
[871]317{
[1052]318    enum { specialized = true };
319    static bool convert(ToType* output, const MultiTypePrimitive& input)
[871]320    {
[1052]321        if (input.getType() == MT_void)
322            return ConvertValue(output, input.getVoid());
323        else if (input.getType() == MT_int)
324            return ConvertValue(output, input.getInt());
325        else if (input.getType() == MT_uint)
326            return ConvertValue(output, input.getUnsignedInt());
327        else if (input.getType() == MT_char)
328            return ConvertValue(output, input.getChar());
329        else if (input.getType() == MT_uchar)
330            return ConvertValue(output, input.getUnsignedChar());
331        else if (input.getType() == MT_short)
332            return ConvertValue(output, input.getShort());
333        else if (input.getType() == MT_ushort)
334            return ConvertValue(output, input.getUnsignedShort());
335        else if (input.getType() == MT_long)
336            return ConvertValue(output, input.getLong());
337        else if (input.getType() == MT_ulong)
338            return ConvertValue(output, input.getUnsignedLong());
339        else if (input.getType() == MT_float)
340            return ConvertValue(output, input.getFloat());
341        else if (input.getType() == MT_double)
342            return ConvertValue(output, input.getDouble());
343        else if (input.getType() == MT_longdouble)
344            return ConvertValue(output, input.getLongDouble());
345        else if (input.getType() == MT_bool)
346            return ConvertValue(output, input.getBool());
347        else
348            return false;
[871]349    }
350};
351
[1052]352// convert from MultiTypeString
353template <class ToType>
354struct ConverterSpecialized<MultiTypeString, ToType, _FromType_>
[871]355{
[1052]356    enum { specialized = true };
357    static bool convert(ToType* output, const MultiTypeString& input)
[871]358    {
[1052]359        if (input.getType() == MT_constchar)
360            return ConvertValue(output, input.getConstChar());
361        else if (input.getType() == MT_string)
362            return ConvertValue(output, input.getString());
363        else
364            return ConvertValue(output, (MultiTypePrimitive)input);
[871]365    }
366};
[1052]367
368// convert from MultiTypeMath
369template <class ToType>
370struct ConverterSpecialized<MultiTypeMath, ToType, _FromType_>
[871]371{
[1052]372    enum { specialized = true };
373    static bool convert(ToType* output, const MultiTypeMath& input)
[871]374    {
[1052]375        if (input.getType() == MT_vector2)
376            return ConvertValue(output, input.getVector2());
377        else if (input.getType() == MT_vector3)
378            return ConvertValue(output, input.getVector3());
379        else if (input.getType() == MT_quaternion)
380            return ConvertValue(output, input.getQuaternion());
381        else if (input.getType() == MT_colourvalue)
382            return ConvertValue(output, input.getColourValue());
383        else if (input.getType() == MT_radian)
384            return ConvertValue(output, input.getRadian());
385        else if (input.getType() == MT_degree)
386            return ConvertValue(output, input.getDegree());
387        else
388            return ConvertValue(output, (MultiTypeString)input);
[871]389    }
390};
391
[1052]392
393////////////////////
394// MATH TO STRING //
395////////////////////
[871]396
397// Vector2 to std::string
398template <>
[1052]399struct ConverterSpecialized<orxonox::Vector2, std::string, _Explicit_>
[871]400{
[1052]401    enum { specialized = true };
402    static bool convert(std::string* output, const orxonox::Vector2& input)
[871]403    {
[1052]404        std::ostringstream ostream;
405        if (ostream << input.x << "," << input.y)
406        {
407            (*output) = ostream.str();
408            return true;
409        }
410        return false;
[871]411    }
412};
413
414// Vector3 to std::string
415template <>
[1052]416struct ConverterSpecialized<orxonox::Vector3, std::string, _Explicit_>
[871]417{
[1052]418    enum { specialized = true };
419    static bool convert(std::string* output, const orxonox::Vector3& input)
[871]420    {
[1052]421        std::ostringstream ostream;
422        if (ostream << input.x << "," << input.y << "," << input.z)
423        {
424            (*output) = ostream.str();
425            return true;
426        }
427        return false;
[871]428    }
429};
430
431// Vector4 to std::string
432template <>
[1052]433struct ConverterSpecialized<orxonox::Vector4, std::string, _Explicit_>
[871]434{
[1052]435    enum { specialized = true };
436    static bool convert(std::string* output, const orxonox::Vector4& input)
[871]437    {
[1052]438        std::ostringstream ostream;
439        if (ostream << input.x << "," << input.y << "," << input.z << "," << input.w)
440        {
441            (*output) = ostream.str();
442            return true;
443        }
444        return false;
[871]445    }
446};
447
448// Quaternion to std::string
449template <>
[1052]450struct ConverterSpecialized<orxonox::Quaternion, std::string, _Explicit_>
[871]451{
[1052]452    enum { specialized = true };
453    static bool convert(std::string* output, const orxonox::Quaternion& input)
[871]454    {
[1052]455        std::ostringstream ostream;
456        if (ostream << input.w << "," << input.x << "," << input.y << "," << input.z)
457        {
458            (*output) = ostream.str();
459            return true;
460        }
461        return false;
[871]462    }
463};
464
465// ColourValue to std::string
466template <>
[1052]467struct ConverterSpecialized<orxonox::ColourValue, std::string, _Explicit_>
[871]468{
[1052]469    enum { specialized = true };
470    static bool convert(std::string* output, const orxonox::ColourValue& input)
[871]471    {
[1052]472        std::ostringstream ostream;
473        if (ostream << input.r << "," << input.g << "," << input.b << "," << input.a)
474        {
475            (*output) = ostream.str();
476            return true;
477        }
478        return false;
[871]479    }
480};
481
482
[1052]483////////////////////
484// STRING TO MATH //
485////////////////////
[871]486
487// std::string to Vector2
488template <>
[1052]489struct ConverterSpecialized<std::string, orxonox::Vector2, _Explicit_>
[871]490{
[1052]491    enum { specialized = true };
492    static bool convert(orxonox::Vector2* output, const std::string& input)
[871]493    {
[1052]494        unsigned int opening_parenthesis, closing_parenthesis = input.find(')');
495        if ((opening_parenthesis = input.find('(')) == std::string::npos) { opening_parenthesis = 0; } else { opening_parenthesis++; }
[871]496
[1052]497        SubString tokens(input.substr(opening_parenthesis, closing_parenthesis - opening_parenthesis), ",", SubString::WhiteSpaces, false, '\\', true, '"', true, '\0', '\0', true, '\0');
498        if (tokens.size() >= 2)
499        {
500            if (!ConvertValue(&(output->x), tokens[0]))
501                return false;
502            if (!ConvertValue(&(output->y), tokens[1]))
503                return false;
[871]504
[1052]505            return true;
506        }
507        return false;
[871]508    }
509};
510
511// std::string to Vector3
512template <>
[1052]513struct ConverterSpecialized<std::string, orxonox::Vector3, _Explicit_>
[871]514{
[1052]515    enum { specialized = true };
516    static bool convert(orxonox::Vector3* output, const std::string& input)
[871]517    {
[1052]518        unsigned int opening_parenthesis, closing_parenthesis = input.find(')');
519        if ((opening_parenthesis = input.find('(')) == std::string::npos) { opening_parenthesis = 0; } else { opening_parenthesis++; }
[871]520
[1052]521        SubString tokens(input.substr(opening_parenthesis, closing_parenthesis - opening_parenthesis), ",", SubString::WhiteSpaces, false, '\\', true, '"', true, '\0', '\0', true, '\0');
522        if (tokens.size() >= 3)
523        {
524            if (!ConvertValue(&(output->x), tokens[0]))
525                return false;
526            if (!ConvertValue(&(output->y), tokens[1]))
527                return false;
528            if (!ConvertValue(&(output->z), tokens[2]))
529                return false;
[871]530
[1052]531            return true;
532        }
533        return false;
[871]534    }
535};
536
537// std::string to Vector4
538template <>
[1052]539struct ConverterSpecialized<std::string, orxonox::Vector4, _Explicit_>
[871]540{
[1052]541    enum { specialized = true };
542    static bool convert(orxonox::Vector4* output, const std::string& input)
[871]543    {
[1052]544        unsigned int opening_parenthesis, closing_parenthesis = input.find(')');
545        if ((opening_parenthesis = input.find('(')) == std::string::npos) { opening_parenthesis = 0; } else { opening_parenthesis++; }
[871]546
[1052]547        SubString tokens(input.substr(opening_parenthesis, closing_parenthesis - opening_parenthesis), ",", SubString::WhiteSpaces, false, '\\', true, '"', true, '\0', '\0', true, '\0');
548        if (tokens.size() >= 4)
549        {
550            if (!ConvertValue(&(output->x), tokens[0]))
551                return false;
552            if (!ConvertValue(&(output->y), tokens[1]))
553                return false;
554            if (!ConvertValue(&(output->z), tokens[2]))
555                return false;
556            if (!ConvertValue(&(output->w), tokens[3]))
557                return false;
[871]558
[1052]559            return true;
560        }
561        return false;
[871]562    }
563};
564
565// std::string to Quaternion
566template <>
[1052]567struct ConverterSpecialized<std::string, orxonox::Quaternion, _Explicit_>
[871]568{
[1052]569    enum { specialized = true };
570    static bool convert(orxonox::Quaternion* output, const std::string& input)
[871]571    {
[1052]572        unsigned int opening_parenthesis, closing_parenthesis = input.find(')');
573        if ((opening_parenthesis = input.find('(')) == std::string::npos) { opening_parenthesis = 0; } else { opening_parenthesis++; }
[871]574
[1052]575        SubString tokens(input.substr(opening_parenthesis, closing_parenthesis - opening_parenthesis), ",", SubString::WhiteSpaces, false, '\\', true, '"', true, '\0', '\0', true, '\0');
576        if (tokens.size() >= 4)
577        {
578            if (!ConvertValue(&(output->w), tokens[0]))
579                return false;
580            if (!ConvertValue(&(output->x), tokens[1]))
581                return false;
582            if (!ConvertValue(&(output->y), tokens[2]))
583                return false;
584            if (!ConvertValue(&(output->z), tokens[3]))
585                return false;
[871]586
[1052]587            return true;
588        }
589        return false;
[871]590    }
591};
592
593// std::string to ColourValue
594template <>
[1052]595struct ConverterSpecialized<std::string, orxonox::ColourValue, _Explicit_>
[871]596{
[1052]597    enum { specialized = true };
598    static bool convert(orxonox::ColourValue* output, const std::string& input)
[871]599    {
[1052]600        unsigned int opening_parenthesis, closing_parenthesis = input.find(')');
601        if ((opening_parenthesis = input.find('(')) == std::string::npos) { opening_parenthesis = 0; } else { opening_parenthesis++; }
[871]602
[1052]603        SubString tokens(input.substr(opening_parenthesis, closing_parenthesis - opening_parenthesis), ",", SubString::WhiteSpaces, false, '\\', true, '"', true, '\0', '\0', true, '\0');
604        if (tokens.size() >= 4)
605        {
606            if (!ConvertValue(&(output->r), tokens[0]))
607                return false;
608            if (!ConvertValue(&(output->g), tokens[1]))
609                return false;
610            if (!ConvertValue(&(output->b), tokens[2]))
611                return false;
612            if (!ConvertValue(&(output->a), tokens[3]))
613                return false;
[871]614
[1052]615            return true;
616        }
617        return false;
[871]618    }
619};
[1052]620
621#endif /* _Convert_H__ */
Note: See TracBrowser for help on using the repository browser.