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

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 12.07.2006, 16:05   #1  
Avick is offline
Avick
Участник
 
15 / 10 (1) +
Регистрация: 03.10.2005
Адрес: Москва
Импорт из html-файлов без использования внешнего парсера возможен?
Господа! Сталкивался ли кто-нибудь с необходимостью импорта данных (в данном случае счетов от поставщика услуг) из html-файла? Проблема усугубляется тем, что файл может быть очень большим - десятки мегабайт...
Старый 12.07.2006, 16:11   #2  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
А придать этому сборищу данных более сговорчивый формат? Например, xls?
Задачка разовая или постоянно надо будет парсить?
Старый 12.07.2006, 16:21   #3  
Avick is offline
Avick
Участник
 
15 / 10 (1) +
Регистрация: 03.10.2005
Адрес: Москва
Цитата:
Сообщение от Gustav
А придать этому сборищу данных более сговорчивый формат? Например, xls?
Задачка разовая или постоянно надо будет парсить?
Задача ежемесячная.
Да и файлов несколько штук, отличных по формату.
Придать сговорчивый формат - это и значит перегнать нужные данные из хтмл в Ексель с помощью чего-либо?
Старый 12.07.2006, 16:25   #4  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Цитата:
Сообщение от Avick
Задача ежемесячная.
Да и файлов несколько штук, отличных по формату.
Придать сговорчивый формат - это и значит перегнать нужные данные из хтмл в Ексель с помощью чего-либо?
Да прямо Excel'ем и открывать.Заюзать Excel через COM-интерфейсы из X++.

Конкретно в Excel - что-нибудь типа объекта QueryTable. Через этот QueryTable - можно хоть к файлу, хоть напрямую в web.


P.S. Посмотрите, как при помощи Excel выкачивается из веба табличная информация здесь:
Получение в Excel полного списка пользователей AxForum

Там пример на VBA, но сделать перевод на X++ задача вполне посильная.

P.P.S. Поиграйтесь сами прямо в Excel: меню Данные - Внешние данные - Создать Web-запрос. Запишите свои действия макрорекордером. Уверен, Вам понравится.

Последний раз редактировалось Gustav; 12.07.2006 в 16:37.
Старый 12.07.2006, 17:15   #5  
Avick is offline
Avick
Участник
 
15 / 10 (1) +
Регистрация: 03.10.2005
Адрес: Москва
[QUOTE=Gustav]

Конкретно в Excel - что-нибудь типа объекта QueryTable. Через этот QueryTable - можно хоть к файлу, хоть напрямую в web.
[QUOTE]
Попробовал.
Возник вопрос: как мне прямо в коде сказать какое именно слово из файла в какую именно ячейку класть?
Без регулярных выражений не обойтись, как мне кажется...
Старый 12.07.2006, 17:26   #6  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Цитата:
Сообщение от Avick
Без регулярных выражений не обойтись, как мне кажется...
Ну эт не знаю, не умею. На эту тему Вы macklakov-а терзайте...
Цитата:
Сообщение от Avick
Возник вопрос: как мне прямо в коде сказать какое именно слово из файла в какую именно ячейку класть?
Общий принцип такой. Нужно два листа Excel (в одной книге, конечно):

один - "приёмник", куда всасывается информация из веба и автоматически распределяется по конкретным ячейкам этого листа - т.е. Ваши "слова" сами залетают в эти ячейки без Вашего участия,

второй - "накопитель", где Вы уже раскладываете данные "по полочкам", т.е. по колоночкам, читая эти данные из "приёмника"


P.S. У вас данные - табличного вида? Или хаотичного?

Вы смотрели пример с "Пользователями АхФорум"? Там содержимое "приемника" меняется в цикле 60 с лишним раз (по 100 пользователей на одной странице): запросили очередную страницу, она загрузилась в "приемник", прочитали ее данные в "накопитель", в "накопителе" продвинулись вниз по таблице на последнюю заполненную "запись", запросили следующую страницу в "приемник" и т.д. в цикле.

По окончании процесса интересующие Вас данные находятся в "накопителе" ("приемник" больше не нужен, можно выбросить его в пропасть как суп харчо). Из "накопителя" грузите данные в Аксапту любым из желаемых методов (через ODBC, ADO и т.п.).

Последний раз редактировалось Gustav; 12.07.2006 в 17:39.
Старый 12.07.2006, 16:28   #7  
macklakov is offline
macklakov
NavAx
Аватар для macklakov
 
2,253 / 980 (37) +++++++
Регистрация: 03.04.2002
Цитата:
Сообщение от Avick
Проблема усугубляется тем, что файл может быть очень большим - десятки мегабайт...
Десятки мегабайт, это не так много. В принципе, можно в память поднять и regExp-ами его просканировать
__________________
Isn't it nice when things just work?
Старый 12.07.2006, 18:43   #8  
belugin is offline
belugin
Участник
Аватар для belugin
Сотрудники Microsoft Dynamics
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии 2011
Лучший по профессии 2009
 
4,622 / 2925 (107) +++++++++
Регистрация: 16.01.2004
Записей в блоге: 5
я бы использовал чтонибудь специально заточенное под разбор HTML (http://www.crummy.com/software/Beaut...mentation.html) а потом транслировал бы в что-нибудь специально заточенное под обмен данными например XML.
Старый 13.07.2006, 13:38   #9  
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   #10  
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   #11  
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   #12  
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, время: 19:07.