Как изменить значение атрибута в новом XML-документе?
У меня есть база данных, где находится тонна XML-файла, который ожидает обновления. Обновление сотрет ВСЕ файлы в базе данных. Каждый новый файл будет таким же, как его предыдущий, за исключением значения атрибута.
Но я хочу сохранить каждый атрибут type
который имеет session
как ценность. Этот атрибут находится в каждом файле несколько раз. Но на данный момент это не работает: атрибуты вообще не обновляются).
Как я могу изменить значение определенного атрибута в новом XML-файле, идентичное в зависимости от другого?
Это то, что я сделал до сих пор...
Поэтому я решил найти способ получить путь к этим атрибутам, используя этот код для каждого файла перед очисткой:
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
Код ниже этой строки, вероятно, не нужен для ответа на мой вопрос, это просто более подробно.
И затем исправьте значение атрибутов после обновления следующим образом:
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
Информация для каждого пути в каждом файле содержится в этом:
//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;}
}
Я использую C# 3.0 (.NET framework 3.5
), Я ДОЛЖЕН использовать XMLDocument
чтобы соответствовать всем остальным в коде.
Вот пример того, что XML
выглядеть до обновления (короткая версия)
<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>
Вот пример того, что XML
выглядеть после обновления (короткая версия)
<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>
Вот пример того, что XML
из того, что я ищу:
<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>
Если мы сравним это с булевой алгеброй, мы получим следующее:
Для х = сессия & у = система
до обновления = после обновления -> Что мы хотим
х = х -> х
х = у -> х
у = х -> х
у = у -> у
Если вам нужна дополнительная информация, просто спросите в комментарии, и я обновлю пост.
2 ответа
Проблема в том, что вы ищете узлы с атрибутом с именем type
со значением session
- затем заменить это значение, если текущее значение system
, Это не сработает, так как значение не может быть и тем и другим.
Вы должны хотеть либо:
foreach (XmlNode node in root.SelectNodes("//node()[@type='session']"))
node.Attributes["type"].Value = "system";
или же
foreach (XmlNode node in root.SelectNodes("//node()[@type='system']"))
node.Attributes["type"].Value = "session";
Обновить
Если у вас есть два XmlDocuments
которые имеют одинаковые иерархии элементов, но разные наборы атрибутов для каждого элемента и хотят распространять некоторую информацию об атрибутах от первого ко второму, вам необходимо пройти иерархии элементов и создать временные таблицы сопоставления между ними. Следующее делает это, предполагая, что элементы соответствуют по имени, а затем по порядку, если существуют повторяющиеся имена (например, в списке):
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);
}
}
И затем назовите это как:
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 Если вы не хотите, чтобы весь старый XmlDocument
в памяти (хотя я не понимаю, почему нет), вы можете построить таблицу поиска элементов с type
атрибут, проиндексированный путем, затем используйте его позже:
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);
}
}
Вам понадобятся следующие методы расширения:
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);
}
}
(Я забыл, что Zip
отсутствует в.Net 3.5.)
Проблема с кодом в вопросе заключается в XmlNodeList
: он создает список объектов, который является ссылкой на XML
файлы. Поэтому, когда файлы стираются, XmlNodeList
ничего не значит Таким образом, изменяя этот список XmlNode для списка string
чтобы эти XPath работали.
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
Где функция getXPath выглядит примерно так:
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());
}
Автор будет изменять string
в XMLNode
теперь будет ссылка на хорошие файлы.
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
XmlList
Класс должен быть изменен, чтобы взять список вместо XmlNodeList