从NSString中提取已知的模式子字符串(不带正则表达式)

时间:2010-05-26 04:49:58

标签: objective-c regex cocoa parsing

为了做到这一点,我真的很想把RegexKit(或我自己的libpcre包装器)放到我的项目中,但在我这样做之前,我想知道Cocoa开发人员如何设法完成这些基本内容的一半而没有真正的错综复杂代码或不与RegexKit或其他正则表达式库链接。

我发现Cocoa不包含任何正则表达式匹配功能。我已经习惯于使用正则表达式来处理所有我没有它们的东西。没有它我可以做我需要的东西,但代码会相当复杂。所以,Cocoa开发者,我问你,“Cocoa方式”是做什么的......

就我而言,问题是编程中的日常问题。 Cocoa必须具备使用内置功能执行此操作的方法。请注意,我想要匹配的元素的位置发生变化,有时会出现“引号”。空白是可变的。

请使用以下字符串:

Content-Type: application/xml; charset=utf-8

Content-Type: text/html; charset="iso-8859-1"

Content-Type: text/plain;
 charset=us-ascii

Content-Type: text/plain; name="example.txt"; charset=utf-8

从所有这些字符串中,您将如何仅使用内置的Cocoa类来确定mime类型(例如text / plain)和charset(例如utf-8)?

我最终会执行一系列-rangeOfString:和子串调用,并使用条件检查来处理可选引号等。有没有办法使用NSScanner执行此操作? NSScanner类对我来说似乎有一个非常天真的API。

像C的sscanf()这样适用于NSString对象的东西是理想的选择。我的大多数字符串解析需求都很简单,例如这个例子,所以正则表达式,虽然我已经习惯了它们,但它们是否过度?

编辑|代码有点冗长,但事实证明NSScanner实际上很容易使用。当你告诉它时,它基本上沿着你的弦走。

创建所需的NSCharacterSet实例最烦人的部分。

- (void)testNSScannerUseCase {
  NSString *testString = @"Content-type: application/xml; name=\"test\";\n charset=\"utf-8\"";

  unsigned int a = 'a', zero = '0';

  // There's probably a quicker way than to make these character sets this way
  NSMutableCharacterSet *alphaNumSet = [NSMutableCharacterSet characterSetWithRange:NSMakeRange(a, 26)];
  [alphaNumSet addCharactersInRange:NSMakeRange(zero, 10)];

  NSMutableCharacterSet *mimeTypeSet = [NSMutableCharacterSet characterSetWithCharactersInString:@"/-"];
  [mimeTypeSet formUnionWithCharacterSet:alphaNumSet];

  NSMutableCharacterSet *charsetSet = [NSMutableCharacterSet characterSetWithCharactersInString:@"-"];
  [charsetSet formUnionWithCharacterSet:alphaNumSet];

  // Initialize a case-insensitive scanner
  NSScanner *scanner = [NSScanner scannerWithString:testString];
  [scanner setCaseSensitive:NO];

  // Prepare to capture mime-type
  NSString *mimeType = nil;

  // Skip past the Content-Type: section
  if ([scanner scanUpToString:@":" intoString:NULL] && [scanner scanString:@":" intoString:NULL]) {
    [scanner scanCharactersFromSet:mimeTypeSet intoString:&mimeType];
  }

  GHAssertEqualStrings(@"application/xml", mimeType, @"Mime-type should be application/xml");

  // Prepare to look for the charset attribute
  NSString *charset = nil;

  // Ignore quotes as well as whitespace
  [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"\r\n\t \""]];

  // Skip past the charset attribute declaration
  if ([scanner scanUpToString:@"charset=" intoString:NULL]
    && [scanner scanString:@"charset=" intoString:NULL]) {

    [scanner scanCharactersFromSet:charsetSet intoString:&charset];
  }

  GHAssertEqualStrings(@"utf-8", charset, @"Charset should be utf-8");
}

使用while循环读取“;”可以使这更聪明一点然后检查它是否是我正在扫描的属性。

我敢说它的基准测试比使用正则表达式更快,而且我的相当长的代码可以重构为更小的代码。

2 个答案:

答案 0 :(得分:2)

我认为你应该以你最初的直觉去做。使用RegexKitLite。添加到项目中非常小而且简单。

另一种选择,如果是iPhone或iPad使用iPhone OS 3.2,您可以使用NSRegularExpressionSearch选项与-rangeOfCharacterFromSet:options:一起使用。

但是,如果我不打算使用正则表达式,我会有一系列的indexOf,rangeOf和substring调用。它可能只有六行,但仍然不如正则表达式那么简单和漂亮。

答案 1 :(得分:1)

如果这些是HTTP Content-Type标头,从技术上讲,根据我对RFC2616的阅读,第二个标题是非法的。您不引用字符集名称。话虽如此,你无法控制你的输入,如果你得到它们,你需要处理它们。

无论如何,假设我们 谈论HTTP标头,即使我确实有一个正则表达式库,我也很想写一个合适的解析器。假设你想要有点懒惰,没有正则表达式库或解析器,你需要做这样的事情:

  • 剥离“内容长度:”。
  • 使用-componentsSeparatedByString:以分号分割。

mime类型是前导和尾随空白区域的第一部分。

现在是棘手的部分。迭代每个剩余的组件。

  • 对于您所在的部分,请确保您拆分的分号未嵌入字符串中。最简单的方法是计算未转义双引号字符的数量,并确保零或两个。如果您在引用的分号上拆分,请重新加入下一个组件并重复
  • 在=符号
  • 处拆分
  • 如果第一部分是charset(不区分大小写),则表示您找到了找到的那个。第二部分是实际的字符集 - 剥离空格和封闭双引号。

上面的内容非常复杂,可能会出现边缘情况失败,但是您创建的任何正则表达式也会很复杂,边缘情况失败,无法读取并且无法使用Xcode调试器进行调试。