Updated license
[anna.git] / source / core / util / TextComposer.cpp
1 // ANNA - Anna is Not Nothingness Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <stdio.h>
38
39 #include <anna/core/functions.hpp>
40 #include <anna/core/mt/Guard.hpp>
41 #include <anna/core/tracing/Logger.hpp>
42 #include <anna/core/tracing/TraceMethod.hpp>
43 #include <anna/core/util/Tokenizer.hpp>
44 #include <anna/core/util/defines.hpp>
45
46 #include <anna/core/util/TextComposer.hpp>
47 #include <anna/core/util/TextVariable.hpp>
48
49 using namespace std;
50 using namespace anna;
51
52 //---------------------------------------------------------------------------------------
53 // (1) Cada uno de los DynamicText se encarga de liberar la memoria de sus componentes.
54 //---------------------------------------------------------------------------------------
55 TextComposer::~TextComposer() {
56   for(variable_iterator ii = begin(), maxii = end(); ii != maxii; ii ++)
57     delete textVariable(ii);
58
59   container::clear();
60   delete a_prefix;
61 }
62
63 /*
64  * Este método se invoca desde el TextManager::create.
65  * Divide la expressión en partes más fáciles de analizar.
66  *
67  * La expresión puede tener la forma "texto inicial ${variable1:%s} siguiente texto ${variable2:%02d} el resto"
68  *
69  * Y quedará como:
70  *    a_prefix = "texto inicial "
71  *    var1 => nombre = variable1 => "%s siguiente texto"
72  *    var2 => nombre = variable2 => "%02d el resto"
73  */
74 void TextComposer::initialize()
75 throw(RuntimeException) {
76   Tokenizer variables(a_expression, "${");
77   Tokenizer idAndText;
78   Tokenizer nameAndFormat;
79   String expression;
80   Variable::Type::_v type;
81   TextVariable* variable;
82
83   // Si el texto no tiene ninguna variable
84   if(variables.size() == 1) {
85     a_prefix = new String(a_expression);
86     return;
87   }
88
89   try {
90     bool init = true;
91
92     for(Tokenizer::const_iterator ii = variables.begin(), maxii = variables.end(); ii != maxii; ii ++) {
93       if(anna_strchr(Tokenizer::data(ii), '}') == NULL) {
94         if(init == true) {
95           a_prefix = new String(Tokenizer::data(ii));
96           init = false;
97           continue;
98         } else
99           throw RuntimeException("Parenthesis are not balanced", ANNA_FILE_LOCATION);
100       }
101
102       // El id [0] contendrá el <name>:<format>
103       // El id [1] contendrá el texto (si es que hay).
104       idAndText.apply(Tokenizer::data(ii), "}");
105       nameAndFormat.apply(idAndText [0], ":");
106
107       if(find(nameAndFormat [0], Exception::Mode::Ignore) != NULL) {
108         String msg("Variable ");
109         msg << nameAndFormat [0] << " already defined";
110         throw RuntimeException(msg, ANNA_FILE_LOCATION);
111       }
112
113       type = calculeType(nameAndFormat [1]);
114       expression = nameAndFormat [1];
115
116       // Es posible que no haya texto que acompañe a identificador
117       if(idAndText.size() > 1)
118         expression += idAndText [1];
119
120       push_back(variable = new TextVariable(nameAndFormat [0], type, expression));
121       LOGDEBUG(
122         String msg("TextComposer::initialize | New Variable: ");
123         msg << variable->asString();
124         Logger::debug(msg, ANNA_FILE_LOCATION)
125       );
126       init = false;
127     }
128   } catch(RuntimeException& ex) {
129     String msg(asString());
130     msg << " | " << ex.getText();
131     throw RuntimeException(msg, ex.getFromFile(), ex.getFromLine());
132   }
133 }
134
135 TextVariable* TextComposer::find(const char* name, const Exception::Mode::_v emode)
136 throw(RuntimeException) {
137   for(variable_iterator ii = begin(), maxii = end(); ii != maxii; ii ++) {
138     if(anna_strcmp(textVariable(ii)->getName(), name) == 0)
139       return textVariable(ii);
140   }
141
142   if(emode == Exception::Mode::Ignore)
143     return NULL;
144
145   String msg(asString());
146   msg << " | TextVariable: " << name << " | It was not found";
147
148   if(emode == Exception::Mode::Throw)
149     throw RuntimeException(msg, ANNA_FILE_LOCATION);
150
151   Logger::error(msg, ANNA_FILE_LOCATION);
152   return NULL;
153 }
154
155 String TextComposer::apply() const
156 throw(RuntimeException) {
157   LOGMETHOD(TraceMethod ttmm(Logger::Local7, "TextComposer::apply", ANNA_FILE_LOCATION));
158   String result;
159 // No hace falta proteger porque sólo se podrá acceder desde el Protector
160 //   Guard <TextComposer> guard (this);
161
162   if(a_prefix != NULL)
163     result = *a_prefix;
164
165   try {
166     a_buffer.clear();
167
168     for(const_variable_iterator ii = begin(), maxii = end(); ii != maxii; ii ++)
169       result += textVariable(ii)->compose(a_buffer);
170
171     LOGDEBUG(
172       String msg("TextComposer { Id: ");
173       msg << a_id << " | Text: " << result << " }";
174       Logger::debug(msg, ANNA_FILE_LOCATION);
175     );
176   } catch(RuntimeException& ex) {
177     String msg(asString());
178     msg << " | " << ex.getText();
179     throw RuntimeException(msg, ANNA_FILE_LOCATION);
180   }
181
182   return result;
183 }
184
185 String TextComposer::asString() const
186 throw() {
187   String result("TextComposer { Id: ");
188   result << a_id;
189   result << " | Expression: " << a_expression;
190   return result << " }";
191 }
192
193 //static
194 TextVariable::Type::_v TextComposer::calculeType(const char* format)
195 throw(RuntimeException) {
196   static const char* typeInteger = "[:digit:]*d";
197   static const char* typeString = "[:digit:]*s";
198   static const char* typeFloat = "([:digit:]*.[:digit:]|.[:digit:]*)f|f";
199   static const char* typeInteger64 = "[:digit:]*(ld|lld)";
200
201   if(*format != '%')
202     throw RuntimeException("Format must begin with character %%", ANNA_FILE_LOCATION);
203
204   if(functions::isLike(typeInteger, format))
205     return Variable::Type::Integer;
206
207   if(functions::isLike(typeString, format))
208     return Variable::Type::String;
209
210   if(functions::isLike(typeFloat, format))
211     return Variable::Type::Float;
212
213   if(functions::isLike(typeInteger64, format))
214     return Variable::Type::Integer64;
215
216   String msg("Format '");
217   msg << format << "' is not recognized";
218   throw RuntimeException(msg, ANNA_FILE_LOCATION);
219 }
220