Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 604
  • Last Modified:

Find if String is missing in memo

Hi all,

In a memo I have some <li> tags that don't have it's closing tag and I need some code to look for those and close them.

This is how it works:

<li>somthing's go here or none...and then the next tag to the <li> is </li>

so, basically, if the tag after <li> is NOT an </li> then insert the </li> tag.

Please let me know if I didn't explain it properly.

hope you can help

thx

st3vo
0
ST3VO
Asked:
ST3VO
  • 22
  • 12
  • 8
  • +1
3 Solutions
 
8080_DiverCommented:
I think the following steps may outline what you need to do:
  1. Extract the portion of the memo text that you need to parse for the <li></li> tags and put it in a string [YourString];
  2. Create a TStringList [YourStringList];
  3. Find the position of the first <li> [TempInt1 := POS('<li>', YourString); ];
    Note: if hits returns a zero, then you are done, go to step 8.
  4. Find the position of the next <li>
    [TempInt2 := POS(',li.', COPY(YourString, TempInt1 + 4, Length(TempString));]
  5. Now, if there is an <li>, add that segment of the string to the TStringList
    [YourStringList.Add(COPY(TempString, TempInt1, TempInt2 - 1); ]
  6. Finally, copy the remaining portion of the string into your string
    [YourString := COPY(YourString, TempInt2, LENGTH(YourString)); ]
  7. Now, loop back to step 3, above.
  8. Reconstruct the segment of the Memo string by concatenating the pieces of the TStringList.
function PerfectListItems(TheStringToPerfect : string): string;
var
  TempResult      : string;
  TempStr         : string;
  FirstLI         : Integer;
  TempStringList  : TStringList;
  TempInt1        : Integer;
  TempInt2        : Integer;
begin
  TempResult  :=  '';
  TempStr         :=  TheStringToPerfect;
  TempStringList  :=  TStringList.Create;
  try
    try
      FirstLI         :=  Pos('<li>', TempStr);
      TempInt1        :=  FirstLI;
      while (TempInt1 > 0) do
      begin
        TempInt2        :=  Pos('<li>', Copy(TempStr, TempInt1 + 4, Length(TempStr));
        if  (TempInt2 = 0)
        then begin
          TempStringList.Add(Copy(TempStr, TempInt1, Length(TempStr))
          TempInt1  :=  0;
        end
        else begin
          TempStringList.Add(Copy(TempStr, TempInt1, TempInt2 - 1)
          TempStr   := Copy(TempStr, TempInt2, Length(TempStr));
          TempInt1  :=  TempInt2
        end; {if}
      end; {while}

      TempResult :=  Copy(TheStringToPerfect, 1, FirstLI - 1);
      for TempInt1 := 0 to (TempStringList.Count - 1) do
      begin
        TempInt2  :=  Length(TempStringList.Strings[TempInt]);
        if  (Copy(TempStringList.Strings[TempInt], TempInt2 - 4, 5) <> '</li>')
        then begin
          TempResult :=  TempResult + TempStringList.Strings[TempInt] + '</li>';
        end
        else begin
          TempResult :=  TempResult + TempStringList.Strings[TempInt];
        end; {if}
      end; {for}
    except
      on E:Exception do
        begin
          raise Exception.Create( 'ERROR: An error occurred during the ' +
                                  'perfecting of the list items; ' +
                                  #13#10 + E.Message);
        end; {on exception}
    end; {try}
  finally
    FreeAndNil(TempStringList);
    PerfectListItems  :=  TempResult;
  end; {try}

end; {function}

Open in new window

0
 
ST3VOAuthor Commented:
Hmmm...how to I get your function to search a whole memo contents and add the missing </li> 's please?

Can't get it to work :o/

thx
0
 
HypoCommented:
Here's a small sample... it's case sensetive, and only detects <li>... let me know if you need it to be case sensetive... the code needs som modeifications for that...

/Hypo
procedure TForm1.Button1Click(Sender: TObject);
var i, j, k : Integer;
    aText : String;
begin
  aText := Memo1.Lines.Text;
  i := Pos('<li>', aText);
  while i <> 0 do begin
    j := PosEx('</li>', aText, i+3);
    k := PosEx('<', aText, i+3);
    if (j = 0) and (k = 0) then
      aText := aText + '<\li>' else
    if (j <> k) then
      Insert('</li>', aText, k);
    i := PosEx('<li>', aText, i+3);
  end;
  Memo1.Lines.Text := aText;
end;

Open in new window

0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
epasquierCommented:
Hypo : great job. You could improve it by only looking for the next '<' position and see if the text at this positions is </li> or not (then it's also easier to do it in case insensitive way at least for closing </li>)

This function will detect <li> or <LI> but not <Li> or <lI> (the last one is my prefered :o) ), but for the closing tag it can detect all variations.

Oh, and while I'm at it, I made a generic function for other tags. It's a bit less readable due to those added functionalities but algo is the same as Hypo's

ST3VO : if this works for you, please accept /Hypo's as solution, as it has happened on other questions that I added a minor change and the original comment was forgotten in the closing (could not say if it was one of yours questions, but I prefer say it before now)

Use it this way :

Memo1.Lines.Text := DetectAndAddClosingTags ( Memo1.Lines.Text , 'li' );

function DetectAndAddClosingTags(aText:String;Tag:String='li'):String;
Var
 OpenTag,UOpenTag,CloseTag:String;
 LOpenTag,LCloseTag,i,j:Integer;
begin
 Tag:=LowerCase(Tag);
 OpenTag:='<'+Tag+'>';
 UOpenTag:=UpperCase(OpenTag);
 CloseTag:='</'+Tag+'>';
 LOpenTag:=Length(OpenTag);
 LCloseTag:=Length(CloseTag);
 i:=1; // init position to be able to use PoxEx even at first loop
 Repeat
  j := PosEx(OpenTag, aText, i);
  i := PosEx(UOpenTag, aText, i); // search Uppercase variation
  if (i=0) And (j=0) Then break; // quit when none found
  if (i=0) Or ((j>0) and (j<i)) Then i:=j; // keep only first & valid 
  i := i + LOpenTag;
  j := PosEx('<', aText, i);
  if (j = 0) then
   aText := aText + CloseTag else
  if LowerCase(Copy(aText,j,LCloseTag))<>CloseTag then
      Insert(CloseTag , aText, j);
 Until False; // "infinite" loop
 Result:=aText;
end;

Open in new window

0
 
ST3VOAuthor Commented:
OK, it aded the missing </li> but in the wrong place.

I had this:

<li img src="blabla" ...  

What the code did was ...

<li></li><img src="blabla" ...  

If should have done this:

</li><img src="blabla" alt="" /></li>

Another example:  

<li><u>hello</u>

should become:

<li><u>hello</u></li>

Does this help?





0
 
epasquierCommented:
ha that is just another problem.
You must tell us what is the tag or piece of text that ALWAYS mark the position where </li> should be
If, as in your example, it's not any tag, then we have a problem
0
 
ST3VOAuthor Commented:
Same issue epasquier....it's adding the closing tag right after ... It might be more complez than expected :o/
0
 
ST3VOAuthor Commented:
Let's see if this helps:

Rules: The </li> tag just comes before an opening <li> tag OR a </ul> or a </ol> tag

So, the code should only add the closing </li> tag right before any of those other tags:

<li> OR </ul> OR </ol>

Usage:
<ol>
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ol>

<ul>
  <li>Coffee</li>
  <li>Tea</li>
  <li>Milk</li>
</ul>

Hope this helps
0
 
ST3VOAuthor Commented:
So, if there is an <li><li> then before the last <li> we insert a closing </li> and so on...

Also if there is a </ol>  before the </ol> there must be a closing </li>

And last but not least  if there is a </ul>  before the </ol> there must be a closing </li>

And opening <li> needs a closing </li> before any other <li>  or </ol> or </ul>

Understand it?

0
 
epasquierCommented:
Yes it helps, as I said the previous message it's another problem and we need to know what other tags we should expect.

I'll look into it
0
 
epasquierCommented:
try this one :

DetectAndAddClosingTags( Memo1.Lines.Text , 'li' , [ 'li' , 'ol', 'ul' ] );
function GetPosOfFirstToken(aText:String;TokenList:TStrings;P:integer=1):integer;
Var
 i,P2:integer;
begin
 Result:=0;
 for i:=0 to TokenList.Count-1 do
  begin
   P2:=PosEx(aText,TokenList[i],P);
   if (P2>0) And ((Result=0) Or (P2<Result) Then Result:=P2;
  end; 
end;

function DetectAndAddClosingTags(aText:String;Tag:String='li';closeTags:Array of string):String;
Var
 OpenTag,CloseTag:String;
 LOpenTag,LCloseTag,i:Integer;
 OpenTokens,CloseTokens:TStrings;
 
begin
 OpenTokens:=TStringList.Create;
 CloseTokens:=TStringList.Create;
 
 Tag:=LowerCase(Tag);
 OpenTokens.Add('<'+Tag+'>');
 OpenTokens.Add(UpperCase(OpenTokens[0]));
 LOpenTag:=Length(OpenTag);
 CloseTag:='</'+Tag+'>';
 LCloseTag:=Length(CloseTag);
 
 for i:=Low(closeTags) to High(closeTags) do
  begin
   CloseTokens.Add('</'+LowerCase(closeTags[i])+'>');
   CloseTokens.Add('</'+UpperCase(closeTags[i])+'>');
  end;
 
 i:=1;
 Repeat
  i:=GetPosOfFirstToken(aText, OpenTokens, i);
  if i=0 Then break; // quit when none found
  i := i + LOpenTag;
  i := GetPosOfFirstToken(aText, CloseTokens, i);
  if (i = 0) then
   begin
    aText := aText + CloseTag;
    break;
   end else
  if LowerCase(Copy(aText,i,LCloseTag))<>CloseTag then
      Insert(CloseTag , aText, i);
 Until False; // "infinite" loop
 Result:=aText;
 OpenTokens.Free;
 CloseTokens.Free;
end;

Open in new window

0
 
epasquierCommented:
ah, one mistake, it's not possible to have a default parameter in the middle of standard parameter

You can't declare this :
function DetectAndAddClosingTags(aText:String;Tag:String='li';closeTags:Array of string):String;

but have to declare this :
function DetectAndAddClosingTags(aText:String;Tag:String;closeTags:Array of string):String;
0
 
ST3VOAuthor Commented:
2nd function gives errors to compile:

[DCC Error] Unit1.pas(308): E2238 Default value required for 'closeTags'

and

[DCC Error] Unit1.pas(977): E2035 Not enough actual parameters

Any ideas pls?
0
 
ST3VOAuthor Commented:
Ignore the last error posted please...

The only error is:

[DCC Error] Unit1.pas(308): E2238 Default value required for 'closeTags'

2nd function ... line 1

Here: function DetectAndAddClosingTags(aText:String;Tag:String='li';closeTags:Array of string):String;

0
 
epasquierCommented:
for the first error yes, it is what I corrected the message before
for the second, I don't know, I suspect you forgot a parameter :

DetectAndAddClosingTags( Memo1.Lines.Text , 'li' , [ 'li' , 'ol', 'ul' ] );
0
 
ST3VOAuthor Commented:
I don't mean the usage code...it's the 2nd function that doesn't compile and returns the error:

function DetectAndAddClosingTags(aText:String;Tag:String='li';closeTags:Array of string):String;
Var
 OpenTag,CloseTag:String;
 LOpenTag,LCloseTag,i:Integer;
 OpenTokens,CloseTokens:TStrings;
 
begin
 OpenTokens:=TStringList.Create;
 CloseTokens:=TStringList.Create;
 
 Tag:=LowerCase(Tag);
 OpenTokens.Add('<'+Tag+'>');
 OpenTokens.Add(UpperCase(OpenTokens[0]));
 LOpenTag:=Length(OpenTag);
 CloseTag:='</'+Tag+'>';
 LCloseTag:=Length(CloseTag);
 
 for i:=Low(closeTags) to High(closeTags) do
  begin
   CloseTokens.Add('</'+LowerCase(closeTags[i])+'>');
   CloseTokens.Add('</'+UpperCase(closeTags[i])+'>');
  end;
 
 i:=1;
 Repeat
  i:=GetPosOfFirstToken(aText, OpenTokens, i);
  if i=0 Then break; // quit when none found
  i := i + LOpenTag;
  i := GetPosOfFirstToken(aText, CloseTokens, i);
  if (i = 0) then
   begin
    aText := aText + CloseTag;
    break;
   end else
  if LowerCase(Copy(aText,i,LCloseTag))<>CloseTag then
      Insert(CloseTag , aText, i);
 Until False; // "infinite" loop
 Result:=aText;
 OpenTokens.Free;
 CloseTokens.Free;
end;


0
 
epasquierCommented:
look above, the post 26101250 just below the code I provided
0
 
ST3VOAuthor Commented:
I know hwat you mean...: DetectAndAddClosingTags( Memo1.Lines.Text , 'li' , [ 'li' , 'ol', 'ul' ] );  = Function usage code...

That's cool...

Its just the function above that gets the error: [DCC Error] Unit1.pas(308): E2238 Default value required for 'closeTags' ... on it's first line...and I cannot compile the code because of the error, so I cannot test it...otherwise all cool!
0
 
epasquierCommented:
argh :o)

Replace the function definition : you still have Tag:String='li'; where you should have Tag:String;
( without ='li' ) which is not allowed with other parameters after, when those do not have also default values. Which is why the compiler complains about not having default values for closeTags
0
 
ST3VOAuthor Commented:
Cool...got it to compile...but tested the code and it's not adding any </li> tags at all for some reason.
0
 
epasquierCommented:
can you post your memo text so that I try too ?
0
 
ST3VOAuthor Commented:
sure

This is what I'm testing it with:

<ul class="list">
<li><img alt="picture" align="left" src="images/icon_1.jpg" width="30" height="30" />some text here just to test
</ul>


As you can see it's got a missing </li> before the </ul>

0
 
epasquierCommented:
there was a few mistakes
1) PosEx order of parameters (substring comes before the total string) - I do that one regularly
2) I forgot to add the open tags (<li>) for possible delimiters to search after the tag is found
3) minor mistake as OpenTag was not saved then LOpenTag = 0

it works now
function GetPosOfFirstToken(aText:String;TokenList:TStrings;P:integer=1):integer;
Var
 i,P2:integer;
begin
 Result:=0;
 for i:=0 to TokenList.Count-1 do
  begin
   P2:=PosEx(TokenList[i],aText,P);
   if (P2>0) And ((Result=0) Or (P2<Result)) Then Result:=P2;
  end; 
end;

function DetectAndAddClosingTags(aText:String;Tag:String;closeTags:Array of string):String;
Var
 OpenTag,CloseTag:String;
 LOpenTag,LCloseTag,i:Integer;
 OpenTokens,CloseTokens:TStrings;
 
begin
 OpenTokens:=TStringList.Create;
 CloseTokens:=TStringList.Create;
 
 Tag:=LowerCase(Tag);
 OpenTag:='<'+Tag+'>';
 OpenTokens.Add(OpenTag);
 OpenTokens.Add(UpperCase(OpenTag));
 LOpenTag:=Length(OpenTag);
 CloseTag:='</'+Tag+'>';
 LCloseTag:=Length(CloseTag);

 for i:=Low(closeTags) to High(closeTags) do
  begin
   CloseTokens.Add('</'+LowerCase(closeTags[i])+'>');
   CloseTokens.Add('</'+UpperCase(closeTags[i])+'>');
  end;
// Add also the opentags
 CloseTokens.AddStrings(OpenTokens);

 i:=1;
 Repeat
  i:=GetPosOfFirstToken(aText, OpenTokens, i);
  if i=0 Then break; // quit when none found
  i := i + LOpenTag;
  i := GetPosOfFirstToken(aText, CloseTokens, i);
  if (i = 0) then
   begin
    aText := aText + CloseTag;
    break;
   end else
  if LowerCase(Copy(aText,i,LCloseTag))<>CloseTag then
      Insert(CloseTag , aText, i);
 Until False; // "infinite" loop
 Result:=aText;
 OpenTokens.Free;
 CloseTokens.Free;
end;

Open in new window

0
 
ST3VOAuthor Commented:
Hi epasquier,

Did ytou manage ro test it?

I just tested it and it does nothing for some reason....I've added your new code above too but nothing happens.

Any ideas? I'm I forgetting something?

thx
0
 
epasquierCommented:
Yes, it worked for me, you probably missed something

I called it like that (result is put in a second memo)

 Memo2.Lines.Text := DetectAndAddClosingTags( Memo1.Lines.Text , 'li' , [ 'li' , 'ol', 'ul' ] );

double check the code and execute with my test text :
// MEMO1
<ol>
  <li>Coffee
  <li>Tea</li>
  <li>Milk<img src="blob.img">
</ol>

// RESULT IN MEMO2
<ol>
  <li>Coffee
  </li><li>Tea</li>
  <li>Milk<img src="blob.img">
</li></ol>

Open in new window

0
 
8080_DiverCommented:
Hmmm...how to I get your function to search a whole memo contents and add the missing </li> 's please?

The assumption I made was that you would be passing whatever portion of the memo field had the list in it (i.e. the portion betwen the <ul> and </ul> or the <ol> and the </ol>).  To search the whole thing, you would need to pass the whole text as a string or modify the function to use TMemo objects..  
Can't get it to work :o/
Well, I didn't test the function since I was at work and being paid to do work things. ;-)  
Re: your issues with not finding "<li" and changing it to "<li>"
As others have stated, this is an entirely differnt problem from the originally stated one.  What this is indicating is that your memo field's entire list is totally malformed.  THat changes the whole picture because what you are saying is that the process has to determine a) whether there is, in fact, some indication of a list item start and then b) if there was, is there some indication of an end of list item.  That means you need to parse for the "less than" symbol to see if you have a potentially start of tag and, then, b), assuming you find it, determine if it is a malformed "<li>", c) correcting any malformations of that tag before d) searching for the next "less than" symbol so that you can e) try to determine whether it is a 1) a potentially malformed "end of list item", 2) a well formed "end of list item", or 3) some other tag, in which case if it is either a well-formed or malformed "start of list item", you will need to remediate the preceding substring to add an "end of list item" tag.
Just as a passing question, is it at all possible that there will be lists within a list?  (I frequently wind up doing that in HTML ;-)?  If so, then you cannot assume that a "Less Than" symbol is either the start or end of a list item and you may need to deal with other malformed tags, as well.  For instance, if you have:
<ol><li some text <ul><li>some more text</li<li>still more text</li></li<lianother line of text</ol
Do you plan to remediate that text so that it is properly formed, as below?
<ol><li> some text <ul><li>some more text</li><li>still more text</li><ul></li<li>another line of text</li></ol>
 
0
 
ST3VOAuthor Commented:
epasquier: Yes, it works on a second memo BUT I need to run the code on only one memo and it doesn't work when I specify just one memo...very strange.

8080_Diver: Thanks for you comment and yes you are right, the LI 's can go even more complex but I didn't want the code to become a nightmare to create so I just asked for some code that could do the basics for now.





0
 
epasquierCommented:
>> Yes, it works on a second memo BUT I need to run the code on only one memo
>> and it doesn't work when I specify just one memo...very strange.

That's more than strange, that's unbelievable. It works for 1 memo just as well, if it does not for you then you have something special with your memo, in other part of your project
0
 
ST3VOAuthor Commented:
I'll investigate further and let you know..thanks again!
0
 
8080_DiverCommented:
ST3VO,
Can you provide the two memo fields texts in file attachments?
0
 
ST3VOAuthor Commented:
It was a false alarm...I though it worked that the last code I cannot get to work...sorry about that...It's just to adding the </li>

You might be asking yourselfs why so many string replaces? Well, I'll tell you as you might have a better solution for this.

I have some html page which I'm loading on twebbrower and then i'm putting the tbrowser on designmode ... when I look into the code after putting the browser on design mode the html code just brakes up completely so I am trying to get it back to normal by fixing the things it breaks (html code wise) ... any ideas here please???

thx

0
 
8080_DiverCommented:
Just out of curiosity, why are you [putting the TWebBrowser in design mode and then trying to look at the code you loaded into it?  
Why not look at the code from the HTML file, instead?
0
 
ST3VOAuthor Commented:
Good question but I am trying to use TWebBrowser as an editor and it works BUT as soon as you switch to design mode the code changes to the worse html code I've ever seen to I am trying to fix that by doing replaces everywhere :o/
0
 
8080_DiverCommented:
Instead of trying to use the TWebBrowser for something for which it was never intended (i.e. as an HTML Editor), try looking at the following link (which is for an html editor ;-):
http://www.chami.com/html-kit/ 
0
 
ST3VOAuthor Commented:
Looks promising but can I use it in Delphi? and does it work in design mode?

thnks

0
 
8080_DiverCommented:
ST3VO,
Please tell me that you are NOT trying to set the contents of the TWebBrowser in Design Mode with the intention of having whatever you set in design mode be what is displayed.  I highly advise the use of text files of type "HTM" or"HTML" to store the HTML code that you want to display in the TWebBrowser rather than trying to put the code directly into (much less actually trying to edit the HTML code in) the TWebBrowser component.
No, HTML-Kit cannot be used "in Delphi" and, therefore, it does not "work in design mode".  t is a tool for editing/creating HTML files that you can then load (programmatically) into the TWebBrowser.  Loading HTML into the TWebBrowser control is a much better approach because you don't have to edit and recompile your application every time you adjust the HTML code.
0
 
ST3VOAuthor Commented:
Ref: Please tell me that you are NOT trying to set the contents of the TWebBrowser in Design Mode ... Yes, that's what I've trying to do. :o/

0
 
8080_DiverCommented:
Why?!?!?!?
0
 
ST3VOAuthor Commented:
Right...this question has gone way off the original question so I'm going to close it and later ask about the editor :o)
0
 
ST3VOAuthor Commented:
Thanks for your time and help!!!
0
 
ST3VOAuthor Commented:
Why not  :o)
0
 
8080_DiverCommented:
'Cause it don't work all that well. ;-)
As an English friend of mine often says, "Horses for courses."  In other words, there are tools that are better for some things than for others.  
Another quote that comes to mind i:
"To a man with a hammer, every problem looks like a nail."
If your intent is to work harder at getting something done, then you would seem to be on the right course.  However, I tend to prefer using tools that are designed ofr a purpose rather than trying to force tools to do things they were never designed for.  My approach tends to let me get more done faster rather than bashing my head on a wall trying to figure out how to turn a screw with a hammer. ;-)
0
 
ST3VOAuthor Commented:
Hahahaha...yeah you are right...I just like to try to not like when things cannot be done so I turn to try to convert a hummer into a screw driver :o)

0

Featured Post

Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

  • 22
  • 12
  • 8
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now