PDA

View Full Version : حرفه ای: افزودن یک Node در انتهای عنصر موردنظر XML (مربوط به Linq to XML)



Saeed_m_Farid
سه شنبه 06 تیر 1391, 10:40 صبح
با سلام
پیرو صحبت هایی که با دوستان کردیم، این سوال رو با جزئیاتش مطرح میکنم:

پیش زمینه:
در یک پروژه که مدیریت کاربران با XML هست، میخوام در انتهای کاربران مربوط به یک حوزه، کاربر موردنظر رو اضافه/ویرایش کنم؛ کلمه عبور هم بصورت MD5 بعنوان attribute در اون node ذخیره میشه

مشکل:
من نمی‌تونم یک کوئری درست بنویسم که دقیقاً اون Node ای که اسمش رو دارم رو بهم بده تا من XElement موردنظرم رو بهش اضافه یا ویرایش کنم، البته یه کارهایی کردم ولی کاملاً من درآوردی هست، که در پایین بهش اشاره می‌کنم ...

Code Snippet
برای جزئیات بیشتر فرض کنید ساختار XML من یه اینصورت هست:
<?xml version="1.0" encoding="utf-8"?>
<X1>
<X2>
<X3>
<!--More...!-->
<X4>
<!--More...!-->
<X5>
<X5_1>
<!--More...!-->
<X5_1>
<!--More...!-->
<X5_n>
<Element> blw-blw-blw </Element>
<Element> blw-blw-blw </Element>
<Element> blw-blw-blw </Element>
<!--More...!-->
<!-- My XElement Must add HHHHHHHEEEEEERRRRREEEEEE!-->
</X5_n>
<!--More...!-->
</X5>
<!--More...!-->
</X4>
<!--More...!-->
</X3>
</X2>
</X1>

که البته واقعیش هم میشه فایل زیر:
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<OurCompany>
<Settings>
<!--More...!-->
<Regions>
<!--More...!-->
<Cities>
<Abeshahmad>
<XMLRPCUser encrypt="5a105e8b9d40e1329780d62ea2265d8a">user1</XMLRPCUser>
<!--More...!-->
<XMLRPCUser encrypt="5a105e8b9d40e1329780d62ea2265d8a">user_n</XMLRPCUser>
<!-- My user Must add HHHHHHHEEEEEERRRRREEEEEE!-->
</City>
<!--More...!-->
</Cities>
<!--More...!-->
</Regions>
<!--More...!-->
</Settings>
</OurCompany>
</Configuration>
همونطورکه می‌بینید من میخوام بجای <!-- My XElement Must add HHHHHHHEEEEEERRRRREEEEEE!--> یک المنت (XElement) اضافه کنم و درصورت موجود بودن تعییرش بدم؛ کاری که کردم این بوده که مجبور شدم یه تابع بنویسم GetSpecifiedNode که بره و XElement موردنظر من رو پیدا کنه و برگردونه (و اگه نباشه ایجاد کنه)، ولی اولاً خیلی احمقانه بنظر میرسه‌(پس حتماً راه بهتری هست) و مهمتر اینکه کاملاً هاردکد هست و اصلاً انعطاف پذیری نداره:

/// <summary>
/// Opening XML configuration file[1] and assign to a XElement
/// instance refereced as root, then find Users child node
/// </summary>
/// <param name="file">
/// Setting file path, default is "Configs.config"
/// </param>
/// <param name="root">
/// Called by reference XElement root node of Xml to assign
/// </param>
/// <returns>
/// Hierarchial Users child node in Xml Structure
/// </returns>
/// <remarks>
/// Note: Created a new xml document if one isn't found,
/// </remarks>
private static XElement GetSpecifiedNode(string file, ref XElement root, string node)
{
try
{
XElement child;

// Configuration setting XML hierarchy nodes
string[] nodes =
new string[] {
"Configuration",
"OurCompany",
"Settings",
"Regions",
"Cities",
node
};

// Root node assignment
if (File.Exists(file))
root = XElement.Load(file);
else
root = new XElement(nodes[0],
new XElement(nodes[1],
new XElement(nodes[2],
new XElement(nodes[3],
new XElement(nodes[4],
new XElement(nodes[5]))));

// Checking if our specified node exist or not?
if (root.Element(nodes[1]).Elements(nodes[2]).Elements(nodes[3]).Elements(nodes[4]).Elements(nodes[5]).Any())
child = root.Element(nodes[1]).Elements(nodes[2]).Elements(nodes[3]).Elements(nodes[4]).Elements(nodes[5]).Last();
else
{
if (!root.Elements(nodes[1]).Elements(nodes[2]).Elements(nodes[3]).Elements(nodes[4]).Any())
root.Add(
new XElement(nodes[0],
new XElement(nodes[1],
new XElement(nodes[2],
new XElement(nodes[3],
new XElement(nodes[4]))));
root.Element(nodes[1]).Element(nodes[2]).Elements(nodes[3]).Elements(nodes[4]).Add(new XElement(nodes[5]));
child = root.Elements(nodes[1]).Elements(nodes[2]).Elements(nodes[3]).Elements(nodes[4]).Elements(nodes[5]).Last();
}

return child;
}
catch (Exception ex)
{
Tracer.LogError(
ex.Message,
"Settings",
(new System.Diagnostics.StackFrame()).GetMethod().Name) ;
throw;
}
}


برای ویرایش هم مشکل رو با یک Extension method بصورت زیر حل کردم‌:

/// <summary>
/// Replace the contents of a node in an XElement hierarchy
/// when the element name and all the attribute names and
/// values match an input element.
/// (If there is no match, the new element can be added.)
/// </summary>
/// <param name="source">
/// Source root XElement to replace/add
/// </param>
/// <param name="node">
/// XElemenet to replacement or add
/// </param>
private static void ReplaceOrAdd(this XElement source, XElement node)
{
var q = from element in source.Elements()
where element.Name == node.Name
&& element.Attributes().All
(a => node.Attributes().Any
(b => a.Name == b.Name && a.Value == b.Value))
select element;

var n = q.LastOrDefault();

if (n == null) source.Add(node);
else n.ReplaceWith(node);
}


با این تفاسیر، تابع افزودن کاربر هم به اینصورت شد:

/// <summary>
/// Add a new authentication entry as username & password if not exist,
/// otherwise replace md5 password.
/// </summary>
/// <param name="username">
/// String value represent username
/// </param>
/// <param name="md5Password">
/// Hashed with MD5 provider password to save and chack later
/// </param>
/// <param name="file">
/// Setting file path, if leave as empty default is "Configs.config"
/// </param>
/// <param name="city">
/// City name that specified User belong it
/// </param>
public static void AddUserInfo(string username,
string pass,
string file = "",
string city = "")
{
try
{
file = (file == String.Empty) ? XmlConfigPath : file;

XElement root = new XElement("Initial"),
child = GetSpecifiedNode(file, ref root, "Users");

// Add user authentication info to specified node
city = (city == String.Empty) ? "Deaulet" : city;
child.ReplaceOrAdd(new XElement("XMLRPCUser", username,
new XAttribute("encrypt", Security.GetMd5Hash(pass))));

// Save change(s) to file
root.Save(file);
}
catch (Exception ex)
{
Tracer.LogError(
ex.Message,
"Settings",
(new System.Diagnostics.StackFrame()).GetMethod().Name) ;
throw;
}
}


جمع بندی:
بی‌زحمت یه حجم کد نگاه نکنید، من چون راهی نداشتم اینهمه خزعبلات سرهم بندی کردم! ولی ممکنه مثلاً تمام کد GetSpecifiedNode با یه خط کد حل بشه، من اون رو میخوام اگه اطلاع داشته باشید.

تشکر و قدردانی:
از تمام کسانی که این پست رو می‌خونن، دلداری میدن، از خدا برام شفا میخوان، راه حلی به ذهنشون میرسه، فحش نمیدن و یا خدای نکرده جایگزینی براش دارن پیشاپیش کمال تقدیر و تشکر بعمل میاد.

والسلام، نامه تمام.

tooraj_azizi_1035
سه شنبه 06 تیر 1391, 13:22 عصر
سلام

How to: Find an Element with a Specific Attribute (http://msdn.microsoft.com/en-us/library/bb387041.aspx)

http://csharpfeeds.com/post/8346/LINQ_To_XML_changing_an_existing_XML_tree.aspx

How to: Find an Element with a Specific Child Element (http://msdn.microsoft.com/en-us/library/bb387053.aspx)

http://csharpfeeds.com/post/8346/LINQ_To_XML_changing_an_existing_XML_tree.aspx

http://stackoverflow.com/questions/331502/linq-to-xml-update-alter-the-nodes-of-an-xml-document

Replace XML with LINQ to XML in ASP.NET (http://www.xmlplease.com/replace-xml-linq)

Saeed_m_Farid
سه شنبه 06 تیر 1391, 15:49 عصر
با تشکر از شما جناب تورج خان
ولی اصلاً پست من رو خوندی؟ اینا که ربطی ندارن، من Replace (http://www.xmlplease.com/replace-xml-linq) یا alter (http://stackoverflow.com/questions/331502/linq-to-xml-update-alter-the-nodes-of-an-xml-document) رو که مشکل ندارم، Child Element (http://msdn.microsoft.com/en-us/library/bb387053.aspx) هم که پیدا میکنم،Attribute (http://msdn.microsoft.com/en-us/library/bb387041.aspx) هم میتونم پیدا کنم؛ مشکل تو پیدا کردن و جایگزینی در زیرشاخه خاصی هست که من parent هاش رو دقیقاً نمیدونم، child element یه چیز دیگه است، بازم ممنون ...

gwbasic
جمعه 09 تیر 1391, 10:50 صبح
سلام
ببخشید که دیر جواب دادم!
من تصورم اینه که شما با این روش hard code کردن مشکل دارین و چون type safe نیست چندان دل چسب نیست و نگهداری اون در آینده سخت تر خواهد بود. نمی دونم امکان استفاده از sql ce و sqlite رو دارین یا نه به این روش دیگه اینچنین مشکلاتی رو ندارین...
اما روشی که من غیر از مورد بالا پیشنهاد می کنم استفاده از serialize ویا XmlSerialize‌هست. به این شکل که شما مدل آبجکتی از (region , City و ...) رو ایجاد می کنید (شاید هم داشته باشین) و در نهایت لیستی از اینها رو (چون حالت تو در تو دارن مثلا لیستی از region که شامل city هم می شود) XmlSerialize کنید و در فایل ذخیره کنید. و موقع load می تونید این فایل رو deserialize‌کنید درنتیجه شما با مدل آبجکتی ای مواجه هستین که type safe‌هست و دیگه کوئری زدن مشکلی نخواهد داشت و برای ذخیره نیز دوباره این لیست رو XmlSerialize می کنید
ببخشید که کدی ننوشتم چون فکر می کنم شما از پسش بر می آین

ضمنا تشکر می کنم بابت نحوه مطرح کردن پرسشتون که کامل و همراه با کد بود!!! این می تونه الگوی خوبی برای بقیه کاربرا باشه

Saeed_m_Farid
جمعه 09 تیر 1391, 14:12 عصر
ممنون بابت توجهتون،
نه، برنامه سیستمی هست و امکان استفاده از بانک اطلاعاتی محدود!
درکل من از XmlSerialize اومدم بیرون که با Linq راحت تر هست، تکنولوژی جدیده و ... ولی خوب فکر کنم باید بصورت ترکیبی استفاده کنم؛
اینو (http://www.hanselman.com/blog/MixingXmlSerializersWithXElementsAndLINQToXML.aspx ) دیده بودم ولی گفتم شاید راه بهتری هست که به ذهن من نمیرسه!