逐个迭代格式%占位符

时间:2014-11-25 07:36:39

标签: ios objective-c

我有一个看起来像

的NSAttributedString

" Somestring 粗体%@模板%f%d blah blah blah"

我希望能够像[NSString stringWithFormat:...]中那样替换格式模板部分,但保留样式,以便替换的字符串与它们周围的样式相匹配(在上面的示例中都是粗体)。 / p>

有没有办法逐个迭代每个格式的%占位符,所以我可以使用一个参数列表来填充字符串?

我不想构建自己的%实现,因为我知道有一百万种不同的格式。

或者我有一个更容易解决的问题吗?

编辑: 我将解释一下我正在解决的一些完整解决方案:

为了让我的团队能够归因于本地化字符串,我已经有了一种写作方式

"key"="test //italic %@// **bold** __underline %d__";

如果说明符位于属性标记之间,我希望将该部分归因。目前我可以创建一个如上所示的属性字符串,下一部分是照顾剩下的说明符。

我按照 Parse属性 - >的顺序进行此操作。应用参数 ...,我可以通过其他方式轻松解决它,但我不希望格式参数混淆属性

3 个答案:

答案 0 :(得分:2)

非常感谢@Rick指出我正确的方向。

他的帖子建议首先遍历参数并在应用格式字符串之前预先转义任何字符串或字符对象。这让我回到了我之前遇到的另一个问题,试图迭代不同类型的参数列表(NSString,int等),就像NSLog一样。我认为我发现 不可能(或者至少真的很难)这样做,NSLog的原因是它知道通过格式说明符期望什么类型(%@,%i等)。

我意识到我实际上可以通过转义参数来获得相同的效果,但是通过转义格式字符串本身。

示例:

format: "test //italic %@//"
args: "text // more text"

步骤:

  1. 首先用// - TAG - //
  2. 替换//的所有实例
  3. 应用参数
  4. 确定样式在// - TAG - //
  5. 之间的适用位置

    显然// - TAG - //仍然可以在参数中写入来破坏样式,但是根据你用作替换的内容,发生这种情况的可能性基本上为零。

答案 1 :(得分:1)

  

我是按照Parse属性的顺序进行的 - >申请论点......,我   可以很容易地解决它,但我不想要格式参数   弄乱属性

为什么不简单地添加转义字符?根据我的理解,如果第一个字符串包含一个双斜线,那么你冒着你提供的例子搞乱的风险?

"key"="test //italic %@// **bold** __underline %d__";

如果%@text // more text会导致格式错误。

如果是这样,那么只需解析NSStringchar类型的每个vararg,以确保它们不包含您为属性保留的任何字符。如果是这样,请添加一些转义字符,然后在解析属性时删除它。

在应用参数后,上面的示例如下所示:

"key"="test //italic text \/\/ more text// **bold** __underline 34__";

之后您解析属性,与之前一样,但忽略前面带有\的字符,并确保删除\

这有点费劲,但我敢打赌它比实现你自己的printf风格的解析器要少得多。

答案 2 :(得分:1)

这是工作代码:

#import <Foundation/Foundation.h>

@interface NSAttributedString (AttributedFormat)
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat, ...;
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat arguments:(va_list)arguments;
@end

@implementation NSAttributedString (AttributedFormat)

- (instancetype)initWithFormat:(NSAttributedString *)attrFormat, ... {
    va_list args;
    va_start(args, attrFormat);
    self = [self initWithFormat:attrFormat arguments:args];
    va_end(args);
    return self;
}

- (instancetype)initWithFormat:(NSAttributedString *)attrFormat arguments:(va_list)arguments {
    NSRegularExpression *regex;
    regex = [[NSRegularExpression alloc] initWithPattern: @"(%.*?[@%dDuUxXoOfeEgGccsSpaAF])"
                                                 options: 0
                                                   error: nil];
    NSString *format = attrFormat.string;
    format = [regex stringByReplacingMatchesInString: format
                                             options: 0
                                               range: NSMakeRange(0, format.length)
                                        withTemplate: @"\0$1\0"];
    NSString *result = [[NSString alloc] initWithFormat:format arguments:arguments];
    NSMutableArray *f_comps = [format componentsSeparatedByString:@"\0"].mutableCopy;
    NSMutableArray *r_comps = [result componentsSeparatedByString:@"\0"].mutableCopy;

    NSMutableAttributedString *output = [[NSMutableAttributedString alloc] init];
    __block int consumed_length = 0;

    [attrFormat enumerateAttributesInRange:NSMakeRange(0, attrFormat.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
        NSMutableString *substr = [NSMutableString string];
        while(f_comps.count > 0 && NSMaxRange(range) >= consumed_length + [(NSString *)f_comps[0] length]){
            NSString *f_str = f_comps[0];
            NSString *r_str = r_comps[0];
            [substr appendString:r_str];
            [f_comps removeObjectAtIndex:0];
            [r_comps removeObjectAtIndex:0];
            consumed_length += f_str.length;
        }

        NSUInteger idx = NSMaxRange(range) - consumed_length;
        if(f_comps.count > 0 && idx > 0) {
            NSString *f_str = f_comps[0];

            NSString *leading = [f_str substringToIndex:idx];
            [substr appendString:leading];

            NSString *trailing = [f_str substringFromIndex:idx];
            [f_comps replaceObjectAtIndex:0 withObject:trailing];
            [r_comps replaceObjectAtIndex:0 withObject:trailing];
            consumed_length += idx;
        }
        [output appendAttributedString:[[NSAttributedString alloc] initWithString:substr attributes:attrs]];
    }];

    return [self initWithAttributedString:output];
}
@end

用法示例:

NSMutableAttributedString *fmt = [[NSMutableAttributedString alloc] initWithString:@"test: "];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: @"Some%%string"
                                                             attributes: @{
                                                                           NSFontAttributeName: [UIFont systemFontOfSize:17]
                                                                           }]];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: @"bold %@ template %.3f %d"
                                                             attributes: @{
                                                                           NSFontAttributeName: [UIFont boldSystemFontOfSize:20],
                                                                           NSForegroundColorAttributeName: [UIColor redColor]
                                                                           }]];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: @"%@ blah blah blah"
                                                             attributes: @{
                                                                           NSFontAttributeName: [UIFont systemFontOfSize:16],
                                                                           NSForegroundColorAttributeName: [UIColor blueColor]
                                                                           }]];

NSAttributedString *result = [[NSAttributedString alloc] initWithFormat:fmt, @"[foo]", 1.23, 56, @"[[bar]]"];

结果:

screenshot

也许这仍有一些错误,但在大多数情况下都应该有效。


(%.*?[@%dDuUxXoOfeEgGccsSpaAF])

此正则表达式匹配"Format Specifiers"。说明符以%开头,以列出的字符结尾。并且可能在它们之间有一些修饰符。它不完美,例如这种非法格式"%__s"应该被忽略但我的正则表达式匹配整个字符串。但只要说明符合法,它就应该有效。

我的代码与之匹配,并在说明符周围插入分隔符:

I'm %s.
I'm <delimiter>%s<delimiter>.

我使用\0作为分隔符。

I'm \0%s\0.
然后插入它。

I'm \0rintaro\0.

然后用分隔符分割格式和结果:

f_comps: ["I'm ", "%s", "."]
r_comps: ["I'm ", "rintaro", "."]

此处,f_comps的总字符串长度与原始归因格式完全相同。然后,使用enumerateAttributesInRange迭代属性,我们可以将属性应用于结果。

我很抱歉,但由于我英语能力差,很难解释enumerateAttributesInRange内的工作:)