Разрешение html-сущностей с помощью NSXMLParser на iPhone - PullRequest
15 голосов
/ 03 марта 2010

Я думаю, что я прочитал каждую веб-страницу, касающуюся этой проблемы, но я все еще не могу найти решение, поэтому вот я.

У меня есть веб-страница HTML, которая не находится под моим контролем, и мне нужно проанализировать ее из приложения для iPhone. Вот пример веб-страницы, о которой я говорю:

<HTML>
  <HEAD>
    <META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  </HEAD>
  <BODY>
    <LI class="bye bye" rel="hello 1">
      <H5 class="onlytext">
        <A name="morning_part">morning</A>
      </H5>
      <DIV class="mydiv">
        <SPAN class="myclass">something about you</SPAN> 
        <SPAN class="anotherclass">
          <A href="http://www.google.it">Bye Bye &egrave; un saluto</A>
        </SPAN>
      </DIV>
    </LI>
  </BODY>
</HTML>

Я использую NSXMLParser, и он будет работать, пока не найдет сущность & egrave; html. Он вызывает foundCharacters: для "Bye Bye", а затем вызывает resolExternalEntityName: systemID :: с entityName "egrave". В этом методе я просто возвращаю символ "è", преобразованный в NSData, foundCharacters вызывается снова, добавляя строку "è" к предыдущей "Bye Bye", а затем анализатор вызывает NSXMLParserUndeclaredEntityError ошибка.

У меня нет DTD, и я не могу изменить html-файл, который я анализирую. У вас есть идеи по этой проблеме? Спасибо всем заранее, Роб.

Обновление (12/03/2010) . После предложения Гриффо у меня получилось что-то вроде этого:

data = [self replaceHtmlEntities:data];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];

где replaceHtmlEntities: (NSData *) выглядит примерно так:

- (NSData *)replaceHtmlEntities:(NSData *)data {

    NSString *htmlCode = [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding];
    NSMutableString *temp = [NSMutableString stringWithString:htmlCode];

    [temp replaceOccurrencesOfString:@"&amp;" withString:@"&" options:NSLiteralSearch range:NSMakeRange(0, [temp length])];
    [temp replaceOccurrencesOfString:@"&nbsp;" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [temp length])];
    ...
    [temp replaceOccurrencesOfString:@"&Agrave;" withString:@"À" options:NSLiteralSearch range:NSMakeRange(0, [temp length])];

    NSData *finalData = [temp dataUsingEncoding:NSISOLatin1StringEncoding];
    return finalData;

}

Но я все еще ищу лучший способ решить эту проблему. Я попробую TouchXml в ближайшие дни, но я все еще думаю, что должен быть способ сделать это с помощью NSXMLParser API, поэтому, если вы знаете, как это сделать, пишите здесь:)

Ответы [ 6 ]

9 голосов
/ 15 марта 2010

После изучения нескольких альтернатив выясняется, что NSXMLParser не будет поддерживать объекты, отличные от стандартных объектов &lt;, &gt;, &apos;, &quot; and &amp;

Приведенный ниже код завершается ошибкой, в результате чего NSXMLParserUndeclaredEntityError.


// Create a dictionary to hold the entities and NSString equivalents
// A complete list of entities and unicode values is described in the HTML DTD
// which is available for download http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent


NSDictionary *entityMap = [NSDictionary dictionaryWithObjectsAndKeys: 
                     [NSString stringWithFormat:@"%C", 0x00E8], @"egrave",
                     [NSString stringWithFormat:@"%C", 0x00E0], @"agrave", 
                     ...
                     ,nil];

NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser setShouldResolveExternalEntities:YES];
[parser parse];

// NSXMLParser delegate method
- (NSData *)parser:(NSXMLParser *)parser resolveExternalEntityName:(NSString *)entityName systemID:(NSString *)systemID {
    return [[entityMap objectForKey:entityName] dataUsingEncoding: NSUTF8StringEncoding];
}

Попытки объявить сущности, добавив в HTML-документ объявления ENTITY, пройдут, однако расширенные сущности не будут возвращены обратно в parser:foundCharacters, а символы è и à будут удалены.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
[
  <!ENTITY agrave "à">
  <!ENTITY egrave "è">
]>

В другом эксперименте я создал полностью действительный документ XML с внутренним DTD

<?xml version="1.0" standalone="yes" ?>
<!DOCTYPE author [
    <!ELEMENT author (#PCDATA)>
    <!ENTITY js "Jo Smith">
]>
<author>&lt; &js; &gt;</author>

Я реализовал метод делегата parser:foundInternalEntityDeclarationWithName:value:;, и стало ясно, что синтаксический анализатор получает данные сущности, однако parser:foundCharacters вызывается только для предварительно определенных сущностей.

2010-03-20 12:53:59.871 xmlParsing[1012:207] Parser Did Start Document
2010-03-20 12:53:59.873 xmlParsing[1012:207] Parser foundElementDeclarationWithName: author model: 
2010-03-20 12:53:59.873 xmlParsing[1012:207] Parser foundInternalEntityDeclarationWithName: js value: Jo Smith
2010-03-20 12:53:59.874 xmlParsing[1012:207] didStartElement: author type: (null)
2010-03-20 12:53:59.875 xmlParsing[1012:207] parser foundCharacters Before: 
2010-03-20 12:53:59.875 xmlParsing[1012:207] parser foundCharacters After: <
2010-03-20 12:53:59.876 xmlParsing[1012:207] parser foundCharacters Before: <
2010-03-20 12:53:59.876 xmlParsing[1012:207] parser foundCharacters After: < 
2010-03-20 12:53:59.877 xmlParsing[1012:207] parser foundCharacters Before: < 
2010-03-20 12:53:59.878 xmlParsing[1012:207] parser foundCharacters After: <  
2010-03-20 12:53:59.879 xmlParsing[1012:207] parser foundCharacters Before: <  
2010-03-20 12:53:59.879 xmlParsing[1012:207] parser foundCharacters After: <  >
2010-03-20 12:53:59.880 xmlParsing[1012:207] didEndElement: author with content: <  >
2010-03-20 12:53:59.880 xmlParsing[1012:207] Parser Did End Document

Я нашел ссылку на учебник по Использование SAX-интерфейса LibXML . xmlSAXHandler, используемый NSXMLParser, позволяет определить обратный вызов getEntity. После вызова getEntity расширение объекта передается обратному вызову characters.

NSXMLParser здесь отсутствует функциональность. Что должно произойти, это то, что NSXMLParser или delegate хранят определения сущностей и предоставляют их для обратного вызова xmlSAXHandler getEntity. Это явно не происходит. Я отправлю отчет об ошибке.

Между тем, более ранний ответ на выполнение замены строки вполне приемлем, если ваши документы маленькие. Ознакомьтесь с упомянутым выше руководством по SAX, а также с примером приложения XMLPerformance от Apple, чтобы узнать, стоит ли реализовывать собственный синтаксический анализатор libxml.

Это было весело.

1 голос
/ 17 апреля 2013

Возможно, меньше хакерского решения - заменить DTD на локальное модифицированное, а все объявления внешних сущностей заменить на локальное.

Вот как я это делаю:

Сначала найдите и замените декларацию DTD документа локальным файлом. Например, замените это:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html><body><a href='a.html'>hi!</a><br><p>Hello</p></body></html>

с этим:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "file://localhost/Users/siuying/Library/Application%20Support/iPhone%20Simulator/6.1/Applications/17065C0F-6754-4AD0-A1EA-9373F6476F8F/App.app/xhtml1-transitional.dtd">
<html><body><a href='a.html'>hi!</a><br><p>Hello</p></body></html>

`` `

Загрузите DTD с URL-адреса W3C и добавьте его в комплект приложения. Вы можете найти путь к файлу с помощью следующего кода:

NSBundle* bundle = [NSBundle bundleForClass:[self class]];
NSString* path = [[bundle URLForResource:@"xhtml1-transitional" withExtension:@"dtd"] absoluteString];

Откройте файл DTD , найдите любую ссылку на внешнюю сущность:

<!ENTITY % HTMLlat1 PUBLIC
   "-//W3C//ENTITIES Latin 1 for XHTML//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent">
%HTMLlat1;      

заменить его содержимым файла сущности (http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent в вышеприведенном случае)

После замены всех внешних ссылок NSXMLParser должен правильно обрабатывать объекты без необходимости загружать каждый удаленный DTD / внешние объекты каждый раз, когда он анализирует файл XML.

0 голосов
/ 24 мая 2012

Поскольку я только начал заниматься разработкой для iOS, я искал ту же вещь и нашел соответствующую запись в списке рассылки: http://www.mail-archive.com/cocoa-dev@lists.apple.com/ msg17706.html

- (NSData *)parser:(NSXMLParser *)parser resolveExternalEntityName: (NSString *)entityName systemID:(NSString *)systemID {       
    NSAttributedString *entityString = [[[NSAttributedString alloc] initWithHTML:[[NSString stringWithFormat:@"&%@;", entityName] dataUsingEncoding:NSUTF8StringEncoding] documentAttributes:NULL] autorelease];

    NSLog(@"resolved entity name: %@", [entityString string]);

    return [[entityString string] dataUsingEncoding:NSUTF8StringEncoding];
}

Это довольно похоже на исходное решение, а также вызывает ошибку синтаксического анализатора NSXMLParserErrorDomain error 26; но он продолжает разбирать после этого. Проблема, конечно, в том, что трудно отличить реальные ошибки друг от друга; -)

0 голосов
/ 04 марта 2010

Я бы попробовал использовать другой парсер, например libxml2 - теоретически я думаю, что нужно уметь работать с плохим HTML.

0 голосов
/ 03 марта 2010

Я думаю, что в этом примере вы столкнетесь с другой проблемой, поскольку это не vaild XML, который ищет NSXMLParser.

Точная проблема, описанная выше, заключается в том, что теги META, LI, HTML и BODY не закрыты, поэтому анализатор выглядит полностью, хотя остальная часть документа ищет закрывающий тег.

Единственный известный мне способ обойти это, если у вас нет доступа к изменению HTML-кода, это отразить его с вставленными закрывающими тегами.

0 голосов
/ 03 марта 2010

Вы можете выполнить замену строки в данных, прежде чем анализировать их с помощью NSXMLParser. Насколько мне известно, NSXMLParser - это UTF-8.

...