У меня есть массив строк следующим образом (каждый элемент содержит как минимум 3 узла по имени xref с атрибутами ref-type и rid)

<xref ref-type="bibr" rid="ref20">[20]</xref> <xref ref-type="bibr" rid="ref21">[21]</xref> <xref ref-type="bibr" rid="ref22">[22]</xref>
<xref ref-type="bibr" rid="ref2">[2]</xref>, <xref ref-type="bibr" rid="ref3">[3]</xref>, <xref ref-type="bibr" rid="ref4">[4]</xref>
<xref ref-type="bibr" rid="ref101">101</xref>, <xref ref-type="bibr" rid="ref102">102</xref>, <xref ref-type="bibr" rid="ref103">103</xref>, <xref ref-type="bibr" rid="ref104">104</xref>, <xref ref-type="bibr" rid="ref106">106</xref>
<xref ref-type="bibr" rid="ref11">[11]</xref> <xref ref-type="bibr" rid="ref12">[12]</xref> <xref ref-type="bibr" rid="ref13">[13]</xref> <xref ref-type="bibr" rid="ref4">[4]</xref>
<xref ref-type="bibr" rid="ref11">[11]</xref> <xref ref-type="bibr" rid="ref12">[12]</xref> <xref ref-type="bibr" rid="ref13">[13]</xref> <xref ref-type="bibr" rid="ref14">[14]</xref>

Я пытаюсь просмотреть каждый элемент массива и найти 3 или более узлов xref, у которых значение соответствующего атрибута rid увеличено на +1, исключая текст rid, и вывести их в консоль или файл.

Я сделал

foreach (var element in xrefs)
{
    XDocument xd = XDocument.Parse("<root>"+element+"</root>",LoadOptions.SetLineInfo);

    int len = xd.Descendants("xref").Count();

    var values = from El in xd.Descendants("xref").Take(len - 2)
        where El.CompareNext() && El.ElementsAfterSelf().FirstOrDefault().CompareNext()
        select El;
    foreach (var value in values)
    {

        Console.WriteLine(new string('-',50)+"\r\n"+element+"\r\n");
    }
}

Где xrefs - массив, а ElementsAfterSelf() - метод, созданный следующим образом

static class T1
{

    public static Boolean CompareNext(this XElement xe)
    {
        return Convert.ToInt16(xe.Attribute("rid").Value.Replace("ref", "")) + 1 == Convert.ToInt16(xe.ElementsAfterSelf().FirstOrDefault().Attribute("rid").Value.Replace("ref", ""));
    }
}

Теперь результат, который он производит, похож на

--------------------------------------------------
<xref ref-type="bibr" rid="ref20">[20]</xref> <xref ref-type="bibr" rid="ref21">[21]</xref> <xref ref-type="bibr" rid="ref22">[22]</xref>


--------------------------------------------------
<xref ref-type="bibr" rid="ref2">[2]</xref>, <xref ref-type="bibr" rid="ref3">[3]</xref>, <xref ref-type="bibr" rid="ref4">[4]</xref>


--------------------------------------------------
<xref ref-type="bibr" rid="ref101">101</xref>, <xref ref-type="bibr" rid="ref102">102</xref>, <xref ref-type="bibr" rid="ref103">103</xref> <xref ref-type="bibr" rid="ref104">104</xref> <xref ref-type="bibr" rid="ref106">106</xref>


--------------------------------------------------
<xref ref-type="bibr" rid="ref101">101</xref>, <xref ref-type="bibr" rid="ref102">102</xref>, <xref ref-type="bibr" rid="ref103">103</xref> <xref ref-type="bibr" rid="ref104">104</xref> <xref ref-type="bibr" rid="ref106">106</xref>


--------------------------------------------------
<xref ref-type="bibr" rid="ref11">[11]</xref>, <xref ref-type="bibr" rid="ref12">[12]</xref> <xref ref-type="bibr" rid="ref13">[13]</xref> <xref ref-type="bibr" rid="ref4">[4]</xref>


--------------------------------------------------
<xref ref-type="bibr" rid="ref11">[11]</xref>, <xref ref-type="bibr" rid="ref12">[12]</xref> <xref ref-type="bibr" rid="ref13">[13]</xref> <xref ref-type="bibr" rid="ref14">[14]</xref>


--------------------------------------------------
<xref ref-type="bibr" rid="ref11">[11]</xref>, <xref ref-type="bibr" rid="ref12">[12]</xref> <xref ref-type="bibr" rid="ref13">[13]</xref> <xref ref-type="bibr" rid="ref14">[14]</xref>

Он пишет строки ниже два раза, но я хочу только один раз, так как это одно и то же

<xref ref-type="bibr" rid="ref101">101</xref>, <xref ref-type="bibr" rid="ref102">102</xref>, <xref ref-type="bibr" rid="ref103">103</xref> <xref ref-type="bibr" rid="ref104">104</xref> <xref ref-type="bibr" rid="ref106">106</xref>
<xref ref-type="bibr" rid="ref11">[11]</xref>, <xref ref-type="bibr" rid="ref12">[12]</xref> <xref ref-type="bibr" rid="ref13">[13]</xref> <xref ref-type="bibr" rid="ref14">[14]</xref>

Кто-нибудь может помочь?

Вот пример XML-файла и полный код я использую

Я пытаюсь найти несколько последовательных узлов <xref ref-type="bibr" rid="ref...">...</xref> (при наличии 3 или более) в некоторых XML-файлах, разделенных запятой или запятую и пробел и запишите их в файл журнала. Последовательные узлы, которые я пытаюсь идентифицировать, должны иметь соответствующие значения атрибута rid, увеличенные на +1 минус текст ref. Любые другие узлы xref с различными значениями rid кроме refX проверять не требуется.

0
Don_B 7 Мар 2018 в 18:58

3 ответа

Лучший ответ

Я не думаю, что версия LINQ могла бы значительно улучшить код или понимание.

Поскольку вы хотите вывести текст между элементами, вы можете написать вспомогательную функцию для вывода XNode между двумя узлами:

var dashes = new String('-', 50);

void WriteNodesBetween(XNode from, XNode to) {
    Console.WriteLine(dashes);
    var xn = from;
    for (; xn != to; xn = xn.NextNode)
        Console.Write(xn.ToString());
    Console.WriteLine(xn.ToString());
}

Затем вы можете преобразовать свои строки в XNode s и сканировать элементы, собирающие последовательно пронумерованные элементы. Собрав их, вы можете вывести все элементы и узлы между ними, если найдено хотя бы три последовательных элемента.

foreach (var element in xrefs) {
    var xd = XDocument.Parse("<root>" + element + "</root>").Descendants("xref");

    var outElements = new List<XElement>() { xd.First() };
    foreach (var el in xd.Skip(1)) {
        if (!outElements.Last().ISSequential(el)) {
            if (outElements.Count >= 3)
                WriteNodesBetween(outElements.First(), outElements.Last());
            outElements.Clear();
        }
        outElements.Add(el);
    }
    if (outElements.Count >= 3)
        WriteNodesBetween(outElements.First(), outElements.Last());
}

Это использует другого помощника, чтобы проверить, являются ли два элемента последовательными.

public static class Ext {
    public static bool ISSequential(this XElement xe, XElement nextxe) => Convert.ToInt16(xe.Attribute("rid").Value.Replace("ref", "")) + 1 == Convert.ToInt16(nextxe.Attribute("rid").Value.Replace("ref", ""));
}
1
NetMage 8 Мар 2018 в 20:50

Я поставил это как отдельный ответ, так как не уверен, что думаю, что он лучше или понятнее, но можно использовать LINQ для поиска последовательностей, группируя их.

Я создал вариант моего расширения Scan, который является реализацией оператора сканирования APL, аналогично Aggregate, но он возвращает промежуточные результаты в виде последовательности.

Расширение ScanPair использует ValueTuple для сопоставления промежуточных результатов с текущим элементом в последовательности результатов:

public static IEnumerable<(TKey Key, T Value)> ScanPair<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<(TKey Key, T Value), T, TKey> combine) {
    using (var srce = src.GetEnumerator()) {
        if (srce.MoveNext()) {
            var prevkv = (seedKey, srce.Current);

            while (srce.MoveNext()) {
                yield return prevkv;
                prevkv = (combine(prevkv, srce.Current), srce.Current);
            }
            yield return prevkv;
        }
    }
}

Используя ScanPair, вы можете создать расширение для группы по предикату:

public static IEnumerable<IGrouping<int, TRes>> GroupByWhile<T, TRes>(this IEnumerable<T> src, Func<T, T, bool> test, Func<T, TRes> result) =>
    src.ScanPair(1, (kvp, cur) => test(kvp.Value, cur) ? kvp.Key : kvp.Key+1)
       .GroupBy(kvp => kvp.Key, kvp => result(kvp.Value));
public static IEnumerable<IGrouping<int, T>> GroupByWhile<T>(this IEnumerable<T> src, Func<T, T, bool> test) => src.GroupByWhile(test, e => e);

Используя GroupByWhile, вы можете создать расширение для группы с помощью последовательных значений:

public static IEnumerable<IGrouping<int, TRes>> GroupBySequential<T, TRes>(this IEnumerable<T> src, Func<T, int> SeqNum, Func<T, TRes> result) => src.GroupByWhile((prev,cur) => SeqNum(prev)+1 == SeqNum(cur), result);
public static IEnumerable<IGrouping<int, T>> GroupBySequential<T>(this IEnumerable<T> src, Func<T, int> SeqNum) => src.GroupBySequential(SeqNum, e => e);

Теперь, когда доступно GroupBySequential, вы можете извлечь последовательности из каждой строки:

var dashes = new String('-', 50);

void WriteNodesBetween(XNode from, XNode to) {
    Console.WriteLine(dashes);
    var xn = from;
    for (; xn != to; xn = xn.NextNode)
        Console.Write(xn.ToString());
    Console.WriteLine(xn.ToString());
}

foreach (var element in xrefs) {
    var xd = XDocument.Parse("<root>" + element + "</root>").Descendants("xref");
    var refseqs = xd.GroupBySequential(xref => xref.RefValue().Value);
    foreach (var seq in refseqs.Where(sg => sg.Count() >= 3))
        WriteNodesBetween(seq.First(), seq.Last());
}
0
NetMage 3 Апр 2018 в 21:35

Ваш xml - это массив элементов, поэтому я не понимаю, что вы пытаетесь сделать.

<Root>
  <xref ref-type="bibr" rid="ref20">[20]</xref> 
  <xref ref-type="bibr" rid="ref21">[21]</xref> 
  <xref ref-type="bibr" rid="ref22">[22]</xref>
  <xref ref-type="bibr" rid="ref2">[2]</xref> 
  <xref ref-type="bibr" rid="ref3">[3]</xref> 
  <xref ref-type="bibr" rid="ref4">[4]</xref>
  <xref ref-type="bibr" rid="ref101">101</xref> 
  <xref ref-type="bibr" rid="ref102">102</xref> 
  <xref ref-type="bibr" rid="ref103">103</xref>
  <xref ref-type="bibr" rid="ref104">104</xref> 
  <xref ref-type="bibr" rid="ref106">106</xref>
  <xref ref-type="bibr" rid="ref11">[11]</xref> 
  <xref ref-type="bibr" rid="ref12">[12]</xref> 
  <xref ref-type="bibr" rid="ref13">[13]</xref> 
  <xref ref-type="bibr" rid="ref4">[4]</xref>
  <xref ref-type="bibr" rid="ref11">[11]</xref> 
  <xref ref-type="bibr" rid="ref12">[12]</xref> 
  <xref ref-type="bibr" rid="ref13">[13]</xref> 
  <xref ref-type="bibr" rid="ref14">[14]</xref>
</Root>
-2
NetMage 8 Мар 2018 в 18:53