PDA

View Full Version : Need SIMPLE file format that includes plain text + embedded commands?



paul_nicholls
10-09-2010, 01:50 AM
Hi all,
for my game The Probe (http://www.pascalgamedevelopment.com/forum/index.php?topic=6429.0) I am wanting to make some simple text files that include these features:

1. plain text for messages
2. some simple formatting like setting colour of some of the text, and end of line markers; "\n" perhaps?
3. can embed names of images so I can display text with images in them, like if I refer to one of the objects in the game I can use text + image.

The way I figure it, I could load the text file, parse it to break it down into it's component parts (or elements), and then when it comes to displaying it, 'execute' each element so it does it's thing - sets some formatting, draws text, etc.

Any ideas?

I could roll my own, maybe use something like XML, or some other system.


Possible XML Example:

Test File.txt

This is some normal text and some <red>red text</red>.<crlf/>and this <blue> text is on a new line</blue>! and now here is an image example <img filename="test.png"/> too :)


hmm...I'm not sure if that is going to work so well somehow though as XML usually needs to have open/close pairs of commands.

Maybe some thing self-created like this?

\n = new line
\$RRGGBB = colour change (2 hex digits per colour part)
\i = image name
\fdd = font size change (2 digits)


Line one\nline two and an image \i"probe.png". Also some \$FF0000red\$000000 text :) Here is some \f12size 12 text\f08

cheers,
Paul

WILL
10-09-2010, 06:01 AM
You could to all out and use XML or something similar, but if I were to make this text/script for something like how you would use it, I'd probably make my own standard and parser for it.

You could take the tag concept of HTML and simply have it token-ize blocks and the parser will act as a state machine, kinda like so...


<text>Some text</text>
<color>color name or RGB(A) value</color>
Line1<br>Line2
<img>texture_or_image_name</img>

...or for the more complex content generation...

<text-x>x location of text drawing</text-x>
<text-y>y location of text drawing</text-y>
<draw-x>x location of image drawing</draw-x>
<draw-y>y location of image drawing</draw-y>
<draw-r>set red color offset for image drawing</draw-r>
<draw-g>set green color offset for image drawing</draw-g>
<draw-b>set blue color offset for image drawing</draw-b>
<draw-a>set alpha offset for image drawing</draw-a>
<reset>set attribute to default value</reset>

I think you get the idea. You could make it as complicated or as simple as you need. If you want the parser to allow only 1 image and 1 text block, (sort of like a standard character dialog box) you could have it ignore extra text or img blocks to safeguard the parser from tripping up.

If you have a few different display 'templates' or configurations you want to use on screen, you could send some values to the parser before you assign the text/script to display your next script.

For example if you wanted to have these displays:

- A character dialog box with 1 image and 1 text block you could put...


// set_dialog_display(num of images, num of text blocks)
set_dialog_display(1,1);

- A player stats display box with 2 images and 2 text displays you could instaed put...


set_dialog_display(2, 2);

You then have to make your game draw each display based on which one you picked, and so on...

paul_nicholls
10-09-2010, 11:56 AM
Thanks for the feed back WILL :)

I am thinking to use the text format + parser to do some horizontally scrolling credits with text, and maybe some images.

I also want to fill windows in-game with text and/or images too, so I don't think I will need any specific location stuff in the format.

I am planning on just filling up the dialog with the image(s) and text, and wrapping the text around at the edges.

cheers,
Paul

WILL
10-09-2010, 12:43 PM
Ah, so you're likely to make sort of a 'simple' documentation format and display engine then? Such as an HTML or .DOC standard for example.

chronozphere
10-09-2010, 01:08 PM
I'd just take XML and use that. You can have a look at HTML to see how formatting is done. The benefit is that you can use an existing XML parser to save time. :)

Hadron Games
10-09-2010, 04:20 PM
or you can write a high level layer to control what you need to:

ScrollText(...)
DisplayDialog(...)
GetInput(...)
etc.

Then expose those high level routines to the scripting system and you have lots of control and flexibility. HGE allows for Pascal, Basic and JavaScript syntax. Compilation is fast and you have the added advantage of x86 execution. Not only that but you can use any of the language constructs offered by each language, even mix and match them.

And if you choose to use XML, see the XML_XXX routines in HGE.

Just offering one more option that maybe helpful.

Brainer
10-09-2010, 08:11 PM
How about creating something similar to BBCode (http://en.wikipedia.org/wiki/BBCode)?

paul_nicholls
11-09-2010, 01:55 AM
or you can write a high level layer to control what you need to:

ScrollText(...)
DisplayDialog(...)
GetInput(...)
etc.

Then expose those high level routines to the scripting system and you have lots of control and flexibility. HGE allows for Pascal, Basic and JavaScript syntax. Compilation is fast and you have the added advantage of x86 execution. Not only that but you can use any of the language constructs offered by each language, even mix and match them.

And if you choose to use XML, see the XML_XXX routines in HGE.

Just offering one more option that maybe helpful.


Thanks for the info Jarrod, I will look into those options :)

cheers,
Paul

paul_nicholls
11-09-2010, 04:56 AM
How about creating something similar to BBCode (http://en.wikipedia.org/wiki/BBCode)?


hmm...BBCode? I hadn't thought of that, that might just be the ticket :)

Thanks Patrick!

cheers,
Paul

Brainer
11-09-2010, 05:01 AM
You can base your code on this (http://nbbc.sourceforge.net/) parser. :)

Hope it helps.

paul_nicholls
11-09-2010, 06:57 AM
You can base your code on this (http://nbbc.sourceforge.net/) parser. :)

Hope it helps.


Thanks Patrick :)

hmm...it is PHP code...I guess I could TRY converting it to Pascal.

On the other hand I only need a small subset of BBCode, so it might be easier (and quicker) to roll my own ;)

EDIT: oh, I see what you mean...I can look at the code and use that as an example for my code :)

cheers,
Paul

Brainer
11-09-2010, 09:22 AM
That's right. :) It shouldn't be too difficult to create one yourself. I suggested writing one 'cause I was thinking about creating such thing for some future works.

Actually, converting this (http://www.ultramegatech.com/blog/2009/04/creating-a-bbcode-parser/) one to Delphi doesn't look like too much work, especially when you have some code to parse regular expressions (http://www.regular-expressions.info/delphi.html). :)

On the other hand, an event-driven class would be the perfect solution, i.e. if the parser finds the "img" tag, it fires an OnBeginTag event and passes the name and its attributes to the event. The declaration could look like the following:


type
{ .: TBBCodeTagType :. }
TBBCodeTagType = (tagColor, tagBold, tagItalic, tagImage);

{ .: TTagEvent :. }
TTagEvent = procedure(Tag: TBBCodeTagType; const Attribute: AnsiString) of object;

{ .: TBBCodeParser :. }
TBBCodeParser = class sealed(TObject)
// Some other code here
public
{ Public declarations }
function Parse(const TextToParse: String): Boolean;

property OnBeginTag: TTagEvent read FOnBeginTag write FOnBeginTag;
property OnEndTag: TTagEvent read FOnEndTag write FOnEndTag;
end;

{ ... }

function TBBCodeParser.Parse(const TextToParse: String): Boolean;
var
FoundTag, TagAttr: String;
begin
Result := False;
FoundTag := '';
TagAttr := '';

// .. Some code here ..
if Assigned(FOnBeginTag) then
begin
if (FoundTag = 'color') then
FOnBeginTag(tagColor, TagAttr);
// and so on
end;
end;

paul_nicholls
11-09-2010, 11:19 AM
That's right. :) It shouldn't be too difficult to create one yourself. I suggested writing one 'cause I was thinking about creating such thing for some future works.

Actually, converting this (http://www.ultramegatech.com/blog/2009/04/creating-a-bbcode-parser/) one to Delphi doesn't look like too much work, especially when you have some code to parse regular expressions (http://www.regular-expressions.info/delphi.html). :)

On the other hand, an event-driven class would be the perfect solution, i.e. if the parser finds the "img" tag, it fires an OnBeginTag event and passes the name and its attributes to the event. The declaration could look like the following:


type
{ .: TBBCodeTagType :. }
TBBCodeTagType = (tagColor, tagBold, tagItalic, tagImage);

{ .: TTagEvent :. }
TTagEvent = procedure(Tag: TBBCodeTagType; const Attribute: AnsiString) of object;

{ .: TBBCodeParser :. }
TBBCodeParser = class sealed(TObject)
// Some other code here
public
{ Public declarations }
function Parse(const TextToParse: String): Boolean;

property OnBeginTag: TTagEvent read FOnBeginTag write FOnBeginTag;
property OnEndTag: TTagEvent read FOnEndTag write FOnEndTag;
end;

{ ... }

function TBBCodeParser.Parse(const TextToParse: String): Boolean;
var
FoundTag, TagAttr: String;
begin
Result := False;
FoundTag := '';
TagAttr := '';

// .. Some code here ..
if Assigned(FOnBeginTag) then
begin
if (FoundTag = 'color') then
FOnBeginTag(tagColor, TagAttr);
// and so on
end;
end;



Nice idea Patrick, you make me want to start right now! LOL

cheers,
Paul

Mirage
11-09-2010, 04:44 PM
My engine includes a markup unit.
http://www.casteng.com/cast2docs/Markup.htm
Command syntax is "["<Command><Arguments>"]".
Output is pure text without commands and an array of tags.
You can easily override its implementation and add commands.

paul_nicholls
13-09-2010, 12:10 PM
My engine includes a markup unit.
http://www.casteng.com/cast2docs/Markup.htm
Command syntax is "["<Command><Arguments>"]".
Output is pure text without commands and an array of tags.
You can easily override its implementation and add commands.

Thanks Mirage! I will take a look at the code :)

cheers,
Paul

paul_nicholls
15-09-2010, 03:56 AM
Hey all,
I have expanded Brainer's BBCode parser example so it is now in a working state :)

It handles these tag types: color, url, img. It can easily be expanded too..

I hope if is useful to someone else too ;)


unit unit_BBCode;
{$ifdef fpc}
{$mode Delphi}
{$endif}
{+H}
interface

uses
SysUtils,
Classes;

const
cBeginTagChar = '[';
cEndTagChar = ']';
cClosingTagChar = '/';

cColorTagIdent = 'color';
cURLTagIdent = 'url';
cImageTagIdent = 'img';

type
TCharSet = set of AnsiChar;
BBCodeParseException = class(Exception);
TBBCodeTagType = (tagUnknown,tagColor,tagURL,tagImage);

const
cBBCodeTagType: array[TBBCodeTagType] of AnsiString = (
'unknown',
'color',
'url',
'img'
);

type
TTagEvent = procedure(Tag: TBBCodeTagType; const Attribute: AnsiString) of object;
TTextEvent = procedure(c: AnsiChar) of object;

TBBCodeParser = class(TObject)
private
FErrorMsg: AnsiString;
FBuffer: AnsiString;
FIndex: Integer;
FCharacter: AnsiChar;
FOnBeginTag: TTagEvent;
FOnEndTag: TTagEvent;
FOnTextCharacter: TTextEvent;
FParsingTag: Boolean;
procedure Error(aErrorMsg: AnsiString);
procedure Expected(aExpectedMsg: AnsiString);
procedure GetChar;
procedure SkipWhiteSpaces;
function GetInteger: AnsiString;
function GetIdentifier: AnsiString;
function GetHexNumber: AnsiString;
function ReadUntil(chars: TCharSet): AnsiString;
procedure Match(aMatchText: AnsiString);
function IsWhiteSpace(c: AnsiChar): Boolean;
function IsDigit(c: AnsiChar): Boolean;
function IsAlpha(c: AnsiChar): Boolean;
function IsHex(c: AnsiChar): Boolean;
function GetColorTagAttributes: AnsiString;
function GetURLTagAttributes: AnsiString;
procedure ParseTag;
procedure ParseText;
public
{ Public declarations }
constructor Create;

function Parse(aTextToParse: AnsiString): Boolean;

property ErrorMsg: AnsiString read FErrorMsg;
property OnBeginTag: TTagEvent read FOnBeginTag write FOnBeginTag;
property OnEndTag: TTagEvent read FOnEndTag write FOnEndTag;
property OnTextCharacter: TTextEvent read FOnTextCharacter write FOnTextCharacter;
end;

implementation

constructor TBBCodeParser.Create;
begin
inherited Create;

FOnBeginTag := nil;
FOnEndTag := nil;
FOnTextCharacter := nil;
FErrorMsg := '';
end;

procedure TBBCodeParser.GetChar;
begin
if FIndex <= Length(FBuffer) then
begin
FCharacter := FBuffer[FIndex];
Inc(FIndex);
end
else
FCharacter := #0;
end;

procedure TBBCodeParser.SkipWhiteSpaces;
begin
while IsWhiteSpace(FCharacter) do
GetChar;
end;

function TBBCodeParser.GetInteger: AnsiString;
begin
Result := '';
if not IsDigit(FCharacter) then
Error('Expected Integer');
while IsDigit(FCharacter) do
begin
Result := Result + FCharacter;
GetChar;
end;
SkipWhiteSpaces;
end;

function TBBCodeParser.GetIdentifier: AnsiString;
begin
Result := '';
if not IsAlpha(FCharacter) then
Error('Expected Identifier');
while IsAlpha(FCharacter) do
begin
Result := Result + FCharacter;
GetChar;
end;
SkipWhiteSpaces;
end;

function TBBCodeParser.GetHexNumber: AnsiString;
begin
Result := '';
if not IsHex(FCharacter) then
Error('Expected Hexadecimal Number');
while IsHex(FCharacter) do
begin
Result := Result + FCharacter;
GetChar;
end;
SkipWhiteSpaces;
end;

function TBBCodeParser.ReadUntil(chars: TCharSet): AnsiString;
begin
Result := '';
while (FCharacter <> #0) and not(FCharacter in chars) do
begin
Result := Result + FCharacter;
GetChar;
end;
end;

function TBBCodeParser.IsWhiteSpace(c: AnsiChar): Boolean;
begin
Result := c in[' ',^I,#10,#13];
end;

function TBBCodeParser.IsDigit(c: AnsiChar): Boolean;
begin
Result := c in['0'..'9'];
end;

function TBBCodeParser.IsAlpha(c: AnsiChar): Boolean;
begin
Result := c in['a'..'z','A'..'Z'];
end;

function TBBCodeParser.IsHex(c: AnsiChar): Boolean;
begin
Result := c in['a'..'f','A'..'F','0'..'9'];
end;

procedure TBBCodeParser.Error(aErrorMsg: AnsiString);
begin
FErrorMsg := aErrorMsg + ' at character index: '+IntToStr(FIndex);
raise BBCodeParseException.Create(aErrorMsg);
end;

procedure TBBCodeParser.Expected(aExpectedMsg: AnsiString);
begin
Error('Expected: "'+aExpectedMsg+'"');
end;

procedure TBBCodeParser.Match(aMatchText: AnsiString);
var
i: Integer;
begin
for i := 1 to Length(aMatchText) do
begin
if FCharacter <> aMatchText[i] then
Expected(aMatchText);
GetChar;
end;
SkipWhiteSpaces;
end;

function TBBCodeParser.GetColorTagAttributes: AnsiString;
begin
Match('=');
Result := ReadUntil([cEndTagChar,cClosingTagChar]);
end;

function TBBCodeParser.GetURLTagAttributes: AnsiString;
begin
Result := '';

if FCharacter = '=' then
begin
Match('=');
Result := ReadUntil([cEndTagChar,cClosingTagChar]);
end;
end;

procedure TBBCodeParser.ParseTag;
var
TagIdent: AnsiString;
TagType: TBBCodeTagType;
TagAttributes: AnsiString;
IsEndTag: Boolean;
begin
FParsingTag := True;
IsEndTag := False;
TagType := tagUnknown;

Match(cBeginTagChar);
// check if tag is end tag
if FCharacter = cClosingTagChar then
begin
GetChar;
IsEndTag := True;
end;

TagIdent := LowerCase(GetIdentifier);
TagAttributes := '';

// read tag information
if TagIdent = cColorTagIdent then
TagType := tagColor
else
if TagIdent = cUrlTagIdent then
TagType := tagUrl
else
if TagIdent = cImageTagIdent then
TagType := tagImage
else
Error('Unknown tag "'+TagIdent+'"');

if not IsEndTag then
begin
case TagType of
tagColor : TagAttributes := GetColorTagAttributes;
tagUrl : TagAttributes := GetUrlTagAttributes;
else
end;
end;

Match(cEndTagChar);

if not IsEndTag and Assigned(FOnBeginTag) then
FOnBeginTag(TagType,TagAttributes)
else
if IsEndTag and Assigned(FOnEndTag) then
FOnEndTag(TagType,TagAttributes);

FParsingTag := False;
end;

procedure TBBCodeParser.ParseText;
begin
if Assigned(FOnTextCharacter) then
FOnTextCharacter(FCharacter);
GetChar;
end;

function TBBCodeParser.Parse(aTextToParse: AnsiString): Boolean;
begin
FParsingTag := False;
FBuffer := aTextToParse;
FIndex := 1;
GetChar;

try
while FCharacter <> #0 do
begin
if not FParsingTag and (FCharacter = cBeginTagChar) then
ParseTag
else
ParseText;
end;
Result := True;
except
Result := False;
end;
end;

end.

The 'only' thing I might do is get rid of the events and make it return an array of tag and/or plain text bits in order to make it nicer to use IMO...

cheers,
Paul

paul_nicholls
15-09-2010, 06:57 AM
Hi all,
there was a slight bug where the Match() routine was chopping off any spaces after the closing tag character ']'.

I have modified that routine now to make skipping white spaces optional (see the changes below)



unit unit_BBCode;
{$ifdef fpc}
{$mode Delphi}
{$endif}
{+H}
interface

uses
SysUtils,
Classes;

const
cBeginTagChar = '[';
cEndTagChar = ']';
cClosingTagChar = '/';

cColorTagIdent = 'color';
cURLTagIdent = 'url';
cImageTagIdent = 'img';

type
TCharSet = set of AnsiChar;
BBCodeParseException = class(Exception);
TBBCodeTagType = (tagUnknown,tagColor,tagURL,tagImage);

const
cBBCodeTagType: array[TBBCodeTagType] of AnsiString = (
'unknown',
'color',
'url',
'img'
);

type
TTagEvent = procedure(Tag: TBBCodeTagType; const Attribute: AnsiString) of object;
TTextEvent = procedure(c: AnsiChar) of object;

TBBCodeParser = class(TObject)
private
FErrorMsg: AnsiString;
FBuffer: AnsiString;
FIndex: Integer;
FCharacter: AnsiChar;
FOnBeginTag: TTagEvent;
FOnEndTag: TTagEvent;
FOnTextCharacter: TTextEvent;
FParsingTag: Boolean;
procedure Error(aErrorMsg: AnsiString);
procedure Expected(aExpectedMsg: AnsiString);
procedure GetChar;
procedure SkipWhiteSpaces;
function GetInteger: AnsiString;
function GetIdentifier: AnsiString;
function GetHexNumber: AnsiString;
function ReadUntil(chars: TCharSet): AnsiString;
procedure Match(aMatchText: AnsiString; aDoSkipWhiteSpaces: Boolean = True);
function IsWhiteSpace(c: AnsiChar): Boolean;
function IsDigit(c: AnsiChar): Boolean;
function IsAlpha(c: AnsiChar): Boolean;
function IsHex(c: AnsiChar): Boolean;
function GetColorTagAttributes: AnsiString;
function GetURLTagAttributes: AnsiString;
procedure ParseTag;
procedure ParseText;
public
{ Public declarations }
constructor Create;

function Parse(aTextToParse: AnsiString): Boolean;

property ErrorMsg: AnsiString read FErrorMsg;
property OnBeginTag: TTagEvent read FOnBeginTag write FOnBeginTag;
property OnEndTag: TTagEvent read FOnEndTag write FOnEndTag;
property OnTextCharacter: TTextEvent read FOnTextCharacter write FOnTextCharacter;
end;

implementation

constructor TBBCodeParser.Create;
begin
inherited Create;

FOnBeginTag := nil;
FOnEndTag := nil;
FOnTextCharacter := nil;
FErrorMsg := '';
end;

procedure TBBCodeParser.GetChar;
begin
if FIndex <= Length(FBuffer) then
begin
FCharacter := FBuffer[FIndex];
Inc(FIndex);
end
else
FCharacter := #0;
end;

procedure TBBCodeParser.SkipWhiteSpaces;
begin
while IsWhiteSpace(FCharacter) do
GetChar;
end;

function TBBCodeParser.GetInteger: AnsiString;
begin
Result := '';
if not IsDigit(FCharacter) then
Error('Expected Integer');
while IsDigit(FCharacter) do
begin
Result := Result + FCharacter;
GetChar;
end;
SkipWhiteSpaces;
end;

function TBBCodeParser.GetIdentifier: AnsiString;
begin
Result := '';
if not IsAlpha(FCharacter) then
Error('Expected Identifier');
while IsAlpha(FCharacter) do
begin
Result := Result + FCharacter;
GetChar;
end;
SkipWhiteSpaces;
end;

function TBBCodeParser.GetHexNumber: AnsiString;
begin
Result := '';
if not IsHex(FCharacter) then
Error('Expected Hexadecimal Number');
while IsHex(FCharacter) do
begin
Result := Result + FCharacter;
GetChar;
end;
SkipWhiteSpaces;
end;

function TBBCodeParser.ReadUntil(chars: TCharSet): AnsiString;
begin
Result := '';
while (FCharacter <> #0) and not(FCharacter in chars) do
begin
Result := Result + FCharacter;
GetChar;
end;
end;

function TBBCodeParser.IsWhiteSpace(c: AnsiChar): Boolean;
begin
Result := c in[' ',^I,#10,#13];
end;

function TBBCodeParser.IsDigit(c: AnsiChar): Boolean;
begin
Result := c in['0'..'9'];
end;

function TBBCodeParser.IsAlpha(c: AnsiChar): Boolean;
begin
Result := c in['a'..'z','A'..'Z'];
end;

function TBBCodeParser.IsHex(c: AnsiChar): Boolean;
begin
Result := c in['a'..'f','A'..'F','0'..'9'];
end;

procedure TBBCodeParser.Error(aErrorMsg: AnsiString);
begin
FErrorMsg := aErrorMsg + ' at character index: '+IntToStr(FIndex);
raise BBCodeParseException.Create(aErrorMsg);
end;

procedure TBBCodeParser.Expected(aExpectedMsg: AnsiString);
begin
Error('Expected: "'+aExpectedMsg+'"');
end;

procedure TBBCodeParser.Match(aMatchText: AnsiString; aDoSkipWhiteSpaces: Boolean = True);
var
i: Integer;
begin
for i := 1 to Length(aMatchText) do
begin
if FCharacter <> aMatchText[i] then
Expected(aMatchText);
GetChar;
end;
if aDoSkipWhiteSpaces then
SkipWhiteSpaces;
end;

function TBBCodeParser.GetColorTagAttributes: AnsiString;
begin
Match('=');
Result := ReadUntil([cEndTagChar,cClosingTagChar]);
end;

function TBBCodeParser.GetURLTagAttributes: AnsiString;
begin
Result := '';

if FCharacter = '=' then
begin
Match('=');
Result := ReadUntil([cEndTagChar,cClosingTagChar]);
end;
end;

procedure TBBCodeParser.ParseTag;
var
TagIdent: AnsiString;
TagType: TBBCodeTagType;
TagAttributes: AnsiString;
IsEndTag: Boolean;
begin
FParsingTag := True;
IsEndTag := False;
TagType := tagUnknown;

Match(cBeginTagChar);
// check if tag is end tag
if FCharacter = cClosingTagChar then
begin
GetChar;
IsEndTag := True;
end;

TagIdent := LowerCase(GetIdentifier);
TagAttributes := '';

// read tag information
if TagIdent = cColorTagIdent then
TagType := tagColor
else
if TagIdent = cUrlTagIdent then
TagType := tagUrl
else
if TagIdent = cImageTagIdent then
TagType := tagImage
else
Error('Unknown tag "'+TagIdent+'"');

if not IsEndTag then
begin
case TagType of
tagColor : TagAttributes := GetColorTagAttributes;
tagUrl : TagAttributes := GetUrlTagAttributes;
else
end;
end;

Match(cEndTagChar,False);

if not IsEndTag and Assigned(FOnBeginTag) then
FOnBeginTag(TagType,TagAttributes)
else
if IsEndTag and Assigned(FOnEndTag) then
FOnEndTag(TagType,TagAttributes);

FParsingTag := False;
end;

procedure TBBCodeParser.ParseText;
begin
if Assigned(FOnTextCharacter) then
FOnTextCharacter(FCharacter);
GetChar;
end;

function TBBCodeParser.Parse(aTextToParse: AnsiString): Boolean;
begin
FParsingTag := False;
FBuffer := aTextToParse;
FIndex := 1;
GetChar;

try
while FCharacter <> #0 do
begin
if not FParsingTag and (FCharacter = cBeginTagChar) then
ParseTag
else
ParseText;
end;
Result := True;
except
Result := False;
end;
end;

end.


I can now read in my credits text file successfully using the above parser (including the BBCode colour formatting) and build up my coloured credit parts! Yay!

See the attachment file 9 to see the formatting.

As I receive text character by character from the parser, I build up a text string.

Depending on the tag I receive, I add the concatenated text string to the list using the current colour from a colour stack, and then push the received colour onto the stack or pop a colour off the stack.

See the code snippet below:


procedure TDocumentLine.OnBeginTag(Tag: TBBCodeTagType; const Attribute: AnsiString);
begin
// add any plain text element that is there using the current colour to the list
if FBBCodeText <> '' then
begin
AddText(FBBCodeFont,FBBCodeText,Cardinal(FBBCodeCo lorStack.Peek));
FBBCodeText := '';
end;

if Tag = tagColor then
FBBCodeColorStack.Push(Pointer(GetHGEColorByName(A ttribute)));
end;

procedure TDocumentLine.OnEndTag(Tag: TBBCodeTagType; const Attribute: AnsiString);
begin
// add any plain text element that is there using the current colour to the list
if FBBCodeText <> '' then
begin
AddText(FBBCodeFont,FBBCodeText,Cardinal(FBBCodeCo lorStack.Peek));
FBBCodeText := '';
end;

if Tag = tagColor then
FBBCodeColorStack.Pop;
end;

procedure TDocumentLine.OnText(c: AnsiChar);
begin
FBBCodeText := FBBCodeText + c;
end;

function TDocumentLine.ParseBBCodeText(aFont: Integer; aBBCodeText: AnsiString; var aErrorMsg: AnsiString): Boolean;
var
BBCodeParser: TBBCodeParser;
begin
BBCodeParser := TBBCodeParser.Create;
FBBCodeColorStack := TStack.Create;
try
FBBCodeText := '';
FBBCodeFont := aFont;
BBCodeParser.OnBeginTag := OnBeginTag;
BBCodeParser.OnEndTag := OnEndTag;
BBCodeParser.OnTextCharacter := OnText;

// add default starting colour
FBBCodeColorStack.Push(Pointer(White));

Result := BBCodeParser.Parse(aBBCodeText);
if not Result then
aErrorMsg := BBCodeParser.ErrorMsg
else
begin
// add any plain text element that is there using the current colour to the list
if FBBCodeText <> '' then
begin
AddText(FBBCodeFont,FBBCodeText,Cardinal(FBBCodeCo lorStack.Peek));
FBBCodeText := '';
end;
end;
finally
BBCodeParser.Free;
FBBCodeColorStack.Free;
end;
end;

cheers,
Paul

paul_nicholls
16-09-2010, 06:49 AM
I hope someone finds this as useful as I did ;)

cheers,
Paul

alexione
18-09-2010, 06:39 AM
This is ideal candidate for PGD library! Let's hope to see one soon :-)

Traveler
18-09-2010, 10:59 AM
Hmm, I see how this can be very useful. I'll keep this thread in mind.
Thanks