segunda-feira, 10 de janeiro de 2011

Upload e Download de Ficheiros de Estrutura Variável


A maioria dos programadores ABAP com alguma experiência conhece os métodos para fazer upload ou download de ficheiros. Mas nem todos os usam de uma forma prática. Neste artigo vou descrever a forma mais simples para usar estes métodos e uma forma mais complexa que permite o tratamento de ficheiros de comprimento de linha variável.
Mas primeiro, uma parte importante, a chamada à pesquisa do nome do ficheiro.

Pesquisa de ficheiro

A não ser que o ficheiro tenha um nome e localização específica, a pesquisa de ficheiro deve ser obrigatória. Não preparar a pesquisa é um erro grotesco que limita a operacionalidade e usabilidade do programa. Um programador deve sempre simplificar o trabalho do utilizador final e isso implica primeiro que tudo que o programador se pergunte como se faz no SAP standard. Ou melhor, o bom standard…
Portanto, normalmente o nome do ficheiro é pedido por parâmetro e a pesquisa é efectuada por F4.

*Primeira consideração, o parâmetro deve ter cumprimento suficiente.
*No exemplo, o parâmetro tem um comprimento de 1024 caracteres,
*mostrando apenas 30. O tamanho máximo do nome do ficheiro varia
*consoante o formato do file system, mas por motivos de simplificação
*vamos considerar 1024 suficiente. Não é nada espectável um nome mais
*longo logo o esforço de tratamento é desnecessário.
PARAMETERSp_file LIKE file_table-filename LENGTH 30.

*A chamada F4 é tratada no evento ON VALUE-REQUEST.
AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
  
PERFORM p_file_f4.

*&---------------------------------------------------------------------*
*&      Form  p_file_f4
*&---------------------------------------------------------------------*
*       Diálogo de Pesquisa de Ficheiro
*----------------------------------------------------------------------*
FORM p_file_f4.
  
DATAl_title TYPE string,
        l_rc 
TYPE i.
  
DATAlt_filetable TYPE filetable.
  
FIELD-SYMBOLS<fs_line> TYPE file_table.

  l_title 
'Ficheiro'.
  
CLEAR lt_filetable[].
  
CALL METHOD cl_gui_frontend_services=>file_open_dialog
    
EXPORTING
      window_title            
l_title
    
CHANGING
      file_table              
lt_filetable
      rc                      
l_rc
    
EXCEPTIONS
      file_open_dialog_failed 
1
      cntl_error              
2
      error_no_gui            
3
      not_supported_by_gui    
4
      
OTHERS                  5.
*não é previsto que caso o código esteja correcto haja erros no
*diálogo, logo não os tratei.
*Pelo motivo indicado em cima, o nome do ficheiro deve estar todo na
*primeira linha.
  LOOP AT lt_filetable ASSIGNING <fs_line>.
    
IF sy-tabix 2.
*mensagem de erro para limitar o nome do ficheiro
      
MESSAGE i899(mmWITH 'Nome de ficheiro demasiado' 'comprido'.
      
EXIT.
    
ENDIF.
    p_file 
<fs_line>-filename.
  
ENDLOOP.
ENDFORM.                                                    "p_file_f4

O exemplo em cima é com o diálogo para abertura de ficheiro. O diálogo para gravar ficheiro é o cl_gui_frontend_services=>file_save_dialog e é ligeiramente diferente, fica aqui um exemplo de como usar:

  DATAl_file TYPE stringl_path TYPE stringl_fullpath TYPE string.
  
CALL METHOD cl_gui_frontend_services=>file_save_dialog
    
CHANGING
      filename             
l_file
      path                 
l_path
      fullpath             
l_fullpath
    
EXCEPTIONS
      cntl_error           
1
      error_no_gui         
2
      not_supported_by_gui 
3
      
OTHERS               4.
*também se poderia controlar o comprimento do nome do ficheiro neste ponto
  p_file 
l_fullpath.

Formato simplificado de Upload/Download

Os métodos de upload e download são os bem conhecidos:
  • cl_gui_frontend_services=>gui_upload
  • cl_gui_frontend_services=>gui_download
Ambos funcionam basicamente da mesma forma.
A forma simples de chamar a função depende apenas do tipo de campos a passar. Se todos os campos tiverem um formato equiparado a texto então basta usar a tabela interna directamente. Senão, será necessário preparar uma segunda tabela interna com apenas campos equiparados a texto.
Neste exemplo todas as entradas da tabela de centros estão na tabela interna it_t001w:

DATAit_t001w TYPE TABLE OF t001w.

O conteúdo da tabela é colocado num ficheiro no frontend:

  DATAl_file TYPE string.
  l_file 
p_file.
  
CALL METHOD cl_gui_frontend_services=>gui_download
    
EXPORTING
      filename                
l_file
      write_field_separator     
'X' 
    
CHANGING
      data_tab                
it_t001w
    
EXCEPTIONS
*      .....
      
OTHERS                  24.
  
IF sy-subrc <> 0.
    
MESSAGE e899(mmWITH 'Erro a gravar ficheiro'.
  
ENDIF.

O tratamento de erros deste método é muito mais relevante dado que a probabilidade do erro é maior.
O parâmetro write_field_separator associa o separador TAB entre campos o que vai permitir abrir o ficheiro facilmente em Excel como um ficheiro *.txt tab delimited, logo é um formato preferencial.
Para este exemplo o upload teria o seguinte aspecto:

  DATAl_file TYPE string.
  l_file 
p_file.
  
CALL METHOD cl_gui_frontend_services=>gui_upload
    
EXPORTING
      filename                
l_file
      has_field_separator     
'X'
    
CHANGING
      data_tab                
it_t001w
    
EXCEPTIONS
*      .....
      
OTHERS                  19.
  
IF sy-subrc <> 0.
    
MESSAGE e899(mmWITH 'Erro a abrir ficheiro'.
  
ENDIF.

Portanto, razoavelmente simples, não é? Ok, vamos complicar. E se à partida temos um ficheiro com colunas dinâmicas?

Upload/Download com estruturas variáveis

Existem formas de criar tabelas internas com colunas dinâmicas mas estes métodos estão fora do âmbito deste artigo. Vamos admitir que a tabela pode ter até 59 colunas, que podem ser campos de texto, datas ou numéricos.
Um ponto extremamente importante diz respeito à conversão entre formato interno e externo. Caso existam campos sujeitos a conversão como códigos de material, datas, números, o programador deve fazer uma escolha consciente do formato do ficheiro. Por regra, o ficheiro que está fora de SAP deve usar o formato de output, ao passo que obviamente o programa e a sua tabela interna devem usar o formato de input. No entanto pode haver excepções dado que o ficheiro provavelmente será processado por uma aplicação externa, por exemplo, o Excel. Aí, considerações como o caracter da casa decimal têm de ser avaliadas. O tratamento de formatos internos e externos também não é do âmbito deste documento, mas serão usados alguns casos a título de exemplo.
Dado que não sabemos o formato, a tabela interna usada tem todos os campos como texto de 30 caracteres:

  TYPESBEGIN OF t_data,
          col1
(30),
          col2
(30),
          col3
(30),
*..............
          col59
(30),
         
END OF t_data.
  
DATAl_file TYPE string.
  
DATAlt_data TYPE TABLE OF t_data.
  
DATAls_data TYPE t_data.
  
DATAl_col TYPE sy-tabix.
  
FIELD-SYMBOLS<fs_field_in><fs_field_out>.
  
FIELD-SYMBOLS<fs_t001w> TYPE t001w.
  
TYPESBEGIN OF t_line,
          fieldname 
TYPE dd03l-fieldname,
          
position TYPE dd03l-position,
         
END OF t_line.
  
DATAlt_line TYPE TABLE OF t_line.
  
FIELD-SYMBOLS <fs_line> TYPE t_line.

  
l_file p_file.

*obtem lista de campos para exemplo
*num caso real teríamos por exemplo uma lista reduzida com 4 ou 5 campos
  
SELECT fieldname position INTO TABLE lt_line FROM dd03l
         
WHERE tabname 'T001W' AND comptype 'E'
         
ORDER BY position.

  
LOOP AT it_t001w ASSIGNING <fs_t001w>"linha a linha
    
CLEAR ls_data.
    
LOOP AT lt_line ASSIGNING <fs_line>"coluna a coluna
      
ADD TO l_col.
      
ASSIGN COMPONENT <fs_line>-fieldname OF STRUCTURE <fs_t001w>
             
TO <fs_field_in>"assigna campo in
      
ASSIGN COMPONENT l_col OF STRUCTURE ls_data
             
TO <fs_field_out>"assigna campo out
*rotinas de conversão. Existem formas mais dinamicas de as determinar,
*pelo elemento de dados
      
IF <fs_line>-fieldname 'KUNNR' OR <fs_line>-fieldname 'LIFNR'.
        
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
          
EXPORTING
            
input  <fs_field_in>
          
IMPORTING
            
output <fs_field_out>.
      
ELSE"outros elementos não sujeitos a conversão
        <fs_field_out> 
<fs_field_in>.
      
ENDIF.
    
ENDLOOP.
    
CLEAR l_col.
    
APPEND ls_data TO lt_data.
  
ENDLOOP.

Após o carregamento da tabela de output, os dados são enviados sem mais tratamento:

  
CALL METHOD cl_gui_frontend_services=>gui_download
    
EXPORTING
      filename                
l_file
      write_field_separator   
'X'
    
CHANGING
      data_tab                
lt_data
    
EXCEPTIONS
      file_write_error        
1
      no_batch                
2
*..............
      
OTHERS                  24.
  
IF sy-subrc <> 0.
    
MESSAGE e899(mmWITH 'Erro a gravar ficheiro'.
  
ENDIF.

Por sua vez upload é feito para esta tabela de output:

  CALL METHOD cl_gui_frontend_services=>gui_upload
    
EXPORTING
      filename                
l_file
      has_field_separator     
'X'
    
CHANGING
      data_tab                
lt_data
    
EXCEPTIONS
      file_open_error         
1
      file_read_error         
2
*..............
      
OTHERS                  19.

Agora os dados são passados da tabela de output para a tabela real, de input, pelo processo inverso de conversão:

  DATAlt_data TYPE TABLE OF t_data.
  
DATAls_data TYPE t_data.
  
DATAl_col TYPE sy-tabix.
  
FIELD-SYMBOLS<fs_field_in><fs_field_out>.
  
FIELD-SYMBOLS<fs_t001w> TYPE t001w.
  
TYPESBEGIN OF t_line,
          fieldname 
TYPE dd03l-fieldname,
          
position TYPE dd03l-position,
         
END OF t_line.
  
DATAlt_line TYPE TABLE OF t_line.
  
FIELD-SYMBOLS <fs_line> TYPE t_line.
*obtem lista de campos para exemplo
*num caso real teríamos por exemplo uma lista reduzida com 4 ou 5 campos
  
SELECT fieldname position INTO TABLE lt_line FROM dd03l
         
WHERE tabname 'T001W' AND comptype 'E'
         
ORDER BY position.

  
LOOP AT it_t001w ASSIGNING <fs_t001w>"linha a linha
    
CLEAR ls_data.
    
LOOP AT lt_line ASSIGNING <fs_line>"coluna a coluna
      
ADD TO l_col.
      
ASSIGN COMPONENT <fs_line>-fieldname OF STRUCTURE <fs_t001w>
             
TO <fs_field_in>"assigna campo in
      
ASSIGN COMPONENT l_col OF STRUCTURE ls_data
             
TO <fs_field_out>"assigna campo out
*rotinas de conversão.
      
IF <fs_line>-fieldname 'KUNNR' OR <fs_line>-fieldname 'LIFNR'.
        
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
          
EXPORTING
            
input  <fs_field_in>
          
IMPORTING
            
output <fs_field_out>.
      
ELSE"outros elementos não sujeitos a conversão
        <fs_field_out> 
<fs_field_in>.
      
ENDIF.
    
ENDLOOP.
    
CLEAR l_col.
    
APPEND ls_data TO lt_data.
  
ENDLOOP.

Sem comentários:

Enviar um comentário