在iPhone上使用NSXMLParser解析html实体

时间:2010-03-03 11:43:55

标签: iphone parsing nsxmlparser html-entities

我想我读过与此问题有关的每一个网页,但我仍然无法找到解决方案,所以我在这里。

我有一个不受我控制的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,它一直顺利,直到找到è html实体。它调用foundCharacters:for“Bye Bye”,然后调用 resolveExternalEntityName:systemID :: ,实体名称为“egrave”。 在这个方法中,我只是返回在NSData中转换的字符“è”,再次调用foundCharacters将字符串“è”添加到前一个“Bye Bye”,然后解析器引发 NSXMLParserUndeclaredEntityError 错误。

我没有DTD,我无法更改我正在解析的html文件。你对这个问题有什么想法吗?在此先感谢各位, 罗布。

更新(12/03/2010)。在Griffo的建议之后我最终得到了类似的东西:

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 个答案:

答案 0 :(得分:9)

在探索了几种替代方案之后,似乎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 "è">
]>

在另一个实验中,我使用内部DTD创建了一个完全有效的xml文档

<?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

我找到了Using the SAX Interface of LibXML教程的链接。 xmlSAXHandler使用的NSXMLParser允许定义getEntity回调。调用getEntity后,实体的扩展将传递给characters回调。

NSXMLParser此处缺少功能。应该发生的是NSXMLParser或其delegate存储实体定义并将其提供给xmlSAXHandler getEntity回调。这显然没有发生。我将提交错误报告。

与此同时,如果您的文档很小,那么执行字符串替换的早期答案是完全可以接受的。查看上面提到的SAX教程以及Apple的XMLPerformance示例应用程序,看看是否值得实现libxml解析器。

这很有趣。

答案 1 :(得分:1)

可能较少 hacky解决方案是将DTD替换为本地修改后的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>

```

从W3C网址下载DTD并将其添加到您的应用包中。您可以使用以下代码找到该文件的路径:

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应正确处理实体,而无需在每次解析XML文件时下载每个远程DTD /外部实体。

答案 2 :(得分:0)

在使用NSXMLParser解析数据之前,您可以在数据中执行字符串替换。据我所知,NSXMLParser仅为UTF-8。

答案 3 :(得分:0)

我认为你将遇到这个例子的另一个问题,因为它不是vaild XML,这是NSXMLParser正在寻找的。

上面的确切问题是标签META,LI,HTML和BODY没有关闭,所以解析器一直看着文档的其余部分寻找它的结束标记。

我知道如果你无权更改HTML的唯一方法就是在插入结束标记的情况下镜像它。

答案 4 :(得分:0)

我会尝试使用不同的解析器,比如libxml2 - 理论上我认为应该能够处理糟糕的HTML。

答案 5 :(得分:0)

由于我刚刚开始进行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;但它确实在那之后继续解析。当然,问题是分开真正的错误更难; - )