2008-03-22

C# 版的 SimpleXML

Views: 11839 | Add Comments

PHP 的 SimpleXML 类让你可以使用对象操作的方法操作 XML, 这是非常方便的, 想一想, a->b 与 a.getChild('b') 哪个更方便. 因为在 C# 中需要操作简单的 XML, 所以我试图实现一个 C# 版的 SimpleXML. 因为 C# 是一种静态型语言, 无法动态地生成一个不存在的类的实例, 所以我使用哈希表(字典, Dictionary)来实现, 正由于 C# 支持索引器, 我的 C# 版的 SimpleXML 类似 PHP 的关联数组. 使用例子如下:

SimpleXml xml = new SimpleXml("<root><a>Hello World!</a></root>");
Console.WriteLine(xml["a"]); // Hello World!

完整的源码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;
using System.Xml;

namespace Ideawu.P2P.Utils
{
    /// <summary>
    /// 表示一个 XML 节点. 以关联数组的方式操作 XML, 类似 PHP 的 SimpleXML.
    /// </summary>
    public class SimpleXml : IEnumerable
    {
        private String name = "";

        private String text = "";

        /// <summary>
        /// 表明该节点是否是同名节点的列表.
        /// </summary>

        private bool isList = false;

        private Dictionary<string, SimpleXml> children = new Dictionary<String, SimpleXml>();

        private Dictionary<string, string> attributes = new Dictionary<string, string>();

        /// <summary>
        /// 创建一个空的 SimpleXml.
        /// </summary>

        public SimpleXml() {
        }

        /// <summary>
        /// 以指定的名称创建一个 SimpleXml.
        /// </summary>
        /// <param name="name">SimpleXml 的名称.</param>
        public SimpleXml(string name) {
            this.name = name;
        }

        /// <summary>

        /// 以指定的名称和文本内容创建一个 SimpleXml.
        /// </summary>
        /// <param name="name">SimpleXml 的名称.</param>
        /// <param name="text">SimpleXml 的文本内容.</param>
        public SimpleXml(string name, string text) {
            this.name = name;
            this.text = text;
        }

        /// <summary>

        /// 名称, 标签名.
        /// </summary>
        public String Name {
            get {
                return name;
            }
            set {
                name = value;
            }
        }

        /// <summary>
        /// 节点的文本(经过 HTML encode).
        /// </summary>
        public String Text {
            get {
                return text;
            }
            set {
                text =  value;
            }
        }

        /// <summary>
        /// 表明该节点是否是同名节点的列表.
        /// </summary>

        public bool IsList {
            get {
                return isList;
            }
        }

        /// <summary>
        /// 获取指定名称的属性值.
        /// </summary>
        /// <param name="key">属性名</param>
        /// <returns>属性值. 如果不存在则为空.</returns>

        public string GetAttribute(String key) {
            string v = null;
            attributes.TryGetValue(key, out v);
            return v;
        }

        /// <summary>
        /// 为节点添加一个属性.
        /// </summary>
        /// <param name="key">属性名.</param>
        /// <param name="value">属性值.</param>

        public void SetAttribute(String key, String value) {
            attributes[key] = value;
        }

        /// <summary>
        /// 删除指定名称的属性.
        /// </summary>
        /// <param name="key">要删除的属性的名称.</param>
        public void RemoveAttribute(String key) {
            attributes.Remove(key);
        }

        /// <summary>

        /// 移除本节点的所有属性.
        /// </summary>
        public void RemoveAllAttribute() {
            attributes.Clear();
        }

        /// <summary>
        /// 获取指定标签名的子节点. 如果要获取的子节点不存在, 则返回 null.
        /// 这和通过索引器获取不一样.
        /// </summary>
        /// <param name="name">子节点的标签名.</param>

        /// <returns>子节点的引用或者 null.</returns>
        public SimpleXml GetChild(string name) {
            SimpleXml c = null;
            children.TryGetValue(name, out c);
            return c;
        }

        /// <summary>
        /// 添加一个带有指定标签名和文本的子节点.
        /// </summary>
        public void AddChild(string name, string text) {
            SimpleXml node = new SimpleXml(name, text);
            this[name] = node;
        }

        /// <summary>

        /// 移除本节点的指定名称(标签名)的子节点.
        /// </summary>
        /// <param name="childName">要移除的子节点的标签名.</param>
        public void RemoveChild(string childName) {
            children.Remove(childName);
        }

        /// <summary>
        /// 移除本 SimpleXml 的所有子节点.
        /// </summary>

        public void RemoveAllChild() {
            children.Clear();
        }

        /// <summary>
        /// 清除本 Simple 所对应的 XML 标签的所有属性和子节点, 但不包括标签本身.
        /// 效果和同时调用 RemoveAllChild 和 RemoveAllAttribute 一样.
        /// </summary>
        public void Clear() {
            children.Clear();
            attributes.Clear();
        }

        /// <summary>
        /// 根据子节点的标签名获取子节点.
        /// </summary>
        /// <param name="childName"></param>

        /// <returns></returns>
        public SimpleXml this[string childName] {
            get {
                SimpleXml obj;
                if (!children.TryGetValue(childName, out obj)) {
                    obj = new SimpleXml();
                    // 让空的节点名表示节点不存在. 而返回一个 SimpleXml 又可以使不抛出 NullReferenceException.
                    //obj.name = childName;
                }
                return obj;
            }
            set {
                SimpleXml obj;
                if (!children.TryGetValue(childName, out obj)) {
                    children[childName] = value;
                } else {
                    // 如果存在相同名字的节点, 创建一个列表类型的节点, 包含所有这些同名节点,
                    // 在内部以 0, 1, 2...命名.
                    /**
                     * 例如: <doc><bs><b/><b/></bs></doc> 可以这样迭代:
                     * foreach(n in xml["bs"]){} 或者 foreach(n in xml["bs"]["b"]){}
                     */
                    if (!obj.isList) {
                        SimpleXml list = new SimpleXml();
                        list.isList = true;
                        list.name = obj.name;
                        list.text = obj.text;
                        children[childName] = list;

                        list.children["0"] = obj;
                        list.children["1"] = value;
                    } else {
                        obj.children[obj.children.Count.ToString()] = value;
                    }
                }
            }
        }

        #region IEnumerable 成员

        /// <summary>

        /// 本 SimpleXml 的所有子节点的迭代.
        /// </summary>
        public IEnumerator GetEnumerator() {
            foreach (SimpleXml obj in children.Values) {
                if (obj.isList && !this.isList) {
                    foreach (SimpleXml c in obj.children.Values) {
                        yield return c;
                    }
                } else {
                    this.GetEnumerator();
                    yield return obj;
                }
            }
        }

        #endregion

        /// <summary>
        /// 返回本节点的名称(标签名).
        /// </summary>
        /// <returns>本节点的名称(标签名).</returns>

        public override String ToString() {
            return text;
        }

        /// <summary>
        /// 将本 SimpleXml 转换成 XML 字符串.
        /// </summary>
        /// <returns>XML 字符串表示的 SimpleXml.</returns>
        public string AsXml() {
            StringBuilder sb = new StringBuilder();
            AsXml(this, sb);
            return sb.ToString();
        }

        /// <summary>

        /// 将本 SimpleXml 所表示的 XML 文本写入到 StringBuilder 中.
        /// </summary>
        /// <param name="sb">要写入的 StringBuilder.</param>
        public void WriteTo(StringBuilder sb) {
            AsXml(this, sb);
        }

        private void AsXml(SimpleXml node, StringBuilder sb) {
            sb.Append("<" + node.name);
            if (node.attributes.Count > 0) {
                foreach (KeyValuePair<String, String> kv in node.attributes) {
                    sb.Append(String.Format(" {0}=\"{1}\"", kv.Key, kv.Value));
                }
            }
            sb.Append(">");

            sb.Append(System.Web.HttpUtility.HtmlEncode(node.text));

            foreach (SimpleXml child in node.children.Values) {
                if (child.isList) {
                    foreach (SimpleXml cc in child) {
                        AsXml(cc, sb);
                    }
                } else {
                    AsXml(child, sb);
                }
            }

            sb.Append("</" + node.name + ">");
        }

        /// <summary>

        /// 从指定的流中载入 XML 文档来构建本 SimpleXml.
        /// </summary>
        /// <param name="stream">要载入的流.</param>
        public void Load(Stream stream) {
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(stream);
            this.Load(xmldoc);
        }

        /// <summary>
        /// 从指定的 XmlDocument 实例中载入 XML 文档来构建本 SimpleXml.
        /// </summary>

        /// <param name="xmldoc">要载入的 XmlDocument 实例.</param>
        public void Load(XmlDocument xmldoc) {
            XmlElement e = xmldoc.DocumentElement;
            SimpleXml node = LoadXmlElement(xmldoc.DocumentElement);
            this.name = node.name;
            this.text = node.text;
            this.children = node.children;
            this.attributes = node.attributes;
        }

        /// <summary>
        /// 从指定的字符串中载入 XML 文档来构建本 SimpleXml.
        /// </summary>
        /// <param name="stream">要载入的字符串.</param>

        public void LoadXml(string xmlstr) {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlstr);
            this.Load(doc);
        }

        private SimpleXml LoadXmlElement(XmlElement e) {
            SimpleXml node = new SimpleXml();
            node.name = e.Name;

            if (e.HasAttributes) {
                foreach (XmlAttribute attr in e.Attributes) {
                    node.attributes[attr.Name] = attr.Value;
                }
            }

            if (e.HasChildNodes) {
                foreach (XmlNode c in e.ChildNodes) {
                    switch (c.NodeType) {
                        case XmlNodeType.CDATA:
                        case XmlNodeType.Text:
                            node.text += c.Value;
                            break;
                        case XmlNodeType.Element:
                            SimpleXml child = LoadXmlElement((XmlElement)c);
                            node[child.name] = child;
                            break;
                    }
                }
            }

            return node;
        }
    }

}

Related posts:

  1. C# 中实现 FIFO 缓冲区–ArrayBuffer
  2. C#环形缓冲
  3. C#封装log4net
  4. 史上最强大的PHP MySQL操作类
  5. C++ STL 迭代器的失效原则
Posted by ideawu at 2008-03-22 19:53:36

Leave a Comment