Reputation: 339
I have a database where is a ton of XML file who's waiting to get update. The update would wipe ALL files in the database. Each new file would be the same as his previous except for the attribute value.
But I want to keep each attributes type
that have session
as value. This attribute is in each file multiple time. But, it's doesn't work for the moment: attributes doesn't update at all).
How can I change the value of an specific attribute in an new XML file identical depending of another one?
This is what I did so far...
So I decided to find a way to get the path to these attributes using this code for each file before the wipe:
List<XmlList> dBPathTypeSession = new List<XmlList>();
/*reader*/
XmlDocument doc = new XmlDocument();
doc.LoadXml(sessionType.Parameters);
XmlNode root = doc.DocumentElement;
//I think my problem come from this line below
XmlNodeList nodes = root.SelectNodes("//node()[@type='session']");
dBPathTypeSession.Add(new XmlList("b_session_type", i, nodes));//table,row,paths
The code below this line is probably not needed to answer my question, this is just more detail.
And then correct the attributes value after the update with this:
XmlDocument doc = new XmlDocument();
doc.LoadXml(sessionType.Parameters);
foreach (XmlNode node in file.Paths)//XmlList file = new XmlList();
{
if (node.Attributes["type"].Value == "system")
{
node.Attributes["type"].Value = "session";
}
}
//push to DB
The informations for every path in each file is contain in this:
//But I think this is pointless for my question
public class XmlList
{
public XmlList(string tablePath, int filePath, XmlNodeList paths)
{
this.TablePath = tablePath;
this.FilePath = filePath;
this.Paths = paths;
}
public string TablePath {get; private set;}
public int FilePath {get; private set;}
public XmlNodeList Paths {get;set;}
}
I'm using C# 3.0(.NET framework 3.5
), I MUST use XMLDocument
to make it fit with everything else in the code.
Here's an example of what the XML
look like before update(short version)
<Session is_hidden="false">
<ID is_static="true">1</ID>
<SESSIONIDX is_static="true">0</SESSIONIDX>
<Timing>
<FirstPresentation display_name="FirstPresentation" type="system"/>
<Pause display_name="Pause" type="system" datatype="float"/>
<Feedback display_name="Feedback" type="session" datatype="float"/>
<Answer display_name="Answer" type="system" datatype="float"/>
</Timing>
<Balls>
<Indexed display_name="Indexed" type="session" datatype="pos_int"/>
<IndexingColor1 display_name="IndexingColor1" type="system" datatype="list">
<list>
<ListItem>RED</ListItem>
<ListItem>BLUE</ListItem>
</list>
</IndexingColor1>
</Balls>
</Session>
Here's an example of what the XML
look like after update(short version)
<Session is_hidden="false">
<ID is_static="true">1</ID>
<SESSIONIDX is_static="true">0</SESSIONIDX>
<Timing>
<FirstPresentation display_name="FirstPresentation" type="session"/>//system change for session
<Pause display_name="Pause" type="system" datatype="float"/>
<Feedback display_name="Feedback" type="system" datatype="float"/>//session change for system
<Answer display_name="Answer" type="system" datatype="float"/>//system stay system
</Timing>
<Balls>
<Indexed display_name="Indexed" type="session" datatype="pos_int"/>//session stay session
<IndexingColor1 display_name="IndexingColor1" type="system" datatype="list">
<list>
<ListItem>RED</ListItem>
<ListItem>BLUE</ListItem>
</list>
</IndexingColor1>
</Balls>
</Session>
Here's an example of what the XML
of what I'm looking for:
<Session is_hidden="false">
<ID is_static="true">1</ID>
<SESSIONIDX is_static="true">0</SESSIONIDX>
<Timing>
<FirstPresentation display_name="FirstPresentation" type="session"/>//system stay session
<Pause display_name="Pause" type="system" datatype="float"/>
<Feedback display_name="Feedback" type="session" datatype="float"/>//session return session
<Answer display_name="Answer" type="system" datatype="float"/>//system stay system
</Timing>
<Balls>
<Indexed display_name="Indexed" type="session" datatype="pos_int"/>//session stay session
<IndexingColor1 display_name="IndexingColor1" type="system" datatype="list">
<list>
<ListItem>RED</ListItem>
<ListItem>BLUE</ListItem>
</list>
</IndexingColor1>
</Balls>
</Session>
If we compare this to boolean algebra, we have this :
For x = session & y = system
before update = after update -> What we want
x = x -> x
x = y -> x
y = x -> x
y = y -> y
If you need more information just ask in comment and I will update the post.
Upvotes: 3
Views: 6717
Reputation: 339
The problem with the code in the question is the XmlNodeList
:it creates a list of object that is link to the XML
files. So when the files get wipe, XmlNodeList
means nothing. So, changing this list of XmlNode for a list of string
to these XPath would work.
XmlNodeList nodes = root.SelectNodes("//node()[@type='session']");
List<string> xPathList = new List<string>();
foreach (XmlNode node in nodes)
{
xPathList.Add(getXPath(node));
}
dBPathTypeSession.Add(new XmlList("b_session_type", i, xPathList));//table,row,paths
Where the getXPath function is something like this:
static public string getXPath(XmlNode _xmlNode)
{
Stack<string> xpath = new Stack<string>();
while (_xmlNode != null)
{
if (_xmlNode as XmlElement != null)
xpath.Push(_xmlNode.Name);
_xmlNode = _xmlNode.ParentNode as XmlElement;
}
return string.Join("/", xpath.ToArray());
}
The writer would modify the string
to XMLNode
that will have the link to the good files now.
foreach (string path in file.Paths)
{
XmlNode node = doc.SelectSingleNode(path);
if (node.Attributes["type"].Value == "system" && node.Attributes["type"].Value != null)
{
node.Attributes["type"].Value = "session";
}
}
//push to DB
The XmlList
class would need to be change to take List instead of XmlNodeList
Upvotes: 1
Reputation: 117026
The problem is that you are searching for nodes with an attribute named type
with value session
-- then replacing that value if the current value is system
. That's not going to work, since the value can't be both.
You must want either:
foreach (XmlNode node in root.SelectNodes("//node()[@type='session']"))
node.Attributes["type"].Value = "system";
or
foreach (XmlNode node in root.SelectNodes("//node()[@type='system']"))
node.Attributes["type"].Value = "session";
Update
If you have two XmlDocuments
that have identical element hierarchies but different sets of attributes for each element, and wish to propagate some attribute information from the first to the second, you need to walk the element hierarchies and create temporary mapping tables between them. The following does that, assuming the elements are corresponded by name, and then by order if duplicate names exist (e.g. in a list):
static void WalkMatchingElements(XmlElement root1, XmlElement root2, Action<XmlElement, XmlElement> action)
{
WalkMatchingElements(root1, root2, (element) => (element.Name), action);
}
static void WalkMatchingElements<TKey>(XmlElement root1, XmlElement root2, Func<XmlElement, TKey> getKey, Action<XmlElement, XmlElement> action)
{
if (EqualityComparer<TKey>.Default.Equals(getKey(root1), getKey(root2)))
action(root1, root2);
var children1GroupedByName = root1.ChildNodes.OfType<XmlElement>().GroupBy(getKey);
var children2LookupByName = root2.ChildNodes.OfType<XmlElement>().ToLookup(getKey);
foreach (var child1group in children1GroupedByName)
{
var child2group = children2LookupByName[child1group.Key];
foreach (var pair in child1group.Zip(child2group, (el1, el2) => new KeyValuePair<XmlElement, XmlElement>(el1, el2)))
WalkMatchingElements(pair.Key, pair.Value, getKey, action);
}
}
And then call it like:
var oldDoc = new XmlDocument();
oldDoc.LoadXml(oldXml);
var newDoc = new XmlDocument();
newDoc.LoadXml(newXml);
WalkMatchingElements(oldDoc.DocumentElement, newDoc.DocumentElement, (elOld, elNew) =>
{
var attrOld = elOld.Attributes["type"];
if (attrOld != null && attrOld.Value == "session")
{
elNew.SetAttribute("type", "system");
}
});
Update2 If you don't want the entire old XmlDocument
in memory at once (though I don't see why not), you can built a lookup table of elements with a type
attribute, indexed by path, then use that later:
const string AttributeName = "type";
var lookup = oldDoc.DocumentElement.DescendantsAndSelf().OfType<XmlElement>().Where(el => el.HasAttribute(AttributeName)).ToLookup(el => el.Path(), el => el.Attributes[AttributeName].Value);
// And then later
WalkMatchingElements(new XmlElement[] { newDoc.DocumentElement }, lookup, (el, oldValue) =>
{
if (oldValue != null && oldValue == "session")
el.SetAttribute(AttributeName, "session");
});
private static void WalkMatchingElements<TValue>(IEnumerable<XmlElement> elements, ILookup<string, TValue> pathLookup, Action<XmlElement, TValue> action)
{
var elementsByPath = elements.GroupBy(el => el.Path());
foreach (var elementsGroup in elementsByPath)
{
foreach (var pair in elementsGroup.Zip(pathLookup[elementsGroup.Key], (el, value) => new KeyValuePair<XmlElement, TValue>(el, value)))
action(pair.Key, pair.Value);
foreach (var element in elementsGroup)
WalkMatchingElements(element.ChildNodes.OfType<XmlElement>(), pathLookup, action);
}
}
You'll need the following extension methods:
public static class XmlNodeExtensions
{
public static string Path(this XmlElement element)
{
if (element == null)
throw new ArgumentNullException();
return element.AncestorsAndSelf().OfType<XmlElement>().Reverse().Aggregate(new StringBuilder(), (sb, el) => sb.Append("/").Append(el.Name)).ToString();
}
public static IEnumerable<XmlNode> AncestorsAndSelf(this XmlNode node)
{
for (; node != null; node = node.ParentNode)
yield return node;
}
public static IEnumerable<XmlNode> DescendantsAndSelf(this XmlNode root)
{
if (root == null)
yield break;
yield return root;
foreach (var child in root.ChildNodes.Cast<XmlNode>())
foreach (var subChild in child.DescendantsAndSelf())
yield return subChild;
}
}
public static class EnumerableExtensions
{
// Back ported from .Net 4.0
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (resultSelector == null) throw new ArgumentNullException("resultSelector");
return ZipIterator(first, second, resultSelector);
}
static IEnumerable<TResult> ZipIterator<TFirst, TSecond, TResult>(IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}
}
(I had forgotten that Zip
is not in .Net 3.5.)
Upvotes: 2