AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX: Программирование
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 13.07.2006, 13:38   #1  
belugin is offline
belugin
Участник
Аватар для belugin
Сотрудники Microsoft Dynamics
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии 2011
Лучший по профессии 2009
 
4,622 / 2925 (107) +++++++++
Регистрация: 16.01.2004
Записей в блоге: 5
причем жутко извратные
Старый 31.07.2006, 10:44   #2  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Несколько дней после обсуждения по горячим следам потихоньку мусолил тему. Не торопясь, в свободное время сваял джоб. Потом еще несколько дней раскрашивал его комментариями. А потом и забывать стал. Сегодня вспомнил, думаю, надо бы выложить, а то совсем забуду...
Цитата:
Сообщение от Gustav
Допустим, мы захотели регулярно читать его полученные и выданные репутации.
...
Однако и на этот случай "номерной неустойчивости таблиц" есть свое решение: веб-страница рассматривается целиком, без деления на таблицы, а точки начала интересующих нас таблиц определяются нахождением на листе "приёмник" ячеек, содержащих текст "Участник получил одобрение от других" и "Участник одобрил других" соответственно.
Джоб выгружает из АхФорума таблички репутаций четырех участников этой дискуссии и сводит их в одной таблице Excel, демонстрируя таким образом один из возможных вариантов получения и парсинга данных из web-страницы или html-файла. (Дальнейший путь информации из Excel в Axapta здесь не рассматривается в силу достаточно хорошей проработанности этой темы на Форуме.)
X++:
static void WebQuery_DemoJob(Args _args)
{
// ---------------------------------------------------------------------------
// ПРИМЕР получения табличной информации из web-страницы в Excel
// ---------------------------------------------------------------------------
    COM xlApp;     // Excel.Application
    
    COM wbks, wbk; // Workbooks, Workbook
    COM wkss;      // Worksheets
    
    COM wksReceiver;    // Worksheet "приёмник"
    COM wksAccumulator; // Worksheet "накопитель"
    
    COM rCells; // все ячейки (Worksheet.Cells) "приёмника"
    COM aCells; // все ячейки (Worksheet.Cells) "накопителя"
    
    COM qts, qt; // QueryTables, QueryTable
    
    COM rngUser; // Range - одиночная ячейка, из значения которой получается имя участника
    
    COM rngTO;   // Range - одиночная ячейка - "Точка Отсчета" ("Точка Опоры") очередной таблицы репутации,
                 // находится поиском на листе "приёмник" строки типа "Участник получил одобрение от других" и др.
                 // дальше от этой ТО "как от печки" всё "вытанцовывается"
    
    COM rngHeaders; // Range - блок ячеек - строка заголовков очередной таблицы репутации на листе "приёмник"
    COM rngRecords; // Range - блок ячеек - строки данных очередной таблицы репутации на листе "приёмник"
    
    COM rngAccHeaders; // Range - блок ячеек - строка заголовков таблицы репутации на листе "накопитель"
    COM rngAccRecords; // Range - блок ячеек - место "вставки" на листе "накопитель" очередной порции данных с листа "приёмник"
    
    COM comTemp; // временный COM-объект для промежуточных операций
                 // по превращению раннего связывания (многоточие VBA)
                 // в позднее связывание (одноточие X++)
    
    COMVariant cValue;
    
    boolean isFirst = true;
    
    int rowFirst, rowLast, countRecords, rowHeader;
    int colFirst, colLast, countCols;
    
    str strConnectionBeg = 'URL;http://axforum.info/forums/member.php?u=';
    // для файла будет что-то вроде strConnectionBeg = 'URL;c:\\myFolder\\myFile.htm'
    // или при использовании @: strConnectionBeg = @'URL;c:\myFolder\myFile.htm'
    str strConnection;
    str strUser;
    
    #define.xlAllTables(2)
    #define.xlSpecifiedTables(3)
    #define.xlWebFormattingNone(3)
    #define.xlDown(-4121)
    #define.xlToRight(-4161)
    #define.xlShiftToRight(-4161)
    
// ===================================================================================================
// СНАЧАЛА несколько служебных методов (потом - основной процесс)
    
// вычленение повторяющихся фрагментов из основного процесса и оформление их процедурами (методами),
// вызываемыми из основного процесса или друг из друга достаточно специфично для конкретной веб-страницы
// конкретный вариант такой декомпозиции обычно становится ясным на этапе предварительного анализа
    
// --------------------------------------------------------------------------------
// начальное оформление таблицы результатов (при обработке данных первого участника)
// --------------------------------------------------------------------------------
    void firstArrange()
    {
    
        // "накопитель" еще пустой
        // набиваем поля
        rngAccHeaders = wksAccumulator.Range(aCells.Item(1, 1), aCells.Item(1, countCols));
        rngAccHeaders.Value2( rngHeaders.Value2() );  // вот это неплохо :-), фактически матричное присваивание A=B
        rngAccRecords = rngAccHeaders.Offset(1, 0);
    
        // необходимое форматирование некоторых колонок "накопителя"
        // Дата
        comTemp = COM::createFromVariant(rngAccRecords.Item(1, 2));
        comTemp = comTemp.EntireColumn();
        comTemp.NumberFormat('ДД.ММ.ГГ чч:мм');
    
        // Автор - если появляется
        // всего в таблице репутации появляется или 3 колонки, или 5 (открываются колонки Очки и Автор)
        // в зависимости от того, сохранили ли вы в куках свой логин-пароль на АхФорум
        // соответственно вы входите на Форум веб-запросом как гость (будет 3 колонки в табл.реп.) или как участник (будет 5 колонок)
        if (countCols > 3)
        {
            // если вход - участника (а не гостя) - "не мальчика, но мужа!" :-)
            // устанавливаем текстовый формат для колонки "Автор" (иначе проблемы с попыткой Excel распознать формулы на символах =>)
            comTemp = COM::createFromVariant(rngAccRecords.Item(1, 4));
            comTemp = comTemp.EntireColumn();
            comTemp.NumberFormat([EMAIL="'@'"]'@'[/EMAIL]);
        }
    
        // на листе "накопитель" вставляем две дополнительные колонки слева от таблицы репутации
        comTemp = rngAccHeaders.Resize(1, 2);
        comTemp = comTemp.EntireColumn();
        comTemp.Insert(#xlShiftToRight);
    
        // расширяем диапазон заголовков на две добавленные колонки
        rngAccHeaders = rngAccHeaders.Offset(0, -2);
        rngAccHeaders = rngAccHeaders.Resize(1, countCols + 2);
    
        // заголовки двух добавленных слева колонок
        COM::createFromVariant(rngAccHeaders.Item(1, 1)).Value2('Участник');
        COM::createFromVariant(rngAccHeaders.Item(1, 2)).Value2('Направление');
    
        // заголовки - жирным
        comTemp = rngAccHeaders.Font();
        comTemp.Bold(true);
    
        // замораживаем строку заголовков
        COM::createFromVariant(rngAccHeaders.Item(2, 1)).Select(); // лист "накопитель" здесь уже активен
        comTemp = xlApp.ActiveWindow();
        comTemp.FreezePanes(true);
    
        isFirst = false;
    
    }
    
// --------------------------------------------------------------------
// обработка таблицы репутации участника (1 или 2 = верхняя или нижняя)
// --------------------------------------------------------------------
    void processReputationTable(int _tableNum)
    {
        switch (_tableNum)
        {
            case 1:
                rngTO = rCells.Find('Участник получил одобрение от других');
                break;
    
            case 2:
                rngTO = rCells.Find('Участник одобрил других');
                break;
        }
    
        if (rngTO == null)
            // выходим, если соответствующая таблица отсутствует в профиле участника
            return;
    
        // эксельный номер первой строки данных
        comTemp = rngTO.Offset(2, 1);
        rowFirst = comTemp.Row();
    
        // эксельный номер последней строки данных
        comTemp = comTemp.End(#xlDown);
        rowLast = comTemp.Row();
    
        // количество записей в данных (эксельных строк в таблице репутации)
        countRecords = rowLast - rowFirst + 1;
    
        // эксельный номер строки заголовков колонок (Тема, Дата и т.п.)
        rowHeader = rowFirst - 1;
    
        // эксельный номер первой колонки
        comTemp = rngTO.Offset(2, 1);
        colFirst = comTemp.Column();
    
        // эксельный номер последней колонки
        comTemp = rngTO.Offset(1, 2);
        comTemp = comTemp.End(#xlToRight);
        colLast = comTemp.Column();
    
        // количество столбцов (эксельных колонок в таблице репутации)
        countCols = colLast - colFirst + 1;
    
        // создаем объектные переменные для диапазонов: Заголовков и Записей
        rngHeaders = wksReceiver.Range(rCells.Item(rowHeader, colFirst), rCells.Item(rowHeader, colLast));
        rngRecords = wksReceiver.Range(rCells.Item(rowFirst, colFirst), rCells.Item(rowLast, colLast));
    
        // "возвращаем на место отъехавшую Тему" :-)
        // слово "Тема" при загрузке в Excel попадает не в ту ячейку, в которую нам надо
        // (все эти нюансы "поведения" изучаются на этапе предварительного анализа конкретной веб-страницы)
        COM::createFromVariant(rngHeaders.Item(1, 1)).Value2('Тема');
    
        // если проход - первый, то выполняем "аранжировку" накопителя
        if (isFirst)
            firstArrange();
    
        // переопределение диапазона в соответствии с требуемым кол-вом записей
        rngAccRecords = rngAccRecords.Resize(countRecords);
    
        // массовое прописывание значений в колонки "Тема"..."Комментарий" на листе "накопитель"
        rngAccRecords.Value2( rngRecords.Value2() ); // фактически "копирование без копирования"
    
        // массовое прописывание значений в колонку "Участник" на листе "накопитель"
        comTemp = rngAccRecords.Resize(COM::createFromObject(rngAccRecords.Rows()).Count(), 1);
        comTemp = comTemp.Offset(0, -2); // эта колонка "левее на 2" относительно колонки "Тема"
        comTemp.Value2(strUser);
    
        // массовое прописывание значений в колонку "Направление" на листе "накопитель"
        comTemp = rngAccRecords.Resize(COM::createFromObject(rngAccRecords.Rows()).Count(), 1);
        comTemp = comTemp.Offset(0, -1); // эта колонка "левее на 1" относительно колонки "Тема"
        switch (_tableNum)
        {
            case 1:
                comTemp.Value2('получил одобрение');
                break;
    
            case 2:
                comTemp.Value2('одобрил');
                break;
        }
    
        // переопределение диапазона для приема следующей порции данных
        rngAccRecords = rngAccRecords.Offset(countRecords);
        // фактически здесь нас интересует переопределенное положение только первой строки нового диапазона (новая "закладка")
        // а необходимое кол-во строк будет задано "выше" на следующей итерации в операторе: rngAccRecords = rngAccRecords.Resize(countRecords);
    
    }
    
// --------------------------------------
// обработка данных из профиля участника
// --------------------------------------
    void processUserData(int _userId)
    {
        strConnection = strConnectionBeg + int2str(_userId);
    
        if (isFirst)
        {
            // если проход - первый, то создаем новый web-запрос
            qts = wksReceiver.QueryTables();
            qt = qts.Add(strConnection, wksReceiver.Range('A1'));
    
            qt.Name('SelectUserProfileFromAxForum');
            // --------------------------------------
            // изначально было так:
            // qt.WebSelectionType(#xlAllTables);
            // но на одном из компьютеров, где были какие-то специфичные настройки Internet Explorer
            // таблицы репутаций стали "разрываться" дополнительными пустыми колонками (какой-то лишний Tab...)
            // --------------------------------------
    
            // --------------------------------------
            // проблема выше была решена за счет исключения из списка таблицы 2
            qt.WebSelectionType(#xlSpecifiedTables);
            qt.WebTables('1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30');
            // на самом деле таблиц на странице "Профиль участника" около 20,
            // остальные (до 30) - добавлены для надежности (ошибки это не вызывает)
            // --------------------------------------
    
            qt.WebFormatting(#xlWebFormattingNone);
            qt.Refresh(false); //BackgroundQuery:=False
        }
        else
        {
            // если не первый проход, то перестраиваем запрос
            qt.Connection(strConnection);
            qt.Refresh(false); //BackgroundQuery:=False
        }
    
        // получение имени участника
        rngUser = rCells.Find('Просмотр профиля: ');
        cValue = rngUser.Value2();
        strUser = strLRTrim(strReplace(cValue.bStr(), 'Просмотр профиля: ', ''));
    
        //обработка 1-й (верхней) таблицы репутации
        processReputationTable(1);
    
        //обработка 2-й (нижней) таблицы репутации
        processReputationTable(2);
    }
    
// ===================================================================================================
// ТЕПЕРЬ основной процесс
    
    xlApp = new COM('Excel.Application');
    
    wbks = xlApp.Workbooks();
    wbk = wbks.Add();
    
    wkss = wbk.Worksheets();
    
    wksReceiver = wkss.Item(1);
    wksReceiver.Name('Receiver');
    
    wksAccumulator = wkss.Item(2);
    wksAccumulator.Name('Accumulator');
    
    rCells = wksReceiver.Cells();
    aCells = wksAccumulator.Cells();
    
    wksAccumulator.Activate();
    
    processUserData( 259); // macklakov
    processUserData(2552); // belugin
    processUserData(5046); // Avick
    processUserData(5597); // Gustav
    
    xlApp.Visible(true);
    
}
Старый 31.07.2006, 11:23   #3  
mazzy is offline
mazzy
Участник
Аватар для mazzy
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
29,472 / 4494 (208) ++++++++++
Регистрация: 29.11.2001
Адрес: Москва
Записей в блоге: 10
Цитата:
Сообщение от Gustav
Не торопясь, в свободное время сваял джоб.
1. Спасибо
2. Чтобы подавать хороший пример начинающим, обработки такого объема лучше сразу делать в классах.
__________________
полезное на axForum, github, vk, coub.
Старый 31.07.2006, 15:41   #4  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Цитата:
Сообщение от mazzy
2. Чтобы подавать хороший пример начинающим, обработки такого объема лучше сразу делать в классах.
Ок. Не против. Впредь буду стараться в подходящих ситуациях делать именно так.

Однако, в данном случае ситуация мне таковой не показалась, поэтому джобинкой и ограничился. Точнее, на класс бы и не пошёл: разбирается конкретная страница (и декомпозиция по методам заточена именно под нее конкретную), а класс всё же предполагает по крайней мере ростки какой-то будущей универсальности. Иначе тот же начинающий увидит проект с классом и завопит: "Вау! Универсалка!" (а мне будет неудобно, что я его "обману")
За это сообщение автора поблагодарили: Corkscrew (1).
 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
BOM без использования модуля Производство Iskorka DAX: Функционал 10 11.04.2011 16:32
Стандартный импорт данных. Обновление sparur DAX: Функционал 0 24.03.2008 19:07
Перехват оконных сообщений без использования внешних компонентов belugin DAX: База знаний и проекты 2 04.05.2007 14:28
casperkamal: HTML based Report in Dynamics Ax Blog bot DAX Blogs 0 20.02.2007 09:40

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 01:09.