XML + XSL + SQL... (восстановление иерархии)

ale-805
Дата: 16.12.2002 18:06:36
Добренький всем приветик из вечного офф-лайна. Передо мной встала задача, которую, как мне кажется, многим случалось решать. И вроде пути есть различные, но как бы так реализовать покраше и поинтеллигентнее?...
Вобщем так. Сразу ТТХ: IIS 5.0 на серваке, база в MS SQL Server 2000, ASP. Суть такова: У меня на сервере лежит XML-файл, характеризующий отчет о списании товаров со складов по опеределенным причинам списания в формате:
<xml>
<reason id="3" name="Брак">
<storage id="9" name="Главный склад">
<product id="11856" name="Товар 1">
<nums quantity="1.000" summa="2.77"/>
</product>
<product id="11906" name="Товар 2">
<nums quantity="1.000" summa="2.38"/>
</product>
<product id="11926" name="Товар 3">
<nums quantity="1.000" summa="2.15"/>
</product>
</storage>
</reason>
</xml>
Это значит, что по причине "Брак" на "Главном складе" было списано: 1 единица "Товара 1" на сумму 2.77 доллара, 1 единица "Товара 2" на сумму 2.38 доллара и 1 единица "Товара 3" на сумму 2.15 доллара. И все бы замечательно, отправляй на клиента и выводи в отчет, но сама проблема заключается в том, что мне нужно еще вывести и иерархию по товарам (в идеале - не только по товарам, но и по любому уровню группировки (причина, склад, товар) (т.к. все справочники являются иерархическими)). Предположим, что "Товар 1" и "Товар 2" находятся в папке "Товары оптовые", а "Товар 3" находится в папке "Товары розничные". В свою очередь и "Товары оптовые" и "Товары розничные" находятся в папке "Товары". В этом случае желательный вид XML такой (структура может быть любой):
<xml>
<reason id="3" name="Брак">
<storage id="9" name="Главный склад">
<product id="11000" name="Товары">
<nums quantity="3.000" summa="7.30"/>
<product id="12000" name="Товары оптовые">
<nums quantity="2.000" summa="5.15"/>
<product id="11856" name="Товар 1">
<nums quantity="1.000" summa="2.77"/>
</product>
<product id="11906" name="Товар 2">
<nums quantity="1.000" summa="2.38"/>
</product>
</product>
<product id="13000" name="Товары розничные">
<nums quantity="1.000" summa="2.15"/>
<product id="11926" name="Товар 3">
<nums quantity="1.000" summa="2.15"/>
</product>
</product>
</product>
</storage>
</reason>
</xml>

Как Вы заметили, количество и сумма должна агрегироваться на каждом уровне товарных групп ("<nums quantity="..." summa="..."/>")
Иерархия в базе данных хранится следующим образом:
в таблице "Products": ID, ParentID, Name
в таблице "ProductsAncestors": self_id, parent_id

В нашем случае, например таблица "Products" будет заполнена так:
ID ParentID Name
---------------------------------------------
1 0 root
11000 1 Товары
12000 11000 Товары оптовые
13000 11000 Товары розничные
11856 12000 Товар 1
11906 12000 Товар 2
11926 13000 Товар 3

таблица "ProductsAncestors" является дополнительной, содержит всю иерархию дерева и будет заполнена так:
self_id parent_id
-----------------------
1 1
11000 1
11000 11000
12000 1
12000 12000
12000 11000
13000 1
13000 13000
13000 11000
11856 1
11856 11856
11856 12000
11856 11000
11906 1
11906 11906
11906 12000
11906 11000
11926 1
11926 11926
11926 13000
11926 11000

Ну какие мысли есть? Начальный XML есть, выгружать из БД новый XML с иерархией а потом каким-нить хитрым XSLT его преобразовывать? Честно говоря, мозгов пока не хватает, как этого всего достичь... Поможите, кто может, проблема-то насущная, I hope...
Спасибо всем огромное заранее за ответы.
qu-qu
Дата: 16.12.2002 19:14:27
Мужик, напрасно ты считаешь, что так сразу наткнешься на такого человека, который уже решил такую же задачу и тут же поделится с тобой - готовым решением. Даже не смотря на то, что многим случалось решать много подобных задач... Но подобное не является тождественным и подгонять свои рецепты под твою задачу - вряд ли кто-то будет, при том, что даже для того чтобы прочитать твой пост (сам по себе длинноватенький) и разобраться в "хитросплетении" неотформатированных отступами тегов - требуется довольно много времени, которого и так постоянно "не хватает ни на что" ... (ИМХО).

Что же касается конкретной задачи, то идея выгружать из БД новый XML с иерархией а потом каким-нить хитрым XSLT его преобразовывать вобщем-то является наиболее здравой, т.к. пересылать Начальный XML на сервер, чтобы его там обработать в соответствии с иерархией, а потом вернуть в новом виде - будет своего рода "неуважением к серверу"... :-)).

А насчет "нехватки мозгов" - не прибедняйся, а спроси прямо - что тебе надо:
консультации по использованию XSLT?
абстрактный пример добавления иерархии с суммированием хитрым XSLT?
конкретный XSL-документ, который выдаст требуемый тебе результат на представленных исходных данных?
jimmers
Дата: 17.12.2002 01:21:14
Насколько я понял задачу, то необходимо сделать примерно следущее:

1. Взять XML документ с данными, что храниться у Вас на сервере
2. Для каждого элемента типа <product> найти всех предков, пользуясь БД (там хранится описание иерархии)
3. В выходном XML перечислить полученные <product> с п.2, вложив друг в друга с учетом иерархии и дополнив
статистикой (аггрегирование)
+
4. Реализовать подобное для любого элемента, не только для <product>.

Первое, что приходит в голову - использовать XSL с тэгом script, что позволит путешествуя по исходному
XML документу, вызывать процедуру, возвращающую предков и далее проводить аггрегирование. В нашем случае
это будет 1 запрос, т.к. рекурсии нет в предложенной схеме хранения иерархии.
jimmers
Дата: 17.12.2002 01:27:58
Типа этого:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="http://mycompany.com/mynamespace"
exclude-result-prefixes="xmlns">
<xsl:output method="xml"/>
<msxsl:script language="JScript" implements-prefix="user">
function GetParents(nodelist){
// Тут следует вытащить из БД предков и для каждого из них
// отыскать в текущем документе оный, если найден - увеличить
// статистику, если нет - создать, причем с учетом вложенности
// На выходе из этой функции должено быть дерево от данного
// элемента и выше...
}
</msxsl:script>
<xsl:template match="/">
<xml>
<xsl:apply-templates/>
</xml>
</xsl:template>
<xsl:template match="/xml/reason/storage">
<reason id="{../@id}" name="{../@name}">s
<storage id="{@id}" name="{@name}">
<xsl:apply-templates/>
</storage>
</reason>
</xsl:template>
<xsl:template match="product">
<product>
<xsl:value-of select="user:GetParents(.)"/>
</product>
</xsl:template>
</xsl:stylesheet>
qu-qu
Дата: 17.12.2002 10:25:43
2 ale-805

Ну вот, на абстрактный пример мы уже разжились... :-))

2 jimmers
на:

// На выходе из этой функции должено быть дерево от данного
// элемента и выше...


Вот это - положение спорное и, скорее всего, ошибочное...
Даже пытаться реализовать его не стоит, т.к. прямо "в голове" можно себе представить, что на выходе XSLT - вместо каждого элемента <product> исходного документа будет поставлена "раскидистая клюква" вверх по иерархии продуктов... :-))

Однако, идея об использовании скрипт-тегов и функций - безусловно продуктивна, т.к. в них легко можно использовать ADODB-объекты для обращения к серверу, и в итоге - информацию об иерархии продуктов можно будет получать непосредственно в процессе обработки исходного документа.

Удачи
jimmers
Дата: 17.12.2002 11:36:11
2:qu-qu

Вы не поняли идею, к сожалению.
Суть в том, что для каждого элемента <product> получаются все предки, далее для
каждого предка можно проверить, есть ли он в результирующем XML, если нет, то
добавить (при этом на забыть, что добавить как внешний, родительский элемент по
отношению к исходному product), если же он уже есть (текущий родитель), то просто
обновить агрегаты у него и поместить текущий элемент product под него. Все это
доступно через DOM.

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

Удачи
ale-805
Дата: 23.12.2002 12:20:26
2 jimmers:
Собственно:
1) Почему ты тогда предлагаешь использовать скрипт, а потом пишешь, что "А вот что до тэга script, то его использование как раз не рекомендуется..."? Не понял! Почему именно не рекомендуется и почему ты предлагаешь его здесь использовать? Совсем запутал, старик! :-)
2) Я всё реализовал скриптом, все нормально, остался один вопросик:
Для элементов, для которых не надо строить иерархию, шаблон у меня такой:

<xsl:template match="reason|storage|product" priority="1">
<xsl:element name="{name()}">
<xsl:for-each select="@*">
<xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>

, т.е. я тупо копирую элемент в выходной XML,
а для элементов, для которых надо строить иерархию, шаблон у меня такой:

<xsl:template match="storage" priority="2">
<xsl:copy-of select="user:GetParents( ., 'Storages' )"/>
</xsl:template>

=================
В данной функции я прохожу по всей иерархии в БД и добавляю все элементы с агрегированием. Это все работает, если я расписываю на иерархии самый вложенный элемент (Товар). А если мне надо расписать и товар и склад? Смотрите, что получается: "<xsl:copy-of select="user:GetParents( ., 'Storages' )"/>" заменяет в выходном файле элемент <storage> на всю его иерархию, а все элементы, которые были внутри склада (товары) я копирую в файл вот так:

for (var i=0; i<node.childNodes.length; i++)
{
Item = node.childNodes.item(i);
cloned = Item.cloneNode(true);
curNode.appendChild( cloned );
}

Соответственно, мне уже не сделать расписывание на иерархию еще и товаров, а нужно позарез. Может надо из функции вызывать себя же, только по товарам?.. Вот думаю...
Если есть лучшее решение, посоветую.. Заранее спасибо!
qu-qu
Дата: 23.12.2002 16:51:30
"Рубить хвост, на котором летишь, это будет похуже, чем - рубить сук, на котором сидишь..." © к/ф "Экипаж"

2 ale-805 & jimmers

Я вот читал jimmers-а и все думал: "а что же будет - когда через DOM, путем добавления "нужной" иерархии, изменится исходная структура обрабатываемого XML-документа? Что тогда начнет обрабатывать XSLT? По каким "правилам"?"

Не мог найти ответа... Хотел подождать - пока ale-805 наступит на эти грабли... (извиняюсь, но самому ковыряться в этих хитросплетениях - времени пока нет). Ну вот, дождался...

Что можно посоветовать в этом случае?
Наверное - написать N функций для использования в теге script. N равняется - число элементов, которые надо группировать по иерархии (storages, reasons, products) плюс 1.
Что они будут делать:
1. Та функция, которая "плюс 1" будет выводить в выходной поток совершенно новый документ XML, построенный исключительно через DOM в процессе переработки исходного документа (наверное ее вызов можно "повесить" на root исходного документа);
2,3... N. Каждая из этих функций - будет добавлять (или обновлять) нужную иерархию по нужному элементу, но не в исходной DOM-структуре, а в "новой" (см. п.п. 1), которую они будут передавать друг-другу как бы "по эстафете".

В этом случае - вложенность элементов product в storage - не должна "пострадать" от добавления новых элементов иерархии.