tags:

views:

533

answers:

3

hi guys I have a mail application. While sending to each recipient I am writing to an XML file named mail.xml. I use the following code:

Dim from As String = txtFrom.Text
Dim txto As String = txtTo.Text
Dim subj As String = txtSubject.Text
Dim body As String = txtBody.Text
Dim settings As New XmlWriterSettings()
settings.Indent = True
settings.NewLineOnAttributes = True
Using writer As XmlWriter = XmlWriter.Create("C:\xmlmailfile.xml", settings)
  writer.WriteStartDocument()
  writer.WriteStartElement("EMail")
  writer.WriteStartElement("From")
  writer.WriteStartAttribute("From")
  writer.WriteValue(from)
  writer.WriteEndAttribute()
  writer.WriteStartElement("To")
  writer.WriteStartAttribute("To")
  writer.WriteValue(txto)
  writer.WriteEndAttribute()
  writer.WriteStartElement("Subject")
  writer.WriteStartAttribute("Subject")
  writer.WriteValue(subj)
  writer.WriteEndAttribute()
  writer.WriteStartElement("Body")
  writer.WriteStartAttribute("Body")
  writer.WriteValue(body)
  writer.WriteEndAttribute()
  writer.WriteEndElement()
  writer.WriteEndDocument()
  writer.Flush()
End Using

And the output is:

<?xml version="1.0" encoding="utf-8" ?> 
<EMail>
  <From From="[email protected]">
  <To To="[email protected]">
    <Subject Subject="Hi">
      <Body Body="Hello" /> 
    </Subject>
  </To>
  </From>
 </EMail>

Here I am not able to append to existing output. Only one "Email" section is being output. I want to add an "Email" section for each recipient. However, in the above code new sections replace previously written ones.

How can I accomplish this?

+4  A: 

An XML document can only have a single root element. I suggest you have a root Emails element with Email elements under it. Note that you still won't be able to append new elements within the same file - you'd have to read the existing file and rewrite it. In theory you could just overwrite the last line (you always know how long it will be, so you could just seek to the right place) but it's more robust to read the file into memory, append a new Email element, and then write out the whole document again.

I also think it's a bit strange to have the Body element within the subject element. I'd suggest a structure like this:

<?xml version="1.0" encoding="utf-8" ?> 
<Emails>
  <Email>
    <From Address="[email protected]">
    <Recipient Type="To" Address="[email protected]" />
    <Recipient Type="Cc" Address="[email protected]" />
    <Subject>Hi</Subject>
    <Body>Body text</Body> 
  </Email>
  <Email>
    <!-- Second email comes here -->
  </Email>
</Emails>
Jon Skeet
Great answer! (as usual)
Cerebrus
Can u show one example how to read the file into memory, append a new Email element and how to modify my code to get above result
@ramyatk06: I haven't got time to do that right now, particularly not in VB, but I'm sure you can find lots of examples online. I suggest that you use either LINQ to XML (if you're using .NET 3.5) or the DOM API (XmlDocument, XmlElement etc) otherwise. XmlWriter is efficient, but harder to get right.
Jon Skeet
A: 

In addition to implementing Jon's suggestion about the necessary modifications to your XML, you will also need to loop over the XmlWriter's WriteXXX methods. Start at the Email element and loop for each recipient.

Cerebrus
A: 

As a response to your comment about modifying it in memory:

Consider writing a schema like this:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
  <xs:complexType name="FromType">
    <xs:attribute name="Address" type="xs:string"/>
  </xs:complexType>
  <xs:simpleType name="RecipientMethod">
    <xs:restriction base="xs:string">
      <xs:enumeration value="To"/>
      <xs:enumeration value="Cc"/>
      <xs:enumeration value="Bcc"/>
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType name="RecipientType">
    <xs:attribute name="Type" type="RecipientMethod"/>
    <xs:attribute name="Address" type="xs:string"/>
  </xs:complexType>
  <xs:complexType name="Email">
    <xs:sequence>
      <xs:element name="From" type="FromType" minOccurs="1" maxOccurs="1"/>
      <xs:element name="Recipient" type="RecipientType" minOccurs="1" maxOccurs="unbounded"/>
      <xs:element name="Subject" type="xs:string" minOccurs="0" maxOccurs="1"/>
      <xs:element name="Body" type="xs:string" minOccurs="0" maxOccurs="1"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="Emails">
    <xs:sequence>
      <xs:element name="Emails" type="Email" minOccurs="1" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="Emails" type="Emails"/>
</xs:schema>

and run the schema compiler xsd.exe on it (here)

With the class it generates, you just set the members, like this:

Emails e= new Emails();
e.Email = new Email[2];
e.Email[0] = new Email();
e.Email[0].From = new FromType();
...

Once the members are set, then do this:

FileStream f ...
XmlSerializer x = new XmlSerializer( e, f );
x.Serialize(e);

Similarly, you can deserialize:

Emails ne = x.DeSerialize();

Then modify it to add a new Email:

Email newEmail = Email[ne.Email.Length + 1];
for(int i = 0; i < ne.Email.Length; i++)
    newEmail[i] = ne.Email[i];
newEmail[ne.Email.Length] = new Email();
....
ne.Email = newEmail;

then serialize it back.

paquetp
apologies, I used C# syntax - you can run xsd.exe to generate VB code instead
paquetp