【问题标题】:Resolving html entities with NSXMLParser on iPhone在 iPhone 上使用 NSXMLParser 解析 html 实体
【发布时间】:2011-01-23 04:08:22
【问题描述】:

我想我阅读了与这个问题相关的每一个网页,但我仍然找不到解决方案,所以我在这里。

我有一个不受我控制的 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: 表示“Bye Bye”,然后调用 resolveExternalEntityName:systemID:: 并使用 entityName 为“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 来做到这一点,所以如果你知道怎么做,请随时在这里写。

【问题讨论】:

  • 附言。我知道 NSXMLParser 是 XML 解析器而不是 HTML 解析器,但我读到 libxml2 也存在同样的问题。 NSXMLParser 似乎比 libxml2 更容易学习,所以我首先尝试了这个,希望它能正常工作。如果没有解决方案,那么我将不得不切换到 libxml2...
  • 正如下面 Griffo 所建议的,我用适当的字符替换了文本中的每个 html 实体,然后用 NSXMLParser 对其进行了解析。现在它正在工作,但我真的很想了解哪种方法是解决此类问题的更好方法。
  • 我用 & & 符号的实体,至少对于多个​​“foundCharacters”调用而言,这是很痛苦的处理。

标签: iphone parsing nsxmlparser html-entities


【解决方案1】:

在探索了几种替代方案后,NSXMLParser 似乎不支持标准实体&amp;lt;, &amp;gt;, &amp;apos;, &amp;quot; and &amp;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 上找到了一个教程链接。 NSXMLParser 使用的 xmlSAXHandler 允许定义 getEntity 回调。在调用getEntity 之后,实体的扩展被传递给characters 回调。

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

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

这很有趣。

【讨论】:

  • :( 这不起作用。它继续引发 NSXMLParserUndeclaredEntityError = 26。:( 我使用了你自己的代码。它进入方法 resolveExternalEntityName 然后引发异常......
  • 可以附上网址吗?我想测试另一个理论。
  • 仍在寻找解决方案。找到了一个可能的答案cocoabuilder.com/archive/cocoa/… 但是它使用了当前 iPhone 操作系统上不可用的 NSAttributedString
  • 哎哟 :(( 同时我尝试了 TouchXml 并阅读了其他解析器...但似乎这是您应该自己完成的任务。:\
  • 哇!你的回答真的很完整!你真的把所有的东西都放进去了,我谢谢你。很好的解释。所以故事的结局是 NSXMLParser 很烂:)
【解决方案2】:

一个可能的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 URL 下载 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/外部实体。

【讨论】:

    【解决方案3】:

    您可以在使用 NSXMLParser 解析数据之前对数据进行字符串替换。据我所知,NSXMLParser 只是 UTF-8。

    【讨论】:

    • 是的,我只是在考虑这个问题,但我不能真正认为这是一个真正的解决方案......因为有方法 resolveExternalEntityName:systemID 的文档说:“委托可以解决外部实体(例如,定位和读取外部声明的 DTD)并将结果作为 NSData 对象提供给解析器对象。”所以它应该存在一种方法来使用它来解析实体并为解析器翻译它......可能我在 NSXMLParser 的逻辑中遗漏了一些东西......
    • 但是我读到 NSXMLDocument 不能用于 iphone 开发,是真的吗?
    • NSXMLDocument 在 TouchXML 中可用。见这里:code.google.com/p/touchcode/wiki/TouchXML
    • 谢谢,我一定会试试的。但是我不能停止思考仅使用 sdk 代码来处理这种情况的正确方法是什么……
    【解决方案4】:

    我认为您将在此示例中遇到另一个问题,因为它不是 NSXMLParser 正在寻找的有效 XML。

    上面的确切问题是标签 META、LI、HTML 和 BODY 没有关闭,因此解析器一直在寻找它的结束标签。

    如果您无权更改 HTML,我所知道的唯一解决方法是使用插入的结束标签对其进行镜像。

    【讨论】:

    • 对不起...示例中的 html 代码只是文件的第一部分。这是我的错。该文件的每个标签都正确关闭。
    【解决方案5】:

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

    【讨论】:

    • 我读到 libxml2 有一个 HTMLparser,但我找不到关于这个的教程、文档或示例,这就是我第一次尝试 NSXMLParser 的原因。
    【解决方案6】:

    自从我开始做 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;但它确实在那之后继续解析。当然,问题是很难区分真正的错误;-)

    【讨论】:

      猜你喜欢
      • 2012-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-11
      • 2012-08-13
      • 2019-08-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多