1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 ///
25 /// \file Tf/TemplateString.cpp
26 
27 #include "pxr/pxr.h"
28 
29 #include "pxr/base/tf/templateString.h"
30 #include "pxr/base/tf/iterator.h"
31 #include "pxr/base/tf/stringUtils.h"
32 
33 using std::string;
34 using std::vector;
35 
36 PXR_NAMESPACE_OPEN_SCOPE
37 
38 #define _ERROR(ptr, ...) \
39     if (ptr) { ptr->push_back(TfStringPrintf(__VA_ARGS__)); }
40 
41 static const char _Sigil      = '$';
42 static const char _OpenQuote  = '{';
43 static const char _CloseQuote = '}';
44 
45 static const char* const _IdentChars =
46     "abcdefghijklmnopqrstuvwxyz"
47     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
48     "0123456789_";
49 
TfTemplateString()50 TfTemplateString::TfTemplateString()
51     : _data(new _Data)
52 {
53 }
54 
55 
TfTemplateString(const string & template_)56 TfTemplateString::TfTemplateString(const string& template_)
57     : _data(new _Data)
58 {
59     _data->template_ = template_;
60 }
61 
62 string
Substitute(const Mapping & mapping) const63 TfTemplateString::Substitute(const Mapping& mapping) const
64 {
65     _ParseTemplate();
66     _EmitParseErrors();
67 
68     vector<string> evalErrors;
69     string result = _Evaluate(mapping, &evalErrors);
70 
71     TF_FOR_ALL(it, evalErrors)
72         TF_CODING_ERROR("%s", it->c_str());
73 
74     return result;
75 }
76 
77 string
SafeSubstitute(const Mapping & mapping) const78 TfTemplateString::SafeSubstitute(const Mapping& mapping) const
79 {
80     _ParseTemplate();
81     _EmitParseErrors();
82     return _Evaluate(mapping);
83 }
84 
85 void
_EmitParseErrors() const86 TfTemplateString::_EmitParseErrors() const
87 {
88     tbb::spin_mutex::scoped_lock lock(_data->mutex);
89     TF_FOR_ALL(it, _data->parseErrors)
90         TF_CODING_ERROR("%s", it->c_str());
91 }
92 
93 TfTemplateString::Mapping
GetEmptyMapping() const94 TfTemplateString::GetEmptyMapping() const
95 {
96     Mapping mapping;
97     if (IsValid()) {
98         tbb::spin_mutex::scoped_lock lock(_data->mutex);
99         TF_FOR_ALL(it, _data->placeholders)
100             mapping.insert(make_pair(it->name, std::string()));
101     }
102     return mapping;
103 }
104 
105 bool
IsValid() const106 TfTemplateString::IsValid() const
107 {
108     _ParseTemplate();
109     tbb::spin_mutex::scoped_lock lock(_data->mutex);
110     return _data->template_.empty() || _data->parseErrors.empty();
111 }
112 
113 vector<string>
GetParseErrors() const114 TfTemplateString::GetParseErrors() const
115 {
116     _ParseTemplate();
117     tbb::spin_mutex::scoped_lock lock(_data->mutex);
118     return _data->parseErrors;
119 }
120 
121 // XXX NOTE: callers must hold a lock on _data->mutex before calling
122 // _FindNextPlaceHolder.
123 bool
124 TfTemplateString::
_FindNextPlaceHolder(size_t * pos,vector<string> * errors) const125 _FindNextPlaceHolder(size_t* pos, vector<string>* errors) const
126 {
127     *pos = _data->template_.find(_Sigil, *pos);
128 
129     if (*pos == string::npos)
130         return false;
131 
132     size_t nextpos = *pos + 1;
133     if (nextpos >= _data->template_.length())
134         return false;
135 
136     if (_data->template_[nextpos] == _Sigil) {
137         // This is a $$ escape sequence.
138         _data->placeholders.push_back(_PlaceHolder("$", *pos, 2));
139         *pos = *pos + 2;
140     }
141     else if (_data->template_[nextpos] == _OpenQuote) {
142         // If the character after the sigil was the open quote character, look
143         // for the matching close quote character.
144         size_t endpos = _data->template_.find_first_not_of(
145             string(_IdentChars) + _OpenQuote, nextpos);
146 
147         if (endpos == string::npos) {
148             _ERROR(errors, "Cannot find close quote for placeholder starting "
149                 "at pos %zu", *pos);
150             *pos = nextpos;
151         }
152         else if (_data->template_[endpos] != _CloseQuote) {
153             _ERROR(errors, "Invalid character '%c' in identifier at pos %zu",
154                 _data->template_[endpos], endpos);
155             *pos = endpos;
156         }
157         else {
158             // len includes the sigil and quote characters.
159             size_t len = endpos - *pos + 1;
160             string name = _data->template_.substr(nextpos + 1, len - 3);
161             if (!name.empty()) {
162                 _data->placeholders.push_back(_PlaceHolder(name, *pos, len));
163             } else {
164                 _ERROR(errors, "Empty placeholder at pos %zu", *pos);
165             }
166             *pos = *pos + len;
167         }
168     }
169     else {
170         // Find the next character not valid within an identifier.
171         size_t endpos =
172             _data->template_.find_first_not_of(_IdentChars, nextpos);
173         size_t len = (endpos == string::npos ?
174                       _data->template_.length() : endpos) - *pos;
175         string name = _data->template_.substr(nextpos, len - 1);
176         if (!name.empty()) {
177             _data->placeholders.push_back(_PlaceHolder(name, *pos, len));
178         } else {
179             // If we find what appears to be a place holder, but the next
180             // character is not legal in a place holder, we just skip it.
181         }
182         *pos = *pos + len;
183     }
184 
185     return true;
186 }
187 
188 void
_ParseTemplate() const189 TfTemplateString::_ParseTemplate() const
190 {
191     tbb::spin_mutex::scoped_lock lock(_data->mutex);
192     if (!_data->parsed) {
193         size_t pos = 0;
194         while (_FindNextPlaceHolder(&pos, &_data->parseErrors));
195         _data->parsed = true;
196     }
197 }
198 
199 string
200 TfTemplateString::
_Evaluate(const Mapping & mapping,vector<string> * errors) const201 _Evaluate(const Mapping& mapping, vector<string>* errors) const
202 {
203     string result;
204     size_t pos = 0;
205 
206     tbb::spin_mutex::scoped_lock lock(_data->mutex);
207 
208     TF_FOR_ALL(it, _data->placeholders) {
209         // Add template content between the end of the last placeholder (or
210         // the start of the template) and the start of the next placeholder.
211         result.insert(result.end(),
212             _data->template_.begin() + pos, _data->template_.begin() + it->pos);
213 
214         if (it->name[0] == _Sigil) {
215             result.insert(result.end(), _Sigil);
216         }
217         else {
218             Mapping::const_iterator mit = mapping.find(it->name);
219             if (mit != mapping.end()) {
220                 result.insert(result.end(),
221                     mit->second.begin(),
222                     mit->second.end());
223             } else {
224                 // Insert the placeholder into the result.
225                 result.insert(result.end(),
226                     _data->template_.begin() + it->pos,
227                     _data->template_.begin() + it->pos + it->len);
228                 _ERROR(errors, "No mapping found for placeholder '%s'",
229                     it->name.c_str());
230             }
231         }
232 
233         pos = it->pos + it->len;
234     }
235 
236     // Add the remainder of the template string.
237     result += _data->template_.substr(pos);
238 
239     return result;
240 }
241 
242 PXR_NAMESPACE_CLOSE_SCOPE
243