Blog Mateusza Chodyły czyli interdyscyplinarnego .NETowca 8-)

Wizualizacja drzewa dokumentu XML (.NET)

17. maja 2010 12:49

Po długiej przerwie związanej z pracą zawodową wracam do studiowania, zwłaszcza że na horyzoncie widać sesję :) Na przedmiot “Programowanie i architektury systemów komponentowych” miałem przygotować projekt. Wybrałem wykorzystanie JAXB do wizualizacji drzewa dokumentu XML. JAXB jest jednym z elementów J2EE, ale nie mogłem się oprzeć okazji zrobienia czegoś podobnego w .NETcie, tak “dla sportu” :).

Zabawę zacząłem od stworzenia XML Schema. Założyłem, że w pliku XML chcę przechowywać korespondencję. Oczywiście zrobiłem bardzo uproszczoną wersję, ale myślę że i tak ciekawą:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="urn:mails-schema"
  elementFormDefault="qualified"
  targetNamespace="urn:mails-schema">

  <!-- korzeń - zbiór maili -->
  <xsd:element name="mails" type="mailsType" />

  <xsd:complexType name="mailsType">
    <xsd:sequence minOccurs="0" maxOccurs="unbounded">
      <xsd:element name="mail" type="mailType" />
    </xsd:sequence>
  </xsd:complexType>

  <!-- pojedyńczy mail -->
  <xsd:complexType name="mailType">
    <xsd:sequence>
      <xsd:element name="envelope" type="envelopeType" minOccurs="1" maxOccurs="1" />
      <xsd:element name="body" type="bodyType" minOccurs="1" maxOccurs="1" />
      <xsd:element name="attachment" type="attachmentType" minOccurs="0" maxOccurs="unbounded" />
    </xsd:sequence>
    <xsd:attribute name="ID" use="required" type="xsd:integer" />
  </xsd:complexType>

  <!-- inf. podstawowe (dla uproszenia pomijam możliwość wysyłki maili do większej ilości użytkowników oraz zaawansowane nagłówki) -->
  <xsd:complexType name="envelopeType">
    <xsd:sequence>
      <xsd:element name="from" type="emailAddress" />
      <xsd:element name="to" type="emailAddress" />
      <xsd:element ref="date" />
      <xsd:element name="subject" type="xsd:string" />
    </xsd:sequence>
  </xsd:complexType>

  <!-- prosta walidacja adresu mailowego -->
  <xsd:simpleType name="emailAddress">
    <xsd:restriction base="xsd:string">
      <xsd:pattern value="^[A-Za-z0-9_\.]+@[A-Za-z0-9]+\.[a-zA-Z]{2,3}$"/>
    </xsd:restriction>
  </xsd:simpleType>

  <xsd:element name="date" type="xsd:dateTime" />  <!-- żeby nie było nudno :) -->

  <!-- ciało (tutaj także pomijam m.in. możliwość wysyłki jako czysty tekst i html) -->
  <xsd:simpleType name="bodyType">
    <xsd:restriction base="xsd:string" />
  </xsd:simpleType>

  <!-- załącznik -->
  <xsd:complexType name="attachmentType">
    <xsd:group ref="attachmentContent" />
    <xsd:attribute name="name" type="xsd:string" use="required" />
  </xsd:complexType>

  <!-- załącznik: typ i zawartość -->
  <xsd:group name="attachmentContent">
    <xsd:sequence>
      <xsd:element name="mimetype">
        <xsd:complexType>
          <xsd:attributeGroup ref="mimeTypeAttributes" />
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="content" type="xsd:string" />
    </xsd:sequence>
  </xsd:group>

  <!-- załącznik - MIME -->
  <xsd:attributeGroup name="mimeTypeAttributes">
    <xsd:attribute name="type" type="mimeTopLevelType" use="required" />
    <xsd:attribute name="subtype" type="xsd:string" use="required" />
  </xsd:attributeGroup>

  <!-- załącznik - wyliczenie dla głównego typu MIME -->
  <xsd:simpleType name="mimeTopLevelType">
    <xsd:restriction base="xsd:string">
      <xsd:enumeration value="text" />
      <xsd:enumeration value="multipart" />
      <xsd:enumeration value="application" />
      <xsd:enumeration value="message" />
      <xsd:enumeration value="image" />
      <xsd:enumeration value="audio" />
      <xsd:enumeration value="video" />
    </xsd:restriction>
  </xsd:simpleType>

</xsd:schema>

Przygotowałem 3 pliki - poprawny:

<?xml version="1.0" encoding="utf-8" ?>
<mails xmlns="urn:mails-schema">  <!-- określenie namespace'a krytycznie ważne -->
  <mail ID="0">
    <envelope>
      <from>piotr.kowalski@gmail.com</from>
      <to>j.nowak@o2.pl</to>
      <date>2010-05-02T10:26:00Z</date>
      <subject>Prezentacja - przypomnienie</subject>
    </envelope>
    <body>
      Pamiętaj o przesłaniu mi naszej prezentacji. Pozdrawiam Piotr
    </body>
  </mail>
  <mail ID="1">
    <envelope>
      <from>j.nowak@o2.pl</from>
      <to>piotr.kowalski@gmail.com</to>
      <date>2010-05-04T15:44:01Z</date>
      <subject>Prezentacja na jutrzejsze zajęcia</subject>
    </envelope>
    <body>
      W załączniku przesyłam prezentację na jutrzejsze spotkanie wraz z obliczeniami. Pozdrawiam Jan
    </body>
    <attachment name="prezentacja.ppt">
      <mimetype type="application" subtype="vnd.ms-powerpoint"/>
      <content>
        /9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAMgAA/+4ADkFkb2JlAGTAAAAAAf/b
        AIQACAYGBgYGCAYGCAwIBwgMDgoICAoOEA0NDg0N...
      </content>
    </attachment>
    <attachment name="obliczenia.xls">
      <mimetype type="application" subtype="vnd.ms-excel"/>
      <content>
        dk9NlNCCCMsyyPCGEYjiPmyCBwoodsEq3Poa9sj9lqLSfAw+L8MO7Vyylj1PQ5OGXiqLVPGI8Uuq
        qwANeuBpNv8AP6MWdqb74Fkk8RhksUfo+galr8k6...
      </content>
    </attachment>
  </mail>
</mails>

I dwa niepoprawne:

<?xml version="1.0" encoding="utf-8" ?>
<mails xmlns="urn:mails-schema">
  <mail id="0">
    <envelope>
      <from>piotr.kowalski@gmail.com</from>
      <to>j.nowak@poczta.onet.pl</to>
      <date>2010-05-02T10:26:00Z</date>
      <subject>Prezentacja - przypomnienie</subject>
    </envelope>
  </mail>  
</mails>
<?xml version="1.0" encoding="utf-8" ?>
<mails xmlns="urn:mails-schema">
  <mail ID="1" priority="high">
    <envelope>
      <from>j.nowak@o2.pl</from>
      <to>piotr.kowalski@gmail.com</to>
      <date>2010-02-31T15:44:01Z</date>
      <subject>Prezentacja na jutrzejsze zajęcia</subject>
    </envelope>
    <body>
      W załączniku przesyłam prezentację na jutrzejsze spotkanie wraz z obliczeniami. Pozdrawiam Jan
    </body>
    <attachment name="prezentacja.ppt">
      <mimetype type="mp3" subtype="vnd.ms-powerpoint"/>
    </attachment>    
  </mail>
</mails>

Dostęp do pliku będzie realizowany z wykorzystaniem klasy XmlSerializer, w konstruktorze której zostanie podany (de)serializowany typ. Aby wygenerować odpowiednie klasy posłużę się narzędziem XML Schema Definition (odpowiednik JAXB w Javie) - polecenie (ścieżka C:\Program Files\Microsoft Visual Studio 9.0\VC):
xsd "<dir>\App_Data\mails.xsd" /classes /outputdir:"<dir>\App_Code" Wygenerowany plik wygląda następująco:

using System.Xml.Serialization;

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
[System.Xml.Serialization.XmlRootAttribute("mails", Namespace = "urn:mails-schema", IsNullable = false)]
public partial class mailsType
{
  private mailType[] mailField;

  [System.Xml.Serialization.XmlElementAttribute("mail")]
  public mailType[] mail
  {
    get
    {
      return this.mailField;
    }
    set
    {
      this.mailField = value;
    }
  }
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
public partial class mailType
{
  private envelopeType envelopeField;
  private string bodyField;
  private attachmentType[] attachmentField;
  private string idField;
  
  public envelopeType envelope
  {
    get
    {
      return this.envelopeField;
    }
    set
    {
      this.envelopeField = value;
    }
  }

  public string body
  {
    get
    {
      return this.bodyField;
    }
    set
    {
      this.bodyField = value;
    }
  }

  [System.Xml.Serialization.XmlElementAttribute("attachment")]
  public attachmentType[] attachment
  {
    get
    {
      return this.attachmentField;
    }
    set
    {
      this.attachmentField = value;
    }
  }

  [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")]
  public string ID
  {
    get
    {
      return this.idField;
    }
    set
    {
      this.idField = value;
    }
  }
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
public partial class envelopeType
{
  private string fromField;
  private string toField;
  private System.DateTime dateField;
  private string subjectField;

  public string from
  {
    get
    {
      return this.fromField;
    }
    set
    {
      this.fromField = value;
    }
  }

  public string to
  {
    get
    {
      return this.toField;
    }
    set
    {
      this.toField = value;
    }
  }

  public System.DateTime date
  {
    get
    {
      return this.dateField;
    }
    set
    {
      this.dateField = value;
    }
  }

  public string subject
  {
    get
    {
      return this.subjectField;
    }
    set
    {
      this.subjectField = value;
    }
  }
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
public partial class attachmentType
{
  private attachmentTypeMimetype mimetypeField;
  private string contentField;
  private string nameField;

  public attachmentTypeMimetype mimetype
  {
    get
    {
      return this.mimetypeField;
    }
    set
    {
      this.mimetypeField = value;
    }
  }

  public string content
  {
    get
    {
      return this.contentField;
    }
    set
    {
      this.contentField = value;
    }
  }

  [System.Xml.Serialization.XmlAttributeAttribute()]
  public string name
  {
    get
    {
      return this.nameField;
    }
    set
    {
      this.nameField = value;
    }
  }
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:mails-schema")]
public partial class attachmentTypeMimetype
{
  private mimeTopLevelType typeField;
  private string subtypeField;

  [System.Xml.Serialization.XmlAttributeAttribute()]
  public mimeTopLevelType type
  {
    get
    {
      return this.typeField;
    }
    set
    {
      this.typeField = value;
    }
  }

  [System.Xml.Serialization.XmlAttributeAttribute()]
  public string subtype
  {
    get
    {
      return this.subtypeField;
    }
    set
    {
      this.subtypeField = value;
    }
  }
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "urn:mails-schema")]
public enum mimeTopLevelType
{
  text,
  multipart,
  application,
  message,
  image,
  audio,
  video,
}

Mamy już wszystkie części, czas więc na kodowanie. Zdecydowałem się na wykorzystanie niemal wyłącznie kontrolek DevExpressa (oprócz Ajaxa i Multiview). Kontrolkę ASPxTreeList można podpiąć bezpośrednio pod plik XML - można obejrzeć demo pod: http://demos.devexpress.com/ASPxTreeListDemos/Data/Hierarchical.aspx. Markup strony przedstawia się następująco:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Assembly="DevExpress.Web.ASPxTreeList.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
  Namespace="DevExpress.Web.ASPxTreeList" TagPrefix="dxwtl" %>
<%@ Register Assembly="DevExpress.Web.ASPxEditors.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
  Namespace="DevExpress.Web.ASPxEditors" TagPrefix="dxe" %>
<%@ Register Assembly="DevExpress.Web.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
  Namespace="DevExpress.Web.ASPxRoundPanel" TagPrefix="dxrp" %>
<%@ Register Assembly="DevExpress.Web.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
  Namespace="DevExpress.Web.ASPxPanel" TagPrefix="dxp" %>
<%@ Register Assembly="DevExpress.Web.v9.1, Version=9.1.2.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a"
  Namespace="DevExpress.Web.ASPxHiddenField" TagPrefix="dxhf" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Przeglądarka plików XML</title>
  <style type="text/css">
    .bulletedList
    {
      font: 9pt Tahoma;
      color: Black;
    }
    #lblError
    {
      font: 9pt Tahoma;
      color: Red;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager1" runat="server">
  </asp:ScriptManager>
  <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
      <br />
      <br />
      <table>
        <tr>
          <td>
            <dxe:ASPxLabel ID="lblAvailableFiles" runat="server" Text="Dostępne pliki XML:">
            </dxe:ASPxLabel>
          </td>
          <td>
            <dxe:ASPxComboBox ID="cmbAvailableFilesList" runat="server" EnableIncrementalFiltering="True"
              AutoPostBack="True" OnSelectedIndexChanged="cmbAvailableFilesList_SelectedIndexChanged"
              ValueType="System.String">
            </dxe:ASPxComboBox>
          </td>
          <td>
            <dxe:ASPxCheckBox ID="chkShowContent" runat="server" Text="Razem z zawartością" AutoPostBack="True"
              OnCheckedChanged="chkShowContent_CheckedChanged">
              <ClientSideEvents CheckedChanged="function(s, e) {
                  var isValid = hiddenFieldIsValidClient.Get(&quot;isValid&quot;);
                if(isValid == false)
                {
                  alert('Plik XML nie jest poprawny');
                  s.SetChecked(!s.GetChecked()); /* najprościej i najlepiej byłoby zablokować kontrolkę, ale chcę pokazać możliwości kontrolek DevExpressa, stąd takie dziwaczne konstrukcje :) */
                  e.processOnServer = false;
                } }" />
            </dxe:ASPxCheckBox>
          </td>
          <td>
            <dxe:ASPxLabel ID="lblError" runat="server" EnableViewState="false">
            </dxe:ASPxLabel>
          </td>
        </tr>
      </table>
      <asp:MultiView ID="multiViewTreeErrors" runat="server">
        <asp:View ID="viewTree" runat="server">
          <!-- KONIECZNE WYŁĄCZENIE DataCacheMode - inaczej 'Object reference not set to an instance of an object.' -->
          <dxwtl:ASPxTreeList ID="treeLstXmlTree" runat="server" AutoGenerateColumns="False"
            ClientInstanceName="treeLstXmlTreeClient" Width="100%" 
            OnCustomDataCallback="treeLstXmlTree_CustomDataCallback" 
            DataCacheMode="Disabled">
            <Styles>
              <FocusedNode BackColor="#99CCFF">
              </FocusedNode>
            </Styles>
            <Settings ShowColumnHeaders="False" />
            <SettingsBehavior AutoExpandAllNodes="True" AllowFocusedNode="True" ProcessFocusedNodeChangedOnServer="False" />
            <Columns>
              <dxwtl:TreeListTextColumn FieldName="value">
              </dxwtl:TreeListTextColumn>
            </Columns>
            <ClientSideEvents CustomDataCallback="function(s, e) { document.getElementById('contentText').innerHTML = e.result; }"
              FocusedNodeChanged="function(s, e) { 
                 var key = treeLstXmlTreeClient.GetFocusedNodeKey();
                 treeLstXmlTreeClient.PerformCustomDataCallback(key); }" />
          </dxwtl:ASPxTreeList>
          <dxrp:ASPxRoundPanel ID="rndPnlContent" runat="server" HeaderText="Zawartość" Width="100%">
            <PanelCollection>
              <dxp:PanelContent runat="server">
                <span id="contentText">
                  <asp:Literal ID="ltrContent" runat="server" Text="(nie wybrano)"></asp:Literal></span>
              </dxp:PanelContent>
            </PanelCollection>
          </dxrp:ASPxRoundPanel>
        </asp:View>
        <asp:View ID="viewErrors" runat="server">
          <asp:BulletedList ID="bulletedListValidationErrors" runat="server" BulletStyle="Numbered"
            CssClass="bulletedList">
          </asp:BulletedList>
        </asp:View>
      </asp:MultiView>
      <dxhf:ASPxHiddenField ID="hiddenFieldIsValid" ClientInstanceName="hiddenFieldIsValidClient"
        runat="server">
      </dxhf:ASPxHiddenField>
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>
</html>

Na co warto zwrócić uwagę:

  • ASPxComboBox pozwala na inkrementacyjne wyszukiwanie i tak jak standardowa kontrolka wykonuje postback do serwera przy zmianie wybranej opcji,
  • dziwna konstrukcja w ASPxCheckBox pokazuje jak wykorzystać ASPxHiddenField jako most pomiędzy serwerem a klientem i na odwrót,
  • wykorzystałem standardowe kontroli: MultiView (uwalnia od żonglowania oddzielnymi panelami) i BulletedList (nie ma potrzeby konstruowania listy i przypisywania jej np. do kontrolki Literal),
  • w ASPxTreeList zastosowałem mechanizm callback do pobierania wartości tagu (dzięki temu nie trzeba wykonywać “pełnego” postbacku strony). Podczas wdrożenia “produkcyjnego” napotkałem na błąd wyskakujący przy wybraniu węzła: 'Object reference not set to an instance of an object.’, który co ciekawe nie występował lokalnie. Po wykorzystaniu ELMAH znalazłem przyczynę - błąd serializacji z TypeConverterem w tle. Sprawdziłem jeszcze raz lokalnie i wszystko działało. Po spytaniu Google okazało się, że podobne błędy występują m.in. z ASPxGridView - wystarczyło wyłączyć cache’owanie danych (EnableRowsCache, u mnie DataCacheMode) i pomogło :)

Code-behind:

using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using DevExpress.Web.ASPxTreeList;
using System.Collections.Specialized;

public partial class _Default : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
    if (!this.IsPostBack)
    {
      DirectoryInfo dirInfo = new DirectoryInfo(this.Server.MapPath("~/App_Data/"));

      foreach (FileInfo fi in dirInfo.GetFiles("*.xml"))
      {
        cmbAvailableFilesList.Items.Add(fi.Name, fi.Name); // ew.: cmbMailsList.Items.Add(fi.Name, fi.FullName); całego obiektu fi nie można przechowywać
      }

      if (cmbAvailableFilesList.Items.Count > 0)
      {
        cmbAvailableFilesList.SelectedIndex = 0;
        validateAndShowSelectedFile();
      }
    }
    else
    {
      if (ViewState["mails"] != null)
      {
        generateTreeView((mailsType)ViewState["mails"]);
      }
    }
  }
  
  private void validateAndShowSelectedFile()
  {
    XmlReader xmlRdrSchema = null, xmlRdrFile = null;
    FileStream fs = null;

    try
    {
      string physicalPath = this.Server.MapPath("~/App_Data/");

      xmlRdrSchema = XmlReader.Create(physicalPath + "mails.xsd");
      XmlSchemaSet xmlSchemaSet = new XmlSchemaSet();
      xmlSchemaSet.Add("urn:mails-schema", xmlRdrSchema);

      XmlReaderSettings xmlRdrSettings = new XmlReaderSettings();
      xmlRdrSettings.ValidationType = ValidationType.Schema;
      xmlRdrSettings.Schemas = xmlSchemaSet;
      xmlRdrSettings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);

      bulletedListValidationErrors.Items.Clear();
      xmlRdrFile = XmlReader.Create(physicalPath + cmbAvailableFilesList.SelectedItem.Value, xmlRdrSettings);
      while (xmlRdrFile.Read()) // wymuszenie walidacji
        ;

      if (bulletedListValidationErrors.Items.Count > 0)
      {
        multiViewTreeErrors.ActiveViewIndex = 1;
        ViewState["mails"] = null;
        hiddenFieldIsValid["isValid"] = false;
      }
      else
      {
        multiViewTreeErrors.ActiveViewIndex = 0;

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(mailsType));
        // nie mogę ponownie wykorzystać XmlReader - provides fast, non-cached, forward-only access to XML data
        fs = new FileStream(physicalPath + cmbAvailableFilesList.SelectedItem.Value, FileMode.Open, FileAccess.Read);
        mailsType mails = (mailsType)xmlSerializer.Deserialize(fs);

        generateTreeView(mails);
        ViewState["mails"] = mails; // w profesjonalnych zastosowaniach korzystaj z prefixów, żeby nie nadpisać przez nieuwagę innych wartości; tutaj następuje proces serializacji
        hiddenFieldIsValid["isValid"] = true;
      }
    }
    catch (XmlException ex)
    {
      lblError.Text = "Wystąpił błąd: " + ex.Message;
    }
    catch (Exception ex)
    {
      lblError.Text = "Wystąpił błąd: " + ex.GetType().ToString();
    }
    finally
    {
      if (xmlRdrSchema != null)
      {
        xmlRdrSchema.Close();
      }
      if (xmlRdrFile != null)
      {
        xmlRdrFile.Close();
      }
      if (fs != null)
      {
        fs.Close();
      }
    }
  }

  private void ValidationCallBack(object sender, ValidationEventArgs e)
  {
    bulletedListValidationErrors.Items.Add(e.Message);
  }

  private void generateTreeView(mailsType mails)
  {
    treeLstXmlTree.ClearNodes();
    for (int i = 0; i < mails.mail.Length; i++)
    {
      NameValueCollection attrs = new NameValueCollection();
      attrs.Add("ID", mails.mail[i].ID);
      TreeListNode mail = createNode(new nodeType("mail", attrs, null), null);

      TreeListNode envelope = createNode(new nodeType("envelope", null, null), mail);
      createNode(new nodeType("from", null, mails.mail[i].envelope.from), envelope);
      createNode(new nodeType("to", null, mails.mail[i].envelope.to), envelope);
      createNode(new nodeType("date", null, mails.mail[i].envelope.date.ToString()), envelope);
      createNode(new nodeType("subject", null, mails.mail[i].envelope.subject), envelope);

      createNode(new nodeType("body", null, mails.mail[i].body), mail);

      if (mails.mail[i].attachment != null)
      {
        for (int j = 0; j < mails.mail[i].attachment.Length; j++)
        {
          attrs = new NameValueCollection();
          attrs.Add("name", mails.mail[i].attachment[j].name);
          TreeListNode attachment = createNode(new nodeType("attachment", attrs, null), mail);
          attrs = new NameValueCollection();
          attrs.Add("type", mails.mail[i].attachment[j].mimetype.type.ToString());
          attrs.Add("subtype", mails.mail[i].attachment[j].mimetype.subtype);
          createNode(new nodeType("mimetype", attrs, null), attachment);
          createNode(new nodeType("content", null, mails.mail[i].attachment[j].content), attachment);
        }
      }
    }
  }

  #region helper members

  private int keySeq = 0;
  private TreeListNode createNode(nodeType node, TreeListNode parentNode)
  {
    TreeListNode treeLstNode = treeLstXmlTree.AppendNode(keySeq++, parentNode);
    treeLstNode["value"] = node;    

    return treeLstNode;
  }

  [Serializable()] // ponieważ między postbackami przechowuję drzewo w ViewState
  private class nodeType
  {
    private string nodeTag, nodeContent;
    private NameValueCollection nodeAttributes;
    internal static bool isChecked = false; // nie może być protected

    public nodeType(string nodeTag, NameValueCollection nodeAttributes, string nodeContent)
    {
      this.nodeTag = nodeTag;
      this.nodeAttributes = nodeAttributes;
      this.nodeContent = nodeContent;
    }

    public string NodeContent
    {
      get
      {
        return nodeContent;
      }
    }

    public override string ToString()
    {
      string attrsConcat = String.Empty;
      if (nodeAttributes != null)
      {
        for (int i = 0; i < nodeAttributes.Count; i++)
        {
          attrsConcat += nodeAttributes.Keys[i] + "=" + nodeAttributes[i];
          if (i < nodeAttributes.Count - 1)
          {
            attrsConcat += ", ";
          }
        }
      }

      if (attrsConcat == String.Empty)
      {
        return nodeTag + ((isChecked == true && nodeContent != null) ? (" = " + nodeContent) : "");
      }
      else
      {
        return nodeTag + " (" + attrsConcat + ")" + ((isChecked == true && nodeContent != null) ? (" = " + nodeContent) : "");
      }
    }
  }

  #endregion  

  protected void cmbAvailableFilesList_SelectedIndexChanged(object sender, EventArgs e)
  {
    validateAndShowSelectedFile();
  }

  protected void chkShowContent_CheckedChanged(object sender, EventArgs e)
  {
    nodeType.isChecked = chkShowContent.Checked;
  }

  protected void treeLstXmlTree_CustomDataCallback(object sender, TreeListCustomDataCallbackEventArgs e)
  {
    string key = e.Argument.ToString();
    TreeListNode node = treeLstXmlTree.FindNodeByKeyValue(key);
    string result = ((nodeType)node.GetValue("value")).NodeContent;
    e.Result = (result == null ? "(brak)" : result);
  }
}

Proces deserializacji ogranicza się do zaznaczonych 3 linijek. Oczywiście dokument można modyfikować i w równie łatwy sposób serializować z powrotem do XML-a. Działającą stronę można zobaczyć tutaj.

Tagi: , ,

Studia

Komentarze

Dodaj komentarz





  • Komentarz
  • Podgląd
Loading



Zmodyfikowany BlogEngine.NET (Bazowa szata graficzna: Mads Kristensen/Zdjęcia panoramiczne)
Hosting dzięki uprzejmości PCSS/Centrum Innowacji Microsoft
(c) Mateusz Chodyła (Panel logowania)