2015-06-23 38 views
6

Ho bisogno di ottenere una sottostringa dei primi N caratteri in uno std :: stringa assunto come utf8. Ho imparato a fatica che lo .substr non funziona ... come ... previsto.Sottostringa di uno std :: string in utf-8? C++ 11

Riferimento: le mie corde, probabilmente simile a questa: missione: \ n \ n1 億 2 千万 匹

+4

Il problema è che UTF-8 è una codifica a lunghezza variabile ogni carattere può contenere da uno a sei byte. Mentre è possibile usare 'std :: string' per memorizzare le stringhe UTF-8, non è possibile utilizzare direttamente le funzioni standard. È possibile * utilizzare * la funzione 'substr', ma è necessario utilizzare un codice speciale per trovare l'inizio e la fine effettivi della sottostringa. A meno che tu non sia preoccupato per lo spazio, potresti voler archiviare le stringhe internamente in una codifica a lunghezza fissa, come UTF-32. –

+0

Come [questo] (http://stackoverflow.com/questions/17103925/how-well-is-unicode-supported-in-c11) link dice: "Unicode non è supportato dalla libreria standard (per ogni ragionevole significato di supportato std :: string non è migliore di std :: vector : è completamente ignaro di Unicode (o di qualsiasi altra rappresentazione/codifica) e tratta semplicemente il suo contenuto come un blob di byte. " – paulsm4

+5

Anche con UTF-32, è possibile escludere involontariamente la combinazione di caratteri (ad es. Accenti). Se proprio ne hai bisogno, prenderei in considerazione ICU (http://site.icu-project.org) o qualche libreria simile su misura per gestire Unicode in tutta la sua gloria. –

risposta

3

ho found questo codice e sto per provarlo.

std::string utf8_substr(const std::string& str, unsigned int start, unsigned int leng) 
{ 
    if (leng==0) { return ""; } 
    unsigned int c, i, ix, q, min=std::string::npos, max=std::string::npos; 
    for (q=0, i=0, ix=str.length(); i < ix; i++, q++) 
    { 
     if (q==start){ min=i; } 
     if (q<=start+leng || leng==std::string::npos){ max=i; } 

     c = (unsigned char) str[i]; 
     if  (
       //c>=0 && 
       c<=127) i+=0; 
     else if ((c & 0xE0) == 0xC0) i+=1; 
     else if ((c & 0xF0) == 0xE0) i+=2; 
     else if ((c & 0xF8) == 0xF0) i+=3; 
     //else if (($c & 0xFC) == 0xF8) i+=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8 
     //else if (($c & 0xFE) == 0xFC) i+=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8 
     else return "";//invalid utf8 
    } 
    if (q<=start+leng || leng==std::string::npos){ max=i; } 
    if (min==std::string::npos || max==std::string::npos) { return ""; } 
    return str.substr(min,max); 
} 

Aggiornamento: Questo ha funzionato bene per il mio problema attuale. Ho dovuto mescolarlo con una funzione get-length-of-utf8encoded-stdsstring.

Questa soluzione ha avuto alcuni avvertimenti sputarono a esso dal mio compilatore:

Some warnings spit out by my compiler.

+0

C++ ha già una stringa di 2 byte, 'std: wstring', che è supportata da algoritmi. È meglio convertire il contenuto UTF8 in Unicode e 'wstring' mentre leggi che riscrive ogni algoritmo per gestire stringhe" magiche "che usano il tipo di stringa ASCII (std :: string) ma si comportano come qualcos'altro –

+9

@PanagiotisKanavos:' std :: wstring 'non è 2 byte. Si prega di leggere http://utf8everywhere.org/ – DanielKO

+0

@DanielKO I riferimenti C++ sono preferibili e sì, wchar_t è 2 o più byte dipendenti dall'implementazione - quindi sono preferibili char16_t o char32_t. Vedo che le cose sono nuovamente in flusso e abbiamo letterali Unicode mappati a 'char16_t *' o 'char32_t'. Esistono anche letterali codificati UTF8 che si associano a 'char *'! C'è anche u16string e u32string. Non so del supporto STL per loro - chi ha spostato il mio formaggio! –

1

Si potrebbe utilizzare la libreria Boost/locale per convertire la stringa utf8 in un wstring. E quindi utilizzare il .substr normale() approccio:

#include <iostream> 
#include <boost/locale.hpp> 

std::string ucs4_to_utf8(std::u32string const& in) 
{ 
    return boost::locale::conv::utf_to_utf<char>(in); 
} 

std::u32string utf8_to_ucs4(std::string const& in) 
{ 
    return boost::locale::conv::utf_to_utf<char32_t>(in); 
} 

int main(){ 

    std::string utf8 = u8"1億2千万匹"; 

    std::u32string part = utf8_to_ucs4(utf8).substr(0,3); 

    std::cout<<ucs4_to_utf8(part)<<std::endl; 
    // prints : 1億2 
    return 0; 
} 
+1

'wstring' non memorizza i singoli caratteri nel singolo' wchar_t' nel caso generale. Funziona solo in un sottoinsieme ristretto di unicode. I nomi delle tue funzioni sono sbagliati: ucs4 non si adatta a un 'wchar_t' a 16 bit. – Yakk

+1

@Yakk Hai ragione. L'ho mescolato con char32_t, che è sempre a 32 bit, corrispondente a una codifica ucs4. (Ho modificato lo snippet di codice di conseguenza.) –

+2

Manca ancora la combinazione di supporto caratteri. Probabilmente hai a che fare con indicatori da sinistra a destra e da destra a sinistra in modi imprevisti https://en.wikipedia.org/wiki/Bi-directional_text#Unicode_bidi_support. [Combinazione di Grapheme Joiner] (https://en.wikipedia.org/wiki/Combining_Grapheme_Joiner), [Combining Character] (https://en.wikipedia.org/wiki/Combining_character), la [BOM] (https: // en.wikipedia.org/wiki/Byte_order_mark), (tag della lingua privi di licenza), [selettori di variazione] (https://en.wikipedia.org/wiki/Variant_form_%28Unicode%29), ecc. – Yakk

0

Sulla base di this risposta che ho scritto la mia funzione utf8 stringa:

void utf8substr(std::string originalString, int SubStrLength, std::string& csSubstring) 
{ 
    int len = 0, byteIndex = 0; 
    const char* aStr = originalString.c_str(); 
    size_t origSize = originalString.size(); 

    for (byteIndex=0; byteIndex < origSize; byteIndex++) 
    { 
     if((aStr[byteIndex] & 0xc0) != 0x80) 
      len += 1; 

     if(len >= SubStrLength) 
      break; 
    } 

    csSubstring = originalString.substr(0, byteIndex); 
}