8 сент. 2008 г.

Apex. Экспорт отчетов.

Как реализовать экспортов отчетов в word, excel и иже своими силами.
Общая идея:
1. Создать таблицу с полем BLOB, куда будут генериться отчеты.
2. Создать OnDemand процесс, который будет из APEX_APPLICATION_PAGE_REGIONS выдергивать запрос для отчета. На основе этого запроса сгенерить xml данные. К этим данным применить xslt stylesheet и результат записывать в созданную до этого таблицу.
3. Написать javascript, который запустит этот процесс, а потом запустит на скачивание сформированный документ.

Ниже приводится "рыба". Понятно, что в итоге можно получить очень гибкую систему, позволяющую иметь полный контроль над выводом отчетов с помощью xslt.
Рабочий пример находится здесь: http://apex.oracle.com/pls/otn/f?p=33753:1:0

Итак, создаем таблицу и последовательность для первичного ключа:

create table breports(
id number(10) constraint pk_breports primary key,
breport blob,
mimetype varchar2(30),
filename varchar2(255),
charset varchar2(10),
created_by varchar2(30),
created_on date default sysdate
)
/
create sequence s_breports start with 1 increment by 1 maxvalue 9999999999
/


Функции:

-- Вспомогательная функция для конвертации в BLOB
create or replace function clob_to_blob(c in clob ) return blob is
pos pls_integer := 1;
buffer raw( 32767 );
res blob;
lob_len pls_integer := dbms_lob.getlength( c );
begin
dbms_lob.createtemporary(res, true);
dbms_lob.open(res, dbms_lob.lob_readwrite);

loop
buffer := utl_raw.cast_to_raw( dbms_lob.substr( c, 16000, pos));

if utl_raw.length( buffer ) > 0 then
dbms_lob.writeappend( res, utl_raw.length( buffer ), buffer );
end if;

pos := pos + 16000;
exit when pos > lob_len;
end loop;

return res; -- res is open here
end clob_to_blob;
/

-- функция экспорта отчета
create or replace
function export_report(p_app number, p_page number, p_region varchar2) return number as
l_query clob;
l_str varchar2(4000);
l_stylesheet xmltype := xmltype('<?xml version="1.0" ?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match="/"><html><body><table><tr><xsl:for-each select="./ROW[1]/*"><xsl:call-template name="header"/></xsl:for-each></tr><xsl:apply-templates select="ROW"/></table></body></html></xsl:template><xsl:template match="ROW"><tr><xsl:for-each select="./*"><xsl:call-template name="data"/></xsl:for-each></tr></xsl:template><xsl:template name="header"><td><xsl:value-of select="name(.)"/></td></xsl:template><xsl:template name="data"><td><xsl:value-of select="text()"/></td></xsl:template></xsl:stylesheet>');
l_mimetype varchar2(30) := 'application/msword';
l_filename varchar2(30);
l_charset varchar2(30) := 'UTF8';
l_created_by varchar2(30) := v('APP_USER');
l_ln number;
l_null varchar2(30);
begin

select region_source into l_query
from APEX_APPLICATION_PAGE_REGIONS
where application_id = p_app
and page_id = p_page
and region_name = p_region;

select s_breports.nextval into l_ln from dual;
l_filename := 'report_'||to_char(l_ln)||'.doc';

l_str := 'insert into breports (id, breport, mimetype, filename, charset, created_by)
select '||to_char(l_ln)||', clob_to_blob(xmltransform(d,s).getclobval()), '''||l_mimetype||''', '''||l_filename||''', '''||l_charset||''', '''||l_created_by||''' from (select xmlagg(column_value) d from table(xmlsequence(cursor('||l_query||')))) xd, (select xmltype('''||l_stylesheet.getclobval()||''') s from dual) xs';
execute immediate l_str using l_null;
commit;
return l_ln;
exception
when no_data_found
then return 0;
end export_report;


В приложении создадим три айтема уровня приложения: TEMP_ITEM_1, TEMP_ITEM_2, TEMP_ITEM_3
И OnDemand процесс:

declare
l_ret number;
begin
l_ret := export_report(:TEMP_ITEM_1, :TEMP_ITEM_2, :TEMP_ITEM_3);
htp.p(apex_util.get_blob_file_src('P2_BREPORT', l_ret));
end;


Дальше финт ушами: нужно мастером создать форму на основе таблицы BREPORTS.Обозначить первичный ключ ID, источник ключа Existing trigger. Из колонок выбрать только BREPORT и не создавать никаких кнопок типа delete и update... В созданной странице (пусть это будет PAGE 2), нужно зайти в свойства айтема P2_BREPORT (типа File Browse) и изменить свойство Source на BREPORT:MIMETYPE:FILENAME::CHARSET:attachment:Download. Это нужно для работы функции apex_util.get_blob_file_src в OnDemand процессе: там параметр 'P2_BREPORT' - имя айтема File Browse на этой форме.

Теперь на странице с отчетом нужно в header вписать скрипт:

<script language="JavaScript" type="text/javascript">
function exportReport(pApp, pPage, pRegion){
var get = new htmldb_Get(null,$x('pFlowID').value,'APPLICATION_PROCESS=EXPORT_REPORT',1);
get.add('TEMP_ITEM_1', pApp);
get.add('TEMP_ITEM_2', pPage);
get.add('TEMP_ITEM_3', pRegion);
gRet = get.get(null);
get = null;
open(gRet, '_parent');
}
</script>


И последнее, в области отчета создать элемент Display as Text (does not save state) у которого source =

<a href="javascript:exportReport(&APP_ID.,&APP_PAGE_ID., 'Report')">Export to Word</a>

Здесь внимание: 'Report' - это имя region'а c отчетом.

Собственно всё.

4 комментария:

Анонимный комментирует...

Пытался сделать как описано, выдает ошибку

The requested URL /pls/apex/Process not found. was not found on this server.

Alp комментирует...

Версия апекса?

Анонимный комментирует...

версия 3.1

Alp комментирует...

Должно работать. Проверьте аккуратно работоспособность процесса, процедур, свойства линка и т.д. Попробуйте подробней описать проблему. В момент нажатия на линк вызывается javascript. Никаких редиректов происходить не должно, т.е. страница должна остаться на месте. Проверьте содержимое таблицы - в ней должна появиться новая запись. Если не появилась, попробуйте определить почему: не запускается процедура, не запускается апекс процесс, не работает javascript...