dlib C++ Library - string.h

// Copyright (C) 2006 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_STRINg_
#define DLIB_STRINg_ 
#include "string_abstract.h"
#include <sstream>
#include "../algs.h"
#include <string>
#include <iostream>
#include <iomanip>
#include "../error.h"
#include "../assert.h"
#include "../uintn.h"
#include <cctype>
#include <algorithm>
#include <vector>
#include "../enable_if.h"
namespace dlib
{
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 inline const typename disable_if<is_same_type<charT,char>,std::string>::type narrow (
 const std::basic_string<charT,traits,alloc>& str
 )
 {
 std::string temp;
 temp.reserve(str.size());
 std::string::size_type i;
 for (i = 0; i < str.size(); ++i)
 {
 if (zero_extend_cast<unsigned long>(str[i]) > 255)
 temp += ' ';
 else
 temp += zero_extend_cast<char>(str[i]);
 }
 return temp;
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 inline const typename enable_if<is_same_type<charT,char>,std::string>::type narrow (
 const std::basic_string<charT,traits,alloc>& str
 )
 { 
 return str;
 }
// ----------------------------------------------------------------------------------------
 template <
 typename traits,
 typename alloc
 >
 const std::basic_string<char,traits,alloc> tolower (
 const std::basic_string<char,traits,alloc>& str
 )
 {
 std::basic_string<char,traits,alloc> temp;
 temp.resize(str.size());
 for (typename std::basic_string<char,traits,alloc>::size_type i = 0; i < str.size(); ++i)
 temp[i] = (char)std::tolower(str[i]);
 return temp;
 }
// ----------------------------------------------------------------------------------------
 template <
 typename traits,
 typename alloc
 >
 const std::basic_string<char,traits,alloc> toupper (
 const std::basic_string<char,traits,alloc>& str
 )
 {
 std::basic_string<char,traits,alloc> temp;
 temp.resize(str.size());
 for (typename std::basic_string<char,traits,alloc>::size_type i = 0; i < str.size(); ++i)
 temp[i] = (char)std::toupper(str[i]);
 return temp;
 }
// ----------------------------------------------------------------------------------------
 template <
 typename traits,
 typename alloc
 >
 bool strings_equal_ignore_case (
 const std::basic_string<char,traits,alloc>& str1,
 const std::basic_string<char,traits,alloc>& str2
 )
 {
 if (str1.size() != str2.size())
 return false;
 for (typename std::basic_string<char,traits,alloc>::size_type i = 0; i < str1.size(); ++i)
 {
 if (std::tolower(str1[i]) != std::tolower(str2[i]))
 return false;
 }
 return true;
 }
 template <
 typename traits,
 typename alloc
 >
 bool strings_equal_ignore_case (
 const std::basic_string<char,traits,alloc>& str1,
 const char* str2
 )
 {
 typename std::basic_string<char,traits,alloc>::size_type i;
 for (i = 0; i < str1.size(); ++i)
 {
 // if we hit the end of str2 then the strings aren't the same length
 if (str2[i] == '0円')
 return false;
 if (std::tolower(str1[i]) != std::tolower(str2[i]))
 return false;
 }
 // This happens when str2 is longer than str1
 if (str2[i] != '0円')
 return false;
 return true;
 }
 template <
 typename traits,
 typename alloc
 >
 bool strings_equal_ignore_case (
 const char* str1,
 const std::basic_string<char,traits,alloc>& str2
 )
 {
 return strings_equal_ignore_case(str2, str1);
 }
// ----------------------------------------------------------------------------------------
 template <
 typename traits,
 typename alloc
 >
 bool strings_equal_ignore_case (
 const std::basic_string<char,traits,alloc>& str1,
 const std::basic_string<char,traits,alloc>& str2,
 unsigned long num
 )
 {
 if (str1.size() != str2.size() && (str1.size() < num || str2.size() < num))
 return false;
 for (typename std::basic_string<char,traits,alloc>::size_type i = 0; i < str1.size() && i < num; ++i)
 {
 if (std::tolower(str1[i]) != std::tolower(str2[i]))
 return false;
 }
 return true;
 }
 template <
 typename traits,
 typename alloc
 >
 bool strings_equal_ignore_case (
 const std::basic_string<char,traits,alloc>& str1,
 const char* str2,
 unsigned long num
 )
 {
 typename std::basic_string<char,traits,alloc>::size_type i;
 for (i = 0; i < str1.size() && i < num; ++i)
 {
 // if we hit the end of str2 then the strings aren't the same length
 if (str2[i] == '0円')
 return false;
 if (std::tolower(str1[i]) != std::tolower(str2[i]))
 return false;
 }
 return true;
 }
 template <
 typename traits,
 typename alloc
 >
 bool strings_equal_ignore_case (
 const char* str1,
 const std::basic_string<char,traits,alloc>& str2,
 unsigned long num
 )
 {
 return strings_equal_ignore_case(str2, str1, num);
 }
// ----------------------------------------------------------------------------------------
 class cast_to_string_error : public error
 {
 public:
 cast_to_string_error():error(ECAST_TO_STRING) {}
 };
 template <
 typename T
 >
 const std::string cast_to_string (
 const T& item 
 )
 {
 std::ostringstream sout;
 sout << item;
 if (!sout)
 throw cast_to_string_error();
 return sout.str();
 }
 // don't declare this if we are using mingw because it apparently doesn't
 // support iostreams with wchar_t?
#if !(defined(__MINGW32__) && (__GNUC__ < 4))
 template <
 typename T
 >
 const std::wstring cast_to_wstring (
 const T& item 
 )
 {
 std::basic_ostringstream<wchar_t> sout;
 sout << item;
 if (!sout)
 throw cast_to_string_error();
 return sout.str();
 }
#endif
// ----------------------------------------------------------------------------------------
 inline std::string pad_int_with_zeros (
 int i,
 unsigned long width = 6
 )
 {
 std::ostringstream sout;
 sout << std::setw(width) << std::setfill('0') << i;
 return sout.str();
 }
// ----------------------------------------------------------------------------------------
 class string_cast_error : public error
 {
 public:
 string_cast_error(const std::string& str):
 error(ESTRING_CAST,"string cast error: invalid string = '" + str + "'") {}
 };
 template <
 typename T
 >
 struct string_cast_helper
 {
 template < typename charT, typename traits, typename alloc >
 static const T cast (
 const std::basic_string<charT,traits,alloc>& str
 )
 {
 std::basic_istringstream<charT,traits,alloc> sin(str);
 T temp;
 sin >> temp;
 if (!sin) throw string_cast_error(narrow(str));
 if (sin.get() != std::char_traits<charT>::eof()) throw string_cast_error(narrow(str)); 
 return temp;
 }
 };
 template <typename C, typename T, typename A>
 struct string_cast_helper<std::basic_string<C,T,A> >
 {
 template < typename charT, typename traits, typename alloc >
 static const std::basic_string<C,T,A> cast (
 const std::basic_string<charT,traits,alloc>& str
 )
 {
 std::basic_string<C,T,A> temp;
 temp.resize(str.size());
 for (unsigned long i = 0; i < str.size(); ++i)
 temp[i] = zero_extend_cast<C>(str[i]);
 return temp;
 }
 };
 template <>
 struct string_cast_helper<bool>
 {
 template < typename charT, typename traits, typename alloc >
 static bool cast (
 const std::basic_string<charT,traits,alloc>& str
 )
 {
 if (str.size() == 1 && str[0] == '1')
 return true;
 if (str.size() == 1 && str[0] == '0')
 return false;
 if (tolower(narrow(str)) == "true")
 return true;
 if (tolower(narrow(str)) == "false")
 return false;
 throw string_cast_error(narrow(str));
 }
 };
#define DLIB_STRING_CAST_INTEGRAL(type) \
 template <> \
 struct string_cast_helper<type> \
 { \
 template < typename charT, typename traits, typename alloc> \
 static type cast ( \
 const std::basic_string<charT,traits,alloc>& str \
 ) \
 { \
 std::basic_istringstream<charT,traits,alloc> sin(str); \
 type temp; \
 if (str.size() > 2 && str[0] == _dT(charT,'0') && str[1] == _dT(charT,'x')) \
 sin >> std::hex >> temp; \
 else \
 sin >> temp; \
 if (!sin) throw string_cast_error(narrow(str)); \
 if (sin.get() != std::char_traits<charT>::eof()) throw string_cast_error(narrow(str)); \
 return temp; \
 } \
 };
 DLIB_STRING_CAST_INTEGRAL(unsigned short)
 DLIB_STRING_CAST_INTEGRAL(short)
 DLIB_STRING_CAST_INTEGRAL(unsigned int)
 DLIB_STRING_CAST_INTEGRAL(int)
 DLIB_STRING_CAST_INTEGRAL(unsigned long)
 DLIB_STRING_CAST_INTEGRAL(long)
 DLIB_STRING_CAST_INTEGRAL(uint64)
 template <
 typename T,
 typename charT,
 typename traits,
 typename alloc
 >
 inline const T string_cast (
 const std::basic_string<charT,traits,alloc>& str
 )
 {
 COMPILE_TIME_ASSERT(is_pointer_type<T>::value == false);
 return string_cast_helper<T>::cast(str);
 }
 template <typename T>
 inline const T string_cast (const char* str){ return string_cast<T>(std::string(str)); }
 template <typename T>
 inline const T string_cast (const wchar_t* str){ return string_cast<T>(std::wstring(str)); }
// ----------------------------------------------------------------------------------------
 class string_assign
 {
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 class string_assign_helper
 {
 public:
 string_assign_helper (
 const std::basic_string<charT,traits,alloc>& str_
 ) : str(str_) {}
 template <typename T>
 operator T () const
 {
 return string_cast<T>(str);
 }
 private:
 const std::basic_string<charT,traits,alloc>& str;
 };
 // -------------
 class char_assign_helper
 {
 public:
 char_assign_helper (
 const char* str_
 ) : str(str_) {}
 template <typename T>
 operator T () const
 {
 return string_cast<T>(str);
 }
 private:
 const char* str;
 };
 // -------------
 class wchar_t_assign_helper
 {
 public:
 wchar_t_assign_helper (
 const wchar_t* str_
 ) : str(str_) {}
 template <typename T>
 operator T () const
 {
 return string_cast<T>(str);
 }
 private:
 const wchar_t* str;
 };
 // -------------
 public:
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 string_assign_helper<charT,traits,alloc> operator=(
 const std::basic_string<charT,traits,alloc>& str
 ) const
 {
 return string_assign_helper<charT,traits,alloc>(str);
 }
 char_assign_helper operator= (
 const char* str
 ) const 
 {
 return char_assign_helper(str);
 }
 wchar_t_assign_helper operator= (
 const wchar_t* str
 ) const 
 {
 return wchar_t_assign_helper(str);
 }
 };
 const string_assign sa = string_assign();
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> wrap_string (
 const std::basic_string<charT,traits,alloc>& str,
 const unsigned long first_pad = 0,
 const unsigned long rest_pad = 0,
 const unsigned long max_per_line = 79
 )
 {
 DLIB_ASSERT ( first_pad < max_per_line && rest_pad < max_per_line && 
 rest_pad >= first_pad,
 "\tconst std::basic_string<charT,traits,alloc> wrap_string()"
 << "\n\tfirst_pad: " << first_pad
 << "\n\trest_pad: " << rest_pad
 << "\n\tmax_per_line: " << max_per_line );
 std::basic_ostringstream<charT,traits,alloc> sout;
 std::basic_istringstream<charT,traits,alloc> sin(str);
 for (unsigned long i = 0; i < rest_pad; ++i)
 sout << _dT(charT," ");
 const std::basic_string<charT,traits,alloc> pad(sout.str());
 sout.str(_dT(charT,""));
 for (unsigned long i = 0; i < first_pad; ++i)
 sout << _dT(charT," ");
 typename std::basic_string<charT,traits,alloc>::size_type remaining = max_per_line - rest_pad;
 std::basic_string<charT,traits,alloc> temp;
 sin >> temp;
 while (sin)
 {
 if (temp.size() > remaining)
 {
 if (temp.size() + rest_pad >= max_per_line)
 {
 std::string::size_type i = 0;
 for (; i < temp.size(); ++i)
 {
 sout << temp[i];
 --remaining;
 if (remaining == 0)
 {
 sout << _dT(charT,"\n") << pad;
 remaining = max_per_line - rest_pad;
 }
 }
 }
 else
 {
 sout << _dT(charT,"\n") << pad << temp;
 remaining = max_per_line - rest_pad - temp.size();
 }
 }
 else if (temp.size() == remaining)
 {
 sout << temp;
 remaining = 0;
 }
 else
 {
 sout << temp;
 remaining -= temp.size();
 }
 sin >> temp;
 if (remaining == 0 && sin)
 {
 sout << _dT(charT,"\n") << pad;
 remaining = max_per_line - rest_pad;
 }
 else
 {
 sout << _dT(charT," ");
 --remaining;
 }
 }
 return sout.str();
 }
 template <
 typename charT
 >
 const std::basic_string<charT> wrap_string (
 const charT* str,
 const unsigned long first_pad = 0,
 const unsigned long rest_pad = 0,
 const unsigned long max_per_line = 79
 ) { return wrap_string(std::basic_string<charT>(str),first_pad,rest_pad,max_per_line); }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> ltrim (
 const std::basic_string<charT,traits,alloc>& str,
 const std::basic_string<charT,traits,alloc>& trim_chars 
 )
 {
 typedef std::basic_string<charT,traits,alloc> string;
 typename string::size_type pos = str.find_first_not_of(trim_chars);
 if (pos != string::npos)
 return str.substr(pos);
 else
 return std::basic_string<charT,traits,alloc>();
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> ltrim (
 const std::basic_string<charT,traits,alloc>& str,
 const charT* trim_chars = _dT(charT," \t\r\n")
 ) { return ltrim(str,std::basic_string<charT,traits,alloc>(trim_chars)); }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> rtrim (
 const std::basic_string<charT,traits,alloc>& str,
 const std::basic_string<charT,traits,alloc>& trim_chars 
 )
 {
 typedef std::basic_string<charT,traits,alloc> string;
 typename string::size_type pos = str.find_last_not_of(trim_chars);
 if (pos != string::npos)
 return str.substr(0,pos+1);
 else
 return std::basic_string<charT,traits,alloc>();
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> rtrim (
 const std::basic_string<charT,traits,alloc>& str,
 const charT* trim_chars = _dT(charT," \t\r\n")
 ) { return rtrim(str,std::basic_string<charT,traits,alloc>(trim_chars)); }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> trim (
 const std::basic_string<charT,traits,alloc>& str,
 const std::basic_string<charT,traits,alloc>& trim_chars 
 )
 {
 typedef std::basic_string<charT,traits,alloc> string;
 typename string::size_type lpos = str.find_first_not_of(trim_chars); 
 if (lpos != string::npos)
 {
 typename string::size_type rpos = str.find_last_not_of(trim_chars);
 return str.substr(lpos,rpos-lpos+1);
 }
 else
 {
 return std::basic_string<charT,traits,alloc>();
 }
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> trim (
 const std::basic_string<charT,traits,alloc>& str,
 const charT* trim_chars = _dT(charT," \t\r\n")
 ) { return trim(str,std::basic_string<charT,traits,alloc>(trim_chars)); }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> rpad (
 const std::basic_string<charT,traits,alloc>& str,
 long pad_length,
 const std::basic_string<charT,traits,alloc>& pad_string 
 )
 {
 typedef std::basic_string<charT,traits,alloc> string;
 // if str is too big then just return str
 if (pad_length <= static_cast<long>(str.size()))
 return str;
 // make the string we will padd onto the string
 string P;
 while (P.size() < pad_length - str.size())
 P += pad_string;
 P = P.substr(0,pad_length - str.size());
 // return the padded string
 return str + P;
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> rpad (
 const std::basic_string<charT,traits,alloc>& str,
 long pad_length,
 const charT* pad_string = _dT(charT," ")
 ) { return rpad(str,pad_length,std::basic_string<charT,traits,alloc>(pad_string)); }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> lpad (
 const std::basic_string<charT,traits,alloc>& str,
 long pad_length,
 const std::basic_string<charT,traits,alloc>& pad_string 
 )
 {
 typedef std::basic_string<charT,traits,alloc> string;
 // if str is too big then just return str
 if (pad_length <= static_cast<long>(str.size()))
 return str;
 // make the string we will padd onto the string
 string P;
 while (P.size() < pad_length - str.size())
 P += pad_string;
 P = P.substr(0,pad_length - str.size());
 // return the padded string
 return P + str;
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> lpad (
 const std::basic_string<charT,traits,alloc>& str,
 long pad_length,
 const charT* pad_string = _dT(charT," ")
 ) { return lpad(str,pad_length,std::basic_string<charT,traits,alloc>(pad_string)); }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> pad (
 const std::basic_string<charT,traits,alloc>& str,
 long pad_length,
 const std::basic_string<charT,traits,alloc>& pad_string 
 )
 {
 const long str_size = static_cast<long>(str.size());
 return rpad(lpad(str,(pad_length-str_size)/2 + str_size,pad_string), 
 pad_length, 
 pad_string);
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> pad (
 const std::basic_string<charT,traits,alloc>& str,
 long pad_length,
 const charT* pad_string = _dT(charT," ")
 ) { return pad(str,pad_length,std::basic_string<charT,traits,alloc>(pad_string)); }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> left_substr (
 const std::basic_string<charT,traits,alloc>& str,
 const std::basic_string<charT,traits,alloc>& delim 
 )
 {
 return str.substr(0,str.find_first_of(delim));
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> left_substr (
 const std::basic_string<charT,traits,alloc>& str,
 const charT* delim = _dT(charT," \n\r\t")
 )
 {
 return str.substr(0,str.find_first_of(delim));
 }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> right_substr (
 const std::basic_string<charT,traits,alloc>& str,
 const std::basic_string<charT,traits,alloc>& delim 
 )
 {
 typename std::basic_string<charT,traits,alloc>::size_type delim_pos = str.find_last_of(delim);
 if (delim_pos != std::basic_string<charT,traits,alloc>::npos)
 return str.substr(delim_pos+1);
 else
 return _dT(charT,"");
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::basic_string<charT,traits,alloc> right_substr (
 const std::basic_string<charT,traits,alloc>& str,
 const charT* delim = _dT(charT," \n\r\t")
 )
 {
 typename std::basic_string<charT,traits,alloc>::size_type delim_pos = str.find_last_of(delim);
 if (delim_pos != std::basic_string<charT,traits,alloc>::npos)
 return str.substr(delim_pos+1);
 else
 return _dT(charT,"");
 }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 std::pair<std::basic_string<charT,traits,alloc>, std::basic_string<charT,traits,alloc> > 
 split_on_first (
 const std::basic_string<charT,traits,alloc>& str,
 const charT* delim = _dT(charT," \n\r\t")
 )
 {
 typename std::basic_string<charT,traits,alloc>::size_type delim_pos = str.find_first_of(delim);
 if (delim_pos != std::basic_string<charT,traits,alloc>::npos)
 return std::make_pair(str.substr(0, delim_pos), str.substr(delim_pos+1));
 else
 return std::make_pair(str, _dT(charT,""));
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 inline std::pair<std::basic_string<charT,traits,alloc>, std::basic_string<charT,traits,alloc> > 
 split_on_first (
 const std::basic_string<charT,traits,alloc>& str,
 const std::basic_string<charT,traits,alloc>& delim 
 )
 {
 return split_on_first(str, delim.c_str());
 }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 std::pair<std::basic_string<charT,traits,alloc>, std::basic_string<charT,traits,alloc> > 
 split_on_last (
 const std::basic_string<charT,traits,alloc>& str,
 const charT* delim = _dT(charT," \n\r\t")
 )
 {
 typename std::basic_string<charT,traits,alloc>::size_type delim_pos = str.find_last_of(delim);
 if (delim_pos != std::basic_string<charT,traits,alloc>::npos)
 return std::make_pair(str.substr(0, delim_pos), str.substr(delim_pos+1));
 else
 return std::make_pair(str, _dT(charT,""));
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 inline std::pair<std::basic_string<charT,traits,alloc>, std::basic_string<charT,traits,alloc> > 
 split_on_last (
 const std::basic_string<charT,traits,alloc>& str,
 const std::basic_string<charT,traits,alloc>& delim 
 )
 {
 return split_on_last(str, delim.c_str());
 }
// ----------------------------------------------------------------------------------------
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::vector<std::basic_string<charT,traits,alloc> > split (
 const std::basic_string<charT,traits,alloc>& str,
 const charT* delim = _dT(charT," \n\r\t")
 )
 {
 std::basic_string<charT,traits,alloc> temp;
 std::vector<std::basic_string<charT,traits,alloc> > res;
 for (unsigned long i = 0; i < str.size(); ++i)
 {
 // check if delim contains the character str[i]
 bool hit = false;
 const charT* d = delim;
 while (*d != '0円')
 {
 if (str[i] == *d)
 {
 hit = true;
 break;
 }
 ++d;
 }
 if (hit)
 {
 if (temp.size() != 0)
 {
 res.push_back(temp);
 temp.clear();
 }
 }
 else
 {
 temp.push_back(str[i]);
 }
 }
 if (temp.size() != 0)
 res.push_back(temp);
 return res;
 }
 template <
 typename charT,
 typename traits,
 typename alloc
 >
 const std::vector<std::basic_string<charT,traits,alloc> > split (
 const std::basic_string<charT,traits,alloc>& str,
 const std::basic_string<charT,traits,alloc>& delim 
 )
 {
 return split(str,delim.c_str());
 }
 inline const std::vector<std::string> split (
 const char* str,
 const char* delim = " \n\r\t"
 )
 {
 return split(std::string(str),delim);
 }
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_STRINg_

AltStyle によって変換されたページ (->オリジナル) /