blob: d5c0c8a7a5fe4057c1b877d9171609c5f88692cd [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Adam Lesinskice5e56e2016-10-21 17:56:45 -070017#include "util/Util.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070018#include "util/BigBuffer.h"
19#include "util/Maybe.h"
20#include "util/StringPiece.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080021
Adam Lesinskice5e56e2016-10-21 17:56:45 -070022#include <utils/Unicode.h>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080023#include <algorithm>
24#include <ostream>
25#include <string>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080026#include <vector>
27
28namespace aapt {
29namespace util {
30
Adam Lesinskice5e56e2016-10-21 17:56:45 -070031static std::vector<std::string> SplitAndTransform(
32 const StringPiece& str, char sep, const std::function<char(char)>& f) {
33 std::vector<std::string> parts;
34 const StringPiece::const_iterator end = std::end(str);
35 StringPiece::const_iterator start = std::begin(str);
36 StringPiece::const_iterator current;
37 do {
38 current = std::find(start, end, sep);
39 parts.emplace_back(str.substr(start, current).ToString());
40 if (f) {
41 std::string& part = parts.back();
42 std::transform(part.begin(), part.end(), part.begin(), f);
43 }
44 start = current + 1;
45 } while (current != end);
46 return parts;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080047}
48
Adam Lesinskice5e56e2016-10-21 17:56:45 -070049std::vector<std::string> Split(const StringPiece& str, char sep) {
50 return SplitAndTransform(str, sep, nullptr);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080051}
52
Adam Lesinskice5e56e2016-10-21 17:56:45 -070053std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
54 return SplitAndTransform(str, sep, ::tolower);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080055}
56
Adam Lesinskice5e56e2016-10-21 17:56:45 -070057bool StartsWith(const StringPiece& str, const StringPiece& prefix) {
58 if (str.size() < prefix.size()) {
59 return false;
60 }
61 return str.substr(0, prefix.size()) == prefix;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070062}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080063
Adam Lesinskice5e56e2016-10-21 17:56:45 -070064bool EndsWith(const StringPiece& str, const StringPiece& suffix) {
65 if (str.size() < suffix.size()) {
66 return false;
67 }
68 return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080069}
70
Adam Lesinskice5e56e2016-10-21 17:56:45 -070071StringPiece TrimWhitespace(const StringPiece& str) {
72 if (str.size() == 0 || str.data() == nullptr) {
73 return str;
74 }
Adam Lesinski3b4cd942015-10-30 16:31:42 -070075
Adam Lesinskice5e56e2016-10-21 17:56:45 -070076 const char* start = str.data();
77 const char* end = str.data() + str.length();
Adam Lesinski3b4cd942015-10-30 16:31:42 -070078
Adam Lesinskice5e56e2016-10-21 17:56:45 -070079 while (start != end && isspace(*start)) {
80 start++;
81 }
Adam Lesinski3b4cd942015-10-30 16:31:42 -070082
Adam Lesinskice5e56e2016-10-21 17:56:45 -070083 while (end != start && isspace(*(end - 1))) {
84 end--;
85 }
Adam Lesinski3b4cd942015-10-30 16:31:42 -070086
Adam Lesinskice5e56e2016-10-21 17:56:45 -070087 return StringPiece(start, end - start);
Adam Lesinski3b4cd942015-10-30 16:31:42 -070088}
89
Adam Lesinskice5e56e2016-10-21 17:56:45 -070090StringPiece::const_iterator FindNonAlphaNumericAndNotInSet(
91 const StringPiece& str, const StringPiece& allowed_chars) {
92 const auto end_iter = str.end();
93 for (auto iter = str.begin(); iter != end_iter; ++iter) {
94 char c = *iter;
95 if ((c >= u'a' && c <= u'z') || (c >= u'A' && c <= u'Z') ||
96 (c >= u'0' && c <= u'9')) {
97 continue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080098 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -070099
100 bool match = false;
101 for (char i : allowed_chars) {
102 if (c == i) {
103 match = true;
104 break;
105 }
106 }
107
108 if (!match) {
109 return iter;
110 }
111 }
112 return end_iter;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800113}
114
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700115bool IsJavaClassName(const StringPiece& str) {
116 size_t pieces = 0;
117 for (const StringPiece& piece : Tokenize(str, '.')) {
118 pieces++;
119 if (piece.empty()) {
120 return false;
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700121 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700122
123 // Can't have starting or trailing $ character.
124 if (piece.data()[0] == '$' || piece.data()[piece.size() - 1] == '$') {
125 return false;
126 }
127
128 if (FindNonAlphaNumericAndNotInSet(piece, "$_") != piece.end()) {
129 return false;
130 }
131 }
132 return pieces >= 2;
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700133}
134
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700135bool IsJavaPackageName(const StringPiece& str) {
136 if (str.empty()) {
137 return false;
138 }
139
140 size_t pieces = 0;
141 for (const StringPiece& piece : Tokenize(str, '.')) {
142 pieces++;
143 if (piece.empty()) {
144 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700145 }
146
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700147 if (piece.data()[0] == '_' || piece.data()[piece.size() - 1] == '_') {
148 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700149 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700150
151 if (FindNonAlphaNumericAndNotInSet(piece, "_") != piece.end()) {
152 return false;
153 }
154 }
155 return pieces >= 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700156}
157
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700158Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
159 const StringPiece& classname) {
160 if (classname.empty()) {
161 return {};
162 }
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700163
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700164 if (util::IsJavaClassName(classname)) {
165 return classname.ToString();
166 }
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700167
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700168 if (package.empty()) {
169 return {};
170 }
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700171
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700172 std::string result(package.data(), package.size());
173 if (classname.data()[0] != '.') {
174 result += '.';
175 }
Adam Lesinski52364f72016-01-11 13:10:24 -0800176
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700177 result.append(classname.data(), classname.size());
178 if (!IsJavaClassName(result)) {
179 return {};
180 }
181 return result;
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700182}
183
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700184static size_t ConsumeDigits(const char* start, const char* end) {
185 const char* c = start;
186 for (; c != end && *c >= '0' && *c <= '9'; c++) {
187 }
188 return static_cast<size_t>(c - start);
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800189}
190
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700191bool VerifyJavaStringFormat(const StringPiece& str) {
192 const char* c = str.begin();
193 const char* const end = str.end();
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800194
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700195 size_t arg_count = 0;
196 bool nonpositional = false;
197 while (c != end) {
198 if (*c == '%' && c + 1 < end) {
199 c++;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800200
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700201 if (*c == '%') {
202 c++;
203 continue;
204 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800205
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700206 arg_count++;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800207
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700208 size_t num_digits = ConsumeDigits(c, end);
209 if (num_digits > 0) {
210 c += num_digits;
211 if (c != end && *c != '$') {
212 // The digits were a size, but not a positional argument.
213 nonpositional = true;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800214 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700215 } else if (*c == '<') {
216 // Reusing last argument, bad idea since positions can be moved around
217 // during translation.
218 nonpositional = true;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800219
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700220 c++;
221
222 // Optionally we can have a $ after
223 if (c != end && *c == '$') {
224 c++;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800225 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700226 } else {
227 nonpositional = true;
228 }
229
230 // Ignore size, width, flags, etc.
231 while (c != end && (*c == '-' || *c == '#' || *c == '+' || *c == ' ' ||
232 *c == ',' || *c == '(' || (*c >= '0' && *c <= '9'))) {
233 c++;
234 }
235
236 /*
237 * This is a shortcut to detect strings that are going to Time.format()
238 * instead of String.format()
239 *
240 * Comparison of String.format() and Time.format() args:
241 *
242 * String: ABC E GH ST X abcdefgh nost x
243 * Time: DEFGHKMS W Za d hkm s w yz
244 *
245 * Therefore we know it's definitely Time if we have:
246 * DFKMWZkmwyz
247 */
248 if (c != end) {
249 switch (*c) {
250 case 'D':
251 case 'F':
252 case 'K':
253 case 'M':
254 case 'W':
255 case 'Z':
256 case 'k':
257 case 'm':
258 case 'w':
259 case 'y':
260 case 'z':
261 return true;
262 }
263 }
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800264 }
265
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700266 if (c != end) {
267 c++;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800268 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700269 }
270
271 if (arg_count > 1 && nonpositional) {
272 // Multiple arguments were specified, but some or all were non positional.
273 // Translated
274 // strings may rearrange the order of the arguments, which will break the
275 // string.
276 return false;
277 }
278 return true;
Adam Lesinskib23f1e02015-11-03 12:24:17 -0800279}
280
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700281static Maybe<std::string> ParseUnicodeCodepoint(const char** start,
282 const char* end) {
283 char32_t code = 0;
284 for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
285 char c = **start;
286 char32_t a;
287 if (c >= '0' && c <= '9') {
288 a = c - '0';
289 } else if (c >= 'a' && c <= 'f') {
290 a = c - 'a' + 10;
291 } else if (c >= 'A' && c <= 'F') {
292 a = c - 'A' + 10;
293 } else {
294 return {};
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800295 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700296 code = (code << 4) | a;
297 }
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700298
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700299 ssize_t len = utf32_to_utf8_length(&code, 1);
300 if (len < 0) {
301 return {};
302 }
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700303
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700304 std::string result_utf8;
305 result_utf8.resize(len);
306 utf32_to_utf8(&code, 1, &*result_utf8.begin(), len + 1);
307 return result_utf8;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800308}
309
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700310StringBuilder& StringBuilder::Append(const StringPiece& str) {
311 if (!error_.empty()) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800312 return *this;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700313 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800314
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700315 // Where the new data will be appended to.
316 size_t new_data_index = str_.size();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800317
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700318 const char* const end = str.end();
319 const char* start = str.begin();
320 const char* current = start;
321 while (current != end) {
322 if (last_char_was_escape_) {
323 switch (*current) {
324 case 't':
325 str_ += '\t';
326 break;
327 case 'n':
328 str_ += '\n';
329 break;
330 case '#':
331 str_ += '#';
332 break;
333 case '@':
334 str_ += '@';
335 break;
336 case '?':
337 str_ += '?';
338 break;
339 case '"':
340 str_ += '"';
341 break;
342 case '\'':
343 str_ += '\'';
344 break;
345 case '\\':
346 str_ += '\\';
347 break;
348 case 'u': {
349 current++;
350 Maybe<std::string> c = ParseUnicodeCodepoint(&current, end);
351 if (!c) {
352 error_ = "invalid unicode escape sequence";
353 return *this;
354 }
355 str_ += c.value();
356 current -= 1;
357 break;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800358 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700359
360 default:
361 // Ignore.
362 break;
363 }
364 last_char_was_escape_ = false;
365 start = current + 1;
366 } else if (*current == '"') {
367 if (!quote_ && trailing_space_) {
368 // We found an opening quote, and we have
369 // trailing space, so we should append that
370 // space now.
371 if (trailing_space_) {
372 // We had trailing whitespace, so
373 // replace with a single space.
374 if (!str_.empty()) {
375 str_ += ' ';
376 }
377 trailing_space_ = false;
378 }
379 }
380 quote_ = !quote_;
381 str_.append(start, current - start);
382 start = current + 1;
383 } else if (*current == '\'' && !quote_) {
384 // This should be escaped.
385 error_ = "unescaped apostrophe";
386 return *this;
387 } else if (*current == '\\') {
388 // This is an escape sequence, convert to the real value.
389 if (!quote_ && trailing_space_) {
390 // We had trailing whitespace, so
391 // replace with a single space.
392 if (!str_.empty()) {
393 str_ += ' ';
394 }
395 trailing_space_ = false;
396 }
397 str_.append(start, current - start);
398 start = current + 1;
399 last_char_was_escape_ = true;
400 } else if (!quote_) {
401 // This is not quoted text, so look for whitespace.
402 if (isspace(*current)) {
403 // We found whitespace, see if we have seen some
404 // before.
405 if (!trailing_space_) {
406 // We didn't see a previous adjacent space,
407 // so mark that we did.
408 trailing_space_ = true;
409 str_.append(start, current - start);
410 }
411
412 // Keep skipping whitespace.
413 start = current + 1;
414 } else if (trailing_space_) {
415 // We saw trailing space before, so replace all
416 // that trailing space with one space.
417 if (!str_.empty()) {
418 str_ += ' ';
419 }
420 trailing_space_ = false;
421 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800422 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700423 current++;
424 }
425 str_.append(start, end - start);
426
427 // Accumulate the added string's UTF-16 length.
428 ssize_t len = utf8_to_utf16_length(
429 reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index,
430 str_.size() - new_data_index);
431 if (len < 0) {
432 error_ = "invalid unicode code point";
433 return *this;
434 }
435 utf16_len_ += len;
436 return *this;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800437}
438
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700439std::u16string Utf8ToUtf16(const StringPiece& utf8) {
440 ssize_t utf16_length = utf8_to_utf16_length(
441 reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
442 if (utf16_length <= 0) {
443 return {};
444 }
445
446 std::u16string utf16;
447 utf16.resize(utf16_length);
448 utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(),
449 &*utf16.begin(), utf16_length + 1);
450 return utf16;
451}
452
453std::string Utf16ToUtf8(const StringPiece16& utf16) {
454 ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
455 if (utf8_length <= 0) {
456 return {};
457 }
458
459 std::string utf8;
460 utf8.resize(utf8_length);
461 utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
462 return utf8;
463}
464
465bool WriteAll(std::ostream& out, const BigBuffer& buffer) {
466 for (const auto& b : buffer) {
467 if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
468 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800469 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700470 }
471 return true;
472}
473
474std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) {
475 std::unique_ptr<uint8_t[]> data =
476 std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
477 uint8_t* p = data.get();
478 for (const auto& block : buffer) {
479 memcpy(p, block.buffer.get(), block.size);
480 p += block.size;
481 }
482 return data;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800483}
484
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700485typename Tokenizer::iterator& Tokenizer::iterator::operator++() {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700486 const char* start = token_.end();
487 const char* end = str_.end();
488 if (start == end) {
489 end_ = true;
490 token_.assign(token_.end(), 0);
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700491 return *this;
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700492 }
493
494 start += 1;
495 const char* current = start;
496 while (current != end) {
497 if (*current == separator_) {
498 token_.assign(start, current - start);
499 return *this;
500 }
501 ++current;
502 }
503 token_.assign(start, end - start);
504 return *this;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700505}
506
507bool Tokenizer::iterator::operator==(const iterator& rhs) const {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700508 // We check equality here a bit differently.
509 // We need to know that the addresses are the same.
510 return token_.begin() == rhs.token_.begin() &&
511 token_.end() == rhs.token_.end() && end_ == rhs.end_;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700512}
513
514bool Tokenizer::iterator::operator!=(const iterator& rhs) const {
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700515 return !(*this == rhs);
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700516}
517
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700518Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok,
519 bool end)
520 : str_(s), separator_(sep), token_(tok), end_(end) {}
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700521
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700522Tokenizer::Tokenizer(StringPiece str, char sep)
523 : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
524 end_(str, sep, StringPiece(str.end(), 0), true) {}
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700525
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700526bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
527 StringPiece* out_entry, StringPiece* out_suffix) {
528 const StringPiece res_prefix("res/");
529 if (!StartsWith(path, res_prefix)) {
530 return false;
531 }
532
533 StringPiece::const_iterator last_occurence = path.end();
534 for (auto iter = path.begin() + res_prefix.size(); iter != path.end();
535 ++iter) {
536 if (*iter == '/') {
537 last_occurence = iter;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700538 }
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700539 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700540
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700541 if (last_occurence == path.end()) {
542 return false;
543 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700544
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700545 auto iter = std::find(last_occurence, path.end(), '.');
546 *out_suffix = StringPiece(iter, path.end() - iter);
547 *out_entry = StringPiece(last_occurence + 1, iter - last_occurence - 1);
548 *out_prefix = StringPiece(path.begin(), last_occurence - path.begin() + 1);
549 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700550}
551
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700552StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) {
553 size_t len;
554 const char16_t* str = pool.stringAt(idx, &len);
555 if (str != nullptr) {
556 return StringPiece16(str, len);
557 }
558 return StringPiece16();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700559}
560
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700561std::string GetString(const android::ResStringPool& pool, size_t idx) {
562 size_t len;
563 const char* str = pool.string8At(idx, &len);
564 if (str != nullptr) {
565 return std::string(str, len);
566 }
567 return Utf16ToUtf8(GetString16(pool, idx));
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700568}
569
Adam Lesinskice5e56e2016-10-21 17:56:45 -0700570} // namespace util
571} // namespace aapt