J'ai une classe simple qui contient essentiellement quelques valeurs. J'ai remplacé la méthode ToString()
pour renvoyer une représentation de chaîne Nice.
Maintenant, je veux créer une méthode ToXml()
, qui retournera quelque chose comme ceci:
<Song>
<Artist>Bla</Artist>
<Title>Foo</Title>
</Song>
Bien sûr, je pourrais simplement utiliser un StringBuilder
ici, mais je voudrais retourner un XmlNode
ou XmlElement
, à utiliser avec XmlDocument.AppendChild
.
Je ne semble pas pouvoir créer un XmlElement
autre que d'appeler XmlDocument.CreateElement
, donc je me demande si j'ai juste oublié quelque chose, ou si je dois vraiment passer un XmlDocument
ou ref XmlElement
avec lequel travailler, ou la fonction renvoie-t-elle une chaîne contenant le XML que je veux?
Vous voudrez peut-être voir comment vous pouvez utiliser les fonctionnalités intégrées de .NET pour sérialiser et désérialiser un objet en XML, plutôt que de créer une méthode ToXML()
sur chaque classe qui est essentiellement juste un objet de transfert de données .
J'ai utilisé ces techniques avec succès sur quelques projets, mais je n'ai pas les détails de mise en œuvre à portée de main pour le moment. J'essaierai de mettre à jour ma réponse avec mes propres exemples un peu plus tard.
Voici quelques exemples que Google a renvoyés:
Sérialisation XML dans .NET par Venkat Subramaniam http://www.agiledeveloper.com/articles/XMLSerialization.pdf
Comment sérialiser et désérialiser un objet en XML http://www.dotnetfunda.com/articles/article98.aspx
Personnalisez la sérialisation XML de votre objet .NET avec les attributs XML .NET http://blogs.Microsoft.co.il/blogs/rotemb/archive/2008/07/27/customize-your-net-object-xml- serialization-with-net-xml-attributes.aspx
Je recommanderais d'utiliser XDoc et XElement de System.Xml.Linq au lieu de XmlDocument. Ce serait mieux et vous pourrez utiliser la puissance LINQ pour interroger et analyser votre XML:
En utilisant XElement, votre méthode ToXml () ressemblera à ceci:
public XElement ToXml()
{
XElement element = new XElement("Song",
new XElement("Artist", "bla"),
new XElement("Title", "Foo"));
return element;
}
De W3C Document Object Model (Core) Level 1 spécification (gras est le mien):
La plupart des API définies par cette spécification sont des interfaces plutôt que des classes. Cela signifie qu'une implémentation réelle n'a besoin que d'exposer des méthodes avec les noms définis et l'opération spécifiée, et non d'implémenter réellement des classes qui correspondent directement aux interfaces. Cela permet aux API DOM d'être implémentées en tant que placage fin au-dessus des applications héritées avec leurs propres structures de données, ou au-dessus des applications plus récentes avec des hiérarchies de classes différentes. Cela signifie également que les constructeurs ordinaires (au sens Java ou C++) ne peuvent pas être utilisés pour créer des objets DOM, car les objets sous-jacents à construire peuvent avoir peu relation avec les interfaces DOM . La solution conventionnelle à cela dans la conception orientée objet est de définir des méthodes d'usine qui créent des instances d'objets qui implémentent les différentes interfaces. Dans le DOM niveau 1, les objets implémentant certains l'interface "X" est créée par une méthode "createX ()" sur l'interface Document; car tous les objets DOM vivent dans le contexte d'un Document spécifique .
AFAIK, vous ne pouvez pas créer de XmlNode
(XmlElement, XmlAttribute, XmlCDataSection
, Etc) sauf XmlDocument
à partir d'un constructeur.
De plus, notez que vous ne pouvez pas utiliser XmlDocument.AppendChild()
pour les nœuds qui ne sont pas créés via les méthodes d'usine du même document. Si vous avez un nœud d'un autre document, vous devez utiliser XmlDocument.ImportNode()
.
Les XmlNodes sont fournis avec une propriété OwnerDocument.
Vous pouvez peut-être simplement faire:
//Node is an XmlNode pulled from an XmlDocument
XmlElement e = node.OwnerDocument.CreateElement("MyNewElement");
e.InnerText = "Some value";
node.AppendChild(e);
Créez un nouveau XmlDocument avec le contenu souhaité, puis importez-le dans votre document existant, en accédant à la propriété OwnerDocument de vos nœuds existants:
XmlNode existing_node; // of some document, where we don't know necessarily know the XmlDocument...
XmlDocument temp = new XmlDocument();
temp.LoadXml("<new><elements/></new>");
XmlNode new_node = existing_node.OwnerDocument.ImportNode(temp.DocumentElement, true);
existing_node.AppendChild(new_node);
Bonne chance.
Vous pouvez renvoyer un XmlDocument
pour la méthode ToXML
dans votre classe, puis lorsque vous allez ajouter l'élément avec le document de résultat, utilisez simplement quelque chose comme:
XmlDocument returnedDocument = Your_Class.ToXML();
XmlDocument finalDocument = new XmlDocument();
XmlElement createdElement = finalDocument.CreateElement("Desired_Element_Name");
createdElement.InnerXML = docResult.InnerXML;
finalDocument.AppendChild(createdElement);
De cette façon, la valeur entière de "Desired_Element_Name" sur votre résultat XmlDocument sera le contenu entier du document retourné.
J'espère que ça aide.
Pourquoi ne pas envisager de créer votre (vos) classe (s) de données comme un simple sous-classe XmlDocument, alors vous obtenez tout cela gratuitement. Vous n'avez pas du tout besoin de sérialiser ou de créer des nœuds hors document, et vous obtenez la structure que vous souhaitez.
Si vous voulez le rendre plus sophistiqué, écrivez une classe de base qui est une sous-classe de XmlDocument, puis donnez-lui des accesseurs de base et vous êtes prêt.
Voici un type générique que j'ai mis en place pour un projet ...
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
namespace FWFWLib {
public abstract class ContainerDoc : XmlDocument {
protected XmlElement root = null;
protected const string XPATH_BASE = "/$DATA_TYPE$";
protected const string XPATH_SINGLE_FIELD = "/$DATA_TYPE$/$FIELD_NAME$";
protected const string DOC_DATE_FORMAT = "yyyyMMdd";
protected const string DOC_TIME_FORMAT = "HHmmssfff";
protected const string DOC_DATE_TIME_FORMAT = DOC_DATE_FORMAT + DOC_TIME_FORMAT;
protected readonly string datatypeName = "containerDoc";
protected readonly string execid = System.Guid.NewGuid().ToString().Replace( "-", "" );
#region startup and teardown
public ContainerDoc( string execid, string datatypeName ) {
root = this.DocumentElement;
this.datatypeName = datatypeName;
this.execid = execid;
if( null == datatypeName || "" == datatypeName.Trim() ) {
throw new InvalidDataException( "Data type name can not be blank" );
}
Init();
}
public ContainerDoc( string datatypeName ) {
root = this.DocumentElement;
this.datatypeName = datatypeName;
if( null == datatypeName || "" == datatypeName.Trim() ) {
throw new InvalidDataException( "Data type name can not be blank" );
}
Init();
}
private ContainerDoc() { /*...*/ }
protected virtual void Init() {
string basexpath = XPATH_BASE.Replace( "$DATA_TYPE$", datatypeName );
root = (XmlElement)this.SelectSingleNode( basexpath );
if( null == root ) {
root = this.CreateElement( datatypeName );
this.AppendChild( root );
}
SetFieldValue( "createdate", DateTime.Now.ToString( DOC_DATE_FORMAT ) );
SetFieldValue( "createtime", DateTime.Now.ToString( DOC_TIME_FORMAT ) );
}
#endregion
#region setting/getting data fields
public virtual void SetFieldValue( string fieldname, object val ) {
if( null == fieldname || "" == fieldname.Trim() ) {
return;
}
fieldname = fieldname.Replace( " ", "_" ).ToLower();
string xpath = XPATH_SINGLE_FIELD.Replace( "$FIELD_NAME$", fieldname ).Replace( "$DATA_TYPE$", datatypeName );
XmlNode node = this.SelectSingleNode( xpath );
if( null != node ) {
if( null != val ) {
node.InnerText = val.ToString();
}
} else {
node = this.CreateElement( fieldname );
if( null != val ) {
node.InnerText = val.ToString();
}
root.AppendChild( node );
}
}
public virtual string FieldValue( string fieldname ) {
if( null == fieldname ) {
fieldname = "";
}
fieldname = fieldname.ToLower().Trim();
string rtn = "";
XmlNode node = this.SelectSingleNode( XPATH_SINGLE_FIELD.Replace( "$FIELD_NAME$", fieldname ).Replace( "$DATA_TYPE$", datatypeName ) );
if( null != node ) {
rtn = node.InnerText;
}
return rtn.Trim();
}
public virtual string ToXml() {
return this.OuterXml;
}
public override string ToString() {
return ToXml();
}
#endregion
#region io
public void WriteTo( string filename ) {
TextWriter tw = new StreamWriter( filename );
tw.WriteLine( this.OuterXml );
tw.Close();
tw.Dispose();
}
public void WriteTo( Stream strm ) {
TextWriter tw = new StreamWriter( strm );
tw.WriteLine( this.OuterXml );
tw.Close();
tw.Dispose();
}
public void WriteTo( TextWriter writer ) {
writer.WriteLine( this.OuterXml );
}
#endregion
}
}
Vous avez besoin de Linq - System.Xml.Linq pour être précis.
Vous pouvez créer XML à l'aide de XElement à partir de zéro - cela devrait à peu près vous trier.
Une autre option consiste à passer un délégué à la méthode, ce qui créera un XmlElement. De cette façon, la méthode cible n'aura pas accès à l'intégralité de XmlDocument, mais pourra créer de nouveaux éléments.
Vous ne pouvez pas renvoyer un XmlElement
ou un XmlNode
, car ces objets existent toujours et uniquement dans le contexte d'un XmlDocument
propriétaire.
La sérialisation XML est un peu plus facile que de renvoyer un XElement
, car tout ce que vous avez à faire est de marquer les propriétés avec des attributs et le sérialiseur fait toute la génération XML pour vous. (De plus, vous obtenez la désérialisation gratuitement, en supposant que vous avez un constructeur sans paramètre et, bien, un tas d'autres choses.)
D'un autre côté, a) vous devez créer un XmlSerializer
pour le faire, b) gérer les propriétés de collection n'est pas tout à fait évident que vous aimeriez que ce soit, et c) la sérialisation XML est assez stupide; vous n'avez pas de chance si vous voulez faire quelque chose d'extraordinaire avec le XML que vous générez.
Dans de nombreux cas, ces problèmes n'ont aucune importance. Pour ma part, je préfère marquer mes propriétés avec des attributs que d'écrire une méthode.