نحوه تبدیل XML در NET Core.
سه شنبه 11 شهریور 1399قبلا در مورد نحوه تبدیل JSON در NET Core. صحبت کردیم. بیشتر آن را به این دلیل نوشتیم که فکر میکردیم افراد برای کار با JSON خود را به سختی میاندازند.
ما فکر میکنیم کار با XML کمی متفاوت است. هنوز کتابخانه استاندارد خوبی برای کار با XML وجود ندارد.
برخلاف JSON که دارای کتابخانه JSON.NET که برای مدیریت هر چیزی است، بیشتر موارد وقتی با XML کار میکنید، از یکی از Parserهای داخلی XML در فریمورک NET Core. استفاده خواهید کرد. اینها ممکن است کمی ناامیدکننده باشند. برخی از این موارد این است که آنها در اوایل ساخت NET. ایجاد شدهاند و به همین دلیل، همیشه باید با موارد قبلی سازگار باشند بنابراین شما در چیزهایی مثل Generics شکست میخورید. مورد دیگر این است که مشخصات واقعی XML که شامل مواردی مانند فضای نامها (namespace) و DTDها میشود، گرچه در ابتدا ساده به نظر میرسد، اما میتواند فوقالعاده خشن باشد. منظور ما از خشن این است که اگر فقط یک قطعه از پازل را از دست بدهید، همه چیز به سادگی کار نخواهد کرد و ممکن است ساعتها طول بکشد تا ببینید چه چیزی اشتباه است.
به هر حال، بیاید سریع برویم و گزینههای مربوط به کار با XML در NET. را بررسی کنیم.
مثالی برای فایل XML
ما میخواهیم از یک فایل XML خیلی ساده استفاده کنیم که دارای المنت، پراپرتی اتربیوت و یک لیست است. ما هنگام بررسی گزینهها از این موارد استفاده میکنیم، بنابراین همیشه سعی میکنیم فایل مشابهای را بخوانیم.
<?xml version="1.0" encoding="utf-8" ?> <MyDocument xmlns="http://www.dotnetcoretutorials.com/namespace"> <MyProperty>Abc</MyProperty> <MyAttributeProperty value="123" /> <MyList> <MyListItem>1</MyListItem> <MyListItem>2</MyListItem> <MyListItem>3</MyListItem> </MyList> </MyDocument>
استفاده از XMLReader
خوب اولین گزینهای که داریم استفاده از کلاس " XMLReader" است. این فقط یک XML Parser رو به جلو است (منظور ما این است که فایل را تقریبا خط به خط میخواند). این گزینه خیلی ابتدایی است. مثلا کد شما ممکن است خیلی شبیه به این باشد:
XmlReaderSettings settings = new XmlReaderSettings(); settings.IgnoreWhitespace = true; using (var fileStream = File.OpenText("test.xml")) using(XmlReader reader = XmlReader.Create(fileStream, settings)) { while(reader.Read()) { switch(reader.NodeType) { case XmlNodeType.Element: Console.WriteLine($"Start Element: {reader.Name}. Has Attributes? : {reader.HasAttributes}"); break; case XmlNodeType.Text: Console.WriteLine($"Inner Text: {reader.Value}"); break; case XmlNodeType.EndElement: Console.WriteLine($"End Element: {reader.Name}"); break; default: Console.WriteLine($"Unknown: {reader.NodeType}"); break; } } }
با خروجی مانند این:
Unknown: XmlDeclaration Start Element: MyDocument. Has Attributes? : True Start Element: MyProperty. Has Attributes? : False Inner Text: Abc End Element: MyProperty Start Element: MyAttributePropety. Has Attributes? : True Start Element: MyList. Has Attributes? : False Start Element: MyListItem. Has Attributes? : False Inner Text: 1 End Element: MyListItem Start Element: MyListItem. Has Attributes? : False Inner Text: 2 End Element: MyListItem Start Element: MyListItem. Has Attributes? : False Inner Text: 3 End Element: MyListItem End Element: MyList End Element: MyDocument
این مورد یادآور استفاده از ADO.NET و خواندن سطر به سطر دادهها و تلاش برای ذخیره کردن آن در یک آبجکت است. ایده کلی به این دلیل است که به صورت خط به خط تبدیل را انجام میدهید، و حافظه کمتری دارد. اما شما همچنین باید هر خط را به صورت جداگانه با هر تعداد جایگشت از عناصر، اتربیوتها، لیستها و غیره مدیریت کنید. ما فکر میکنیم تنها دلیل استفاده از این متد میتواند در صورتی باشد که شما فایلهای خیلی بزرگ XML را دارید (بیشتر از 100 مگابایت) یا به دنبال موارد بسیار خاص هستید. مثلا فقط میخواهید یک عنصر را از فایل بخوانید، و وقتی به دنبال آن یک عنصر هستید، نمیخواهید همه چیز را لود کنید.
نکته دیگری که میخواهیم به آن اشاره کنم این است که فضای نامهای XML و مشکل پیرامون آنها همراه با XMLReader نیستند.
استفاده از XPathDocument/XPathNavigator
خوب روش دیگر دریافت گرههای (Node) منحصربهفرد XML که میتواند یک سند را جستجو کند، استفاده از آبجکت XPathNavigator است.
ابتدا، کد را ببینیم:
using (var fileStream = File.Open("test.xml", FileMode.Open)) { //Load the file and create a navigator object. XPathDocument xPath = new XPathDocument(fileStream); var navigator = xPath.CreateNavigator(); //Compile the query with a namespace prefix. XPathExpression query = navigator.Compile("ns:MyDocument/ns:MyProperty"); //Do some BS to get the default namespace to actually be called ns. var nameSpace = new XmlNamespaceManager(navigator.NameTable); nameSpace.AddNamespace("ns", "http://www.dotnetcoretutorials.com/namespace"); query.SetContext(nameSpace); Console.WriteLine("My Property Value : " + navigator.SelectSingleNode(query).Value); }
اگر بخواهیم صادقانه بگوییم این کد خوبی نیست، و به یک دلیل میتوانیم بگوییم که بد است. فضای نامها در اینجا واقعا باعث زحمت میشوند. در مورد خاص ما، از آنجایی که ما یک فضای نام پیشفرض (default namespace) داریم، این تنها راهی بود که میتوانستیم با XPath کار کنیم. بدون فضای نام کارها واقعا به سهولت انجام میشوند. میتوانیم بگوییم در هنگام کار با XML هر دردسری که تا به حال داشتهایم به خاطر فضای نامها بوده است.
بنابراین اجازه دهید موردی را به شما بگویم. اگر سندی که با آن کار میکنید از فضای نامها استفاده نمیکند (یا مایل به حذف آنها هستید)، و باید از اکسپشن XPath برای دریافت یک گره استفاده کنید، پس استفاده از XMLNavigator در واقع گزینه بدی نیست.
استفاده از XMLDocument
XMLDocument میتواند مانند به روز رسانی XPathNavigator رفتار کند. این چند متد سادهتر برای لود کردن اسناد دارد، و به شما امکان میدهد XMLDocuments را در حافظه نیز اصلاح کنید.
XmlDocument document = new XmlDocument(); document.Load("test.xml"); XmlNamespaceManager m = new XmlNamespaceManager(document.NameTable); m.AddNamespace("ns", "http://www.dotnetcoretutorials.com/namespace"); Console.WriteLine(document.SelectSingleNode("ns:MyDocument/ns:MyProperty", m).InnerText);
به طور کلی شما هنوز هم باید با برخی فضای نامها سر و کار داشته باشید (مثلا Default Namespaces به خوبی مدیریت نمیشود)، و شما هنوز هم باید هر عنصر را طبق نیاز خود یک به یک دریافت کنید. اما اگر میخواهید فقط یک زیر مجموعه کوچک از XML را لود کنید، این گزینه خوبی است. این واقعیت که میتوانید XML را تغییر دهید و آن را در فایل ذخیره کنید نیز یک مورد خوب است.
استفاده از XMLSerializer
به نظر ما XMLSerializer بهترین گزینه برای تبدیل XML در NET Core. است. اگر قبلا از JSONDocument از JSON.NET استفاده کردهاید، راهاندازی آن خیلی شبیه است.
ابتدا به سادگی یک کلاس ایجاد میکنیم که از فایل XML شکل گرفته است. ما از مجموعهای از اتربیوتها برای مشخص کردن نحوه خواندن سند، اینکه از کدام فضای نام استفاده میکنیم، حتی اینکه کدام نوع عنصر را برای deserialize امتحان میکنیم (اتربیوت، عنصر یا آرایه)، استفاده میکنیم.
[XmlRoot("MyDocument", Namespace = "http://www.dotnetcoretutorials.com/namespace")] public class MyDocument { public string MyProperty { get; set; } public MyAttributeProperty MyAttributeProperty { get; set; } [XmlArray] [XmlArrayItem(ElementName = "MyListItem")] public List MyList { get; set; } } public class MyAttributeProperty { [XmlAttribute("value")] public int Value { get; set; } }
واقعا ساده است. و سپس XML را خوانده و آن را به این کلاس تبدیل میکنیم:
using (var fileStream = File.Open("test.xml", FileMode.Open)) { XmlSerializer serializer = new XmlSerializer(typeof(MyDocument)); var myDocument = (MyDocument)serializer.Deserialize(fileStream); Console.WriteLine($"My Property : {myDocument.MyProperty}"); Console.WriteLine($"My Attribute : {myDocument.MyAttributeProperty.Value}"); foreach(var item in myDocument.MyList) { Console.WriteLine(item); } }
این مورد بدون تلاش برای دریافت فضای نام درست، بدون تلاش برای درست کار کردن Xpath، کار میکند. احتمالا وقتی شروع به استفاده از XMLSerializer میکنید، تعجب خواهید کرد که چرا تا به حال خود را به زحمت انداخته و سعی کردهاید تا دوباره به صورت دستی مستندات XML را بخوانید.
اکنون یک اخطار بزرگ وجود دارد. اگر واقعا به بخش بزرگی از مستندات توجه نمیکنید و فقط سعی میکنید تا یک عنصر عمیق را دریافت کنید، ایجاد این کلاسها و مدلهای بزرگ فقط برای دریافت یک عنصر میتواند شما را به زحمت زیادی بیاندازد.
با این حال، در 99% موارد، از XMLSerializer برای تبدیل XML استفاده کنید. نسبت به گزینههای دیگر کمتر شکننده است و الگوی بسیار مشابهای را برای JSON serialization دنبال میکند که به این معناست که هر کسی که با یکی از اینها کار کرده باشد، میتواند با دیگری نیز کار کند.
- برنامه نویسان
- 1k بازدید
- 3 تشکر