Определить положение символа в строке UTF NSString из смещения байта (было смещение SQLite () и проблема кодирования) - PullRequest
4 голосов
/ 13 сентября 2011

Рассказ : У меня есть UTF NSString и байтовое смещение. Я хочу знать символ смещения этого байта. Как я могу это сделать?

Ниже приводится длинная история, если вы осмелитесь:

Согласно этой документации смещения () функция возвращает смещение байта внутри столбца термина. Я проиндексировал некоторый текст и использую это смещение, чтобы указать на определенный раздел текста при отображении результатов.

Критическая проблема заключается в том, что, используя это байтовое смещение, я не могу указать правильное местоположение термина. Иногда он указывает правильно, иногда это 3/4 символа от правильной точки.

Мой стол очень прост:

CREATE VIRTUAL TABLE t1 USING fts4(file, body, page);

Если я сделаю запрос, например:

SELECT page, body, offsets(t1) from t1 where body match 'and';

Я получаю:

...........
502|1 0 427 3
505|1 0 370 3 1 0 1307 3 1 0 1768 3
506|1 0 10 3 1 0 1861 3 1 0 2521 3

...........

В качестве примера, если я укажу на символ 427 тела, я не получу правильную позицию 'и', но отскочу от него на 2/3 символа. То же самое, если я иду к 370, и если я иду к 10, я получаю правильную позицию.

Где я не прав?

Ответы [ 3 ]

0 голосов
/ 01 января 2014

За ответ @ метатации смещение указывается в байтах, а не в символах. Текст в вашей базе данных, вероятно, является Unicode в кодировке UTF8, и в этом случае любой не-ASCII символ представляется несколькими байтами . Примеры символов, отличных от ASCII, включают символы с акцентами (à, ö и т. Д.), Умные кавычки, символы из наборов нелатинских символов (греческий, кириллица, большинство азиатских наборов символов и т. Д.) И т. Д.

Если байты в базе данных SQLite представляют собой строки Юникода в кодировке UTF8, вы можете определить истинное смещение символов Юникода для данного байтового смещения следующим образом:

NSUInteger characterOffsetForByteOffsetInUTF8String(NSUInteger byteOffset, const char *string) {
    /*
     * UTF-8 represents ASCII characters in a single byte. Characters with a code
     * point from U+0080 upwards are represented as multiple bytes. The first byte
     * always has the two most significant bits set (i.e. 11xxxxxx). All subsequent
     * bytes have the most significant bit set, the next most significant bit unset
     * (i.e. 10xxxxxx).
     * 
     * We use that here to determine character offsets. We step through the first
     * `byteOffset` bytes of `string`, incrementing the character offset result
     * every time we come across a byte that doesn't match 10xxxxxx, i.e. where
     * (byte & 11000000) != 10000000
     *
     * See also: http://en.wikipedia.org/wiki/UTF-8#Description
     */
    NSUInteger characterOffset = 0;
    for (NSUInteger i = 0; i < byteOffset; i++) {
        char c = string[i];
        if ((c & 0xc0) != 0x80) {
            characterOffset++;
        }
    }
    return characterOffset;
}

Предупреждение: если вы используете смещение символа для индексации в NSString, имейте в виду, что NSString использует UTF-16 под капотом, поэтому отображаются символы с кодовой точкой Unicode выше, чем U + FFFF на пару из 16-битных значений. Как правило, вы не будете сталкиваться с этим для текстового содержимого, но если вы заботитесь об особо непонятных наборах символов, или о некоторых нетекстовых символах, которые Unicode может представлять, таких как Emojis, то приведенный выше алгоритм потребует улучшений, чтобы удовлетворить их.

(Фрагмент кода из моего проекта - не стесняйтесь его использовать.)

0 голосов
/ 08 августа 2015

Вдохновлен этой темой, и в частности решением Саймона;вот как я это делаю.

Возможно, есть более "быстрый" способ, чем возвращать NSRange, но мне нужно, чтобы он выделил NSAttributedString.

extension String {

    func charRangeForByteRange(range : NSRange) -> NSRange {

        let bytes = [UInt8](utf8)

        var charOffset = 0

        for i in 0..<range.location {
            if ((bytes[i] & 0xc0) != 0x80) { charOffset++ }
        }

        let location = charOffset

        for i in range.location..<(range.location + range.length) {
            if ((bytes[i] & 0xc0) != 0x80) { charOffset++ }
        }

        let length = charOffset - location

        return NSMakeRange(location, length)
    }
}
0 голосов
/ 14 октября 2011

Посмотрите документы Sqlite FTS3 , и вы заметите, что смещения и длины указаны в байтах , а не в символах.

Вы должны применить смещение и длину перед декодированием байтов в строку символов, чтобы отобразить правильное смещение. Смещение, полученное из Sqlite, считает каждый байт многобайтовых символов, тогда как вы используете это смещение для подсчета символов .

Ваш индексированный текст, вероятно, содержит 3 или 4 символа, которые составляют два байта. Отсюда проблема «на 3 или 4».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...