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