XmlReader - 我需要编辑一个元素并生成一个新的元素。

11

我正在覆盖一个接受XmlReader参数的方法,需要查找特定的元素,添加属性,然后创建一个新的XmlReader或用修改后的内容替换现有的XmlReader。 我正在使用C#4.0

我已经调查了使用XElement(Linq),但似乎无法操作现有元素并添加属性和值。

我知道XmlWriter具有WriteAttributeString方法,这很棒,但我不确定如何将它们组合在一起。

我希望能够做类似于以下伪代码的事情!

public XmlReader DoSomethingWonderful(XmlReader reader)
{
   Element element = reader.GetElement("Test");
   element.SetAttribute("TestAttribute","This is a test");
   reader.UpdateElement(element);
   return reader;
}
5个回答

17

XmlReader/Writer是顺序访问的流。您需要在一端进行读取,以所需方式处理流,并在另一端进行写出。优点是您不需要将整个内容读入内存并构建DOM,这是任何基于XmlDocument的方法得到的结果。

以下方法可以帮助您入门:

private static void PostProcess(Stream inStream, Stream outStream)
{
    var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " };

    using (var reader = XmlReader.Create(inStream))
    using (var writer = XmlWriter.Create(outStream, settings)) {
        while (reader.Read()) {
            switch (reader.NodeType) {
                case XmlNodeType.Element:
                    writer.WriteStartElement(reader.Prefix, reader.Name, reader.NamespaceURI);
                    writer.WriteAttributes(reader, true);

                    //
                    // check if this is the node you want, inject attributes here.
                    //

                    if (reader.IsEmptyElement) {
                        writer.WriteEndElement();
                    }
                    break;

                case XmlNodeType.Text:
                    writer.WriteString(reader.Value);
                    break;

                case XmlNodeType.EndElement:
                    writer.WriteFullEndElement();
                    break;

                case XmlNodeType.XmlDeclaration:
                case XmlNodeType.ProcessingInstruction:
                    writer.WriteProcessingInstruction(reader.Name, reader.Value);
                    break;

                case XmlNodeType.SignificantWhitespace:
                    writer.WriteWhitespace(reader.Value);
                    break;
            }
        }
    }
}

这种方法不如自行派生XmlWriter干净,但我发现它更加容易。

[编辑]

打开两个流的示例可能类似于以下内容:

using (FileStream readStream = new FileStream(@"c:\myFile.xml", FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write)) {
  using (FileStream writeStream = new FileStream(@"c:\myFile.xml", FileMode.OpenOrCreate, FileAccess.Write)) {
    PostProcess(readStream, writeStream);
  }
}

2
我使用以下的“胶带编程”方法修复了它。
    public XmlReader FixUpReader(XmlReader reader)
    {
       reader.MoveToContent();

        string xml = reader.ReadOuterXml();

        string dslVersion = GetDSLVersion();
        string Id = GetID();

        string processedValue = string.Format("<ExampleElement dslVersion=\"{1}\" Id=\"{2}\" ", dslVersion, Id);
        xml = xml.Replace("<ExampleElement ", processedValue);
        MemoryStream ms = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(xml));
        XmlReaderSettings settings = new XmlReaderSettings();

        XmlReader myReader = XmlReader.Create(ms);
        myReader.MoveToContent();
        return myReader;
    }

我觉得这样做很不正规,但它确实有效...


1
我不确定它是否有效。你返回的读取器将无法移动到此元素之后的元素,因为该元素(实际上,原始读取器正在处理的XML文档中的所有其他内容)不在其支持流中。 - Robert Rossney

1

我更倾向于将xml加载到XmlDocument对象中,并使用Attributes集合来修改值,然后调用Save方法来更新该值。以下代码适用于我。

 public static void WriteElementValue ( string sName, string element, string value)
 {
  try
  {
    var node = String.Format("//elements/element[@name='{0}']", sName);
    var doc = new XmlDocument { PreserveWhitespace = true };
    doc.Load(configurationFileName);

    var nodeObject = doc.SelectSingleNode(node);

     if (nodeObject == null)
         throw new XmlException(String.Format("{0} path does not found any matching 
         node", node));

    var elementObject = nodeObject[element];

    if (elementObject != null)
    {
       elementObject.Attributes["value"].Value = value;
    }

    doc.Save(configurationFileName);
}
catch (Exception ex)
{
   throw new ExitLevelException(ex, false);
}

}

我也注意到当你使用XmlWriter或XmlSerializer时,空格没有被正确地保留,有时这可能会很烦人。

1

使用XmlReader不容易做到这一点 - 至少不是在从读取器中读取整个XML文档,搞乱它,然后从结果创建一个新的XmlReader之前。虽然这样做会破坏使用XmlReader的很多优点 - 即能够流式传输大型文档。

您可以可能XmlReader派生,将大多数方法调用转发到现有的读取器,但在适当的位置拦截它们以添加额外的属性等...但我怀疑该代码会非常复杂和脆弱。


谢谢回复,我本以为(希望)这将是一个简单的操作。我不可能是唯一想要读取XML到某个元素,编辑它,然后输出更改结果的人。- 如果你今天在Reading听到比平常更多的咒骂声...那就是我! - Phill Duffy
@Phill:做那个很容易,但恐怕不是以流式方式。 :( - Jon Skeet
子类化 XmlReader(实际上你应该子类化 XmlTextReader,除非你想要实现很多抽象方法)是一个有趣的挑战。 - Robert Rossney
@RobertRossney 不再使用XmlTextReader:https://dev59.com/questions/AGsz5IYBdhLWcg3wJUYo - nawfal

-2
        string newvalue = "10";
        string presentvalue = "";
        string newstr = "";
        XmlReader xmlr = XmlReader.Create(new StringReader(str));

        while (xmlr.Read())
        {
            if (xmlr.NodeType == XmlNodeType.Element)
            {
                if (xmlr.Name == "priority")
                {
                    presentvalue = xmlr.ReadElementContentAsString();
                    newstr = str.Replace(presentvalue, newvalue);
                }
            }

        }

//newstr 可以写回文件... 这就是编辑过的 xml


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接