【问题标题】:Cocoa: How to save NSAttributedString to JSONCocoa:如何将 NSAttributedString 保存为 JSON
【发布时间】:2014-03-24 21:06:49
【问题描述】:

我有一个NSAttributedString 对象作为自定义对象的属性。我需要将此自定义对象以 JSON 格式保存到磁盘。稍后我需要通过网络将此 JSON 数据发送到 Java 服务器。
我不能使用NSSAttributedString 对象的-(NSString) string 方法,因为我需要能够从磁盘和服务器上重建属性字符串。

【问题讨论】:

  • 所以您已经定义了自己的属性存储格式?或者你想使用二进制存档?
  • 可能最简单的做法是 dataFromRange 然后将数据转换为 Base64 编码。但即使这样也有点乱。
  • 好吧,我不确定...现在我对任何事情都持开放态度,只要我能够将其写入 JSON 格式的文件并通过网络发送并能够重建原始形式的字符串。我也愿意将字符串和属性分别存储为 JSON 中的字符串,然后再重建对象!
  • @HotLicks 我不知道该怎么做,或者为什么它很棘手:/
  • @Marci-man iOS 和 OS X 对于这类东西基本上是相同的。有些东西只适用于 OS X,但很少反过来。

标签: objective-c json cocoa nsattributedstring


【解决方案1】:

NSAttributedString 有两个属性:

  • 字符串
  • 属性“运行”数组

每个“运行”都有:

  • 它适用的整数范围
  • 键/值属性字典

使用 enumerateAttributesInRange:options:usingBlock: 将其表示为 JSON 非常容易。

类似:

{
  "string" : "Hello World",
  "runs" : [
    {
      "range" : [0,3],
      "attributes" : {
        "font" : {
          "name" : "Arial",
          "size" : 12
        }
      }
    },
    {
      "range" : [3,6],
      "attributes" : {
        "font" : {
          "name" : "Arial",
          "size" : 12
        },
        "color" : [255,0,0]
      }
    },
    {
      "range" : [9,2],
      "attributes" : {
        "font" : {
          "name" : "Arial",
          "size" : 12
        }
      }
    }
  ]
}

编辑:这是一个示例实现:

// create a basic attributed string
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:@"Hello World" attributes:@{NSFontAttributeName: [NSFont fontWithName:@"Arial" size:12]}];
[attStr addAttribute:NSForegroundColorAttributeName value:[NSColor redColor] range:NSMakeRange(3, 6)];

// build array of attribute runs
NSMutableArray *attributeRuns = [NSMutableArray array];
[attStr enumerateAttributesInRange:NSMakeRange(0, attStr.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
  NSArray *rangeArray = @[[NSNumber numberWithUnsignedInteger:range.location],
                          [NSNumber numberWithUnsignedInteger:range.length]];

  NSMutableDictionary *runAttributes = [NSMutableDictionary dictionary];
  [attrs enumerateKeysAndObjectsUsingBlock:^(id attributeName, id attributeValue, BOOL *stop) {

    if ([attributeName isEqual:NSFontAttributeName]) { // convert font values into a dictionary with the name and size
      attributeName = @"font";
      attributeValue = @{@"name": [(NSFont *)attributeValue displayName],
                         @"size": [NSNumber numberWithFloat:[(NSFont *)attributeValue pointSize]]};

    } else if ([attributeName isEqualToString:NSForegroundColorAttributeName]) { // convert foreground colour values into an array with red/green/blue as a number from 0 to 255
      attributeName = @"color";
      attributeValue = @[[NSNumber numberWithInteger:([(NSColor *)attributeValue redComponent] * 255)],
                         [NSNumber numberWithInteger:([(NSColor *)attributeValue greenComponent] * 255)],
                         [NSNumber numberWithInteger:([(NSColor *)attributeValue blueComponent] * 255)]];

    } else { // skip unknown attributes
      NSLog(@"skipping unknown attribute %@", attributeName);
      return;
    }


    [runAttributes setObject:attributeValue forKey:attributeName];
  }];

  // save the attributes (if there are any)
  if (runAttributes.count == 0)
    return;

  [attributeRuns addObject:@{@"range": rangeArray,
                             @"attributes": runAttributes}];
}];

// build JSON output
NSDictionary *jsonOutput = @{@"string": attStr.string,
                             @"runs": attributeRuns};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonOutput options:NSJSONWritingPrettyPrinted error:NULL];

NSLog(@"%@", [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]);
exit(0);

【讨论】:

  • 你能告诉我怎么做吗,也许是一个例子。我真的很感激!
  • 我正在考虑对所有可能的属性枚举运行一个循环(我不确定如何!)。即使我知道,我也不确定这在所有情况下如何帮助我!我的意思是,我可以为字符串的不同部分设置不同的属性!
  • @Marci-man enumerateAttributesInRange:options:usingBlock: 将为您运行循环并执行您为每个属性部分提供的块。我将更新我的答案以进行演示。
  • 你能帮我解决这个问题,还有另一个问题,我如何读回数据并从中构建 NSAttributedString?
【解决方案2】:

您可以尝试从 RTFFromRange 开始:

来自文档:有关支持 RTF 的 OS X 方法的信息,...,请参阅 NSAttributedString Application Kit Additions Reference。

RTF 应该是自包含的。 RTFFromRange:返回 NSData;我认为它可能是某种编码中的字符数据,因此应该很容易转换为 NSString。

(抱歉,刚刚看到该方法仅适用于 MacOS X)。

【讨论】:

【解决方案3】:

您可以使用这个简单的代码 sn-p 将 NSAttributedString 转换为 XML,而无需实际解析 NSAttributedString。如果您能负担得起冗长的文本输出,这可以成为 JSON 的人类可读替代方案。

也可用于解码回NSAttributedString

    let data = NSMutableData()

    let archiver = NSKeyedArchiver(forWritingWithMutableData: data)
    archiver.outputFormat = .XMLFormat_v1_0
    textView.attributedText.encodeWithCoder(archiver)
    archiver.finishEncoding()

    let textAsString = NSString(data: data, encoding: NSUTF8StringEncoding)'

【讨论】:

    【解决方案4】:

    Swift 5 版本的@AbhiBeckertanswer

    let attributedString: NSAttributedString! // Input -> NSAttributedString
    let RUN_ATTRIBUTES_ARRAY: NSMutableArray = []
    
    attributedString!.enumerateAttributes(in: .init(location: 0, length: attributedString!.length), options: [], using: { attributedDictionary, range, stop in // Retrieve all of attributed string's attributes
        let runAttributes: NSMutableDictionary = NSMutableDictionary()
        
        // Convert each attribute's value to a JSON formattable type
        for attribute in attributedDictionary {
            if (attribute.key == .font) {
                let values: NSDictionary = [
                    "name": (attribute.value as! NSFont).displayName!,
                    "size": (attribute.value as! NSFont).pointSize
                ]
                
                runAttributes.setValue(values, forKey: "font") // Apply the value with its key to a mutable dictionary
            }
        }
        
        // Add the previously accumulated values to a mutable array along with the corresponding range
        RUN_ATTRIBUTES_ARRAY.add([
            "range": [range.lowerBound, range.upperBound],
            "attributes": runAttributes
        ])
    })
    
    // Create a dictionary with the attributes and the text value
    let dictionary: NSDictionary = [
        "string": attributedString!.string,
        "runs": RUN_ATTRIBUTES_ARRAY
    ]
    
    // Convert the dictionary to JSON
    try {
        let jsonData: Data = try JSONSerialization.data(withJSONObject: dictionary, options: [.prettyPrinted, .sortedKeys])
        print(jsonData) // Output -> JSON
    } catch {
        print("Error converting dictionary to JSON")
    }
    

    以下代码将 JSON 转换回属性字符串:

    private func convertAttributesFromJSONToDictionary(_ attributes: Any) -> [NSAttributedString.Key: Any]? {
        if let attrValue: [String: [String: Any]] = (attributes as? [String: [String: Any]]) {
            /*
             attrValue = [
                 "font" : {
                   "name" : "Helvetica",
                   "size" : 12
                 },
                 "color" : [255,0,0]
             ]
             */
            var attrDict: [NSAttributedString.Key: Any] = [:]
            
            for (key, value) in attrValue { // Loop through each attribute
                if (key == "font") {
                    // Retrieve all attribute values
                    var name: String = "Helvetica"
                    var size: CGFloat = 12
                    
                    for (fontKey, fontValue) in value {
                        if (fontKey == "name") {
                            name = (fontValue as! String)
                        } else if (fontKey == "size") {
                            size = (fontValue as! CGFloat)
                        }
                    }
                    
                    if let font: NSFont = NSFont(name: name, size: size) {
                        // Add retrieved values to a dictionary
                        attrDict.updateValue(font, forKey: .font)
                    } else {
                        print("Unable to implement font attribute")
                    }
                }
            }
            
            return attrDict // Return filled dictionary
        }
        
        return nil
    }
    
    public func convertJSONToAttributedString() {
        var dictionary: [String: Any]! // Input -> JSON
        
        // Create attributed string with text string
        guard let string: String = (dictionary["string"] as? String) else {
            print("Incorrect json structure {string}")
        }
        
        let attrString: NSMutableAttributedString = NSMutableAttributedString(string: string)
        
        if let runsDict: [[String: Any]] = (dictionary["runs"] as? [[String: Any]]) { // Check for 'runs' key in JSON data
            /*
             runsDict = [
                 {
                   "attributes" : {Any},
                   "range" : [Int]
                 }, {
                   "attributes" : {Any},
                   "range" : [Int]
                 }
             ]
             */
            for run in runsDict { // Loop through each attributes and range section
                var attributes: [NSAttributedString.Key: Any] = [:]
                var range: NSRange?
                
                for (key, value) in run {
                    // Retrieve all attributes and the range
                    if (key == "attributes") {
                        if let attrDict: [NSAttributedString.Key: Any] = convertAttributesFromJSONToDictionary(value) {
                            attributes = attrDict
                        }
                    } else if (key == "range") {
                        if let rangeValue: [Int] = (value as? [Int]) {
                            range = NSRange(location: rangeValue[0], length: (rangeValue[1] - rangeValue[0]))
                        }
                    }
                    
                    // Add retrieved attributes and range to the attributed string
                    if ((key == "attributes" || key == "range") && range != nil) {
                        attrString.addAttributes(attributes, range: range!)
                    }
                }
            }
        } else {
            print("Incorrect json structure {runs}")
        }
    
        print(attrString) // Output -> NSAttributedString
    }
    

    【讨论】:

      猜你喜欢
      • 2015-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-27
      • 2021-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多