Based on a given JSON, I need to create an insert statement in a stored procedure. For example I have this JSON:
{
"tableName": "test_table",
"columName":["col_1","col_2","col3"],
"columnValue":["test1","test2","test3"]
}
The "columName" is an array with dynamic number of values and the "columnValue" is an array with the values for each position of the "columnName" array.
What I need to do is to create an insert statement dynamically. For example, for the given JSON, the insert statement looks like this:
INSERT INTO test_table("col_1", "col_2", "col_3") VALUES("test1", "test2", "test3")
The test_table table has 27 columns. In JSON I receive data for one, two, three or all columns. How can I build the select statement based on dynamic number of columns.
I am using APEX_JSON.
Thank you!
SET SERVEROUTPUT ON SIZE 999999
CLEAR SCREEN
DECLARE
l_json_text VARCHAR2(32767);
l_json_values apex_json.t_values;
l_statement VARCHAR2(32767);
FUNCTION add_quotes (string_i VARCHAR2) RETURN VARCHAR2
IS
BEGIN
RETURN '"' ||string_i||'"';
END;
BEGIN
l_json_text := '{
"tableName": "test_table",
"columName":["col_1","col_2","col3"],
"columnValue":["test1","test2","test3"]
}
';
apex_json.parse(
p_values => l_json_values,
p_source => l_json_text
);
l_statement := 'INSERT INTO ';
l_statement := l_statement || apex_json.get_varchar2(p_path => 'tableName', p_values => l_json_values);
l_statement := l_statement || '(';
FOR j IN 1..apex_json.get_count(p_path => 'columName', p0 => 1, p_values => l_json_values) LOOP
l_statement := l_statement || add_quotes(apex_json.get_varchar2(p_path => 'columName[%d]', p0 => j, p_values => l_json_values));
l_statement := l_statement || ',';
END LOOP;
l_statement := RTRIM(l_statement,',');
l_statement := l_statement || ') values (';
FOR j IN 1..apex_json.get_count(p_path => 'columnValue', p0 => 1, p_values => l_json_values) LOOP
l_statement := l_statement || add_quotes(apex_json.get_varchar2(p_path => 'columnValue[%d]', p0 => j, p_values => l_json_values));
l_statement := l_statement || ',';
END LOOP;
l_statement := RTRIM(l_statement,',');
l_statement := l_statement || ')';
dbms_output.put_line(l_statement);
END;
/
INSERT INTO test_table("col_1","col_2","col3") values ("test1","test2","test3")
PL/SQL procedure successfully completed.
declare
v_count number;
v_tableName varchar2(100 char);
v_columnName varchar2(100 char);
v_columnValue varchar2(100 char);
v_sqlStatement varchar2(2000 char);
v_json clob:= '{"tableName": "test_table","columnName":["col_1","col_2","col3"],"columnValue":["test1","test2","test3"]}';
begin
apex_json.parse(v_json);
v_count := apex_json.get_count(p_path=>'columnName');
v_tableName := apex_json.get_varchar2(p_path => 'tableName');
FOR i IN 1..v_count loop
v_columnName := apex_json.get_varchar2(p_path => 'columnName[%d]',p0 => i);
v_columnValue := apex_json.get_varchar2(p_path => 'columnValue[%d]',p0 => i);
v_sqlStatement := 'insert into' || v_tableName || '("' || v_columnName' || '" values("' || v_columnValue || '");' ;
execute immediate v_sqlStatement;
end loop;
end;
Related
I'm tyring to write files for every cursor execution as shown in below sql. The data_payload column will have more than 1000 characters.
There is an exception at utl_file.put line.
If I use SUBRTR of 1000 characters, then the file writes successfully.
Is there any other alternate function to handle this?
SET SERVEROUTPUT ON
DECLARE
l_file_name VARCHAR2(100);
l_chr_payload VARCHAR2(4000);
fhandle utl_file.file_type;
CURSOR payload_cur IS
SELECT data_payload,
request_reference
FROM temp_tbl
WHERE message_type = 'ORDERCREATE'
AND ROWNUM < 2;
TYPE payload_typ IS
TABLE OF payload_cur%rowtype INDEX BY BINARY_INTEGER;
l_col_payload payload_typ;
BEGIN
OPEN payload_cur;
l_col_payload.DELETE;
FETCH payload_cur
BULK COLLECT INTO l_col_payload;
CLOSE payload_cur;
FOR i IN 1..l_col_payload.count LOOP
l_file_name := l_col_payload(i).request_reference
|| '_'
|| i
|| '.json';
dbms_output.put_line('l_file_name' || l_file_name);
fhandle := utl_file.fopen(
'TMP_DIR' -- File location
,
l_file_name -- File name
,
'w' -- Open mode: w = write.
);
l_chr_payload := substr(
l_col_payload(i).data_payload,
1,
1000
);
utl_file.put(
fhandle,
l_chr_payload
);
utl_file.fclose(fhandle);
END LOOP;
EXCEPTION
WHEN OTHERS
THEN
dbms_output.put_line('ERROR: '
|| sqlcode
|| ' - '
|| sqlerrm);
RAISE;
END;
/
Assuming that temp_tbl.data_payload is a CLOB data type then you want to loop through the payload reading substring chunks (without needing to store the substrings in a temporary variable).
If you want to write more than 1024 characters at one time then you need to specify the 4th argument to UTL_FILE.FOPEN(location, filename, openmode, max_linesize) as the default is 1024.
Something like this (untested as I don't have your tables or a directory to write to):
DECLARE
l_index PLS_INTEGER := 0;
CURSOR payload_cur IS
SELECT data_payload,
request_reference
FROM temp_tbl
WHERE message_type = 'ORDERCREATE'
AND ROWNUM < 2;
TYPE payload_typ IS TABLE OF payload_cur%rowtype;
l_col_payload payload_typ;
BEGIN
OPEN payload_cur;
LOOP
-- In general, you should limit the number of rows you process at one time.
-- In your case this is not necessary due to the `ROWNUM < 2` filter.
FETCH payload_cur BULK COLLECT INTO l_col_payload LIMIT 10;
EXIT WHEN payload_cur%NOTFOUND;
FOR i IN 1..l_col_payload.count LOOP
l_index := l_index + 1;
DECLARE
l_file_name VARCHAR2(100);
c_amt CONSTANT PLS_INTEGER := 32000;
l_length PLS_INTEGER := COALESCE(dbms_lob.getlength(l_col_payload(i).data_payload),0);
l_offset PLS_INTEGER := 1;
l_fhandle utl_file.file_type;
BEGIN
l_file_name := l_col_payload(i).request_reference || '_' || l_index || '.json';
dbms_output.put_line('l_file_name' || l_file_name);
l_fhandle := utl_file.fopen('TMP_DIR', l_file_name, 'w', 32760);
WHILE ( l_offset <= l_length ) LOOP
utl_file.put(
l_fhandle,
dbms_lob.substr(l_col_payload(i).data_payload,c_amt,l_offset)
);
utl_file.fflush(l_fhandle);
l_offset := l_offset + c_amt;
END LOOP;
utl_file.fclose(l_fhandle);
END;
END LOOP;
END LOOP;
CLOSE payload_cur;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('ERROR: ' || sqlcode || ' - ' || sqlerrm);
RAISE;
END;
/
I have defined a user-defined exception but my function is not raising the exception .
****Spec*****
FUNCTION get_property_id (
p_owner IN app.bc_entities.entity_id%TYPE,
p_object_type_id IN app.bc_custom_attribute_masters.object_type_id%TYPE,
p_custom_attribute_master_id IN
app.bc_custom_attribute_masters.custom_attribute_master_id%TYPE,
p_non_rev_controlled_p IN NUMBER DEFAULT 0
)
RETURN NUMBER;
****BODY*****
FUNCTION get_custom_attribute_master_id (
p_owner IN app.bc_entities.entity_id%TYPE,
p_object_type_id IN app.bc_custom_attribute_masters.object_type_id%TYPE,
p_property_id IN NUMBER,
p_non_rev_controlled_p IN NUMBER DEFAULT 0
)
RETURN app.bc_custom_attribute_masters.custom_attribute_master_id%TYPE
IS
l_custom_attribute_master_id
APP.BC_CUSTOM_ATTRIBUTE_MASTERS.CUSTOM_ATTRIBUTE_MASTER_ID%TYPE;
l_object_property_id NUMBER := 0;
v_sql varchar2(32767);
r_error bc_errors%rowtype;
TYPE templatecurtype IS REF CURSOR;
c_result templatecurtype;
l_column_name VARCHAR2(50);
invalid_property_id EXCEPTION ;
BEGIN
r_error.code_unit := 'Get_Custom_Attribute_Master_Id';
r_error.error_context := 'Calling Get_Custom_Attribute_Master_Id ';
r_error.error_data := ' Get_Custom_Attribute_Master_Id :Property Id :'||to_char(p_property_id)||', OBJECT_ID:'||to_char(p_object_type_id);
FOR V_ROW IN
(SELECT * FROM app.CUSTOM_PROPERTY_TABLES_DEFS WHERE object_type_id =
p_object_type_id AND
(
(p_object_type_id = 20 AND REV_CONTROLLED_P = p_non_rev_controlled_p) OR (p_object_type_id <> 20 AND NVL(REV_CONTROLLED_P,0) = NVL(p_non_rev_controlled_p,0) ) )
)
LOOP
IF v_row.FIRST_PROPERTY_COLUMN_NAME IS NOT NULL THEN
l_column_name := v_row.FIRST_PROPERTY_COLUMN_NAME;
ELSE
l_column_name := v_row.PROPERTY_COLUMN_NAME;
END IF;
v_sql := ' SELECT civ.'||l_column_name || ' FROM '||v_row.table_name ||' civ WHERE '||
v_row.property_column_name ||' = :property_id ';
dbms_output.put_line ( '1-v_sql '||v_sql);
r_error.error_data := v_sql;
OPEN c_result FOR v_sql
USING p_property_id ;
FETCH c_result INTO l_object_property_id ;
CLOSE c_result ;
IF l_object_property_id IS NULL THEN
RAISE invalid_property_id;
END IF;
v_sql :='Select civ.custom_attribute_master_id FROM ' || v_row.table_name||' civ WHERE civ.' || l_column_name || '= :property_id and civ.custom_attribute_master_id is not null
and rownum = 1';
dbms_output.put_line ( '2-v_sql '||v_sql);
OPEN c_result FOR v_sql USING l_object_property_id ;
FETCH c_result INTO l_custom_attribute_master_id;
CLOSE c_result ;
RETURN l_custom_attribute_master_id ;
END LOOP;
EXCEPTION
WHEN invalid_property_id THEN
r_error.error_stack := dbms_utility.format_call_stack()||chr(10)||dbms_utility.format_error_backtrace();
r_error.message := ' p_property_id ( '||p_property_id||' ) is invalid ';
r_error.error_code := SQLCODE;
r_error.notified_p := null;
PKG_ERROR.LOG(p_code_unit=>r_error.code_unit
,p_message=>r_error.message
,p_application=>V_APPLICATION
,p_database_name=>V_DB_NAME
,p_error_owner=>p_owner
,p_error_user=>p_owner
,p_error_module=>V_MODULE
,p_error_object=>V_APP_OBJECT
,p_error_type=>app.pkg_error.error
,p_error_code=>r_error.error_code
,p_error_stack=>r_error.error_stack
,p_error_context=>r_error.error_context
,p_error_data=>r_error.error_data
,p_notified_p=>r_error.notified_p);
RAISE_APPLICATION_ERROR ( -20098, r_error.message ) ;
WHEN OTHERS THEN
r_error.error_stack := dbms_utility.format_call_stack()||chr(10)||dbms_utility.format_error_backtrace();
r_error.message := SQLERRM;
r_error.error_code := SQLCODE;
r_error.notified_p := null;
PKG_ERROR.LOG(p_code_unit=>r_error.code_unit
,p_message=>r_error.message
,p_application=>V_APPLICATION
,p_database_name=>V_DB_NAME
,p_error_owner=>p_owner
,p_error_user=>p_owner
,p_error_module=>V_MODULE
,p_error_object=>V_APP_OBJECT
,p_error_type=>app.pkg_error.error
,p_error_code=>r_error.error_code
,p_error_stack=>r_error.error_stack
,p_error_context=>r_error.error_context
,p_error_data=>r_error.error_data
,p_notified_p=>r_error.notified_p);
RAISE;
END get_custom_attribute_master_id;
with TdmBCElections.Create(Self) do
begin
with dmBCElections, qryParties do
begin
SQL.Clear;
if rgpParty.ItemIndex = 0 then
SQL.Text := 'SELECT StrConv(P_Surname, 3), StrConv(P_Names, 3) ' +
'FROM Parties WHERE P_Type = "HEAD"'
else
SQL.Text := 'SELECT StrConv(P_Surname, 3), StrConv(P_Names, 3) ' +
'FROM Parties WHERE P_Type = "TEACHER"';
Open;
while not Eof do
begin
cmbDetails.Items.Add(qryParties['StrConv(P_Surname, 3)'] + ', ' +
qryParties['StrConv(P_Names, 3)']);
Next;
end;
end;
end;
The code above gives me the following error message:
How do I call the table fields when StrConv is applied to them?
You can assign an alias to the fields:
with TdmBCElections.Create(Self) do
begin
with dmBCElections, qryParties do
begin
if rgpParty.ItemIndex = 0 then
SQL.Text := 'SELECT StrConv(P_Surname, 3) as ConvertedSurname, StrConv(P_Names, 3) as ConvertedNames ' +
'FROM Parties WHERE P_Type = "HEAD"'
else
SQL.Text := 'SELECT StrConv(P_Surname, 3) as ConvertedSurname, StrConv(P_Names, 3) as ConvertedNames ' +
'FROM Parties WHERE P_Type = "TEACHER"';
Open;
while not Eof do
begin
cmbDetails.Items.Add(qryParties['ConvertedSurname'] + ', ' +
qryParties['ConvertedNames']);
Next;
end;
end;
end;
Otherwise, you can use field indexes instead of names:
with TdmBCElections.Create(Self) do
begin
with dmBCElections, qryParties do
begin
if rgpParty.ItemIndex = 0 then
SQL.Text := 'SELECT StrConv(P_Surname, 3), StrConv(P_Names, 3) ' +
'FROM Parties WHERE P_Type = "HEAD"'
else
SQL.Text := 'SELECT StrConv(P_Surname, 3), StrConv(P_Names, 3) ' +
'FROM Parties WHERE P_Type = "TEACHER"';
Open;
while not Eof do
begin
cmbDetails.Items.Add(qryParties.Fields[0].AsString + ', ' + qryParties.Fields[1].AsString);
Next;
end;
end;
end;
Either way, I suggest you consider using a parameterized query instead:
SQL.Text := 'SELECT ... FROM Parties WHERE P_Type = :PType';
if rgpParty.ItemIndex = 0 then
Parameters.ParamByName('PType').Value := 'HEAD'
else
Parameters.ParamByName('PType').Value := 'TEACHER';
I'm using the script below in order to fetch JSON file from MongoDB, parse it and then insert it into Oracle table.
The script works fine in a sense that it inserts all values correctly into Oracle table. That includes the value Photo which is an image of base64 formate and it is much larger than 32KB.
The column Photo in the table Appery_Photos is of the type CLOB while column DecodedPhoto is of the type BLOB.
The problem lies in the line blobOriginal := base64decode1(Photo); which I used to decode the CLOB into BLOB. The function base64decode1 has been replaced with several functions (i.e. decode_base64 , base64DecodeClobAsBlob_plsql, base64decode , from_base64 & finally JSON_EXT.DECODE).
The result was the same for all of them. That is, the resultant BLOB object cannot be openned as an image in any of images editors (I'm using Oracle SQL Developer to download it).
I checked CLOB, and I could not find any newlines \n, nor could I find any spaces (only + signs found). Furthermore, I inserted CLOB value into the base64-image-converter and it displays the image correctly. In addition, I tried to encode the resultant BLOB in base64 back in order to further validate (using the opposite functions provided in the links above), the resultant base64 is not the same at all.
BEGIN
l_http_request := UTL_HTTP.begin_request('https://api.appery.io/rest/1/db/collections/Photos?where=%7B%22Oracle_Flag%22%3A%22Y%22%7D' , 'GET' , 'HTTP/1.1');
-- ...set header's attributes
UTL_HTTP.set_header(l_http_request, 'X-Appery-Database-Id', '53f2dac5e4b02cca64021dbe');
l_http_response := UTL_HTTP.get_response(l_http_request);
BEGIN
LOOP
UTL_HTTP.read_text(l_http_response, buf);
l_response_text := l_response_text || buf;
END LOOP;
EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
NULL;
END;
l_list := json_list(l_response_text);
FOR i IN 1..l_list.count
LOOP
A_id := json_ext.get_string(json(l_list.get(i)),'_id');
l_val := json_ext.get_json_value(json(l_list.get(i)),'Photo');
dbms_lob.createtemporary(Photo, true, 2);
json_value.get_string(l_val, Photo);
dbms_output.put_line(dbms_lob.getlength(Photo));
dbms_output.put_line(dbms_lob.substr(Photo, 20, 1));
blobOriginal := base64decode1(Photo);
A_Name := json_ext.get_string(json(l_list.get(i)),'Name');
Remarks := json_ext.get_string(json(l_list.get(i)),'Remarks');
Status := json_ext.get_string(json(l_list.get(i)),'Status');
UserId := json_ext.get_string(json(l_list.get(i)),'UserId');
A_Date := json_ext.get_string(json(l_list.get(i)),'Date');
A_Time := json_ext.get_string(json(l_list.get(i)),'Time');
MSG_status := json_ext.get_string(json(l_list.get(i)),'MSG_status');
Oracle_Flag := json_ext.get_string(json(l_list.get(i)),'Oracle_Flag');
acl := json_ext.get_string(json(l_list.get(i)),'acl');
INSERT
INTO Appery_Photos
(
A_id,
Photo,
DecodedPhoto,
A_Name,
Remarks,
Status,
UserId,
A_Date,
A_Time,
MSG_status ,
Oracle_Flag,
acl
)
VALUES
(
A_id,
Photo,
blobOriginal,
A_Name,
Remarks,
Status,
UserId,
A_Date,
A_Time,
MSG_status ,
Oracle_Flag,
acl
);
dbms_lob.freetemporary(Photo);
END LOOP;
-- finalizing
UTL_HTTP.end_response(l_http_response);
EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
UTL_HTTP.end_response(l_http_response);
END;
Any help is deeply appreciated.
I found that is not in the function I used in the base64 decoding. Instead, the value I have is not base64 encoded strings, but base64 encode dataURi's, something like
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAA
So I have to use something like: clobbase642blob( substr( Photo, instr( Photo, ',' ) + 1 ) )
The following script is inspired by Oracle Community answer
DECLARE
l_param_list VARCHAR2(512);
l_http_request UTL_HTTP.req;
l_http_response UTL_HTTP.resp;
l_response_text CLOB;
--l_response_text VARCHAR2(32767);
buf VARCHAR2(32767);
l_list json_list;
l_val json_value;
A_id VARCHAR2(100);
Photo CLOB;
A_Name VARCHAR2(100);
Remarks VARCHAR2(100);
Status VARCHAR2(100);
UserId VARCHAR2(100);
A_Date VARCHAR2(100);
A_Time VARCHAR2(100);
MSG_status VARCHAR2(100);
Oracle_Flag VARCHAR2(100);
acl VARCHAR2(100);
obj json_list;
blobOriginal BLOB := empty_blob();
clobInBase64 CLOB;
substring VARCHAR2(2000);
tmp BLOB;
n pls_integer := 0;
substring_length pls_integer := 2000;
------------------------------------------------------
FUNCTION clobbase642blob(
p_clob CLOB )
RETURN BLOB
IS
t_blob BLOB;
t_buffer VARCHAR2(32767);
t_pos NUMBER := 1;
t_size NUMBER := nls_charset_decl_len( 32764, nls_charset_id( 'char_cs' ) );
t_len NUMBER;
t_tmp raw(32767);
BEGIN
dbms_lob.createtemporary( t_blob, true );
t_len := LENGTH( p_clob );
LOOP
EXIT
WHEN t_pos > t_len;
t_buffer := REPLACE( REPLACE( SUBSTR( p_clob, t_pos, t_size ), chr(10) ), chr(13) );
t_pos := t_pos + t_size;
WHILE t_pos 0
LOOP
t_buffer := t_buffer || REPLACE( REPLACE( SUBSTR( p_clob, t_pos, 1 ), chr(10) ), chr(13) );
t_pos := t_pos + 1;
END LOOP;
t_tmp := utl_encode.base64_decode( utl_raw.cast_to_raw( t_buffer ) );
dbms_lob.writeappend( t_blob, utl_raw.length( t_tmp ), t_tmp );
END LOOP;
RETURN t_blob;
END;
------------------------------------------------------
BEGIN
-- service's input parameters
-- preparing Request...
l_http_request := UTL_HTTP.begin_request('https://api.appery.io/rest/1/db/collections/Photos?where=%7B%22Oracle_Flag%22%3A%22Y%22%7D' , 'GET' , 'HTTP/1.1');
-- ...set header's attributes
UTL_HTTP.set_header(l_http_request, 'X-Appery-Database-Id', '53f2dac5e4b02cca64021dbe');
l_http_response := UTL_HTTP.get_response(l_http_request);
BEGIN
LOOP
UTL_HTTP.read_text(l_http_response, buf);
l_response_text := l_response_text || buf;
END LOOP;
EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
NULL;
END;
l_list := json_list(l_response_text);
FOR i IN 1..l_list.count
LOOP
A_id := json_ext.get_string(json(l_list.get(i)),'_id');
--deal with base64 URI photo >32KB
l_val := json_ext.get_json_value(json(l_list.get(i)),'Photo');
dbms_lob.createtemporary(Photo, true, 2);
json_value.get_string(l_val, Photo);
--dbms_output.put_line(dbms_lob.getlength(Photo));
--dbms_output.put_line(dbms_lob.substr(Photo, 20, 1));
blobOriginal := clobbase642blob( SUBSTR( Photo, 24 ) );
A_Name := json_ext.get_string(json(l_list.get(i)),'Name');
Remarks := json_ext.get_string(json(l_list.get(i)),'Remarks');
Status := json_ext.get_string(json(l_list.get(i)),'Status');
UserId := json_ext.get_string(json(l_list.get(i)),'UserId');
A_Date := json_ext.get_string(json(l_list.get(i)),'Date');
A_Time := json_ext.get_string(json(l_list.get(i)),'Time');
MSG_status := json_ext.get_string(json(l_list.get(i)),'MSG_status');
Oracle_Flag := json_ext.get_string(json(l_list.get(i)),'Oracle_Flag');
acl := json_ext.get_string(json(l_list.get(i)),'acl');
INSERT
INTO Appery_Photos
(
A_id,
Photo,
DecodedPhoto,
A_Name,
Remarks,
Status,
UserId,
A_Date,
A_Time,
MSG_status ,
Oracle_Flag,
acl
)
VALUES
(
A_id,
Photo,
blobOriginal,
A_Name,
Remarks,
Status,
UserId,
A_Date,
A_Time,
MSG_status ,
Oracle_Flag,
acl
);
dbms_lob.freetemporary(Photo);
END LOOP;
-- finalizing
UTL_HTTP.end_response(l_http_response);
EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
UTL_HTTP.end_response(l_http_response);
END;
/
It is Base64 or HexBinary...
This works for HexBinary
function DESERIALIZE_HEX_BLOB(P_SERIALIZATION CLOB)
return BLOB
is
V_BLOB BLOB;
V_OFFSET INTEGER := 1;
V_AMOUNT INTEGER := 32000;
V_INPUT_LENGTH NUMBER := DBMS_LOB.GETLENGTH(P_SERIALIZATION);
V_HEXBINARY_DATA VARCHAR2(32000);
begin
if (P_SERIALIZATION is NULL) then return NULL; end if;
DBMS_LOB.CREATETEMPORARY(V_BLOB,TRUE,DBMS_LOB.CALL);
while (V_OFFSET <= V_INPUT_LENGTH) loop
V_AMOUNT := 32000;
DBMS_LOB.READ(P_SERIALIZATION,V_AMOUNT,V_OFFSET,V_HEXBINARY_DATA);
V_OFFSET := V_OFFSET + V_AMOUNT;
DBMS_LOB.APPEND(V_BLOB,TO_BLOB(HEXTORAW(V_HEXBINARY_DATA)));
end loop;
return V_BLOB;
end;
--
And could probably be modified to handle Base64 without too much trouble.
All I want to do is to implement "Export to excel" option of a classical webbrowser, to Delphi2007 commands...... When I am using this option from a webbrowser to export a 12000 rows table it takes less than a minute to export the table from any web browser from windows. Trying to implement this in Delphi using 2D Array it takes 10 minutes... Trying to implement the export with parsing technique (Stringlists, strings, Pos(tr), pos (td) & some other string functions) it takes a long... Hence, which are the commands of a webbrowser to export an html table to excel that I have to convert them to Delphi? Should I use javascript inside Delphi? Should I use pointers? Should I use HTML entities? xml?...Any ideas? Thank you in advance.
2D ARRAY
Excel:= CreateOleObject('Excel.Application');
ovTable := WebBrowser1.OleObject.Document.all.tags('TABLE').item(0);
arrayn:=VarArrayCreate([1, ovTable.Rows.Length, 1, ovTable.Rows.Item(1).Cells.Length], varvariant);
for i:=0 to (ovTable.Rows.Length - 1) do
begin
for j := 0 to (ovTable.Rows.Item(i).Cells.Length - 1) do
Begin
arrayn[i+1, j+1]:=ovTable.Rows.Item(i).Cells.Item(j).InnerText;
Application.ProcessMessages;
end;end;
WS.range[ws.cells[1, 1], ws.cells[ovTable.Rows.Length, ovTable.Rows.Item(1).Cells.Length]].value:=arrayn;
Excel.WorkBooks[1].SaveAs(directorylistbox1.Directory+'\'+'test.xlsx');
WS := Excel.WorkBooks.close;
Excel.quit;
Excel:=unassigned;
HTML PARSING
function HTMLCleanUp(L : string) : string;
const
CSVTempSeparator = #255; //replaced by a comma
CRLF = #13#10;
var
P1,P2 : integer;
begin
P1 := Pos('<',L); //clean-up anything between <>
while (P1>0) do //WHILE1
begin
P2 := Pos('>',L);
if (P2>0)
then Begin Delete(L,P1,P2-P1+1); end;
P1 := Pos('<',L);
end; //WHILE1
L:=StringReplace(L,' ','-',[rfReplaceAll]);
L:=StringReplace(L,'-01','',[rfReplaceAll]);
L:=StringReplace(L,'-02','',[rfReplaceAll]);
L:=StringReplace(L,'-03','',[rfReplaceAll]);
Result := Trim(L);
end;
function HTMLTableToCSV(HTML,CSV : TStringList) : boolean;
const
CRLF = #13#10;
CSVTempSeparator = #9;
var
P1,P2,P3,P4, p5, P6, p11, p22 : integer;
S,TmpStr,CSVStr : string;
begin
Result := True;
S := Trim(StringReplace(HTML.Text,CRLF,'',[rfReplaceAll]));
P1 := PosEx('<TR',S, 1); //CASE SENSITIVE , TR->FIRST ROW
CSVStr := '';
while (P1>0) do //while1
begin
P2 := PosEx('</TR',S, P1);
if (P2>0) //if1
then begin
TmpStr := Copy(S,P1,P2-P1+1);
//Delete(S,P1,P2-P1+1);
CSVStr := ''; p11:=1;p22:=1;
P11 := PosEx('<TH',TmpStr,1);
while (P11>0) do //while2
begin
P22 := PosEx('</TH',TmpStr, P11);
if (P22>0) //if2
then begin
CSVStr :=
//CSVStr+Trim(Copy(TmpStr,P1+4,P2-P1-4));//+CSVTempSeparator;
CSVStr+Trim(Copy(TmpStr,P11,P22-P11))+CSVTempSeparator;
//Delete(TmpStr,P1,P2-P1+1);
end //if2
else begin
Result := False;
Exit;
end; //if2
P11 := PoseX('<TH',TmpStr, P22);
end; //while2
P11 := PosEx('<TD',TmpStr, 1);
while (P11>0) do //while2
begin
P22 := PosEx('</TD',TmpStr, P11);
if (P22>0) //if2
then begin
CSVStr :=
//CSVStr+Trim(Copy(TmpStr,P1+4,P2-P1-4));//+CSVTempSeparator;
CSVStr+Trim(Copy(TmpStr,P11,P22-P11))+CSVTempSeparator;
//Delete(TmpStr,P1,P2-P1+1);
end //if2
else begin
Result := False;
Exit;
end; //if2
P11 := PosEx('<TD',TmpStr,P22);
end; //while2
end //if1
else begin
Result:=false;
exit;
end; //if1
CSV.Add(HTMLCleanUp(CSVStr));
P1 := PosEx('<TR',S,P2); //CASE SENSITIVE
end; //while1
end;
procedure TForm11.Button1Click(Sender: TObject);
const
xlExcel7 = $00000027;
TmpFileName='c:\test\Test.txt';
VAR
Excel: Olevariant;
HTMLStrList,CSVSTRList : TStringList;
begin
HTMLStrList := TStringList.Create;
try
HTMLStrList.LoadFromFile('C:\test\TestTable1.htm');
CSVSTRList := TStringList.Create;
try
if HTMLTableToCSV(HTMLStrList,CSVSTRList)
then Begin
CSVSTRList.SaveToFile(TmpFileName);
Excel:= CreateOleObject('Excel.Application');
Excel.WorkBooks.opentext(TmpFileName);//OPEN TXT WITH EXCEL
Excel.DisplayAlerts := False;
Excel.WorkBooks[1].SaveAs('c:\test\Nisa.xls', xlExcel7);//SAVE TAB DELIMITED TEXT FILE
Excel.WorkBooks[1].close;
Excel.quit;
Excel:=unassigned;
End
else ShowMessage('Error converting HTML table to CSV');
finally
CSVSTRList.Free;
end;
finally
HTMLStrList.Free;
DeleteFile(TmpFileName);
end;
end;
procedure TForm11.FormCreate(Sender: TObject);
begin
webBrowser1.Navigate('http://samples.msdn.microsoft.com/workshop/samples/author/tables/HTML_ Table.htm');
end;
procedure TForm11.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
var
Document: IHtmlDocument2;
CurWebrowser : IWebBrowser;
TopWebBrowser: IWebBrowser;
WindowName : string;
begin
CurWebrowser := pDisp as IWebBrowser;
TopWebBrowser := (ASender as TWebBrowser).DefaultInterface;
if CurWebrowser=TopWebBrowser then
begin
document := webbrowser1.document as IHtmlDocument2;
memo3.lines.add(trim(document.body.innerhtml)); // to get html
ShowMessage('Document is complete.')
end;
end;
end.
I found the solution...HTML Table Parsing in Less than a second!
function HTMLCleanUp(L : string) : string;
var
P1,P2 : integer;
begin
P1 := Pos('<',L); //clean-up anything between <>
while (P1>0) do //WHILE1
begin
P2 := Pos('>',L);
if (P2>0)
then Begin Delete(L,P1,P2-P1+1); end;
P1 := Pos('<',L);
end; //WHILE1
L:=StringReplace(L,' ','-',[rfReplaceAll]);
Result := Trim(L);
end;
procedure TForm11.WB_SaveAs_HTML(WB : TWebBrowser; const FileName : string) ;
var
PersistStream: IPersistStreamInit;
Stream: IStream;
FileStream: TFileStream;
begin
if not Assigned(WB.Document) then
begin
ShowMessage('Document not loaded!') ;
Exit;
end;
PersistStream := WB.Document as IPersistStreamInit;
FileStream := TFileStream.Create(FileName, fmCreate) ;
try
Stream := TStreamAdapter.Create(FileStream, soReference) as IStream;
if Failed(PersistStream.Save(Stream, True)) then ShowMessage('SaveAs HTML fail!') ;
finally
FileStream.Free;
end;
end; (* WB_SaveAs_HTML *)
procedure TForm11.Button1Click(Sender: TObject);
const
xlExcel7 = $00000027;
TmpFileName='c:\test\xxxx.txt';
CRLF = #13#10;
CSVTempSeparator = #9; //#255; //replaced by a comma
ADPNEWHOTURL = 'http://samples.msdn.microsoft.com/workshop/samples/author/tables/HTML_Table.htm';
VAR
Excel, WS: Olevariant;
P1,P2,P3,P4, p5, P6, p11, p22 : integer;
i, j: Integer;
buffer,rawHTM,TmpStr,CSVStr:string;
HTMFile : TextFile;
CSVSTRList : TStringList;
begin
CSVSTRList := TStringList.Create;
WB_SaveAs_HTML(WebBrowser1,TmpFileName) ;
AssignFile(HTMFile, TmpFileName);//read the HTML file
Reset(HTMFile);
while not EOF(HTMFile) do begin
ReadLn(HTMFile, buffer);
rawHTM := Concat(rawHTM, buffer);
end;
i:=1;j:=1;
rawHTM := Trim(StringReplace(rawHTM,CRLF,'',[rfReplaceAll]));
P1 := PosEx('<TR',rawHTM, 1); //CASE SENSITIVE , TR->FIRST ROW
while (P1>0) do //while1
begin
P2 := PosEx('</TR',rawHTM, P1);
if (P2>0) //if1
then begin
TmpStr := Copy(rawHTM,P1,P2-P1+1);
CSVStr := '';p11:=1;p22:=1;
P11 := PosEx('<TH',TmpStr,1);
while (P11>0) do //while2
begin
P22 := PosEx('</TH',TmpStr, P11);
if (P22>0) //if2
then begin
CSVStr :=CSVStr+
HTMLCleanUp(Trim(Copy(TmpStr,P11,P22-P11)))+CSVTempSeparator; j:=j+1;
end //if2
else begin
Exit;
end; //if2
P11 := PoseX('<TH',TmpStr, P22);
end; //while2
P11 := PosEx('<TD',TmpStr, 1);
while (P11>0) do //while2
begin
P22 := PosEx('</TD',TmpStr, P11);
if (P22>0) //if2
then begin
CSVStr :=CSVStr+
HTMLCleanUp(Trim(Copy(TmpStr,P11,P22-P11)))+CSVTempSeparator; j:=j+1;
end //if2
else begin
Exit;
end; //if2
P11 := PosEx('<TD',TmpStr,P22);
end; //while2
end //if1
else begin
exit;
end; //if1
CSVSTRList.Add(CSVStr);
P1 := PosEx('<TR',rawHTM,P2); i:=i+1; j:=1; //CASE SENSITIVE
end; //while1
CSVSTRList.SaveToFile('c:\test\xxx2.txt');
Excel:= CreateOleObject('Excel.Application');
Excel.WorkBooks.opentext('c:\test\xxx2.txt');//OPEN TXT WITH EXCEL
Excel.visible := True;
CloseFile(HTMFile);
DeleteFile(TmpFileName);
end;