homer512

C++ encoding conversion

Nov 16th, 2013
212
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 18.12 KB | None | 0 0
  1. /*
  2.  * Copyright 2013 Florian Philipp
  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. #include <locale>
  17. // using std::locale, std::codecvt
  18. #include <cwchar>
  19. // using std::mbstate_t
  20. #include <string>
  21. // using std::string
  22. #include <cstddef>
  23. // using EXIT_SUCCESS
  24. #include <stdexcept>
  25. // using std::domain_error, std::runtime_error
  26. #include <vector>
  27. // using std::vector
  28. #include <cstring>
  29. // using std::strlen
  30. #include <iterator>
  31. // using std::distance
  32. #include <sstream>
  33. // using std::ostringstream, std::hex, std::endl
  34. #include <iostream>
  35. // using std::cout
  36. #include <memory>
  37. // using std::unique_ptr
  38.  
  39. namespace conv {
  40.   namespace internal {
  41.     /**
  42.      * Shorthand notation for the conversion facet
  43.      */
  44.     typedef std::codecvt<wchar_t, char, std::mbstate_t> cvt_t;
  45.  
  46.     /**
  47.      * Base class for EncodingState
  48.      */
  49.     class EncodingStateBase
  50.     {
  51.       const std::locale locale;
  52.       std::mbstate_t state;
  53.       cvt_t::result result;
  54.     protected:
  55.       EncodingStateBase(const std::locale& locale):
  56.     locale(locale),
  57.     state(std::mbstate_t() /* needs explicit default initialization */)
  58.       {}
  59.       ~EncodingStateBase() {}
  60.       void set_result(cvt_t::result result) { this->result = result; }
  61.       /**
  62.        * One of two overloaded functions dispatching to cvt_t::in or cvt_t::out
  63.        * depending on the parameters
  64.        *
  65.        * Simplifies the implementation of EncodingState::encode
  66.        */
  67.       static cvt_t::result do_convert(const cvt_t& cvt, std::mbstate_t& state,
  68.                       const char* in, const char* in_end,
  69.                       const char*& in_mark, wchar_t* out,
  70.                       wchar_t* out_end, wchar_t*& out_mark)
  71.       {
  72.     return cvt.in(state, in, in_end, in_mark, out, out_end, out_mark);
  73.       }
  74.       static cvt_t::result do_convert(const cvt_t& cvt, std::mbstate_t& state,
  75.                       const wchar_t* in, const wchar_t* in_end,
  76.                       const wchar_t*& in_mark, char* out,
  77.                       char* out_end, char*& out_mark)
  78.       {
  79.     return cvt.out(state, in, in_end, in_mark, out, out_end, out_mark);
  80.       }
  81.     public:
  82.       /**
  83.        * \return the locale used for the conversion
  84.        */
  85.       const std::locale& get_locale() const { return locale; }
  86.       /**
  87.        * \return a reference to the conversion state
  88.        */
  89.       std::mbstate_t& get_mbstate() { return state; }
  90.       /**
  91.        * \return the result of the last conversion. Undefined if encode has not
  92.        * yet been called
  93.        */
  94.       cvt_t::result get_result() const { return result; }
  95.     };
  96.  
  97.     /**
  98.      * Keeps track of information relevant for an ongoing conversion between
  99.      * wide and narrow characters
  100.      *
  101.      * \tparam source_char_t const char or const wchar_t
  102.      * \tparam target_char_t char or wchar_t
  103.      */
  104.     template<class source_char_t, class target_char_t>
  105.     class EncodingState: public EncodingStateBase
  106.     {
  107.     private:
  108.       source_char_t* first_unconverted;
  109.       source_char_t* const end_unconverted;
  110.       target_char_t* target_buf;
  111.       target_char_t* const end_target_buf;
  112.     public:
  113.       /**
  114.        * Initializes the encoding state
  115.        *
  116.        * \param locale the encoding used for conversion. Needs to have the cvt_t
  117.        * facet
  118.        * \param source_begin first character to convert
  119.        * \param source_end pointer behind the last character to convert
  120.        * \param target_begin pointer to an output buffer. Needs to be large
  121.        * enough
  122.        * \param target_end pointer to the end of the output buffer
  123.        */
  124.       EncodingState(const std::locale& locale, source_char_t* source_begin,
  125.             source_char_t* source_end, target_char_t* target_begin,
  126.             target_char_t* target_end):
  127.     EncodingStateBase(locale), first_unconverted(source_begin),
  128.     end_unconverted(source_end), target_buf(target_begin),
  129.     end_target_buf(target_end)
  130.       {}
  131.       /**
  132.        * \return a pointer to the first character that has not been converted,
  133.        * yet
  134.        */
  135.       source_char_t* get_first_unconverted() const { return first_unconverted; }
  136.       /**
  137.        * \return a pointer to the end of the input
  138.        */
  139.       source_char_t* get_end_unconverted() const { return end_unconverted; }
  140.       /**
  141.        * \return a pointer to the first unused character in the output buffer
  142.        */
  143.       target_char_t* get_target_buf() const { return target_buf; }
  144.       /**
  145.        * \return a pointer to the end of the output buffer
  146.        */
  147.       target_char_t* get_end_target() const { return end_target_buf; }
  148.       /**
  149.        * Skips the next input character, adding replacement to the output
  150.        *
  151.        * No range checks are performed prior to inserting the replacement into
  152.        * the output buffer
  153.        *
  154.        * \param replacement a character in the output encoding
  155.        */
  156.       void replace_first(target_char_t replacement)
  157.       {
  158.     ++first_unconverted;
  159.     *(target_buf++) = replacement;
  160.       }
  161.       /**
  162.        * Converts using the current state
  163.        *
  164.        * Updates first_unconverted, target_buf, mbstate and result
  165.        * \throw std::bad_cast if the locale does not have the cvt_t facet
  166.        */
  167.       void encode()
  168.       {
  169.     const cvt_t& converter = std::use_facet<cvt_t>(get_locale());
  170.     source_char_t* source_mark;
  171.     target_char_t* target_mark;
  172.     set_result(do_convert(converter, get_mbstate(),
  173.                   first_unconverted, end_unconverted, source_mark,
  174.                   target_buf, end_target_buf, target_mark));
  175.     first_unconverted = source_mark;
  176.     target_buf = target_mark;
  177.       }
  178.     };
  179.    
  180.     /**
  181.      * Instanziation of EncodingState for conversions from narrow characters
  182.      * to wide characters
  183.      */
  184.     typedef EncodingState<const char, wchar_t> WidenEncodingState;
  185.  
  186.     /**
  187.      * Instanziation of EncodingState for conversions from wide characters
  188.      * to narrow characters
  189.      */
  190.     typedef EncodingState<const wchar_t, char> NarrowEncodingState;
  191.  
  192.     /**
  193.      * Interface class of the strategy pattern to determine the way an
  194.      * EncodingConverter reacts to conversion errors
  195.      */
  196.     class EncodingErrorStrategy
  197.     {
  198.     public:
  199.       virtual ~EncodingErrorStrategy() {}
  200.       /**
  201.        * Evaluates and possibly updates the EncodingState after an encoding
  202.        * attempt
  203.        *
  204.        * \param state the current conversion state, including a valid result
  205.        * \return true if conversion shall be attempted again with the same
  206.        * (updated) state
  207.        * \throw any std::exception, depending on the specialization
  208.        */
  209.       virtual bool evaluate_continue(WidenEncodingState& state) = 0;
  210.       virtual bool evaluate_continue(NarrowEncodingState& state) = 0;
  211.     };
  212.   } /* namespace internal */
  213.  
  214.   /**
  215.    * EncodingErrorStrategy which replaces offending characters with valid ones
  216.    */
  217.   class ReplaceEncodingError: public internal::EncodingErrorStrategy
  218.   {
  219.     const wchar_t wide_replacement;
  220.     const char narrow_replacement;
  221.  
  222.     /**
  223.      * Checks the state result, inserting the replacement if necessary
  224.      *
  225.      * \tparam char_t a character type compatible with
  226.      * encoding_state_t::replace_first
  227.      * \tparam encoding_state_t a template instantiation of
  228.      * internal::EncodingState
  229.      * \param state the current encoding state including a valid result
  230.      * \param replacement the character to insert on encoding errors
  231.      */
  232.     template<class char_t, class encoding_state_t>
  233.     static bool replace_on_error(encoding_state_t& state, char_t replacement)
  234.     {
  235.       if(state.get_result() != internal::cvt_t::error)
  236.     return false;
  237.       state.replace_first(replacement);
  238.       return true;
  239.     }
  240.   public:
  241.     /**
  242.      * \param wide_replacement a wide character valid in both locales used by
  243.      * containing EncodingConverter
  244.      * \param narrow_replacement a narrow character valid in the target encoding
  245.      * used by the containing EncodingConverter
  246.      */
  247.     ReplaceEncodingError(wchar_t wide_replacement, char narrow_replacement):
  248.       wide_replacement(wide_replacement), narrow_replacement(narrow_replacement)
  249.     {}
  250.     virtual ~ReplaceEncodingError() {}
  251.     /**
  252.      * \see internal::EncodingErrorStrategy::evaluate_continue
  253.      */
  254.     virtual bool evaluate_continue(internal::WidenEncodingState& state)
  255.     {
  256.       return replace_on_error(state, wide_replacement);
  257.     }
  258.     /**
  259.      * \see internal::EncodingErrorStrategy::evaluate_continue
  260.      */
  261.     virtual bool evaluate_continue(internal::NarrowEncodingState& state)
  262.     {
  263.       return replace_on_error(state, narrow_replacement);
  264.     }
  265.   };
  266.   /**
  267.    * Default EncodingErrorStrategy used by EncodingConverter. Throws an
  268.    * std::domain_error on encoding errors
  269.    */
  270.   class SignalEncodingError: public internal::EncodingErrorStrategy
  271.   {
  272.     /**
  273.      * Produces an exception
  274.      *
  275.      * \param character the offending wide or narrow character cast to integer
  276.      * \param loc the locale that could not handle the character
  277.      * \throw std::bad_alloc if memory allocation fails
  278.      * \throw std::domain_error in the normal case
  279.      */
  280.     static void encoding_error(int character, const std::locale& loc)
  281.     {
  282.       std::ostringstream err;
  283.       err << "Invalid character 0x" << std::hex << character
  284.       << " for locale " << loc.name();
  285.     throw std::domain_error(err.str());
  286.     }
  287.     /**
  288.      * Produces an exception if the encoding state contains an error
  289.      *
  290.      * \tparam encoding_state_t a template instantiation of
  291.      * internal::EncodingState
  292.      * \param state the current encoding state containing a result
  293.      * \return false if there was no error
  294.      * \throw std::domain_error on error
  295.      * \throw std::bad_alloc if memory allocation fails
  296.      */
  297.     template<class encoding_state_t>
  298.     static bool throw_on_error(encoding_state_t& state)
  299.     {
  300.       if(state.get_result() == internal::cvt_t::error)
  301.     encoding_error(*(state.get_first_unconverted()), state.get_locale());
  302.       return false;
  303.     }
  304.   public:
  305.     virtual ~SignalEncodingError() {}
  306.     /**
  307.      * \see internal::EncodingErrorStrategy::evaluate_continue
  308.      * \throw std::domain_error on error
  309.      * \throw std::bad_alloc if memory allocation fails
  310.      */
  311.     virtual bool evaluate_continue(internal::WidenEncodingState& state)
  312.     {
  313.       return throw_on_error(state);
  314.     }
  315.     /**
  316.      * \see internal::EncodingErrorStrategy::evaluate_continue
  317.      * \throw std::domain_error on error
  318.      * \throw std::bad_alloc if memory allocation fails
  319.      */
  320.     virtual bool evaluate_continue(internal::NarrowEncodingState& state)
  321.     {
  322.       return throw_on_error(state);
  323.     }
  324.   };
  325.   /**
  326.    * Converter between two encodings
  327.    */
  328.   class EncodingConverter
  329.   {
  330.     /**
  331.      * locale determining the input encoding
  332.      */
  333.     const std::locale source_locale;
  334.     /**
  335.      * locale determining the output encoding
  336.      */
  337.     const std::locale target_locale;
  338.     /**
  339.      * internal buffer for widened characters
  340.      *
  341.      * std::wstring is not used because read/write access to the raw array
  342.      * pointer is required
  343.      */
  344.     std::vector<wchar_t> wide;
  345.     /**
  346.      * internal buffer for characters in the target encoding
  347.      *
  348.      * std::string is not used because read/write access to the raw array
  349.      * pointer is required
  350.      */
  351.     std::vector<char> narrow;
  352.     /**
  353.      * Strategy determining how encoding errors are handled. Never NULL
  354.      */
  355.     std::unique_ptr<internal::EncodingErrorStrategy> result_strategy;
  356.    
  357.     /**
  358.      * Handles the actual conversion process
  359.      *
  360.      * Precondition: Sufficient memory is allocated
  361.      * Postcondition: out is filled with valid characters and its size is
  362.      * correctly set. In case of exceptions, out is in a valid but undefined
  363.      * state
  364.      *
  365.      * \tparam source_char_t character type of the input. char or wchar_t
  366.      * \tparam target_char_t character type of the output, wchar_t or char
  367.      * \param locale determines the encoding used for the conversion
  368.      * \param in pointer to a character array which is to be converted
  369.      * \param in_end pointer after the end of in
  370.      * \param out vector used for output. Memory needs to be pre-allocated
  371.      * \throw std::bad_cast if locale does not have the cvt_t facet
  372.      * \throw any std::exception if result_strategy wants to
  373.      */
  374.     template<class source_char_t, class target_char_t>
  375.     void do_convert(const std::locale& locale, const source_char_t* in,
  376.             const source_char_t* in_end,
  377.             std::vector<target_char_t>& out)
  378.     {
  379.       typedef internal::EncodingState<const source_char_t, target_char_t>
  380.     encoding_state_t;
  381.       encoding_state_t state(locale, in, in_end, out.data(),
  382.                  out.data() + out.size());
  383.       do {
  384.     state.encode();
  385.       } while(state.get_result() != internal::cvt_t::ok
  386.           && result_strategy->evaluate_continue(state));
  387.       /* shrink out to the actual size */
  388.       const std::size_t chars_n = std::distance(out.data(),
  389.                         state.get_target_buf());
  390.       out.resize(chars_n);
  391.     }
  392.     /**
  393.      * Fills the wide member, updating its size
  394.      *
  395.      * On exceptions, wide remains valid but its content and size are undefined
  396.      *
  397.      * \param first input character in the source_locale encoding
  398.      * \param last pointer after the last input character
  399.      * \throw std::bad_alloc if memory allocation for wide fails
  400.      * \throw std::bad_cast if source_locale does not have the cvt_t facet
  401.      * \throw any std::exception if result_strategy wants to
  402.      */
  403.     void to_widechar(const char* first, const char* const last)
  404.     {
  405.       /* reserve enough memory for any conceivable interpretation of the input.
  406.        * No encoding needs more characters in its wide representation than its
  407.        * narrow multibyte or fixed size encoding requires bytes
  408.        */
  409.       const std::size_t bytes_n = std::distance(first, last);
  410.       wide.resize(bytes_n);
  411.       do_convert(source_locale, first, last, wide);
  412.     }
  413.     /**
  414.      * Fills the narrow member with data from the wide member, updating its size
  415.      *
  416.      * Pre-condition: wide contains characters in the valid widened source
  417.      * encoding
  418.      * Post-condition: narrow contains only characters in the target encoding.
  419.      * In case of exceptions, narrow remains valid but in undefined state
  420.      *
  421.      * \throw std::bad_alloc if memory allocation for narrow fails
  422.      * \throw std::bad_cast if the target locale does not have the cvt_t facet
  423.      * \throw any std::exception if result_strategy wants to
  424.      */
  425.     void to_multibyte()
  426.     {
  427.       /* reserve memory in narrow based on a worst-case estimate */
  428.       const internal::cvt_t& converter =
  429.     std::use_facet<internal::cvt_t>(target_locale);
  430.       narrow.resize(wide.size() * converter.max_length());
  431.       do_convert(target_locale, wide.data(), wide.data() + wide.size(), narrow);
  432.     }
  433.     EncodingConverter(const EncodingConverter& o);
  434.     EncodingConverter& operator=(const EncodingConverter& o);
  435.   public:
  436.     /**
  437.      * Initialized the EncodingConverter with the given locales and the
  438.      * SignalEncodingError strategy
  439.      *
  440.      * \param locale_from a library-specific name of a locale, e.g. "en_US.UTF8"
  441.      * \param locale_to a a library-specific name of a locale, e.g.
  442.      * "en_US.ISO-8859-1"
  443.      * \throw std::runtime_error if one of the locales is unsupported
  444.      */
  445.     EncodingConverter(const char* locale_from, const char* locale_to):
  446.       source_locale(locale_from),
  447.       target_locale(locale_to),
  448.       result_strategy(new SignalEncodingError)
  449.     {}
  450.     /**
  451.      * Changes the EncodingErrorStrategy
  452.      *
  453.      * \param strategy for handling encoding errors. Must not be NULL. Has to be
  454.      * new-allocated. The EncodingConverter takes ownership
  455.      */
  456.     void set_error_behavior(internal::EncodingErrorStrategy* strategy)
  457.     {
  458.       result_strategy.reset(strategy);
  459.     }
  460.     /**
  461.      * Transcodes the given characters from the source locale to the target
  462.      * locale
  463.      *
  464.      * \param first pointer to a character array in the encoding used by the
  465.      * source locale
  466.      * \param last pointer behind the end of the character array specified by
  467.      * first
  468.      * \return a string in the encoding specified by the target locale
  469.      * \throw std::bad_alloc if memory allocation fails
  470.      * \throw std::bad_cast if one of the locales does not support encoding
  471.      * conversion
  472.      * \throw any std::exception on encoding errors depending on used
  473.      * EncodingErrorStrategy
  474.      */
  475.     std::string operator()(const char* first, const char* last)
  476.     {
  477.       to_widechar(first, last);
  478.       to_multibyte();
  479.       return std::string(narrow.begin(), narrow.end());
  480.     }
  481.     /**
  482.      * \see operator()(const char*, const char*)
  483.      * \param c_str a '\0'-terminated string in the encoding specified by the
  484.      * source locale
  485.      */
  486.     std::string operator()(const char* c_str)
  487.     {
  488.       return (*this)(c_str, c_str + std::strlen(c_str));
  489.     }
  490.     /**
  491.      * \see operator()(const char*, const char*)
  492.      * \param str a string in the encoding specified by the source locale
  493.      */
  494.     std::string operator()(const std::string& str)
  495.     {
  496.       return (*this)(str.data(), str.data() + str.size());
  497.     }
  498.   };
  499. } /* namespace conv */
  500.  
  501. /**
  502.  * Converts all command line parameters from UTF-8 to ISO-8859-1
  503.  * Replaces characters that cannot be converted with '?'
  504.  * Writes the output to stdout
  505.  */
  506. int main(int argc, char** argv)
  507. {
  508.   conv::EncodingConverter conv("de_DE.UTF-8", "en_US.ISO-8859-1");
  509.   conv.set_error_behavior(new conv::ReplaceEncodingError(L'?', '?'));
  510.   for(int i = 1; i < argc; ++i)
  511.     std::cout << conv(argv[i]) << std::endl;
  512.   return EXIT_SUCCESS;
  513. }
Advertisement
Add Comment
Please, Sign In to add comment