【问题标题】:Updating data in complex XML更新复杂 XML 中的数据
【发布时间】:2012-03-08 20:02:06
【问题描述】:

我正在编写一个函数来更新我的 XML,但遇到了一些问题。我希望我可以直接将旧的 XElement 设置为更新的 XElement,但这在 foreach 循环中不起作用(尽管应该只是一个查询结果)。我打算对每个字段进行硬编码,但后来决定使用循环。但是,当它到达该部分及其子元素时,它会将其全部作为单个元素读取并弄乱文件

var myDevice = from c in appDataXml.Descendants("device")
               where (string)c.Element("name") == App.CurrentDeviceName 
                      && (string)c.Element("ip") == App.CurrentIp
               select c;

if (myDevice.Any())
{
    foreach (XElement device in myDevice)
    {
        //device.Element("customname").Value = updatedDevice.Element("customname").Value;

        for (int a = 0; a < device.Elements().Count(); a++)
        {
            string field = device.Elements().ElementAt(a).Name.ToString();
            device.Element(field).Value = updatedDevice.Element(field).Value;
        }
    }
}

示例 XML

<Devices>
  <device>
    <name>blah</name>
    <customname>custom</customname>
    <ip>192.168.1.100</ip>
    <port>1000</port>
    <sources>
      <source>
        <code>00</code>
        <name>blah2</name>
        <hidden>False</hidden>
      </source>
      <source>
      ...etc
    </sources>
  </device>

【问题讨论】:

    标签: c# .net xml linq linq-to-xml


    【解决方案1】:

    您需要对您的 xml 进行抽象(创建类),它将帮助您进行 CRUD 和搜索。

    示例:(使用这些扩展:http://searisen.com/xmllib/extensions.wiki

    public class Device
    {
        const bool ELEMENT = false;
        const bool ATTRIBUTE = true;
    
        XElement self;
        public Device(XElement self) { this.self = self; }
    
        public string CustomName 
        { 
            get { return self.Get("customname", string.Empty); }
            set { self.Set("customname", value, ELEMENT); }
        }
        public string Name { get { return self.Get("name", string.Empty); } }
        public string Ip { get { return self.Get("ip", "0.0.0.0"); } }
        public int Port { get { return self.Get("port", 0); } }
    
        public Source[] Sources
        {
            get { return _Sources ?? (_Sources = self.GetEnumerable("sources/source", xsource => new Source(xsource)).ToArray()); }
        }
        Source[] _Sources;
    
        public class Source
        {
            XElement self;
            public Source(XElement self) { this.self = self; }
    
            public string Code { get { return self.Get("code", string.Empty); } }
            public string Name { get { return self.Get("name", string.Empty); } }
            public bool Hidden { get { return self.Get("hidden", false); } }
        }
    }
    

    一个使用它的例子:

    XElement xdevices = XElement.Load(file.FullName);
    Device[] devices = xdevices.GetEnumerable("device", xdevice => new Device(xdevice)).ToArray();
    var myDevice = devices
         .Where(d => d.Name == App.CurrentDeviceName 
                  && d.Ip == App.CurrentIp);
    
    foreach (Device device in myDevice)
    {
        string name = device.Name;
        foreach (Device.Source source in device.Sources)
        {
            string sourceName = source.Name;
        }
        device.CustomName = "new name";
    }
    xdevices.Save(file.FullName);
    

    这是一种思维方式的改变,因此您不必担心如何引用值,而是创建类来读取/写入 xml,而您只需将数据获取/传递给类,即然后读取/写入 xml。

    编辑: ---- 添加到 XElementConversions 类 ----

    为了与文件保持一致,为了正常工作,我做了详细的版本。您可以修改它们以制作其他类型,例如 bool、DateTime 等。

    public static int GetInt(this XElement source, string name, int defaultValue)
    {
        source = NameCheck(source, name, out name);
        return GetInt(source, source.GetDefaultNamespace() + name, defaultValue);
    }
    
    public static int GetInt(this XElement source, XName name, int defaultValue)
    {
        int result;
        if (Int32.TryParse(GetString(source, name, null), out result))
            return result;
        return defaultValue;
    }
    

    【讨论】:

    • 看起来这是一个 Windows Phone 7 项目很重要 - 扩展器使用的 TypeDescriptor 似乎没有实现
    • 您可以尝试创建自己的:msdn.microsoft.com/en-us/library/ayybcxe5.aspx
    • 或者,您可以创建GetInt()GetBool() 等使用GetString() 例如int、GetInt() { return Convert.ToInt32(GetString(...)); }
    • 对不起,我不知道如何实现这个
    • 添加到帖子底部两个 GetInt() 的一个采用字符串元素名称,另一个采用更复杂的命名空间和元素名称。
    【解决方案2】:

    你的意思是var myDevice = ... select c; 最多会产生 1 个元素吗?

    如果是这样,则将孔 .Any()foreach() 替换为 .Single()

    var myDevices = ... select c;
    var myDevice = myDevices.Single();  // exception when Count != 1
    

    然后我看不出为什么这不起作用:

     myDevice.Element("customname").Value = updatedDevice.Element("customname").Value;
    

    【讨论】:

    • 那行确实有效,但我做了一个循环,所以我不必为每个字段(即 ip、端口)做一行,然后我不确定如何处理源子-项目
    【解决方案3】:

    您不应该使用XElement.Value,而是在您的设备XElement 中迭代XElement 元素并更改它们的XText 节点。或者,您可以使用XAttribute,这样您就不需要制作这么多的嵌套元素。

    <Devices>
     <Device Name="blah" IP="192.168.1.100" Port="1000">
       <Sources>
         <Source Code="00" Name="blah2" Hidden="false"/>
       </Sources>
     </Device>
    </Devices>
    

    XElement.Value 是所有XText 节点的串联字符串,这些节点是XElement 的后代。如果您 set 它,您将覆盖所有子元素。阅读更多关于XElement.Value

    【讨论】:

      【解决方案4】:

      不要使用XElement.Value Property。使用XElement.ReplaceWith Method。值的 getter 输出

      包含此元素的所有文本内容的字符串。如果有多个文本节点,它们将被连接起来。

      这意味着子元素的标签将被剥离。

      试试这一行,而不是使用 Value:

      device.Element(field).ReplaceWith(updatedDevice.Element(field));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-18
        • 1970-01-01
        • 1970-01-01
        • 2018-10-21
        • 1970-01-01
        • 2016-09-05
        相关资源
        最近更新 更多