views:

543

answers:

5

I am overriding a method which has an XmlReader being passed in, I need to find a specific element, add an attribute and then either create a new XmlReader or just replace the existing one with the modified content. I am using C#4.0

I have investigated using XElement (Linq) but I can't seem to manipulate an existing element and add an attribute and value.

I know that the XmlWriter has WriteAttributeString which would be fantastic but again I am not sure how it all fits together

I would like to be able to do something like --- This is pseudo-code!

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

You can't easily do this with XmlReader - at least, not without reading the whole XML document in from the reader, futzing with it and then creating a new XmlReader from the result. That defeats a lot of the point of using XmlReader though - namely the ability to stream large documents.

You could potentially derive from XmlReader, forwarding most method calls to the existing reader but intercepting them where appropriate to add extra attributes etc... but I suspect that code would be really quite complex and fragile.

Jon Skeet
Thanks for the reply, I thought (hoped) this would be nice simple operation. I can't be the only person who has ever wanted to read in XML upto a certain element, edit it and then output the changed result. - If you hear more swearing than usual in Reading today.... it's me!!
Phill Duffy
@Phill: It's easy to do that, just not in a streaming manner I'm afraid :(
Jon Skeet
Subclassing `XmlReader` (actually you'd subclass `XmlTextReader`, unless you felt like implementing a LOT of abstract methods) is an interesting challenge.
Robert Rossney
A: 

I fixed it using the following duct tape coding

    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;
    }

I feel dirty for doing it this way but it is working....

Phill Duffy
I wouldn't be so sure that it's working. The reader you return isn't going to be able to move to the element after this one, because that element (in fact, all the rest of the XML document that the original reader was processing) isn't in its backing stream.
Robert Rossney
A: 

I would rather prefer to load the xml into XmlDocument object and use Attributes collection to modify the value and call Save method to update this value. Below code works for me.

 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);
}

}

I've also observed when you use XmlWriter or XmlSerializer the whitespaces were not correctly preserved, this could be annoying sometimes

Vadi
+1  A: 

XmlReader/Writer are sequential access streams. You will have to read in on one end, process the stream how you want, and write it out the other end. The advantage is that you don't need to read the whole thing into memory and build a DOM, which is what you'd get with any XmlDocument-based approach.

This method should get you started:

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.
                    //
                    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;
            }
        }
    }
}

This is not quite as clean as deriving your own XmlWriter, but I find that it's much easier.

Ilia Jerebtsov
A: 
        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 can be written back to file... that is the edited xml

Harsha