mirror of https://github.com/odrling/Aegisub
254 lines
6.7 KiB
C++
254 lines
6.7 KiB
C++
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
|
|
//
|
|
// Permission to use, copy, modify, and distribute this software for any
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
// copyright notice and this permission notice appear in all copies.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
//
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
#include <libaegisub/format.h>
|
|
|
|
#include <libaegisub/charset_conv.h>
|
|
#include <libaegisub/fs_fwd.h>
|
|
|
|
#include <boost/filesystem/path.hpp>
|
|
|
|
#ifdef _MSC_VER
|
|
#define WCHAR_T_ENC "utf-16le"
|
|
#else
|
|
#define WCHAR_T_ENC "utf-32le"
|
|
#endif
|
|
|
|
namespace {
|
|
template<typename Char>
|
|
int actual_len(int max_len, const Char *value) {
|
|
int len = 0;
|
|
while (value[len] && (max_len <= 0 || len < max_len)) ++len;
|
|
return len;
|
|
}
|
|
|
|
template<typename Char>
|
|
int actual_len(int max_len, std::basic_string<Char> value) {
|
|
if (max_len > 0 && static_cast<size_t>(max_len) < value.size())
|
|
return max_len;
|
|
return value.size();
|
|
}
|
|
|
|
template<typename Char>
|
|
void do_write_str(std::basic_ostream<Char>& out, const Char *str, int len) {
|
|
out.write(str, len);
|
|
}
|
|
|
|
template<typename Src, typename Dst>
|
|
void convert_and_write(std::basic_ostream<Dst>& out, const Src *str, int len, const char *src_enc, const char *dst_enc) {
|
|
Dst buffer[512];
|
|
agi::charset::Iconv cd(src_enc, dst_enc);
|
|
size_t in_len = len * sizeof(Src);
|
|
const char *in = reinterpret_cast<const char *>(str);
|
|
|
|
size_t res;
|
|
do {
|
|
char *out_buf = reinterpret_cast<char *>(buffer);
|
|
size_t out_len = sizeof(buffer);
|
|
res = cd(&in, &in_len, &out_buf, &out_len);
|
|
if (res == 0) cd(nullptr, nullptr, &out_buf, &out_len);
|
|
|
|
out.write(buffer, (sizeof(buffer) - out_len) / sizeof(Dst));
|
|
} while (res == (size_t)-1 && errno == E2BIG);
|
|
}
|
|
|
|
void do_write_str(std::ostream& out, const wchar_t *str, int len) {
|
|
convert_and_write(out, str, len, WCHAR_T_ENC, "utf-8");
|
|
}
|
|
|
|
void do_write_str(std::wostream& out, const char *str, int len) {
|
|
convert_and_write(out, str, len, "utf-8", WCHAR_T_ENC);
|
|
}
|
|
|
|
template<typename Char>
|
|
struct format_parser {
|
|
agi::format_detail::formatter_state<Char> &s;
|
|
|
|
void read_and_append_up_to_next_specifier() {
|
|
for (std::streamsize len = 0; ; ++len) {
|
|
// Ran out of format specifiers; not an error due to that
|
|
// translated strings may not need them all
|
|
if (!s.fmt[len]) {
|
|
s.out.write(s.fmt, len);
|
|
s.fmt += len;
|
|
return;
|
|
}
|
|
|
|
if (s.fmt[len] == '%') {
|
|
if (s.fmt[len + 1] == '%') {
|
|
s.out.write(s.fmt, len);
|
|
s.fmt += len + 1;
|
|
len = 0;
|
|
continue;
|
|
}
|
|
|
|
s.out.write(s.fmt, len);
|
|
s.fmt += len;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int read_int() {
|
|
int i = 0;
|
|
for (; *s.fmt_cur >= '0' && *s.fmt_cur <= '9'; ++s.fmt_cur)
|
|
i = 10 * i + (*s.fmt_cur - '0');
|
|
return i;
|
|
}
|
|
|
|
void parse_flags() {
|
|
for (; ; ++s.fmt_cur) {
|
|
switch (*s.fmt_cur) {
|
|
// Not supported: ' ' (add a space before positive numers to align with negative)
|
|
case '#':
|
|
s.out.setf(std::ios::showpoint | std::ios::showbase);
|
|
continue;
|
|
case '0':
|
|
// overridden by left alignment ('-' flag)
|
|
if (!(s.out.flags() & std::ios::left)) {
|
|
// Use internal padding so that numeric values are
|
|
// formatted correctly, eg -00010 rather than 000-10
|
|
s.out.fill('0');
|
|
s.out.setf(std::ios::internal, std::ios::adjustfield);
|
|
}
|
|
continue;
|
|
case '-':
|
|
s.out.fill(' ');
|
|
s.out.setf(std::ios::left, std::ios::adjustfield);
|
|
continue;
|
|
case '+':
|
|
s.out.setf(std::ios::showpos);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void parse_width() {
|
|
if (*s.fmt_cur >= '0' && *s.fmt_cur <= '9')
|
|
s.width = read_int();
|
|
else if (*s.fmt_cur == '*') {
|
|
s.read_width = true;
|
|
s.pending = true;
|
|
++s.fmt_cur;
|
|
}
|
|
}
|
|
|
|
void parse_precision() {
|
|
if (*s.fmt_cur != '.') return;
|
|
++s.fmt_cur;
|
|
|
|
// Ignoring negative precision because it's dumb and pointless
|
|
if (*s.fmt_cur >= '0' && *s.fmt_cur <= '9')
|
|
s.precision = read_int();
|
|
else if (*s.fmt_cur == '*') {
|
|
s.read_precision = true;
|
|
s.pending = true;
|
|
++s.fmt_cur;
|
|
}
|
|
else
|
|
s.precision = 0;
|
|
}
|
|
|
|
void parse_length_modifiers() {
|
|
// Where "parse" means "skip" since we don't need them
|
|
for (Char c = *s.fmt_cur;
|
|
c == 'l' || c == 'h' || c == 'L' || c == 'j' || c == 'z' || c == 't';
|
|
c = *++s.fmt_cur);
|
|
}
|
|
|
|
void parse_format_specifier() {
|
|
s.width = 0;
|
|
s.precision = -1;
|
|
s.out.fill(' ');
|
|
s.out.unsetf(
|
|
std::ios::adjustfield |
|
|
std::ios::basefield |
|
|
std::ios::boolalpha |
|
|
std::ios::floatfield |
|
|
std::ios::showbase |
|
|
std::ios::showpoint |
|
|
std::ios::showpos |
|
|
std::ios::uppercase);
|
|
|
|
// Don't touch fmt until the specifier is fully applied so that if we
|
|
// have insufficient arguments it'll get passed through to the output
|
|
s.fmt_cur = s.fmt + 1;
|
|
|
|
parse_flags();
|
|
parse_width();
|
|
parse_precision();
|
|
parse_length_modifiers();
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace agi {
|
|
|
|
template<typename StreamChar, typename Char>
|
|
void writer<StreamChar, const Char *>::write(std::basic_ostream<StreamChar>& out, int max_len, const Char *value) {
|
|
do_write_str(out, value, actual_len(max_len, value));
|
|
}
|
|
|
|
template<typename StreamChar, typename Char>
|
|
void writer<StreamChar, std::basic_string<Char>>::write(std::basic_ostream<StreamChar>& out, int max_len, std::basic_string<Char> const& value) {
|
|
do_write_str(out, value.data(), actual_len(max_len, value));
|
|
}
|
|
|
|
template struct writer<char, const char *>;
|
|
template struct writer<char, const wchar_t *>;
|
|
template struct writer<wchar_t, const char *>;
|
|
template struct writer<wchar_t, const wchar_t *>;
|
|
template struct writer<char, std::string>;
|
|
template struct writer<char, std::wstring>;
|
|
template struct writer<wchar_t, std::string>;
|
|
template struct writer<wchar_t, std::wstring>;
|
|
|
|
namespace format_detail {
|
|
|
|
template<typename Char>
|
|
bool formatter<Char>::parse_next() {
|
|
format_parser<Char> parser{*static_cast<formatter_state<Char> *>(this)};
|
|
parser.read_and_append_up_to_next_specifier();
|
|
if (!*this->fmt) return false;
|
|
parser.parse_format_specifier();
|
|
return true;
|
|
}
|
|
|
|
template<typename Char>
|
|
formatter<Char>::~formatter() {
|
|
// Write remaining formatting string
|
|
for (std::streamsize len = 0; ; ++len) {
|
|
if (!this->fmt[len]) {
|
|
this->out.write(this->fmt, len);
|
|
return;
|
|
}
|
|
|
|
if (this->fmt[len] == '%' && this->fmt[len + 1] == '%') {
|
|
this->out.write(this->fmt, len);
|
|
this->fmt += len + 1;
|
|
len = 0;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
template class formatter<char>;
|
|
template class formatter<wchar_t>;
|
|
|
|
} }
|
|
|