Moderator
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
|
Несколько дней после обсуждения по горячим следам потихоньку мусолил тему. Не торопясь, в свободное время сваял джоб. Потом еще несколько дней раскрашивал его комментариями. А потом и забывать стал. Сегодня вспомнил, думаю, надо бы выложить, а то совсем забуду...
Цитата:
Сообщение от 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);
}
|