Устранение 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 è 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:@"&" withString:@"&" options:NSLiteralSearch range:NSMakeRange(0, [temp length])];
[temp replaceOccurrencesOfString:@" " withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [temp length])];
...
[temp replaceOccurrencesOfString:@"À" withString:@"À" options:NSLiteralSearch range:NSMakeRange(0, [temp length])];
NSData *finalData = [temp dataUsingEncoding:NSISOLatin1StringEncoding];
return finalData;
}
Но я все еще ищу лучший способ решить эту проблему. Я попробую TouchXml в ближайшие дни, но я все же думаю, что должен быть способ сделать это с помощью NSXMLParser API, поэтому, если вы знаете, как это сделать, напишите здесь:)
Ответы
Ответ 1
После изучения нескольких альтернатив, похоже, NSXMLParser не будет поддерживать объекты, отличные от стандартных объектов <, >, ', " and &
Приведенный ниже код не дает результата 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>< &js; ></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
; но после этого он продолжает разбираться. Проблема, конечно, в том, что сложнее рассказать об истинных ошибках; -)