2012-06-26 12 views
9

Mam ciąg i początek oraz długość, z której wyodrębni się podciąg. Obie pozycje (początek i długość) są oparte na przesunięciach bajtów w oryginalnym łańcuchu UTF8.Wyciąganie podłańcuchów według utf-8 pozycji bajtowych

Istnieje jednak pewien problem:

rozpoczęcia i długość są w bajtach, więc nie mogę użyć „podciąg”. Ciąg znaków UTF8 zawiera kilka znaków wielobajtowych. Czy istnieje nadproduktywny sposób robienia tego? (Nie ma potrzeby dekodowania bajtów ...)

przykład: var oryg = '你 好吗'

S, e może być od 3,3 do ekstrakcji drugi znak (好). Szukam pomocy:

var result = orig.substringBytes(3,3); 

Pomoc!

Aktualizacja # 1 W C/C++ po prostu rzuciłbym to do tablicy bajtów, ale nie jestem pewien, czy istnieje odpowiednik w javascript. BTW, tak, moglibyśmy przetworzyć go na tablicę bajtów i przetworzyć z powrotem na ciąg znaków, ale wydaje się, że powinien być szybki sposób na wycięcie go we właściwym miejscu. Wyobraź sobie, że "orig" to 1000000 znaków, s = 6 bajtów i l = 3 bajty.

Aktualizacja # 2 Dzięki zerkms pomocny przekierowania, skończyło się z następujących, które dokłada NIE prawo działać - działa prawo wielobajtowych ale zawiedli się na jeden bajt.

function substrBytes(str, start, length) 
{ 
    var ch, startIx = 0, endIx = 0, re = ''; 
    for (var i = 0; 0 < str.length; i++) 
    { 
     startIx = endIx++; 

     ch = str.charCodeAt(i); 
     do { 
      ch = ch >> 8; // a better way may exist to measure ch len 
      endIx++; 
     } 
     while (ch); 

     if (endIx > start + length) 
     { 
      return re; 
     } 
     else if (startIx >= start) 
     { 
      re += str[i]; 
     } 
    } 
} 

Aktualizacja # 3 Nie sądzę przesuwanie kod char naprawdę działa. Czytam dwa bajty, gdy poprawna odpowiedź to trzy ... jakoś zawsze o tym zapominam. Punkt kodowy jest taki sam dla UTF8 i UTF16, ale liczba bajtów wziętych na kodowanie zależy od kodowania !!! Tak więc nie jest to właściwy sposób.

+0

Początek i długość 'substr' mają charakter, a nie bajty. – nhahtdh

+0

http://stackoverflow.com/q/1240408/251311 – zerkms

+1

@zerkms - Znalazłem to również, chociaż myślę, że dekodowanie całego ciągu znaków na bajty, wybranie podłańcucha i cofnięcie byłoby naprawdę nieefektywne. Co jeśli jest 10000000 znaków i chcę bajty 6-12? Wydaje się, że konwersja całego ciągu znaków byłaby okropnym pomysłem. – tofutim

Odpowiedz

7

miałem czas zabawy błahy z tym. Mam nadzieję że to pomoże.

Ponieważ JavaScript nie zezwala na bezpośredni dostęp do bajtu na łańcuchu, jedynym sposobem znalezienia pozycji początkowej jest skanowanie do przodu.


Aktualizacja # 3 Nie sądzę przesuwanie kod char naprawdę działa. Czytam dwa bajty, gdy poprawna odpowiedź to trzy ... jakoś zawsze o tym zapominam. Punkt kodowy jest taki sam dla UTF8 i UTF16, ale liczba bajtów wziętych na kodowanie zależy od kodowania !!! Tak więc nie jest to właściwy sposób.

To nie jest poprawne - w rzeczywistości nie ma łańcucha znaków UTF-8 w javascript. Zgodnie ze specyfikacją ECMAScript 262 wszystkie ciągi - bez względu na kodowanie wejściowe - muszą być przechowywane wewnętrznie jako UTF-16 ("[sekwencja] 16-bitowych liczb całkowitych bez znaku").

Biorąc pod uwagę to, 8-bitowe przesunięcie jest poprawne (ale niepotrzebne).

Źle jest założenie, że twoja postać jest przechowywana jako sekwencja 3-bajtowy ...
W rzeczywistości wszystkie znaków w JS (ECMA-262) ciąg 16 bitów (2 bajty) długości.

Można to obejść, konwertując wielobajtowe znaki do utf-8 ręcznie, jak pokazano w poniższym kodzie.


Zobacz szczegóły wyjaśniono w moim przykładzie kodu:

function encode_utf8(s) 
{ 
    return unescape(encodeURIComponent(s)); 
} 

function substr_utf8_bytes(str, startInBytes, lengthInBytes) { 

    /* this function scans a multibyte string and returns a substring. 
    * arguments are start position and length, both defined in bytes. 
    * 
    * this is tricky, because javascript only allows character level 
    * and not byte level access on strings. Also, all strings are stored 
    * in utf-16 internally - so we need to convert characters to utf-8 
    * to detect their length in utf-8 encoding. 
    * 
    * the startInBytes and lengthInBytes parameters are based on byte 
    * positions in a utf-8 encoded string. 
    * in utf-8, for example: 
    *  "a" is 1 byte, 
      "ü" is 2 byte, 
     and "你" is 3 byte. 
    * 
    * NOTE: 
    * according to ECMAScript 262 all strings are stored as a sequence 
    * of 16-bit characters. so we need a encode_utf8() function to safely 
    * detect the length our character would have in a utf8 representation. 
    * 
    * http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf 
    * see "4.3.16 String Value": 
    * > Although each value usually represents a single 16-bit unit of 
    * > UTF-16 text, the language does not place any restrictions or 
    * > requirements on the values except that they be 16-bit unsigned 
    * > integers. 
    */ 

    var resultStr = ''; 
    var startInChars = 0; 

    // scan string forward to find index of first character 
    // (convert start position in byte to start position in characters) 

    for (bytePos = 0; bytePos < startInBytes; startInChars++) { 

     // get numeric code of character (is >128 for multibyte character) 
     // and increase "bytePos" for each byte of the character sequence 

     ch = str.charCodeAt(startInChars); 
     bytePos += (ch < 128) ? 1 : encode_utf8(str[startInChars]).length; 
    } 

    // now that we have the position of the starting character, 
    // we can built the resulting substring 

    // as we don't know the end position in chars yet, we start with a mix of 
    // chars and bytes. we decrease "end" by the byte count of each selected 
    // character to end up in the right position 
    end = startInChars + lengthInBytes - 1; 

    for (n = startInChars; startInChars <= end; n++) { 
     // get numeric code of character (is >128 for multibyte character) 
     // and decrease "end" for each byte of the character sequence 
     ch = str.charCodeAt(n); 
     end -= (ch < 128) ? 1 : encode_utf8(str[n]).length; 

     resultStr += str[n]; 
    } 

    return resultStr; 
} 

var orig = 'abc你好吗?'; 

alert('res: ' + substr_utf8_bytes(orig, 0, 2)); // alerts: "ab" 
alert('res: ' + substr_utf8_bytes(orig, 2, 1)); // alerts: "c" 
alert('res: ' + substr_utf8_bytes(orig, 3, 3)); // alerts: "你" 
alert('res: ' + substr_utf8_bytes(orig, 6, 6)); // alerts: "好吗" 
+0

zaktualizowano, aby ta funkcja była kompatybilna z wejściem UTF-8. (jeśli string był początkowo utf-8, a pozycje bajtowe również z ciągu utf-8) – Kaii

0

System.ArraySegment jest użyteczny, ale trzeba konstruktora z wejściem tablicy i offset i indeksowania.

+0

Czy to w javascript? A może po prostu biblioteka C#? – tofutim

1
function substrBytes(str, start, length) 
{ 
    var buf = new Buffer(str); 
    return buf.slice(start, start+length).toString(); 
} 

AyB

+0

Próbowałem tego, ale nie mam obiektu Buffer(). z jakich frameworków korzystałeś? – Kaii

+0

Znajduje się w pliku node.js – tofutim

+0

To nie działa dla mnie w pliku Node.js. Zwraca kilka znaków zapytania. Zwykły substr działa dobrze. – Gavin

5

@Kaii „s odpowiedź jest prawie poprawne, ale nie ma w nim błędów. To nie obsługuje znaków Unicode, które są od 128 do 255. Oto wersja poprawiona (wystarczy zmienić 256 do 128):

function encode_utf8(s) 
{ 
    return unescape(encodeURIComponent(s)); 
} 

function substr_utf8_bytes(str, startInBytes, lengthInBytes) { 

    /* this function scans a multibyte string and returns a substring. 
    * arguments are start position and length, both defined in bytes. 
    * 
    * this is tricky, because javascript only allows character level 
    * and not byte level access on strings. Also, all strings are stored 
    * in utf-16 internally - so we need to convert characters to utf-8 
    * to detect their length in utf-8 encoding. 
    * 
    * the startInBytes and lengthInBytes parameters are based on byte 
    * positions in a utf-8 encoded string. 
    * in utf-8, for example: 
    *  "a" is 1 byte, 
      "ü" is 2 byte, 
     and "你" is 3 byte. 
    * 
    * NOTE: 
    * according to ECMAScript 262 all strings are stored as a sequence 
    * of 16-bit characters. so we need a encode_utf8() function to safely 
    * detect the length our character would have in a utf8 representation. 
    * 
    * http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf 
    * see "4.3.16 String Value": 
    * > Although each value usually represents a single 16-bit unit of 
    * > UTF-16 text, the language does not place any restrictions or 
    * > requirements on the values except that they be 16-bit unsigned 
    * > integers. 
    */ 

    var resultStr = ''; 
    var startInChars = 0; 

    // scan string forward to find index of first character 
    // (convert start position in byte to start position in characters) 

    for (bytePos = 0; bytePos < startInBytes; startInChars++) { 

     // get numeric code of character (is >= 128 for multibyte character) 
     // and increase "bytePos" for each byte of the character sequence 

     ch = str.charCodeAt(startInChars); 
     bytePos += (ch < 128) ? 1 : encode_utf8(str[startInChars]).length; 
    } 

    // now that we have the position of the starting character, 
    // we can built the resulting substring 

    // as we don't know the end position in chars yet, we start with a mix of 
    // chars and bytes. we decrease "end" by the byte count of each selected 
    // character to end up in the right position 
    end = startInChars + lengthInBytes - 1; 

    for (n = startInChars; startInChars <= end; n++) { 
     // get numeric code of character (is >= 128 for multibyte character) 
     // and decrease "end" for each byte of the character sequence 
     ch = str.charCodeAt(n); 
     end -= (ch < 128) ? 1 : encode_utf8(str[n]).length; 

     resultStr += str[n]; 
    } 

    return resultStr; 
} 

var orig = 'abc你好吗?©'; 

alert('res: ' + substr_utf8_bytes(orig, 0, 2)); // alerts: "ab" 
alert('res: ' + substr_utf8_bytes(orig, 2, 1)); // alerts: "c" 
alert('res: ' + substr_utf8_bytes(orig, 3, 3)); // alerts: "你" 
alert('res: ' + substr_utf8_bytes(orig, 6, 6)); // alerts: "好吗" 
alert('res: ' + substr_utf8_bytes(orig, 15, 2)); // alerts: "©" 

Nawiasem mówiąc, jest to bug fix i powinno być przydatne dla tych, którzy mają ten sam problem. Dlaczego recenzenci odrzucili moją propozycję edycji ze względu na zmianę "za dużo" lub "za mało"? @Adam Eberlin@Kjuly@Jasonw

+0

wziąłem to na kredyt i zredagowałem moją odpowiedź. dzięki za twoje ostre oczy – Kaii

0

Dla użytkowników IE, kody w powyższym odpowiedź wyjście będzie undefined. Ponieważ w IE nie jest obsługiwany str[n], innymi słowy, nie można używać ciągu jako tablicy. Twoja potrzeba zastąpienia str[n] przez str.charAt(n). Kod powinien być;

function encode_utf8(s) { 
    return unescape(encodeURIComponent(s)); 
} 

function substr_utf8_bytes(str, startInBytes, lengthInBytes) { 

    var resultStr = ''; 
    var startInChars = 0; 

    for (bytePos = 0; bytePos < startInBytes; startInChars++) { 
     ch = str.charCodeAt(startInChars); 
     bytePos += (ch < 128) ? 1 : encode_utf8(str.charAt(startInChars)).length; 
    } 

    end = startInChars + lengthInBytes - 1; 

    for (n = startInChars; startInChars <= end; n++) { 
     ch = str.charCodeAt(n); 
     end -= (ch < 128) ? 1 : encode_utf8(str.charAt(n)).length; 

     resultStr += str.charAt(n); 
    } 

    return resultStr; 
} 
0

Może to wykorzystać do zliczenia bajtu i przykładu. Zlicza 你 znak ma 2 bajty, zamiast 3 bajty następują po funkcji @ Kaii:

jQuery.byteLength = function(target) { 
    try { 
     var i = 0; 
     var length = 0; 
     var count = 0; 
     var character = ''; 
     // 
     target = jQuery.castString(target); 
     length = target.length; 
     // 
     for (i = 0; i < length; i++) { 
      // 1 文字を切り出し Unicode に変換 
      character = target.charCodeAt(i); 
      // 
      // Unicode の半角 : 0x0 - 0x80, 0xf8f0, 0xff61 - 0xff9f, 0xf8f1 - 
      // 0xf8f3 
      if ((character >= 0x0 && character < 0x81) 
        || (character == 0xf8f0) 
        || (character > 0xff60 && character < 0xffa0) 
        || (character > 0xf8f0 && character < 0xf8f4)) { 
       // 1 バイト文字 
       count += 1; 
      } else { 
       // 2 バイト文字 
       count += 2; 
      } 
     } 
     // 
     return (count); 
    } catch (e) { 
     jQuery.showErrorDetail(e, 'byteLength'); 
     return (0); 
    } 
}; 

for (var j = 1, len = value.length; j <= len; j++) { 
    var slice = value.slice(0, j); 
    var slength = $.byteLength(slice); 
    if (slength == 106) { 
     $(this).val(slice); 
     break; 
    } 
} 
Powiązane problemy