Related
I have a view (DB_ADMIN.VW_DBA_MONITOR_CURRENTLYEXEC) to be able to visualize the transactions in Oracle. This view makes use of those already existing in the system: v$sql, V$SQLSTATS, v$sqlarea and gv$process.
And the structure and type of columns was as follows:
I have a procedure that attempts to use such a view, format the output to a single HTML string under certain conditions, and send it via email. Here is a simple summary of that procedure:
DECLARE
V_BODY_TEXT CLOB;
BEGIN
select utl_i18n.unescape_reference(xmlagg(xmlelement(e,'
<tr>
<td>' || ltrim(rtrim(TO_CLOB(SID))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(SQL_ID))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(USERNAME))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(ONAME))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(MACHINE))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(WAIT_CLASS))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(EVENT))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(MODULE))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(LOGON_TIME))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(START_TIME))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(TIME))) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(SQL_TEXT))) || '</th>
</tr>
','').extract('//text()')).getclobval()) x
INTO V_BODY_TEXT
FROM DB_ADMIN.VW_DBA_MONITOR_CURRENTLYEXEC WHERE "TIME" > 300;
END;
The error presented by the execution of the above code is because the concatenated result only admits 4000 characters and it doesn't matter if I try to cast (TO_CLOB) field by field or the entire concatenated string, I still get the same error every time. result size is exceeded:
About the above code, the use of the TO_CLOB function, is my last attempt to try to solve the problem.
Said error does not allow any "substring" or "len" type action to be applied to it either.
I've also tried creating a table with all the fields of type CLOB, then inserting the result of the view, and using the above procedure now with the table, but I keep getting the same error every time the generated html code exceeds 4000 characters.
Therefore, I go to this community to see what other options they give me to get out of the problem.
The xmlelement function only accepts up to 4000 characters if the initialization parameter MAX_STRING_SIZE = STANDARD, and 32767 characters if MAX_STRING_SIZE = EXTENDED, so there's no point in converting to CLOB that early in your process. See here.
Also, the utl_i18n.unescape_reference function only accepts VARCHAR2 input, not CLOB. See here. This is the most likely place your query is failing.
Try this, working exclusively with VARCHAR2, doing explicit conversions from NUMBER and DATE types, and saving the aggregation into the largest string to the very end:
DECLARE
V_BODY_TEXT CLOB;
BEGIN
select xmlagg(utl_i18n.unescape_reference(xmlelement(e,'
<tr>
<td>' || TO_CHAR(SID) || '</th>
<td>' || ltrim(rtrim(SQL_ID)) || '</th>
<td>' || ltrim(rtrim(USERNAME)) || '</th>
<td>' || ltrim(rtrim(ONAME)) || '</th>
<td>' || ltrim(rtrim(MACHINE)) || '</th>
<td>' || ltrim(rtrim(WAIT_CLASS)) || '</th>
<td>' || ltrim(rtrim(EVENT)) || '</th>
<td>' || ltrim(rtrim(MODULE)) || '</th>
<td>' || TO_CHAR(LOGON_TIME,'DD-MON-YYYY') || '</th>
<td>' || TO_CHAR(START_TIME,'DD-MON-YYYY') || '</th>
<td>' || TO_CHAR(TIME) || '</th>
<td>' || ltrim(rtrim(TO_CLOB(SQL_TEXT))) || '</th>
</tr>
',''))) x
INTO V_BODY_TEXT
FROM DB_ADMIN.VW_DBA_MONITOR_CURRENTLYEXEC WHERE "TIME" > 300;
END;
XMLType methods are deprecated since Oracle 11gr2, use modern (though, it's hard to tell about XML) SQL functions to access XML data without additional serialization/deserialization issues:
XMLQUERY for extraction.
XMLCAST for deserialization. It does decoding of HTML/XML stuff like & etc to their text values.
Below is an example of three characters padded with zeroes up to 5000 bytes (so resulting in too large value for varchar2)
with t(col) as (
select
xmlelement(e,
to_clob(rpad('<>&', 4000, '0'))
|| to_clob(rpad('0', 1000, '0'))
)
from dual
)
, deser as (
select
xmlcast(xmlquery(
'//text()'
passing col
returning content
) as clob) as res
from t
)
select
length(res),
trim(trailing '0' from res) as res
from deser
LENGTH(RES)
RES
5000
<>&
fiddle
However, you may avoid concatenation at all and serialize SQL result directly into XML with dbms_xmlgen package. Below is an example:
with function f_get_html(
p_rows int
)
return clob
as
l_ctx number;
l_cursor sys_refcursor;
l_res clob;
begin
/* Cursor object is used to be able to use parameters for statement.
In case of static statement may be replaced by string literal
containing SQL statement directly in newcontext call
*/
open l_cursor for
select
dummy as "td", level as "td",
'<>&' as "td"
from dual
connect by level < p_rows
;
/*Use context to set row tags to <tr>*/
l_ctx := dbms_xmlgen.newcontext(
l_cursor
);
dbms_xmlgen.setrowtag(l_ctx, 'tr');
/*Extract content as HTML-formatted clob*/
select xmlserialize(content xmlquery(
'/ROWSET/*'
passing dbms_xmlgen.getxmltype(l_ctx)
returning content
) as clob)
into l_res
from dual;
return l_res;
end;
select f_get_html(4) as res_html
from dual
RES_HTML
<tr><td>X</td><td>1</td><td><>&</td></tr><tr><td>X</td><td>2</td><td><>&</td></tr><tr><td>X</td><td>3</td><td><>&</td></tr>
fiddle
Finally, the solution I found to avoid the various restrictions of the functions used to generate xml code or converters like the utl_i18n.unescape_reference function, was to use a loop with FOR:
DECLARE
V_BODY CLOB;
V_TABLE_BODY CLOB := NULL;
BEGIN
--> DataSource:
FOR r IN (
SELECT SID,
SQL_ID,
USERNAME,
ONAME,
MACHINE,
WAIT_CLASS,
EVENT,
MODULE,
LOGON_TIME,
START_TIME,
TIME,
SQL_TEXT
FROM DB_ADMIN.VW_DBA_MONITOR_CURRENTLYEXEC
WHERE "TIME" > 300
)
LOOP
--> Table Body:
V_TABLE_BODY := V_TABLE_BODY ||
'<tr>'||
'<td>'||trim(r.SID)||'</td>' ||
'<td>'||trim(r.SQL_ID)||'</td>' ||
'<td>'||trim(r.USERNAME)||'</td>' ||
'<td>'||trim(r.ONAME)||'</td>' ||
'<td>'||trim(r.MACHINE)||'</td>' ||
'<td>'||trim(r.WAIT_CLASS)||'</td>' ||
'<td>'||trim(r.EVENT)||'</td>' ||
'<td>'||trim(r.MODULE)||'</td>' ||
'<td>'||trim(r.LOGON_TIME)||'</td>' ||
'<td>'||trim(r.START_TIME)||'</td>' ||
'<td>'||trim(r.TIME)||'</td>' ||
'<td>'||trim(r.SQL_TEXT)||'</td>' ||
'</tr>';
END LOOP;
----
IF V_TABLE_BODY IS NOT NULL THEN
--> HTML Body:
V_BODY :=
'
<body>
<table border="1">
<tr>
<th>SID</th>
<th>SQL_ID</th>
<th>USERNAME</th>
<th>ONAME</th>
<th>MACHINE</th>
<th>WAIT_CLASS</th>
<th>EVENT</th>
<th>MODULE</th>
<th>LOGON_TIME</th>
<th>START_TIME</th>
<th>TIME</th>
<th>SQL_TEXT</th>
</tr>' ||
V_TABLE_BODY || --> Table Body
'</table>
</body>
';
-- Send Mail:
DB_ADMIN.USP_SEND_MAIL(V_BODY); --> Referential
END IF;
I am getting data in Refcursor and binding in HTML table using for loop to send an email. When I am executive my procedure then it is giving me error of "numeric or value error: character string buffer too small". I have tried CLOB but still not get any success.
Error :-
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "XXCOMM.XX_EMS_MAIL_PRC_MAINTENANCE", line 113
ORA-06512: at line 6
Below id my procedure in which I am trying to send email.
create or replace PROCEDURE XX_EMS_MAIL_PRC_maintenance
(
p_site_id IN VARCHAR2
)
AS
l_in_date VARCHAR2(200) := NULL;
CURSOR cur_maintenance_list (p_site_id VARCHAR2)
IS
SELECT
c.instrument_no "Instrument_Number",
d.instrument_name "Instrument_Name",
d.eqp_srl_no "EQP_Serial_Number",
d.ownership "Ownership",
d.mfg_name "Mfg_Name",
d.model_no "Model_Number",
b.plan "Plan",
a.detail "Plan_Detail",
c.activity_id "Activity_Id",
c.maintenance_date "Maintenance_Date",
c.maintenance_due_date "Maintenance_Due_Date"
FROM
activity_detail a,
activity_master b,
maintenance_schedule c,
instrument_master d,
emp_master m
WHERE
a.plan_id = b.plan_id
AND d.instrument_no = c.instrument_no
AND c.plan_id = b.plan_id
AND c.activity_id = a.activity_id
AND ( c.instrument_no,
c.plan_id,
c.activity_id,
c.maintenance_due_date ) = (
SELECT
e.instrument_no,
e.plan_id,
e.activity_id,
MAX(e.maintenance_due_date)
FROM
maintenance_schedule e
WHERE
c.plan_id = e.plan_id
AND c.instrument_no = e.instrument_no
AND c.activity_id = e.activity_id
GROUP BY
e.instrument_no,
e.plan_id,
e.activity_id
)
AND trunc(c.maintenance_due_date) BETWEEN to_date('01-JUL-2022', 'DD-MON-RRRR') AND to_date('01-JUL-2022', 'DD-MON-RRRR')
AND d.p_manitenance_req <> 'N'
AND ( m.emp_initial = d.prepared_by
OR to_char(m.emp_no) = d.prepared_by )
AND ( b.site_id = d.site_id
OR d.site_id = '001' );
p_to sys_refcursor;
p_cc sys_refcursor;
p_bcc sys_refcursor;
v_html_msg VARCHAR2(32672) := NULL;
--v_txt_msg varchar2(250) := 'WelCome ....'||chr(10)||'this is a test mail';
v_to VARCHAR2 (1000) := NULL;
v_cc VARCHAR2 (1000) := NULL;
v_bcc VARCHAR2 (2000) := NULL;
v_from VARCHAR2 (150) := 'abc.com';
v_db_name varchar2(25) := null;
BEGIN
BEGIN
v_db_name := null;
SELECT listagg(EMAIL, ',') WITHIN GROUP (order by Email) INTO v_to FROM sml.xx_lsp_email_master WHERE email_type = 'TO' AND function_name = 'EMS_App_maintenance' AND isactive='Y';
SELECT listagg(EMAIL, ',') WITHIN GROUP (order by Email) INTO v_cc FROM sml.xx_lsp_email_master WHERE email_type = 'CC' AND function_name = 'EMS_App_maintenance' AND isactive='Y';
--SELECT listagg(EMAIL, ',') WITHIN GROUP (order by Email) INTO v_bcc FROM sml.xx_lsp_email_master WHERE email_type = 'BCC' AND function_name = 'EMS_App';
v_html_msg :='<html><head></head><body><p>Dear All,<br/><br/>
Please find the details of Maintenance due list for this week in Equipment Management System</p><br/>
<table border=1>
<tr>
<th> I/S Number </th>
<th> Instrument Name </th>
<th> EQP Serial Number </th>
<th> Ownership </th>
<th> Make </th>
<th> Model Number </th>
<th> Plan </th>
<th> Plan Detail </th>
<th> Activity Id </th>
<th> Maintenance Date </th>
<th> Maintenance Due Date </th>
</tr>';
FOR i IN cur_maintenance_list (p_site_id)
LOOP
--SELECT (trunc(to_date(i."maintenance_date", 'DD-MON-RRRR') - 15)) into l_in_date from dua
v_html_msg :=
v_html_msg
|| '<tr align="left"><td>'
|| i."Instrument_Number"
|| '</td><td>'
|| i."Instrument_Name"
|| '</td><td>'
|| i."EQP_Serial_Number"
|| '</td><td>'
|| i."Ownership"
|| '</td><td>'
|| i."Mfg_Name"
|| '</td><td>'
|| i."Model_Number"
|| '</td><td>'
|| i."Plan"
|| '</td><td>'
|| i."Plan_Detail"
|| '</td><td>'
|| i."Activity_Id"
|| '</td><td>'
|| i."Maintenance_Date"
|| '</td><td>'
|| i."Maintenance_Due_Date"
|| '</td>
</Tr>';
END LOOP;
v_html_msg :=
v_html_msg
|| '</table>
<p>This is a system generated mail. Please do not reply on this mail.
<br/>
<br/>
Regards,
<br/>
EQP.
</p>
</body>
</html>';
send_mail_recipient(p_to => v_to,
p_cc => v_cc,
p_bcc => v_bcc,
p_from => v_from,
p_subject => 'Mail notification about Maintenance Due for this week Equipment Management System',
--p_text_msg => v_txt_msg,
p_html_msg => v_html_msg,
p_smtp_host => 'smtprelay.com');
END;
null;
END XX_EMS_MAIL_PRC_maintenance;
Line 113 is
v_html_msg :=
v_html_msg
|| '<tr align="left"><td>'
...
That variable is declared as
v_html_msg VARCHAR2(32672)
which means that - as you're processing data in a loop - its length exceeds 32672 characters in length.
What to do? Use CLOB instead, or check whether there's really that much data (i.e. maybe cursor returned more rows than you'd expect - check its SELECT statement).
The main problem is that v_html_msg is defined as VARCHAR2(32672) and the loop is trying to add more than 32672 characters there.
The full solution is to change v_html_msg datatype to CLOB. However, it will require you to refactor this procedure XX_EMS_MAIL_PRC_maintenance and other dependent one, like send_mail_recipient.
The workaround may be to send mail with only partial data (of course if it is OK with your business requirements). You can add an "if" in the loop so once the length of v_html_msg is larger than 32000 - stop adding more items there. For example:
FOR i IN cur_maintenance_list (p_site_id)
LOOP
if length(v_html_msg) <30000 then --<<< NEW CODE
v_html_msg :=
v_html_msg
|| '<tr align="left"><td>'
|| i."Instrument_Number"
|| '</td><td>'
|| i."Instrument_Name"
|| '</td><td>'
|| i."EQP_Serial_Number"
|| '</td><td>'
|| i."Ownership"
|| '</td><td>'
|| i."Mfg_Name"
|| '</td><td>'
|| i."Model_Number"
|| '</td><td>'
|| i."Plan"
|| '</td><td>'
|| i."Plan_Detail"
|| '</td><td>'
|| i."Activity_Id"
|| '</td><td>'
|| i."Maintenance_Date"
|| '</td><td>'
|| i."Maintenance_Due_Date"
|| '</td>
</Tr>';
else --<<< NEW CODE
v_html_msg :=
v_html_msg ||'... and more rows exist...';
end if;
END LOOP;
Do any database engines have the concept of a C-like macro? Here would be an example where I'd just like to make it more readable:
SELECT
SUM(Profit) AS Total,
(SELECT AVG(Profit) FROM This
WHERE Category=This.Category AND Product=This.Product
AND PARSE_DATE('%M %d%, Y', CONCAT(This.Month ' 1, ' This.Year))
BETWEEN PARSE_DATE('%M %d%, Y', CONCAT(This.Month ' 1, ' This.Year))
AND PARSE_DATE('%M %d%, Y', CONCAT(This.Month ' 1, ' This.Year))-INTERVAL 3 MONTH
FROM Tbl
I would rather have something that looks like:
#define dt PARSE_DATE('%M %d%, Y', CONCAT(This.Month ' 1, ' This.Year))
SELECT
SUM(Profit) AS Total,
(SELECT AVG(Profit) FROM This
WHERE Category=This.Category AND Product=This.Product
AND dt BETWEEN dt AND (dt-INTERVAL 3 MONTH)
FROM Tbl
Does something like that exist or commonly-used in the major DBMSs?
From Oracle 12, you can declare a function inside a sub-query factoring (WITH) clause:
WITH FUNCTION dt (month INT, year INT) RETURN DATE AS
BEGIN
RETURN TO_DATE(year || '-' || month || '-01', 'YYYY-MM-DD');
END;
SELECT *
FROM this
WHERE dt(this.month, this.year)
BETWEEN ADD_MONTHS(dt(this.month, this.year), -3)
AND dt(this.month, this.year);
db<>fiddle here
From Oracle 21, you can write SQL macros:
CREATE FUNCTION dt (month INT, year INT)
RETURN VARCHAR2 SQL_MACRO(SCALAR)
AS
BEGIN
RETURN 'TO_DATE(year || ''-'' || month || ''-01'', ''YYYY-MM-DD'')';
END;
/
Then would use it as:
SELECT *
FROM this
WHERE dt(this.month, this.year)
BETWEEN ADD_MONTHS(dt(this.month, this.year), -3)
AND dt(this.month, this.year);
And the query would get rewritten as:
SELECT *
FROM this
WHERE TO_DATE(this.year || '-' || this.month || '-01', 'YYYY-MM-DD')
BETWEEN ADD_MONTHS(TO_DATE(this.year || '-' || this.month || '-01', 'YYYY-MM-DD'), -3)
AND TO_DATE(this.year || '-' || this.month || '-01', 'YYYY-MM-DD');
I'm building a crystal report that ouputs XML-formatted text for a third party application.
The 3rd party app can't handle non-alphanumeric characters, so I have to convert them to HTML to be processed. At present, I use this code to catch the most common characters:
stringvar output := {table.fieldName};
output := Trim(output);
output := Replace (output,chrW(38),"&");
output := Replace (output,chrW(59),";");
output := Replace (output,"!","!");
output := Replace (output,chr(34),""");
output := Replace (output,chrW(35),"#");
output := Replace (output,"$","$");
output := Replace (output,"%","%");
output := Replace (output, chrW(39),"'");
output := Replace (output,"(","(");
output := Replace (output,")",")");
output := Replace (output,"*","*");
output := Replace (output,"+","+");
output := Replace (output,",",",");
output := Replace (output,"-","-");
output := Replace (output,".",".");
output := Replace (output,"/","/");
output := Replace (output,":",":");
output := Replace (output,"<","<");
output := Replace (output,"=","=");
output := Replace (output,">",">");
output := Replace (output,"?","?");
output := Replace (output,"#","#");
output := Replace (output,"[","[");
output := Replace (output,"\","\");
output := Replace (output,"]","]");
output := Replace (output,"^","^");
output := Replace (output,"_","_");
output := Replace (output,"`","`");
output := Replace (output,"{","{");
output := Replace (output,"|","|");
output := Replace (output,"}","}");
output := Replace (output,"~","~");
output := Replace (output, chrW(145),"");
output := Replace (output, chrW(146),"");
output := Replace (output, chrW(147),"");
output := Replace (output, chrW(148),"");
output := Replace (output, chrW(8212),"—");
output := Replace (output, chrW(8217),"’");
output := Replace (output, chrW(8220),"“");
output := Replace (output, chrW(8221),"”");
output := Replace (output,"£","");
It's unweildly and needs maintenance. I'm adding characters to this as I find them, but I'm left wondering if there's a way, possibly using AscW and ChrW, to dynamically identify and convert non-alphanumeric character to it's html/ascii equivalent within the string.
I appear to have found this solution by adapting code designed to remove non-alphanumeric characters:
stringvar input := {table.fieldName};
stringvar output := '';
numbervar i;
input := Trim(input);
for i := 1 to Length(input) Step 1 do
// 0-9 is 48-57
// A-Z is 65-90
// a-z is 97-122
if not(input[i] in [chr(32), Chr(48),Chr(49),Chr(50),Chr(51),Chr(52),Chr(53),Chr(54),Chr(55),Chr(56),Chr(57),Chr(65),Chr(66),Chr(67),Chr(68),Chr(69),Chr(70),Chr(71),Chr(72),Chr(73),Chr(74),Chr(75),Chr(76),Chr(77),Chr(78),Chr(79),Chr(80),Chr(81),Chr(82),Chr(83),Chr(84),Chr(85),Chr(86),Chr(87),Chr(88),Chr(89),Chr(90),Chr(97),Chr(98),Chr(99),Chr(100),Chr(101),Chr(102),Chr(103),Chr(104),Chr(105),Chr(106),Chr(107),Chr(108),Chr(109),Chr(110),Chr(111),Chr(112),Chr(113),Chr(114),Chr(115),Chr(116),Chr(117),Chr(118),Chr(119),Chr(120),Chr(121),Chr(122)])
then
(output := output + "&#"+ cstr(ascw(input[i]),0)+";";)
else
if (input[i] in [chr(32), Chr(48),Chr(49),Chr(50),Chr(51),Chr(52),Chr(53),Chr(54),Chr(55),Chr(56),Chr(57),Chr(65),Chr(66),Chr(67),Chr(68),Chr(69),Chr(70),Chr(71),Chr(72),Chr(73),Chr(74),Chr(75),Chr(76),Chr(77),Chr(78),Chr(79),Chr(80),Chr(81),Chr(82),Chr(83),Chr(84),Chr(85),Chr(86),Chr(87),Chr(88),Chr(89),Chr(90),Chr(97),Chr(98),Chr(99),Chr(100),Chr(101),Chr(102),Chr(103),Chr(104),Chr(105),Chr(106),Chr(107),Chr(108),Chr(109),Chr(110),Chr(111),Chr(112),Chr(113),Chr(114),Chr(115),Chr(116),Chr(117),Chr(118),Chr(119),Chr(120),Chr(121),Chr(122)])
then
(output := output + input[i];) ;
output
Not perfect, but it works. Does anyone have a better solution?
I've got a report that's supposed to take a grid control and produce HTML output. One of the columns in the grid can display any of a number of values, or <Any>. When this gets output to HTML, of course, it ends up blank.
I could probably write up some routine to use StringReplace to turn that into <Any> so it would display this particular case correctly, but I figure there's probably one in the RTL somewhere that's already been tested and does it right. Anyone know where I could find it?
I am 99 % sure that such a function does not exist in the RTL (as of Delphi 2009). Of course - however - it is trivial to write such a function.
Update
HTTPUtil.HTMLEscape is what you are looking for:
function HTMLEscape(const Str: string): string;
I don't dare to publish the code here (copyright violation, probably), but the routine is very simple. It encodes "<", ">", "&", and """ to <, >, &, and ". It also replaces characters #92, #160..#255 to decimal codes, e.g. \.
This latter step is unnecessary if the file is UTF-8, and also illogical, because higher special characters, such as ∮ are left as they are, while lower special characters, such as ×, are encoded.
Update 2
In response to the answer by Stijn Sanders, I made a simple performance test.
program Project1;
{$APPTYPE CONSOLE}
uses
Windows, SysUtils;
var
t1, t2, t3, t4: Int64;
i: Integer;
str: string;
const
N = 100000;
function HTMLEncode(const Data: string): string;
var
i: Integer;
begin
result := '';
for i := 1 to length(Data) do
case Data[i] of
'<': result := result + '<';
'>': result := result + '>';
'&': result := result + '&';
'"': result := result + '"';
else
result := result + Data[i];
end;
end;
function HTMLEncode2(Data: string):string;
begin
Result:=
StringReplace(
StringReplace(
StringReplace(
StringReplace(
Data,
'&','&',[rfReplaceAll]),
'<','<',[rfReplaceAll]),
'>','>',[rfReplaceAll]),
'"','"',[rfReplaceAll]);
end;
begin
QueryPerformanceCounter(t1);
for i := 0 to N - 1 do
str := HTMLEncode('Testing. Is 3*4<3+4? Do you like "A & B"');
QueryPerformanceCounter(t2);
QueryPerformanceCounter(t3);
for i := 0 to N - 1 do
str := HTMLEncode2('Testing. Is 3*4<3+4? Do you like "A & B"');
QueryPerformanceCounter(t4);
Writeln(IntToStr(t2-t1));
Writeln(IntToStr(t4-t3));
Readln;
end.
The output is
532031
801969
It seems here is a small contest :) Here is a one more implementation:
function HTMLEncode3(const Data: string): string;
var
iPos, i: Integer;
procedure Encode(const AStr: String);
begin
Move(AStr[1], result[iPos], Length(AStr) * SizeOf(Char));
Inc(iPos, Length(AStr));
end;
begin
SetLength(result, Length(Data) * 6);
iPos := 1;
for i := 1 to length(Data) do
case Data[i] of
'<': Encode('<');
'>': Encode('>');
'&': Encode('&');
'"': Encode('"');
else
result[iPos] := Data[i];
Inc(iPos);
end;
SetLength(result, iPos - 1);
end;
Update 1: Updated initially provided incorrect code.
Update 2: And the times:
HTMLEncode : 2286508597
HTMLEncode2: 3577001647
HTMLEncode3: 361039770
I usually just use this code:
function HTMLEncode(Data:string):string;
begin
Result:=
StringReplace(
StringReplace(
StringReplace(
StringReplace(
StringReplace(
Data,
'&','&',[rfReplaceAll]),
'<','<',[rfReplaceAll]),
'>','>',[rfReplaceAll]),
'"','"',[rfReplaceAll]),
#13#10,'<br />'#13#10,[rfReplaceAll]);
end;
(copyright? it's open source)
Unit HTTPApp has a function called HTMLEncode. It has also other HTML/HTTP related functions.
I dont know in which delphi version it was introduced but, there is the System.NetEncoding unit which has:
TNetEncoding.HTML.Encode
TNetEncoding.HTML.Decode
functions. Read up here. You dont need external libraries anymore for that.
From unit Soap.HTTPUtil or simply HTTPUtil for older delphi versions, you can use
function HTMLEscape(const Str: string): string;
var
i: Integer;
begin
Result := '';
for i := Low(Str) to High(Str) do
begin
case Str[i] of
'<' : Result := Result + '<'; { Do not localize }
'>' : Result := Result + '>'; { Do not localize }
'&' : Result := Result + '&'; { Do not localize }
'"' : Result := Result + '"'; { Do not localize }
{$IFNDEF UNICODE}
#92, Char(160) .. #255 : Result := Result + '&#' + IntToStr(Ord(Str[ i ])) +';'; { Do not localize }
{$ELSE}
// NOTE: Not very efficient
#$0080..#$FFFF : Result := Result + '&#' + IntToStr(Ord(Str[ i ])) +';'; { Do not localize }
{$ENDIF}
else
Result := Result + Str[i];
end;
end;
end;
how about that way of replacing special characters:
function HtmlWeg(sS: String): String;
var
ix,cc: Integer;
sC, sR: String;
begin
result := sS;
ix := pos('\u00',sS);
while ix >0 do
begin
sc := copy(sS,ix+4,2) ;
cc := StrtoIntdef('$' +sC,32);
sR := '' + chr(cc);
sS := Stringreplace(sS, '\u00'+sC,sR,[rfreplaceall]) ;
ix := pos('\u00',sS);
end;
result := sS;
end;
My function combines the for-loop with a minimal reallocation of the string:
function HtmlEncode(const Value: string): string;
var
i: Integer;
begin
Result := Value;
i := 1;
while i <= Length(Result) do
begin
if Result[i] = '<' then
begin
Result[i] := '&';
Insert('lt;', Result, i + 1);
Inc(i, 4);
end
else if Result[i] = '>' then
begin
Result[i] := '&';
Insert('gt;', Result, i + 1);
Inc(i, 4);
end
else if Result[i] = '"' then
begin
Result[i] := '&';
Insert('quot;', Result, i + 1);
Inc(i, 6);
end
else if Result[i] = '&' then
begin
Insert('amp;', Result, i + 1);
Inc(i, 5);
end
else
Inc(i);
end;
end;
in delphi You have the function
THTMLEncoding.HTML.Encode