Рекурсивный обход xml
От: .alex Ниоткуда  
Дата: 03.02.23 14:14
Оценка:
Есть например вот такой xml:

<?xml version="1.0" encoding="utf-8"?>
<main>
<fio n="attr">
<name>name1</name>
<pasp>
<pi>pi1</pi>
<pi>pi2</pi>
</pasp>
</fio>
<fio n="attr">
<name>name2</name>
</fio>
<sub_node>
<cr>
<val>val1</val>
<val>val2</val>
<extra>
<ex>ex1</ex>
<ex>ex2</ex>
</extra>
</cr>
<cr>
<val>val3</val>
<val>val4</val>
</cr>
</sub_node>
</main>

Мне нужно вывести определенные его элементы в плоскую таблицу по принципу "каждый с каждым". В mssql сервере я делаю это примерно так:
declare @sCmd nvarchar(max), @sFullPath varchar(max), @x xml
set @sFullPath = 'c:\in\test2.xml'
set @sCmd = N'select @xml = cast(t.data as xml) from OPENROWSET (BULK '+quotename(@sFullPath, N'''') + N', SINGLE_BLOB) t(data)'
exec sp_executesql @sCmd, N'@xml xml output', @x output;
 
select distinct
    fio.n.value('name[1]', 'varchar(32)') 'fio.name'
    , pi_.n.value('.', 'varchar(32)') 'pi_'
 
    , val.n.value('.', 'varchar(5)') 'val'
    , ex.n.value('.', 'varchar(10)') 'ex'
 
from @x.nodes('main') as main(n)
    outer apply main.n.nodes('fio[@n="attr"]') as fio(n)
        outer apply fio.n.nodes('pasp/pi') as pi_(n)
    outer apply main.n.nodes('sub_node/cr') as cr(n)
        outer apply cr.n.nodes('val') as val(n)
        outer apply cr.n.nodes('extra/ex') as ex(n)

Ну и получаемый результат:

name1 pi1 val1 ex1
name1 pi1 val1 ex2
name1 pi1 val2 ex1
name1 pi1 val2 ex2
name1 pi1 val3 NULL
name1 pi1 val4 NULL
name1 pi2 val1 ex1
name1 pi2 val1 ex2
name1 pi2 val2 ex1
name1 pi2 val2 ex2
name1 pi2 val3 NULL
name1 pi2 val4 NULL
name2 NULL val1 ex1
name2 NULL val1 ex2
name2 NULL val2 ex1
name2 NULL val2 ex2
name2 NULL val3 NULL
name2 NULL val4 NULL

А вот теперь вопрос, как это можно сделать в любом алгоритмическом языке? Например vba/c++/java и пр. в которых есть циклы, рекурсия, ну и SelectNodes(), SelectSingleNode()..
Я как понимаю нужно реализовать рекурсивный обход дерева xml, но что-то никак не соображу как... Подскажите пожалуйста...
Re: Рекурсивный обход xml
От: vsb Казахстан  
Дата: 03.02.23 15:18
Оценка:
Надо с помощью XPath выбрать списки интересующих тебя узлов и по ним запустить нужное число вложенных циклов.
Re[2]: Рекурсивный обход xml
От: .alex Ниоткуда  
Дата: 05.02.23 16:50
Оценка:
Здравствуйте, vsb, Вы писали:
vsb>Надо с помощью XPath выбрать списки интересующих тебя узлов и по ним запустить нужное число вложенных циклов.
Пробовал, что-то не шибко получается... Особонно если на входе будет подобный хмл, но например без нодов <fio> и "верхний" цикл "не запустит" вложенные с другими нодами... Если понятно про что я...
Re: Рекурсивный обход xml
От: Mr.Delphist  
Дата: 08.02.23 09:57
Оценка: +1
Здравствуйте, .alex, Вы писали:

A>Есть например вот такой xml:

A><skip>
A>Мне нужно вывести определенные его элементы в плоскую таблицу по принципу "каждый с каждым".
A><skip>
A>

A>name1 pi1 val1 ex1
A>name1 pi1 val1 ex2
A>name1 pi1 val2 ex1
A>name1 pi1 val2 ex2
A><skip>
A>name2 NULL val3 NULL
A>name2 NULL val4 NULL

A>А вот теперь вопрос, как это можно сделать в любом алгоритмическом языке?

Звучит как задача для XSLT: описываем трансформацию, получаем выходные данные нужного формата. Кодировать это врукопашную — выглядит сомнительным.
Re: Рекурсивный обход xml
От: Mr.Delphist  
Дата: 08.02.23 11:31
Оценка:
Здравствуйте, .alex, Вы писали:

Типа такого XSLT-преобразования, выдаёт следующий результат (по образцу допилить колонки val и ext)

name1 pi1
name1 pi2
name2 NULL


Note: не забыть отредактировать строчку с комментарием, иначе будет строчка независимых символов, а нужен один символ переноса строки (глюк сайта, он не делает энкодинг для спецсимволов при рендеринге)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="html" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <xsl:for-each select="/main/fio">
      <xsl:variable name="my_name" select="name"/>
      <xsl:choose>
        <xsl:when test="pasp/pi">
          <xsl:for-each select="pasp/pi">
            <xsl:value-of select="concat($my_name, ' ')"/>
            <xsl:value-of select="."/>
            <xsl:text>& # 10;</xsl:text> <!-- тут убрать пробелы между символами, глюк RSDN -->
          </xsl:for-each>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="concat($my_name, ' NULL')"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
</xsl:transform>
Re[2]: Рекурсивный обход xml
От: .alex Ниоткуда  
Дата: 08.02.23 14:09
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:
MD>Звучит как задача для XSLT: описываем трансформацию, получаем выходные данные нужного формата. Кодировать это врукопашную — выглядит сомнительным.
А вот нужно именно "врукопашную"... Не подскажете может где какие примеры поискать?
Re[3]: Рекурсивный обход xml
От: Mr.Delphist  
Дата: 08.02.23 15:19
Оценка:
Здравствуйте, .alex, Вы писали:

A>А вот нужно именно "врукопашную"... Не подскажете может где какие примеры поискать?


Ну вот следующий ответ от меня:
https://rsdn.org/forum/xml/8466126.1
Автор: Mr.Delphist
Дата: 08.02.23


Рукопашным будет позвать этот XSLT, а остальное описывается трансформацией, Например, что-то типа такого:
https://learn.microsoft.com/en-us/dotnet/standard/linq/use-xslt-transform-xml-tree
string xslt = @"<?xml version='1.0'?>
    <xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
        <xsl:template match='/Parent'>
            <Root>
                <C1>
                <xsl:value-of select='Child1'/>
                </C1>
                <C2>
                <xsl:value-of select='Child2'/>
                </C2>
            </Root>
        </xsl:template>
    </xsl:stylesheet>";

var oldDocument = new XDocument(
    new XElement("Parent",
        new XElement("Child1", "Child1 data"),
        new XElement("Child2", "Child2 data")
    )
);

var newDocument = new XDocument();

using (var stringReader = new StringReader(xslt))
{
    using (XmlReader xsltReader = XmlReader.Create(stringReader))
    {
        var transformer = new XslCompiledTransform();
        transformer.Load(xsltReader);
        using (XmlReader oldDocumentReader = oldDocument.CreateReader())
        {
            using (XmlWriter newDocumentWriter = newDocument.CreateWriter())
            {
                transformer.Transform(oldDocumentReader, newDocumentWriter);
            }
        }
    }
}

string result = newDocument.ToString();
Console.WriteLine(result);
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.