Create string-function shorten_stl_types
[libi2ncommon] / src / stringfunc.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20 /** @file
21  *
22  * (c) Copyright 2007-2008 by Intra2net AG
23  */
24
25 #include <iostream>
26 #include <string>
27 #include <sstream>
28 #include <stdexcept>
29 #include <algorithm>
30 #include <cmath>    // for round()
31
32 #include <wchar.h>
33 #include <stdlib.h>
34 #include <iconv.h>
35 #include <i18n.h>
36
37 #include <boost/numeric/conversion/cast.hpp>
38 #include <boost/foreach.hpp>
39
40 #include <stringfunc.hxx>
41
42 using namespace std;
43
44 namespace I2n
45 {
46
47
48 namespace
49 {
50
51 const std::string hexDigitsLower("0123456789abcdef");
52 const std::string hexDigitsUpper("0123456789ABCDEF");
53
54
55 struct UpperFunc
56 {
57    char operator() (char c)
58    {
59       return std::toupper(c);
60    }
61 }; // eo struct UpperFunc
62
63
64 struct LowerFunc
65 {
66    char operator() (char c)
67    {
68       return std::tolower(c);
69    }
70 }; // eo struct LowerFunc
71
72
73 } // eo namespace <anonymous>
74
75
76
77 /**
78  * default list of Whitespaces (" \t\r\n");
79  */
80 const std::string Whitespaces = " \t\r\n";
81
82 /**
83  * default list of lineendings ("\r\n");
84  */
85 const std::string LineEndings= "\r\n";
86
87
88
89 /**
90  * @brief checks if a string begins with a given prefix.
91  * @param[in,out] str the string which is tested
92  * @param prefix the prefix which should be tested for.
93  * @return @a true iff the prefix is not empty and the string begins with that prefix.
94  */
95 bool has_prefix(const std::string& str, const std::string& prefix)
96 {
97    if (prefix.empty() || str.empty() || str.size() < prefix.size() )
98    {
99       return false;
100    }
101    return str.compare(0, prefix.size(), prefix) == 0;
102 } // eo has_prefix(const std::string&,const std::string&)
103
104
105 /**
106  * @brief checks if a string ends with a given suffix.
107  * @param[in,out] str the string which is tested
108  * @param suffix the suffix which should be tested for.
109  * @return @a true iff the suffix is not empty and the string ends with that suffix.
110  */
111 bool has_suffix(const std::string& str, const std::string& suffix)
112 {
113    if (suffix.empty() || str.empty() || str.size() < suffix.size() )
114    {
115       return false;
116    }
117    return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
118 } // eo has_suffix(const std::string&,const std::string&)
119
120
121 /**
122  * cut off characters from a given list from front and end of a string.
123  * @param[in,out] str the string which should be trimmed.
124  * @param charlist the list of characters to remove from beginning and end of string
125  * @return the result string.
126  */
127 std::string trim_mod(std::string& str, const std::string& charlist)
128 {
129    // first: trim the beginning:
130    std::string::size_type pos= str.find_first_not_of (charlist);
131    if (pos == std::string::npos)
132    {
133       // whole string consists of charlist (or is already empty)
134       str.clear();
135       return str;
136    }
137    else if (pos>0)
138    {
139       // str starts with charlist
140       str.erase(0,pos);
141    }
142    // now let's look at the tail:
143    pos= str.find_last_not_of(charlist) +1;  // note: we already know there is at least one other char!
144    if ( pos < str.size() )
145    {
146       str.erase(pos, str.size()-pos);
147    }
148    return str;
149 } // eo trim_mod(std::string&,const std::string&)
150
151
152
153 /**
154  * removes last character from a string when it is in a list of chars to be removed.
155  * @param[in,out] str the string.
156  * @param what the list of chars which will be tested for.
157  * @return the resulting string with last char removed (if applicable)
158  */
159 std::string chomp_mod(std::string& str, const std::string& what)
160 {
161    if (str.empty() || what.empty() )
162    {
163       return str;
164    }
165    if (what.find(str.at (str.size()-1) ) != std::string::npos)
166    {
167       str.erase(str.size() - 1);
168    }
169    return str;
170 } // eo chomp_mod(std::string&,const std::string&)
171
172
173 /**
174  * @brief converts a string to lower case.
175  * @param[in,out] str the string to modify.
176  * @return the string
177  */
178 std::string to_lower_mod(std::string& str)
179 {
180    std::transform(str.begin(), str.end(), str.begin(), LowerFunc() );
181    return str;
182 } // eo to_lower_mod(std::string&)
183
184
185 /**
186  * @brief converts a string to upper case.
187  * @param[in,out] str the string to modify.
188  * @return the string
189  */
190 std::string to_upper_mod(std::string& str)
191 {
192    std::transform( str.begin(), str.end(), str.begin(), UpperFunc() );
193    return str;
194 } // eo to_upper_mod(std::string&)
195
196
197
198 /**
199  * cut off characters from a given list from front and end of a string.
200  * @param str the string which should be trimmed.
201  * @param charlist the list of characters to remove from beginning and end of string
202  * @return the result string.
203  */
204 std::string trim (const std::string& str, const std::string& charlist)
205 {
206    // first: trim the beginning:
207    std::string::size_type pos0= str.find_first_not_of(charlist);
208    if (pos0 == std::string::npos)
209    {
210       // whole string consists of charlist (or is already empty)
211       return std::string();
212    }
213    // now let's look at the end:
214    std::string::size_type pos1= str.find_last_not_of(charlist);
215    return str.substr(pos0, pos1 - pos0 + 1);
216 } // eo trim(const std:.string&,const std::string&)
217
218
219 /**
220  * removes last character from a string when it is in a list of chars to be removed.
221  * @param str the string.
222  * @param what the list of chars which will be tested for.
223  * @return the resulting string with last char removed (if applicable)
224  */
225 std::string chomp (const std::string& str, const std::string& what)
226 {
227    if (str.empty() || what.empty() )
228    {
229       return str;
230    }
231    if (what.find(str.at (str.size()-1) ) != std::string::npos)
232    {
233       return str.substr(0, str.size()-1);
234    }
235    return str;
236 } // eo chomp(const std:.string&,const std::string&)
237
238
239 /**
240  * @brief returns a lower case version of a given string.
241  * @param str the string
242  * @return the lower case version of the string
243  */
244 std::string to_lower (const std::string& str)
245 {
246    std::string result(str);
247    return to_lower_mod(result);
248 } // eo to_lower(const std::string&)
249
250
251 /**
252  * @brief returns a upper case version of a given string.
253  * @param str the string
254  * @return the upper case version of the string
255  */
256 std::string to_upper(const std::string& str)
257 {
258    std::string result(str);
259    return to_upper_mod(result);
260 } // eo to_upper(const std::string&)
261
262
263
264 /**
265  * @brief removes a given suffix from a string.
266  * @param str the string.
267  * @param suffix the suffix which should be removed if the string ends with it.
268  * @return the string without the suffix.
269  *
270  * If the string ends with the suffix, it is removed. If the the string doesn't end
271  * with the suffix the original string is returned.
272  */
273 std::string remove_suffix(const std::string& str, const std::string& suffix)
274 {
275    if (has_suffix(str,suffix) )
276    {
277       return str.substr(0, str.size()-suffix.size() );
278    }
279    return str;
280 } // eo remove_suffix(const std::string&,const std::string&)
281
282
283
284 /**
285  * @brief removes a given prefix from a string.
286  * @param str the string.
287  * @param prefix the prefix which should be removed if the string begins with it.
288  * @return the string without the prefix.
289  *
290  * If the string begins with the prefix, it is removed. If the the string doesn't begin
291  * with the prefix the original string is returned.
292  */
293 std::string remove_prefix(const std::string& str, const std::string& prefix)
294 {
295    if (has_prefix(str,prefix) )
296    {
297       return str.substr( prefix.size() );
298    }
299    return str;
300 } // eo remove_prefix(const std::string&,const std::string&)
301
302
303 /**
304  * split a string to key and value delimited by a given delimiter.
305  * The resulting key and value strings are trimmed (Whitespaces removed at beginning and end).
306  * @param str the string which should be splitted.
307  * @param[out] key the resulting key
308  * @param[out] value the resulting value
309  * @param delimiter the delimiter between key and value; default is '='.
310  * @return @a true if the split was successful.
311  */
312 bool pair_split(
313    const std::string& str,
314    std::string& key,
315    std::string& value,
316    char delimiter)
317 {
318    std::string::size_type pos = str.find (delimiter);
319    if (pos == std::string::npos) return false;
320    key= str.substr(0,pos);
321    value= str.substr(pos+1);
322    trim_mod(key);
323    trim_mod(value);
324    return true;
325 } // eo pair_split(const std::string&,std::string&,std::string&,char)
326
327
328 /**
329  * splits a string by given delimiter
330  *
331  * @param[in] str the string which should be splitted.
332  * @param[out] result the list resulting from splitting  @a str.
333  * @param[in] delimiter the delimiter (word/phrase) at which @a str should be splitted.
334  * @param[in] omit_empty should empty parts not be stored?
335  * @param[in] trim_list list of characters the parts should be trimmed by.
336  *  (empty string results in no trim)
337  */
338 void split_string(
339    const std::string& str,
340    std::list<std::string>& result,
341    const std::string& delimiter,
342    bool omit_empty,
343    const std::string& trim_list
344 )
345 {
346    std::string::size_type pos, last_pos=0;
347    bool delimiter_found= false;
348    while ( last_pos < str.size()  && last_pos != std::string::npos)
349    {
350       pos= str.find(delimiter, last_pos);
351       std::string part;
352       if (pos == std::string::npos)
353       {
354          part= str.substr(last_pos);
355          delimiter_found= false;
356       }
357       else
358       {
359          part= str.substr(last_pos, pos-last_pos);
360          delimiter_found=true;
361       }
362       if (pos != std::string::npos)
363       {
364          last_pos= pos+ delimiter.size();
365       }
366       else
367       {
368          last_pos= std::string::npos;
369       }
370       if (!trim_list.empty() ) trim_mod (part, trim_list);
371       if (omit_empty && part.empty() ) continue;
372       result.push_back( part );
373    }
374    // if the string ends with a delimiter we need to append an empty string if no omit_empty
375    // was given.
376    // (this way we keep the split result consistent to a join operation)
377    if (delimiter_found && !omit_empty)
378    {
379       result.push_back("");
380    }
381 } // eo split_string(const std::string&,std::list< std::string >&,const std::string&,bool,const std::string&)
382
383
384 /** call split_string with list<string>, converts result to vector; vector is clear()-ed first
385  *
386  * Note: Uses 3 O(n)-operations: list.size, vector.resize and std::swap_ranges;
387  *       not sure whether there is a better way to do this
388  * */
389 void split_string(
390    const std::string& str,
391    std::vector<std::string>& result,
392    const std::string& delimiter,
393    bool omit_empty,
394    const std::string& trim_list
395 )
396 {
397     std::list<std::string> tmp;
398     split_string(str, tmp, delimiter, omit_empty, trim_list);
399     std::size_t size = tmp.size();   // this is O(n)
400     result.clear();
401     result.resize(size);             // also O(n)
402     std::swap_ranges(tmp.begin(), tmp.end(), result.begin());   // also O(n)
403 }
404
405 /**
406  * splits a string by a given delimiter
407  * @param str the string which should be splitted.
408  * @param delimiter delimiter the delimiter (word/phrase) at which @a str should be splitted.
409  * @param[in] omit_empty should empty parts not be stored?
410  * @param[in] trim_list list of characters the parts should be trimmed by.
411  *  (empty string results in no trim)
412  * @return the list resulting from splitting @a str.
413  */
414 std::list<std::string> split_string(
415    const std::string& str,
416    const std::string& delimiter,
417    bool omit_empty,
418    const std::string& trim_list
419 )
420 {
421    std::list<std::string> result;
422    split_string(str, result, delimiter, omit_empty, trim_list);
423    return result;
424 } // eo split_string(const std::string&,const std::string&,bool,const std::string&)
425
426
427 /**
428  * @brief joins a list of strings into a single string.
429  *
430  * This funtion is (basically) the reverse operation of @a split_string.
431  *
432  * @param parts the list of strings.
433  * @param delimiter the delimiter which is inserted between the strings.
434  * @return the joined string.
435  */
436 std::string join_string(
437    const std::list< std::string >& parts,
438    const std::string& delimiter
439 )
440 {
441    std::string result;
442    if (! parts.empty() )
443    {
444       std::list< std::string >::const_iterator it= parts.begin();
445       result = *it;
446       while ( ++it != parts.end() )
447       {
448          result+= delimiter;
449          result+= *it;
450       }
451    }
452    return result;
453 } // eo join_string(const std::list< std::string >&,const std::string&)
454
455
456 /** @brief same as join_string for list, except uses a vector */
457 std::string join_string(
458    const std::vector< std::string >& parts,
459    const std::string& delimiter
460 )
461 {
462    std::string result;
463    if (! parts.empty() )
464    {
465       std::vector< std::string >::const_iterator it= parts.begin();
466       result = *it;
467       while ( ++it != parts.end() )
468       {
469          result+= delimiter;
470          result+= *it;
471       }
472    }
473    return result;
474 } // eo join_string(const std::vector< std::string >&,const std::string&)
475
476
477
478 /*
479 ** conversions
480 */
481
482
483 /**
484  * @brief returns a hex string from a binary string.
485  * @param str the (binary) string
486  * @param upper_case_digits determine whether to use upper case characters for digits A-F.
487  * @return the string in hex notation.
488  */
489 std::string convert_binary_to_hex(
490    const std::string& str,
491    bool upper_case_digits
492 )
493 {
494    std::string result;
495    std::string hexDigits(upper_case_digits ? hexDigitsUpper : hexDigitsLower);
496    for ( std::string::const_iterator it= str.begin();
497          it != str.end();
498          ++it)
499    {
500       result.push_back( hexDigits[ ( (*it) >> 4) & 0x0f ] );
501       result.push_back( hexDigits[ (*it) & 0x0f ] );
502    }
503    return result;
504 } // eo convert_binary_to_hex(const std::string&,bool)
505
506
507 /**
508  * @brief converts a hex digit string to binary string.
509  * @param str hex digit string
510  * @return the binary string.
511  *
512  * The hex digit string may contains white spaces or colons which are treated
513  * as delimiters between hex digit groups.
514  *
515  * @todo rework the handling of half nibbles (consistency)!
516  */
517 std::string convert_hex_to_binary(
518    const std::string& str
519 )
520 throw (std::runtime_error)
521 {
522    std::string result;
523    char c= 0;
524    bool hasNibble= false;
525    bool lastWasWS= true;
526    for ( std::string::const_iterator it= str.begin();
527          it != str.end();
528          ++it)
529    {
530       std::string::size_type p = hexDigitsLower.find( *it );
531       if (p== std::string::npos)
532       {
533          p= hexDigitsUpper.find( *it );
534       }
535       if (p == std::string::npos)
536       {
537          if (   ( Whitespaces.find( *it ) != std::string::npos) // is it a whitespace?
538                 or ( *it == ':') // or a colon?
539             )
540          {
541             // we treat that as a valid delimiter:
542             if (hasNibble)
543             {
544                // 1 nibble before WS is treate as lower part:
545                result.push_back(c);
546                // reset state:
547                hasNibble= false;
548             }
549             lastWasWS= true;
550             continue;
551          }
552       }
553       if (p == std::string::npos )
554       {
555          throw runtime_error("illegal character in hex digit string: " + str);
556       }
557       lastWasWS= false;
558       if (hasNibble)
559       {
560          c<<=4;
561       }
562       else
563       {
564          c=0;
565       }
566       c+= (p & 0x0f);
567       if (hasNibble)
568       {
569          //we already had a nibble, so a char is complete now:
570          result.push_back( c );
571          hasNibble=false;
572       }
573       else
574       {
575          // this is the first nibble of a new char:
576          hasNibble=true;
577       }
578    }
579    if (hasNibble)
580    {
581       //well, there is one nibble left
582       // let's do some heuristics:
583       if (lastWasWS)
584       {
585          // if the preceeding character was a white space (or a colon)
586          // we treat the nibble as lower part:
587          //( this is consistent with shortened hex notations where leading zeros are not noted)
588          result.push_back( c );
589       }
590       else
591       {
592          // if it was part of a hex digit chain, we treat it as UPPER part (!!)
593          result.push_back( c << 4 );
594       }
595    }
596    return result;
597 } // eo convert_hex_to_binary(const std::string&)
598
599
600 static list<string>& alloc_template_starts()
601 {
602     static list<string> result;
603     if (result.empty())
604     {
605         result.push_back("std::list");
606         result.push_back("std::vector");
607     }
608     return result;
609 }
610
611 string shorten_stl_types(const string &input)
612 {
613     string output = input;
614
615     // first: replace fixed string for std::string
616     replace_all(output, "std::basic_string<char, std::char_traits<char>, std::allocator<char> >",
617                         "std::string");
618
619     // loop over list/vector/... that have an allocator, e.g.
620     // std::list< some_type_here, std::allocator<some_type_here> >
621     string::size_type start, comma, end, len, start_text_len;
622     int n_open_brackets;
623     string allocator_text;
624     BOOST_FOREACH(const string &start_text, alloc_template_starts())
625     {
626         start = 0;
627         comma = 0;
628         end = 0;
629         start_text_len = start_text.length();
630         while( (start=output.find(start_text+"<", start)) != string::npos )
631         {
632             len = output.length();
633             start += start_text_len+1;   // start next iter and tests here after opening bracket
634
635             // now comes the tricky part: find matching ',' and the closing '>' even if "subtype" is template again
636             comma = start;
637             n_open_brackets = 1;    // the bracket right after start_text counts as first
638             while (comma < len && n_open_brackets > 0)
639             {
640                 if (output[comma] == ',' && n_open_brackets == 1)
641                     break;
642                 else if (output[comma] == '<')
643                     ++n_open_brackets;
644                 else if (output[comma] == '>')
645                     --n_open_brackets;
646                 ++comma;
647             }
648             end = comma+1;
649             while (end < len && n_open_brackets > 0)
650             {
651                 if (output[end] == '<')
652                     ++n_open_brackets;
653                 else if (output[end] == '>')
654                 {
655                     --n_open_brackets;
656                     if (n_open_brackets == 0)
657                         break;  // do not increment end
658                 }
659                 ++end;
660             }
661
662             // check that start < comma < end < len && n_open_brackets == 0
663             if (start >= comma || comma >= end || end >= len || n_open_brackets != 0)
664                 continue;   // input seems to be of unexpected form
665
666             // check that type in allocator is same as until comma
667             string type = output.substr(start, comma-start);
668             if (type[type.length()-1] == '>')
669                 allocator_text = string("std::allocator<") + type + " > ";
670             else
671                 allocator_text = string("std::allocator<") + type + "> ";
672             if (output.substr(comma+2, end-comma-2) == allocator_text)
673                 output.replace(comma+2, end-comma-2, "_alloc_");
674         }
675     }
676
677     return output;
678 }
679
680 } // eo namespace I2n
681
682
683
684
685 std::string iso_to_utf8(const std::string& isostring)
686 {
687    string result;
688
689    iconv_t i2utf8 = iconv_open("UTF-8", "ISO-8859-1");
690
691    if (iso_to_utf8 == (iconv_t)-1)
692       throw runtime_error("iconv can't convert from ISO-8859-1 to UTF-8");
693
694    size_t in_size=isostring.size();
695    size_t out_size=in_size*4;
696
697    char *buf = (char *)malloc(out_size+1);
698    if (buf == NULL)
699       throw runtime_error("out of memory for iconv buffer");
700
701    char *in = (char *)isostring.c_str();
702    char *out = buf;
703    iconv(i2utf8, &in, &in_size, &out, &out_size);
704
705    buf[isostring.size()*4-out_size]=0;
706
707    result=buf;
708
709    free(buf);
710    iconv_close(i2utf8);
711
712    return result;
713 }
714
715 std::string utf8_to_iso(const std::string& utf8string)
716 {
717    string result;
718
719    iconv_t utf82iso = iconv_open("ISO-8859-1","UTF-8");
720
721    if (utf82iso == (iconv_t)-1)
722       throw runtime_error("iconv can't convert from UTF-8 to ISO-8859-1");
723
724    size_t in_size=utf8string.size();
725    size_t out_size=in_size;
726
727    char *buf = (char *)malloc(out_size+1);
728    if (buf == NULL)
729       throw runtime_error("out of memory for iconv buffer");
730
731    char *in = (char *)utf8string.c_str();
732    char *out = buf;
733    iconv(utf82iso, &in, &in_size, &out, &out_size);
734
735    buf[utf8string.size()-out_size]=0;
736
737    result=buf;
738
739    free(buf);
740    iconv_close(utf82iso);
741
742    return result;
743 }
744
745 wchar_t* utf8_to_wbuf(const std::string& utf8string)
746 {
747    iconv_t utf82wstr = iconv_open("UCS-4LE","UTF-8");
748
749    if (utf82wstr == (iconv_t)-1)
750       throw runtime_error("iconv can't convert from UTF-8 to UCS-4");
751
752    size_t in_size=utf8string.size();
753    size_t out_size= (in_size+1)*sizeof(wchar_t);
754
755    wchar_t *buf = (wchar_t *)malloc(out_size);
756    if (buf == NULL)
757       throw runtime_error("out of memory for iconv buffer");
758
759    char *in = (char *)utf8string.c_str();
760    char *out = (char*) buf;
761    if (iconv(utf82wstr, &in, &in_size, &out, &out_size) == (size_t)-1)
762       throw runtime_error("error converting char encodings");
763
764    buf[ ( (utf8string.size()+1)*sizeof(wchar_t)-out_size) /sizeof(wchar_t) ]=0;
765
766    iconv_close(utf82wstr);
767
768    return buf;
769 }
770
771 std::string utf7imap_to_utf8(const std::string& utf7imapstring)
772 {
773    string result;
774
775    iconv_t utf7imap2utf8 = iconv_open("UTF-8","UTF-7-IMAP");
776
777    if (utf7imap2utf8 == (iconv_t)-1)
778       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
779
780    size_t in_size=utf7imapstring.size();
781    size_t out_size=in_size*4;
782
783    char *buf = (char *)malloc(out_size+1);
784    if (buf == NULL)
785       throw runtime_error("out of memory for iconv buffer");
786
787    char *in = (char *)utf7imapstring.c_str();
788    char *out = buf;
789    iconv(utf7imap2utf8, &in, &in_size, &out, &out_size);
790
791    buf[utf7imapstring.size()*4-out_size]=0;
792
793    result=buf;
794
795    free(buf);
796    iconv_close(utf7imap2utf8);
797
798    return result;
799 }
800
801 std::string utf8_to_utf7imap(const std::string& utf8string)
802 {
803    string result;
804
805    iconv_t utf82utf7imap = iconv_open("UTF-7-IMAP", "UTF-8");
806
807    if (utf82utf7imap == (iconv_t)-1)
808       throw runtime_error("iconv can't convert from UTF-7-IMAP to UTF-8");
809
810    // UTF-7 is base64 encoded, a buffer 10x as large
811    // as the utf-8 buffer should be enough. If not the string will be truncated.
812    size_t in_size=utf8string.size();
813    size_t out_size=in_size*10;
814
815    char *buf = (char *)malloc(out_size+1);
816    if (buf == NULL)
817       throw runtime_error("out of memory for iconv buffer");
818
819    char *in = (char *)utf8string.c_str();
820    char *out = buf;
821    iconv(utf82utf7imap, &in, &in_size, &out, &out_size);
822
823    buf[utf8string.size()*10-out_size]= 0;
824
825    result=buf;
826
827    free(buf);
828    iconv_close(utf82utf7imap);
829
830    return result;
831 }
832
833 // Tokenize string by (html) tags
834 void tokenize_by_tag(vector<pair<string,bool> > &tokenized, const std::string &input)
835 {
836    string::size_type pos, len = input.size();
837    bool inside_tag = false;
838    string current;
839
840    for (pos = 0; pos < len; pos++)
841    {
842       if (input[pos] == '<')
843       {
844          inside_tag = true;
845
846          if (!current.empty() )
847          {
848             tokenized.push_back( make_pair(current, false) );
849             current = "";
850          }
851
852          current += input[pos];
853       }
854       else if (input[pos] == '>' && inside_tag)
855       {
856          current += input[pos];
857          inside_tag = false;
858          if (!current.empty() )
859          {
860             tokenized.push_back( make_pair(current, true) );
861             current = "";
862          }
863       }
864       else
865          current += input[pos];
866    }
867
868    // String left over in buffer?
869    if (!current.empty() )
870       tokenized.push_back( make_pair(current, false) );
871 } // eo tokenize_by_tag
872
873
874 std::string strip_html_tags(const std::string &input)
875 {
876    // Pair first: string, second: isTag
877    vector<pair<string,bool> > tokenized;
878    tokenize_by_tag (tokenized, input);
879
880    string output;
881    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
882    for (token = tokenized.begin(); token != tokens_end; ++token)
883       if (!token->second)
884          output += token->first;
885
886    return output;
887 } // eo strip_html_tags
888
889
890 // Smart-encode HTML en
891 string smart_html_entities(const std::string &input)
892 {
893    // Pair first: string, second: isTag
894    vector<pair<string,bool> > tokenized;
895    tokenize_by_tag (tokenized, input);
896
897    string output;
898    vector<pair<string,bool> >::const_iterator token, tokens_end = tokenized.end();
899    for (token = tokenized.begin(); token != tokens_end; ++token)
900    {
901       // keep HTML tags as they are
902       if (token->second)
903          output += token->first;
904       else
905          output += html_entities(token->first);
906    }
907
908    return output;
909 }
910
911
912 string::size_type find_8bit(const std::string &str)
913 {
914    string::size_type l=str.size();
915    for (string::size_type p=0; p < l; p++)
916       if (static_cast<unsigned char>(str[p]) > 127)
917          return p;
918
919    return string::npos;
920 }
921
922 // encoded UTF-8 chars into HTML entities
923 string html_entities(std::string str)
924 {
925    // Normal chars
926    replace_all (str, "&", "&amp;");
927    replace_all (str, "<", "&lt;");
928    replace_all (str, ">", "&gt;");
929    replace_all (str, "\"", "&quot;");
930    replace_all (str, "'", "&#x27;");
931    replace_all (str, "/", "&#x2F;");
932
933    // Umlauts
934    replace_all (str, "\xC3\xA4", "&auml;");
935    replace_all (str, "\xC3\xB6", "&ouml;");
936    replace_all (str, "\xC3\xBC", "&uuml;");
937    replace_all (str, "\xC3\x84", "&Auml;");
938    replace_all (str, "\xC3\x96", "&Ouml;");
939    replace_all (str, "\xC3\x9C", "&Uuml;");
940
941    // Misc
942    replace_all (str, "\xC3\x9F", "&szlig;");
943
944    // conversion of remaining non-ASCII chars needed?
945    // just do if needed because of performance
946    if (find_8bit(str) != string::npos)
947    {
948       // convert to fixed-size encoding UTF-32
949       wchar_t* wbuf=utf8_to_wbuf(str);
950       ostringstream target;
951
952       // replace all non-ASCII chars with HTML representation
953       for (int p=0; wbuf[p] != 0; p++)
954       {
955          unsigned int c=wbuf[p];
956
957          if (c <= 127)
958             target << static_cast<unsigned char>(c);
959          else
960             target << "&#" << c << ';';
961       }
962
963       free(wbuf);
964
965       str=target.str();
966    }
967
968    return str;
969 } // eo html_entities(std::string)
970
971 // convert HTML entities to something that can be viewed on a basic text console (restricted to ASCII-7)
972 string html_entities_to_console(std::string str)
973 {
974    // Normal chars
975    replace_all (str, "&amp;", "&");
976    replace_all (str, "&lt;", "<");
977    replace_all (str, "&gt;", ">");
978    replace_all (str, "&quot;", "\"");
979    replace_all (str, "&#x27;", "'");
980    replace_all (str, "&#x2F;", "/");
981
982    // Umlauts
983    replace_all (str, "&auml;", "ae");
984    replace_all (str, "&ouml;", "oe");
985    replace_all (str, "&uuml;", "ue");
986    replace_all (str, "&Auml;", "Ae");
987    replace_all (str, "&Ouml;", "Oe");
988    replace_all (str, "&Uuml;", "Ue");
989
990    // Misc
991    replace_all (str, "&szlig;", "ss");
992
993    return str;
994 }
995
996 // find_html_comments + remove_html_comments(str, comments)
997 void remove_html_comments(string &str)
998 {
999     vector<CommentZone> comments = find_html_comments(str);
1000     remove_html_comments(str, comments);
1001 }
1002
1003 // find all html comments, behaving correctly if they are nested; ignores comment tags ("<!--FOO .... BAR-->")
1004 // If there are invalid comments ("-->" before "<!--" or different number of closing and opening tags),
1005 // then the unknown index of corresponding start/end tag will be represented by a string::npos
1006 // Indices are from start of start tag until first index after closing tag
1007 vector<CommentZone> find_html_comments(const std::string &str)
1008 {
1009     static const string START = "<!--";
1010     static const string CLOSE = "-->";
1011     static const string::size_type START_LEN = START.length();
1012     static const string::size_type CLOSE_LEN = CLOSE.length();
1013
1014     vector<CommentZone> comments;
1015
1016     // in order to find nested comments, need either recursion or a stack
1017     vector<string::size_type> starts;      // stack of start tags
1018
1019     string::size_type pos = 0;
1020     string::size_type len = str.length();
1021     string::size_type next_start, next_close;
1022
1023     while (pos < len)     // not really needed but just in case
1024     {
1025         next_start = str.find(START, pos);
1026         next_close = str.find(CLOSE, pos);
1027
1028         if ( (next_start == string::npos) && (next_close == string::npos) )
1029             break;   // we are done
1030
1031         else if ( (next_start == string::npos) || (next_close < next_start) )  // close one comment (pop)
1032         {
1033             if (starts.empty())    // closing tag without a start
1034                 comments.push_back(CommentZone(string::npos, next_close+CLOSE_LEN));
1035             else
1036             {
1037                 comments.push_back(CommentZone(starts.back(), next_close+CLOSE_LEN));
1038                 starts.pop_back();
1039             }
1040             pos = next_close + CLOSE_LEN;
1041         }
1042
1043         else if ( (next_close == string::npos) || (next_start < next_close) )  // start a new comment (push)
1044         {
1045             starts.push_back(next_start);
1046             pos = next_start + START_LEN;
1047         }
1048     }
1049
1050     // add comments that have no closing tag from back to front (important for remove_html_comments!)
1051     while (!starts.empty())
1052     {
1053         comments.push_back(CommentZone(starts.back(), string::npos));
1054         starts.pop_back();
1055     }
1056
1057     return comments;
1058 }
1059
1060 // remove all html comments foundby find_html_comments
1061 void remove_html_comments(std::string &str, const vector<CommentZone> &comments)
1062 {
1063     // remember position where last removal started
1064     string::size_type last_removal_start = str.length();
1065
1066     // Go from back to front to not mess up indices.
1067     // This requires that bigger comments, that contain smaller comments, come AFTER
1068     // the small contained comments in the comments vector (i.e. comments are ordered by
1069     // their closing tag, not their opening tag). This is true for results from find_html_comments
1070     BOOST_REVERSE_FOREACH(const CommentZone &comment, comments)
1071     {
1072         if (comment.first == string::npos)
1073         {
1074             str = str.replace(0, comment.second, "");   // comment starts "before" str --> delete from start
1075             break;   // there can be no more
1076         }
1077         else if (comment.first >= last_removal_start)
1078         {
1079             continue;    // this comment is inside another comment that we have removed already
1080         }
1081         else if (comment.second == string::npos)   // comment ends "after" str --> delete until end
1082         {
1083             str = str.replace(comment.first, string::npos, "");
1084             last_removal_start = comment.first;
1085         }
1086         else
1087         {
1088             str = str.replace(comment.first, comment.second-comment.first, "");
1089             last_removal_start = comment.first;
1090         }
1091     }
1092 }
1093
1094 bool replace_all(string &base, const char *ist, const char *soll)
1095 {
1096    string i=ist;
1097    string s=soll;
1098    return replace_all(base,&i,&s);
1099 }
1100
1101 bool replace_all(string &base, const string &ist, const char *soll)
1102 {
1103    string s=soll;
1104    return replace_all(base,&ist,&s);
1105 }
1106
1107 bool replace_all(string &base, const string *ist, const string *soll)
1108 {
1109    return replace_all(base,*ist,*soll);
1110 }
1111
1112 bool replace_all(string &base, const char *ist, const string *soll)
1113 {
1114    string i=ist;
1115    return replace_all(base,&i,soll);
1116 }
1117
1118 bool replace_all(string &base, const string &ist, const string &soll)
1119 {
1120    bool found_ist = false;
1121    string::size_type a=0;
1122
1123    if (ist.empty() )
1124       throw runtime_error ("replace_all called with empty search string");
1125
1126    while ( (a=base.find(ist,a) ) != string::npos)
1127    {
1128       base.replace(a,ist.size(),soll);
1129       a=a+soll.size();
1130       found_ist = true;
1131    }
1132
1133    return found_ist;
1134 }
1135
1136 /**
1137  * @brief replaces all characters that could be problematic or impose a security risk when being logged
1138  * @param str the original string
1139  * @param replace_with the character to replace the unsafe chars with
1140  * @return a string that is safe to send to syslog or other logfiles
1141  *
1142  * All chars between 0x20 (space) and 0x7E (~) (including) are considered safe for logging.
1143  * See e.g. RFC 5424, section 8.2 or the posix character class "printable".
1144  * This eliminates all possible problems with NUL, control characters, 8 bit chars, UTF8.
1145  *
1146  */
1147 std::string sanitize_for_logging(const std::string &str, const char replace_with)
1148 {
1149     std::string output=str;
1150
1151     const string::size_type len = output.size();
1152     for (std::string::size_type p=0; p < len; p++)
1153         if (output[p] < 0x20 || output[p] > 0x7E)
1154             output[p]=replace_with;
1155
1156     return output;
1157 }
1158
1159 #if 0
1160 string to_lower(const string &src)
1161 {
1162    string dst = src;
1163
1164    string::size_type pos, end = dst.size();
1165    for (pos = 0; pos < end; pos++)
1166       dst[pos] = tolower(dst[pos]);
1167
1168    return dst;
1169 }
1170
1171 string to_upper(const string &src)
1172 {
1173    string dst = src;
1174
1175    string::size_type pos, end = dst.size();
1176    for (pos = 0; pos < end; pos++)
1177       dst[pos] = toupper(dst[pos]);
1178
1179    return dst;
1180 }
1181 #endif
1182
1183 const int MAX_UNIT_FORMAT_SYMBOLS = 6;
1184
1185 const string shortUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
1186         " B",
1187         " KB",
1188         " MB",
1189         " GB",
1190         " TB",
1191         " PB"
1192 };
1193
1194 const string longUnitFormatSymbols[MAX_UNIT_FORMAT_SYMBOLS] = {
1195         i18n_noop(" Bytes"),
1196         i18n_noop(" KBytes"),
1197         i18n_noop(" MBytes"),
1198         i18n_noop(" GBytes"),
1199         i18n_noop(" TBytes"),
1200         i18n_noop(" PBytes")
1201 };
1202
1203
1204 long double rounding_upwards(
1205         const long double number,
1206         const int rounding_multiplier
1207 )
1208 {
1209     long double rounded_number;
1210     rounded_number = number * rounding_multiplier;
1211     rounded_number += 0.5;
1212     rounded_number = (int64_t) (rounded_number);
1213     rounded_number = (long double) (rounded_number) / (long double) (rounding_multiplier);
1214
1215     return rounded_number;
1216 }
1217
1218
1219 string nice_unit_format(
1220         const int64_t input,
1221         const UnitFormat format,
1222         const UnitBase base
1223 )
1224 {
1225    // select the system of units (decimal or binary)
1226    int multiple = 0;
1227    if (base == UnitBase1000)
1228    {
1229        multiple = 1000;
1230    }
1231    else
1232    {
1233        multiple = 1024;
1234    }
1235
1236    long double size = input;
1237
1238    // check the size of the input number to fit in the appropriate symbol
1239    int sizecount = 0;
1240    while (size > multiple)
1241    {
1242        size = size / multiple;
1243        sizecount++;
1244
1245        // rollback to the previous values and stop the loop when cannot
1246        // represent the number length.
1247        if (sizecount >= MAX_UNIT_FORMAT_SYMBOLS)
1248        {
1249            size = size * multiple;
1250            sizecount--;
1251            break;
1252        }
1253    }
1254
1255    // round the input number "half up" to multiples of 10
1256    const int rounding_multiplier = 10;
1257    size = rounding_upwards(size, rounding_multiplier);
1258
1259    // format the input number, placing the appropriate symbol
1260    ostringstream out;
1261    out.setf (ios::fixed);
1262    if (format == ShortUnitFormat)
1263    {
1264        out.precision(1);
1265        out << size << i18n( shortUnitFormatSymbols[sizecount].c_str() );
1266    }
1267    else
1268    {
1269        out.precision (2);
1270        out << size << i18n( longUnitFormatSymbols[sizecount].c_str() );
1271    }
1272
1273    return out.str();
1274 } // eo nice_unit_format(int input)
1275
1276
1277 string nice_unit_format(
1278         const double input,
1279         const UnitFormat format,
1280         const UnitBase base
1281 )
1282 {
1283     // round as double and cast to int64_t
1284     // cast raised overflow error near max val of int64_t (~9.2e18, see unittest)
1285     int64_t input_casted_and_rounded =
1286         boost::numeric_cast<int64_t>( round(input) );
1287
1288     // now call other
1289     return nice_unit_format( input_casted_and_rounded, format, base );
1290 } // eo nice_unit_format(double input)
1291
1292
1293 string escape(const string &s)
1294 {
1295    string out(s);
1296    string::size_type p;
1297
1298    p=0;
1299    while ( (p=out.find_first_of("\"\\",p) ) !=out.npos)
1300    {
1301       out.insert (p,"\\");
1302       p+=2;
1303    }
1304
1305    p=0;
1306    while ( (p=out.find_first_of("\r",p) ) !=out.npos)
1307    {
1308       out.replace (p,1,"\\r");
1309       p+=2;
1310    }
1311
1312    p=0;
1313    while ( (p=out.find_first_of("\n",p) ) !=out.npos)
1314    {
1315       out.replace (p,1,"\\n");
1316       p+=2;
1317    }
1318
1319    out='"'+out+'"';
1320
1321    return out;
1322 } // eo scape(const std::string&)
1323
1324
1325 string descape(const string &s, int startpos, int &endpos)
1326 {
1327    string out;
1328
1329    if (s.at(startpos) != '"')
1330       throw out_of_range("value not type escaped string");
1331
1332    out=s.substr(startpos+1);
1333    string::size_type p=0;
1334
1335    // search for the end of the string
1336    while ( (p=out.find("\"",p) ) !=out.npos)
1337    {
1338       int e=p-1;
1339       bool escaped=false;
1340
1341       // the " might be escaped with a backslash
1342       while (e>=0 && out.at (e) =='\\')
1343       {
1344          if (escaped == false)
1345             escaped=true;
1346          else
1347             escaped=false;
1348
1349          e--;
1350       }
1351
1352       if (escaped==false)
1353          break;
1354       else
1355          p++;
1356    }
1357
1358    // we now have the end of the string
1359    out=out.substr(0,p);
1360
1361    // tell calling prog about the endposition
1362    endpos=startpos+p+1;
1363
1364    // descape all \ stuff inside the string now
1365    p=0;
1366    while ( (p=out.find_first_of("\\",p) ) !=out.npos)
1367    {
1368       switch (out.at(p+1) )
1369       {
1370          case 'r':
1371             out.replace(p,2,"\r");
1372             break;
1373          case 'n':
1374             out.replace(p,2,"\n");
1375             break;
1376          default:
1377             out.erase(p,1);
1378       }
1379       p++;
1380    }
1381
1382    return out;
1383 } // eo descape(const std::string&,int,int&)
1384
1385
1386 string escape_shellarg(const string &input)
1387 {
1388    string output = "'";
1389    string::const_iterator it, it_end = input.end();
1390    for (it = input.begin(); it != it_end; ++it)
1391    {
1392       if ( (*it) == '\'')
1393          output += "'\\'";
1394
1395       output += *it;
1396    }
1397
1398    output += "'";
1399    return output;
1400 }