How to perform the analysis of its own html tags in the text

advertisements

I have task to realize own tags that making text bold, underline or strikethrough with any nesting. Like a

*bold text* _underlinetext_ -strikethrough-

Also I need to make own hyperlink like a

[link | http://stackoverflow.com]

The first thought that came - it apply regexp. The code:

View.prototype.parseText = function(text) {

text = text.replace(/\*([^\*]+)\*/g, '<b>$1</b>');
text = text.replace(/\_([^\_]+)\_/g, '<u>$1</u>');
text = text.replace(/\-([^\-]+)\-/g, '<s>$1</s>');
text = text.replace(/\[([^\|].+)\|(.+)\]/g, '<a href="$2">$1</a>');

return text;};

It's working but I need extensibility. Regex is not a good idea, since it's hardcoded. How to realize that task with finite state machine (or any jQuery plugin))? I would be grateful for any help.


I can suggest you the following implementation http://jsfiddle.net/NwRCm/5/

It uses the State design pattern (little modified because of JavaScript and the purpose). Under the surface all states are implemented with regular expressions but that's the most efficient way, in my opinion.

/* View definition */

function View(container) {
    this.container = container;
    this._parsers = [];
    this._currentState = 0;
};

View.prototype.parse = function(text) {

    var self = this;
    this._parsers.forEach(function (e) {
        self._parse(e);
    });

    return this.container.innerHTML;

};

View.prototype._parse = function (parser) {
    var text = parser.parse(this.container.innerHTML);
    this.container.innerHTML = text;
    return text;
};

View.prototype.nextState = function () {
    if (this._currentState < this._parsers.length) {
        return this._parse(this._parsers[this._currentState++]);
    }
    return null;
};

View.prototype.addParser = function (parser) {
    if (parser instanceof Parser) {
        return this._parsers.push(parser);
    } else {
        throw 'The parser you\'re trying to add is not an instance of Parser';
    }
};
/* end of the View definition */

/* Simulation of interface */
function Parser() {};

Parser.prototype.parse = function () {
    throw 'Not implemented!';
};

/* Implementation of bold parser */
function BoldParser() {};

BoldParser.prototype = new Parser();

BoldParser.prototype.parse = function (text) {
    text = text.replace(/\*([^\*]+)\*/g, '<b>$1</b>');
    return text;
};

/* Implementation of underline parser */
function UnderlineParser() {};

UnderlineParser.prototype = new Parser();

UnderlineParser.prototype.parse = function (text) {
    text = text.replace(/\_([^\_]+)\_/g, '<u>$1</u>');
    return text;
};

/* Link parser */
function LinkParser() {};

LinkParser.prototype = new Parser();

LinkParser.prototype.parse = function (text) {
    text = text.replace(/\[([^\|].+)\|(.+)\]/g, '<a href="$2">$1</a>');
    return text;
};

var v = new View(document.getElementById('container'));
v.addParser(new UnderlineParser());
v.addParser(new BoldParser());
v.addParser(new LinkParser());
v.nextState();
v.nextState();
v.nextState();

​Let me look a little deeper in the implementation. First we have a base "class" (constructor function) View. Each view has it's base container and a list of parsers, it also remember which parser should be applied next.

After that we have the "abstract class" (constructor function with method in the prototype which throws an exception) named Parser it defines a method parse which must be implemented by each parser.

After that we just define different concrete parsers and add them to the view. We can pass the states one by one (View's nextState) or pass all states in a single method call (View's parse). We can dynamically add new parsers.

A thing which can be approved is including flyweight factory for managing the parsers.

Approach with the "abstract" constructor function is also very useful when implementing different patterns like Template method for example.

  • Edit may be there's a bit overhead because of the definition of all these constructor functions and objects. Everything can be done with callbacks i.e. each state to be a different function. I used this approach because I was looking for the easiest for understanding, clear from language specific features answer. I hope that I achieved it.