Устранение html-объектов с помощью NSXMLParser на iPhone

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

У меня есть веб-страница 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="#" onclick="location.href='http://www.google.it'; return false;">Bye Bye &egrave; un saluto</A>
        </SPAN>
      </DIV>
    </LI>
  </BODY>
</HTML>

Я использую NSXMLParser, и он идет хорошо, пока не найдет объект & egrave; html. Он вызывает foundCharacters: для "Bye Bye", а затем он вызывает resolveExternalEntityName: systemID:: с именем entity "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, поэтому, если вы знаете, как это сделать, напишите здесь:)

Ответы

Ответ 1

После изучения нескольких альтернатив, похоже, 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 самостоятельно.

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

Ответ 2

Возможно, менее хакерское решение заменяет 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 файла.

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

Поскольку я только начал заниматься разработкой iOS, я искал одну и ту же вещь и нашел соответствующую запись списка рассылки: http://www.mail-archive.com/[email protected]/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; но после этого он продолжает разбираться. Проблема, конечно, в том, что сложнее рассказать об истинных ошибках; -)