Создание XmlNode/XmlElement в С# без XmlDocument?
У меня есть простой класс, который по существу просто содержит некоторые значения. Я переопределил метод ToString()
, чтобы вернуть красивое строковое представление.
Теперь я хочу создать метод ToXml()
, который вернет что-то вроде этого:
<Song>
<Artist>Bla</Artist>
<Title>Foo</Title>
</Song>
Конечно, я мог бы просто использовать StringBuilder
здесь, но я хотел бы вернуть XmlNode
или XmlElement
, который будет использоваться с XmlDocument.AppendChild
.
Кажется, я не могу создать XmlElement
, кроме вызова XmlDocument.CreateElement
, поэтому я задаюсь вопросом, не упустил ли я что-либо вообще, или если мне действительно нужно пройти либо XmlDocument
, либо ref XmlElement
для работы или функция возвращает строку, содержащую XML, который я хочу?
Ответы
Ответ 1
Вы можете посмотреть, как вы можете использовать встроенные функции .NET для сериализации и десериализации объекта в XML, вместо того, чтобы создавать метод ToXML()
для каждого класса, который по существу является объектом передачи данных.
Я успешно использовал эти методы в нескольких проектах, но сейчас нет данных о реализации. Я попытаюсь обновить свой ответ своими собственными примерами позже.
Вот несколько примеров, которые Google вернул:
Сериализация XML в .NET by Venkat Subramaniam http://www.agiledeveloper.com/articles/XMLSerialization.pdf
Сериализация и десериализация объекта в XML http://www.dotnetfunda.com/articles/article98.aspx
Настроить XML-сериализацию XML-объекта с атрибутами .NET XML http://blogs.microsoft.co.il/blogs/rotemb/archive/2008/07/27/customize-your-net-object-xml-serialization-with-net-xml-attributes.aspx
Ответ 2
Я бы рекомендовал использовать XDoc и XElement of System.Xml.Linq вместо XmlDocument. Это было бы лучше, и вы сможете использовать возможности LINQ для запросов и анализа XML:
Используя XElement, ваш метод ToXml() будет выглядеть следующим образом:
public XElement ToXml()
{
XElement element = new XElement("Song",
new XElement("Artist", "bla"),
new XElement("Title", "Foo"));
return element;
}
Ответ 3
Из W3C спецификация Document Object Model (Core) уровня 1 (жирный мой):
Большинство API, определенных этим спецификации скорее являются интерфейсами чем классы. Это означает, что фактическая реализация требует только разоблачения методы с определенными именами и указанной операции, а не на самом деле внедрять классы, которые соответствуют непосредственно к интерфейсам. Эта позволяет реализовать API DOM как тонкий шпон поверх наследия приложения с собственными данными структур или сверху новых приложения с различным классом Иерархии. Это также означает, что обычные конструкторы (в Java или С++) не могут быть использованы для создания DOM, поскольку объекты, которые должны быть построены, могут иметь малое отношение к DOM интерфейсы. Обычное решение к этому в объектно-ориентированном дизайне определить методы factory, которые создают экземпляры объектов, которые реализуют различные интерфейсы. В DOM Уровень 1, объекты, реализующие некоторые интерфейс "X" создается Метод createX() в документе интерфейс; это потому, что все DOM объекты живут в контексте конкретный документ.
AFAIK, вы не можете создать XmlNode
(XmlElement, XmlAttribute, XmlCDataSection
и т.д.), кроме XmlDocument
из конструктора.
Кроме того, обратите внимание, что вы не можете использовать XmlDocument.AppendChild()
для узлов, которые не создаются с помощью методов factory документа того же. Если у вас есть node из другого документа, вы должны использовать XmlDocument.ImportNode()
.
Ответ 4
XmlNodes поставляются с свойством OwnerDocument.
Возможно, вы можете просто сделать:
//Node is an XmlNode pulled from an XmlDocument
XmlElement e = node.OwnerDocument.CreateElement("MyNewElement");
e.InnerText = "Some value";
node.AppendChild(e);
Ответ 5
Вы можете вернуть XmlDocument
для метода ToXML
в своем классе, а затем, когда вы собираетесь добавить элемент с результирующим документом, просто используйте что-то вроде:
XmlDocument returnedDocument = Your_Class.ToXML();
XmlDocument finalDocument = new XmlDocument();
XmlElement createdElement = finalDocument.CreateElement("Desired_Element_Name");
createdElement.InnerXML = docResult.InnerXML;
finalDocument.AppendChild(createdElement);
Таким образом, все значение "Desired_Element_Name" в вашем XmlDocument результата будет полным содержимым возвращенного документа.
Надеюсь, это поможет.
Ответ 6
Создайте новый XmlDocument с нужным содержимым и затем импортируйте его в существующий документ, получив доступ к свойству OwnerDocument существующих узлов:
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);
Удачи.
Ответ 7
Вам нужно Linq - System.Xml.Linq, чтобы быть точным.
Вы можете создавать XML с помощью XElement с нуля - это в значительной степени поможет вам.
Ответ 8
Другой вариант - передать делегат методу, который создаст XmlElement. Таким образом, целевой метод не получит доступ ко всему XmlDocument, но сможет создавать новые элементы.
Ответ 9
Почему бы не подумать о создании своих классов данных как только подкласса XmlDocument, тогда вы получите все это бесплатно. Вам не нужно сериализовывать или создавать какие-либо внедомовые узлы вообще, и вы получаете нужную структуру.
Если вы хотите сделать его более сложным, напишите базовый класс, который является подклассом XmlDocument, затем дайте ему базовые аксессоры, и вы настроены.
Вот общий тип, который я собрал для проекта...
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
}
}
Ответ 10
Вы не можете вернуть XmlElement
или XmlNode
, потому что эти объекты всегда существуют и существуют только в контексте владельца XmlDocument
.
Сериализация XML немного проще, чем возвращение XElement
, потому что все, что вам нужно сделать, это отметить свойства атрибутами, а сериализатор выполняет все XML-генерации для вас. (Кроме того, вы получаете бесплатную десериализацию, предполагая, что у вас есть конструктор без параметров и, ну, куча других вещей.)
С другой стороны, a) вам нужно создать XmlSerializer
, чтобы сделать это, b) работа с свойствами коллекции не совсем понятна, как вам может показаться, и c) XML-сериализация довольно тупой; вам не повезло, если вы хотите сделать что-нибудь интересное с XML, который вы создаете.
Во многих случаях эти проблемы не имеют значения. Я бы предпочел бы отмечать мои свойства атрибутами, а не писать метод.
Ответ 11
XmlDocumnt xdoc = new XmlDocument;
XmlNode songNode = xdoc.CreateNode(XmlNodeType.Element, "Song", schema)
xdoc.AppendChild.....