Специальные символы в NSString из HTML - PullRequest
5 голосов
/ 12 февраля 2010

Я получаю данные из источника XML и анализирую их с помощью tbxml. Все работает нормально, пока я не получу латинскую букву вроде «é», которая будет отображаться как: Код:

é

Я не вижу подходящего метода NSString для выполнения преобразования. Есть идеи?

Ответы [ 2 ]

4 голосов
/ 14 февраля 2010

Вы можете использовать регулярное выражение. Регулярное выражение - это решение и причина всех проблем! :)

В приведенном ниже примере используется, по крайней мере, на момент написания этой статьи, невыпущенная версия RegexKitLite 4.0. Вы можете получить снимок разработки 4.0 через svn:

shell% svn co http://regexkit.svn.sourceforge.net/svnroot/regexkit regexkit

В примерах ниже используется новая функция 4.0 Blocks для поиска и замены é символов.

Этот первый пример является «более простым» из двух. Он только обрабатывает десятичные символьные объекты, такие как é, а не шестнадцатеричные символьные объекты, такие как é. Если вы можете гарантировать, что у вас никогда не будет шестнадцатеричных символов, это будет хорошо:

#import <Foundation/Foundation.h>
#import "RegexKitLite.h"

int main(int argc, char *charv[]) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  NSString *string = @"A test: &#233; and &#xe9; ? YAY! Even >0xffff are handled: &#119808; or &#x1D400;, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)";
  NSString *regex = @"&#([0-9]+);";

  NSString *replacedString = [string stringByReplacingOccurrencesOfRegex:regex usingBlock:^NSString *(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop) {
      NSUInteger u16Length = 0UL, u32_ch = [capturedStrings[1] integerValue];
      UniChar u16Buffer[3];

      if (u32_ch <= 0xFFFFU)       { u16Buffer[u16Length++] = ((u32_ch >= 0xD800U) && (u32_ch <= 0xDFFFU)) ? 0xFFFDU : u32_ch; }
      else if (u32_ch > 0x10FFFFU) { u16Buffer[u16Length++] = 0xFFFDU; }
      else                         { u32_ch -= 0x0010000UL; u16Buffer[u16Length++] = ((u32_ch >> 10) + 0xD800U); u16Buffer[u16Length++] = ((u32_ch & 0x3FFUL) + 0xDC00U); }

      return([NSString stringWithCharacters:u16Buffer length:u16Length]);
    }];

  NSLog(@"replaced: '%@'", replacedString);

  return(0);
}

Скомпилируйте и запустите с:

shell% gcc -arch i386 -g -o charReplace charReplace.m RegexKitLite.m -framework Foundation -licucore
shell% ./charReplace
2010-02-13 22:51:48.909 charReplace[35527:903] replaced: 'A test: é and &#xe9; ? YAY! Even >0xffff are handled: ? or &#x1D400;, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)'

Символ 0x1d4000 может не отображаться в вашем браузере, но он выглядит как жирный A в окне терминала.

«Три строки» в середине блока замены обеспечивают правильное преобразование UTF-32 символов, которые> 0xFFFF. Я добавил это для полноты и правильности. Недопустимые UTF-32 символьные значения (0xd800 - 0xdfff) превращаются в U+FFFD или REPLACEMENT CHARACTER. Если вы можете «гарантировать», что у вас никогда не будет &#...; символьных сущностей, которые> 0xFFFF (или 65535) и всегда являются «законными» UTF-32, то вы можете удалить эти строки и упростить всю заблокировать что-то вроде:

return([NSString stringWithFormat:@"%C", [capturedStrings[1] integerValue]]);

Во втором примере используются как десятичные, так и шестнадцатеричные символы:

#import <Foundation/Foundation.h>
#import "RegexKitLite.h"

int main(int argc, char *charv[]) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  NSString *string = @"A test: &#233; and &#xe9; ? YAY! Even >0xffff are handled: &#119808; or &#x1D400;, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)";
  NSString *regex = @"&#(?:([0-9]+)|x([0-9a-fA-F]+));";

  NSString *replacedString = [string stringByReplacingOccurrencesOfRegex:regex usingBlock:^NSString *(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop) {
      NSUInteger u16Length = 0UL, u32_ch = 0UL;
      UniChar u16Buffer[3];

      CFStringRef cfSelf = (capturedRanges[1].location != NSNotFound) ? (CFStringRef)capturedStrings[1] : (CFStringRef)capturedStrings[2];
      UInt8 buffer[64];
      const char *cptr;

      if((cptr = CFStringGetCStringPtr(cfSelf, kCFStringEncodingMacRoman)) == NULL) {
        CFRange range     = CFRangeMake(0L, CFStringGetLength(cfSelf));
        CFIndex usedBytes = 0L;
        CFStringGetBytes(cfSelf, range, kCFStringEncodingUTF8, '?', false, buffer, 60L, &usedBytes);
        buffer[usedBytes] = 0;
        cptr              = (const char *)buffer;
      }

      u32_ch = strtoul(cptr, NULL, (capturedRanges[1].location != NSNotFound) ? 10 : 16);

      if (u32_ch <= 0xFFFFU)       { u16Buffer[u16Length++] = ((u32_ch >= 0xD800U) && (u32_ch <= 0xDFFFU)) ? 0xFFFDU : u32_ch; }
      else if (u32_ch > 0x10FFFFU) { u16Buffer[u16Length++] = 0xFFFDU; }
      else                         { u32_ch -= 0x0010000UL; u16Buffer[u16Length++] = ((u32_ch >> 10) + 0xD800U); u16Buffer[u16Length++] = ((u32_ch & 0x3FFUL) + 0xDC00U); }

      return([NSString stringWithCharacters:u16Buffer length:u16Length]);
    }];

  NSLog(@"replaced: '%@'", replacedString);

  return(0);
}

Снова скомпилируйте и запустите:

shell% gcc -arch i386 -g -o charReplace charReplace.m RegexKitLite.m -framework Foundation -licucore
shell% ./charReplace
2010-02-13 22:52:02.182 charReplace[35540:903] replaced: 'A test: é and é ? YAY! Even >0xffff are handled: ? or ?, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)'

Обратите внимание на разницу в выходных данных по сравнению с первым: первый по-прежнему содержал &#xe9;, и в этом он заменен. Опять же, это немного длинновато, но я выбираю полноту и правильность.

В обоих примерах метод stringByReplacingOccurrencesOfRegex: может быть заменен следующим для «дополнительной скорости», но вы должны обратиться к документации, чтобы увидеть предостережения относительно использования RKLRegexEnumerationFastCapturedStringsXXX. Важно отметить, что использование его в приведенном выше примере не является проблемой и совершенно безопасно (и одна из причин, по которой я добавил опцию в RegexKitLite).

  NSString *replacedString = [string stringByReplacingOccurrencesOfRegex:regex options:RKLNoOptions inRange:NSMakeRange(0UL, [string length]) error:NULL enumerationOptions:RKLRegexEnumerationFastCapturedStringsXXX usingBlock:^NSString *(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop) {

Другой ответ на ваш вопрос указал на этот вопрос о переполнении стека с ответом . Различия между этим решением и этим решением (основанные только на быстром повторении):

Это решение:

  • Требуется внешняя библиотека (RegexKitLite).
  • Использует блоки для выполнения своей работы, которая пока недоступна "везде". Хотя существует вероятных блоков , которые позволяют использовать блоки в Mac OS X 10.5 и IPhone OS 2.2+ (я думаю). Они поддержали изменения блоков 10,6 gcc и сделали их доступными.

Другое решение:

  • Использует стандартные базовые классы, работает везде.
  • Немного менее корректно в обработке некоторых UTF-32 символов кодовых точек (вероятно, это не проблема на практике).
  • Обрабатывает пару общих именованных сущностей символов, таких как &gt;. Это можно легко добавить к вышесказанному.

Я не тестировал ни одно из этих решений, но я хотел бы поспорить на большие суммы денег, что решение RegexKitLite, использующее RKLRegexEnumerationFastCapturedStringsXXX, превосходит решение NSScanner.

А если вы действительно хотите добавить именованные символьные объекты, вы можете изменить регулярное выражение на что-то вроде:

NSString *regex = @"&(?:#(?:([0-9]+)|x([0-9a-fA-F]+))|([a-zA-Z][a-zA-Z0-9]+));";

Примечание: Я вообще не проверял выше.

Capture # 3 должен содержать «имя сущности персонажа», которое затем можно использовать для поиска. Действительно причудливый способ сделать это - иметь NSDictionary, который содержит именованный символ как key и NSString object, содержащий символ, которому соответствует это имя. Вы можете даже сохранить все это как внешний .plist ресурс и лениво загрузить его по требованию с помощью чего-то вроде:

NSDictionary *namedCharactersDictionary = [NSDictionary dictionaryWithContentsOfFile:@"namedCharacters.plist"];

Вы бы явно настроили его, чтобы использовать NSBundle, чтобы получить путь к каталогу ресурсов вашего приложения, но вы поняли эту идею. Затем вы добавите еще одну проверку состояния в блоке:

if(capturedRanges[3].location != NSNotFound) {
  NSString *namedCharacter = [namedCharactersDictionary objectForKey:capturedStrings[3]];
  return((namedCharacter == NULL) ? capturedStrings[0] : namedCharacter);
}

Если указанный символ находится в словаре, он его заменит. В противном случае он возвращает полный &notfound; соответствующий текст (т. Е. "Ничего не делает").

3 голосов
/ 12 февраля 2010

Это кажется довольно распространенной проблемой. Проверьте Декодирование символов HTML в Objective-C / Cocoa Touch

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