You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用XMLCoder基于标签正确解码XML中的Workout分段

基于XML标签正确解码不同元素的Swift解决方案

待解码的XML文件

<workout_file>
    <author>afaag</author>
    <name>afa</name>
    <description>ag326 wges</description>
    <sportType>bike</sportType>
    <tags>
    </tags>
    <workout>
        <Warmup Duration="600" PowerLow="0.25" PowerHigh="0.75"/>
        <Cooldown Duration="600" PowerLow="0.75" PowerHigh="0.25"/>
    </workout>
</workout_file>

当前的Swift解码代码(基于XMLCoder)

struct Workout: Codable {
  let workout: [WorkoutSegment]
  
  enum CodingKeys: String, CodingKey {
    case workout = "workout"
  }

  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    var workoutSegments = [WorkoutSegment]()
    var workoutContainer = try container.nestedUnkeyedContainer(forKey: .workout)
    
    while !workoutContainer.isAtEnd {

     if let warmUp = try? workoutContainer.decode(WarmUp.self) {
        print("decoding WarmUp")
        workoutSegments.append(WorkoutSegment(warmUp: warmUp))
      } else if let coolDown = try? workoutContainer.decode(CoolDown.self) {
        print("decoding CoolDown")
        workoutSegments.append(WorkoutSegment(coolDown: coolDown))
      } else {
        // If the next element cannot be decoded, exit the loop
        print("=== oops ===")
        break
      }
    }
    
    self.workout = workoutSegments
  }
}


struct WorkoutSegment: Codable {
  let warmUp: WarmUp?
  let coolDown: CoolDown?
  
  enum CodingKeys: String, CodingKey {
    case warmUp = "Warmup"
    case coolDown = "Cooldown"
  }
  
  init(warmUp: WarmUp? = nil, coolDown: CoolDown? = nil) {
    self.warmUp = warmUp
    self.coolDown = coolDown
  }
}


// <Warmup Duration="300" PowerLow="0.42449999" PowerHigh="0.70449996" />
struct WarmUp: Codable {
  let duration: Double
  let powerLow: Double
  let powerHigh: Double
  let textevent: [TextEvent]?
  
  enum CodingKeys: String, CodingKey {
    case duration = "Duration"
    case powerLow = "PowerLow"
    case powerHigh = "PowerHigh"
    case textevent = "textevent"
  }
}

struct CoolDown: Codable {
  let duration: Double
  let powerLow: Double
  let powerHigh: Double
  let textevent: [TextEvent]?
  
  enum CodingKeys: String, CodingKey {
    case duration = "Duration"
    case powerLow = "PowerLow"
    case powerHigh = "PowerHigh"
    case textevent = "textevent"
  }
}

问题现象

原本期望代码能通过<Warmup>、<Cooldown>标签区分不同元素,但实际运行时:

  • 原代码只会解码第一个出现的元素类型(比如XML中Warmup在前时,只打印decoding WarmUp,Cooldown被忽略)
  • 调换XML中两个元素的顺序后,又会把两个元素都解码成第一个出现的类型(比如Cooldown在前时,打印两次decoding CoolDown)
  • 修改为同时尝试解码两种类型后,会重复解码同一个元素(打印两次decoding WarmUp)

核心原因是WarmUp和CoolDown的结构完全一致(属性名、类型都相同),XMLCoder在尝试用其中一个类型解码另一个标签的元素时,会因为属性匹配成功而错误解码,无法区分标签名。

解决方案

要基于标签名正确区分元素,需要先获取当前元素的标签名称,再选择对应的类型解码。具体修改如下:

1. 添加动态CodingKey

用于读取XML元素的标签名:

private struct DynamicCodingKey: CodingKey {
    var stringValue: String
    var intValue: Int?
    
    init?(stringValue: String) {
        self.stringValue = stringValue
        self.intValue = nil
    }
    
    init?(intValue: Int) {
        self.stringValue = "\(intValue)"
        self.intValue = intValue
    }
}

2. 优化WorkoutSegment为枚举(可选,更简洁)

用枚举替代带可选值的结构体,更清晰地表示不同类型的训练段:

enum WorkoutSegment: Codable {
    case warmUp(WarmUp)
    case coolDown(CoolDown)
    
    enum CodingKeys: String, CodingKey {
        case warmUp = "Warmup"
        case coolDown = "Cooldown"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let warmUp = try container.decodeIfPresent(WarmUp.self, forKey: .warmUp) {
            self = .warmUp(warmUp)
        } else if let coolDown = try container.decodeIfPresent(CoolDown.self, forKey: .coolDown) {
            self = .coolDown(coolDown)
        } else {
            throw DecodingError.dataCorrupted(
                DecodingError.Context(
                    codingPath: decoder.codingPath,
                    debugDescription: "无法识别的WorkoutSegment类型"
                )
            )
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .warmUp(let warmUp):
            try container.encode(warmUp, forKey: .warmUp)
        case .coolDown(let coolDown):
            try container.encode(coolDown, forKey: .coolDown)
        }
    }
}

3. 修改Workout的解码逻辑

在循环中先获取元素的标签名,再匹配对应的类型解码:

struct Workout: Codable {
    let workout: [WorkoutSegment]
    
    enum CodingKeys: String, CodingKey {
        case workout = "workout"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        var workoutSegments = [WorkoutSegment]()
        var workoutContainer = try container.nestedUnkeyedContainer(forKey: .workout)
        
        while !workoutContainer.isAtEnd {
            // 获取当前元素的键容器,用于读取标签名
            let elementContainer = try workoutContainer.nestedContainer(keyedBy: DynamicCodingKey.self)
            guard let elementKey = elementContainer.allKeys.first else {
                continue
            }
            
            // 根据标签名选择对应的解码类型
            switch elementKey.stringValue {
            case "Warmup":
                let warmUp = try elementContainer.decode(WarmUp.self, forKey: elementKey)
                workoutSegments.append(.warmUp(warmUp))
                print("decoding WarmUp")
            case "Cooldown":
                let coolDown = try elementContainer.decode(CoolDown.self, forKey: elementKey)
                workoutSegments.append(.coolDown(coolDown))
                print("decoding CoolDown")
            default:
                print("未知元素: \(elementKey.stringValue)")
                continue
            }
        }
        
        self.workout = workoutSegments
    }
}

4. 补充缺失的TextEvent结构体

原代码中未定义TextEvent,需补充(根据实际XML中的字段调整):

struct TextEvent: Codable {
    // 示例:根据实际XML的textevent属性添加字段
    let text: String?
    enum CodingKeys: String, CodingKey {
        case text = "Text"
    }
}

效果验证

修改后运行代码,会正确打印:

decoding WarmUp
decoding CoolDown

并且workout数组中会包含正确的WarmUp和CoolDown类型的元素。

内容的提问来源于stack exchange,提问作者app4g

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 19:39:50