Shortcode parser to embed content into any text-string. The parser is fully-programmable and works asynchronously, using promises.
Will parse both Wordpress shortcodes and similar embed/inserts from other platforms. This allows a fully bespoke solution if you are building your own platform.
Parser can apply handler functions to specific tags or even use fallback regular expressions and functions. These fallbacks mean it is possible to link content in without specific tag ids.
npm install shortcode-insert
To save to your package.json
npm install --save shortcode-insert
const Shortcode = require('shortcode-insert');
// Create a new parser instance
const parser = Shortcode();
let sampleText = 'This is my test code: [[HELLO]]';
parser.add('HELLO', tag=>{ // add a handler for the 'HELLO' tag
return 'HELLO WORLD';
});
parser.parse(sampleText).then(parsedText=>{
console.log(parsedText); // will log 'This is my test code: HELLO WORLD' to the console.
});
The default tag delimiters are [[ and ]]. To switch to wordpress-style shortcodes is straight-forward. All you have to do istell it to use different delimiters. You can define this in an options object passed to the parser factory.
Wordpress shortcode parser example:
const Shortcode = require('shortcode-insert');
// Create a new parser instance
const parser = Shortcode({ // define the delimiters as single square brackets like in Wordpress.
start: '[',
end: ']'
});
let sampleText = '[woocommerce_cart]';
parser.add('woocommerce_cart', tag=>{ // add handler for woocomerce cart
// do something interesting
return 'My parsed text';
});
parser.parse(sampleText).then(parsedText=>{
console.log(parsedText);
});
Handlers are super easy to create. All you need to do is call the add() method with the required tag name and the handler function.
parser.add('HELLO', tag=>{ // add handler for HELLO tag
return 'HELLO WORLD';
});
In the above a handler for the tag HELLO is created. All tag names are case-sensative, so: HELLO, Hello and hello are all different.
The return from a handler is always wrapped in a promise and resolved before parser completes. This means that you can return plain text or a promise, which resolves to plain text. In either case plain text is expected as the result. Theresult is what you want to replace the tag with.
If we want to, say query a database and then insert data after the results have returned we need to use promises. So, a more advanced example of a handler might be:
parser.add('CONTENT', tag=>{ // add handler for CONTENT tag
return new Promise((resolve, reject)=>{
mydatabase.query('SELECT * FROM content WHERE ID=1', (err, rows, fields)=>{
if (err) return reject(err);
resolve(rows[0].content);
});
});
});
The tag parameter supplied to the handler function has the following format:
- tagName: string Name of the tag.
- endTag: boolean Is this a closing tag? Not normally true unless self-closing.
- fullMatch string The full tag content, including the delimiters and opening and closing tags and content.
- tagContents string The contents of the opening tag (this is the tag name and attributes astext not the content between any opening and closing tags).
- start integer The start position in the string being parsed for this tag.
- end integer The end position for this tag in the string being parsed (this is the final closing point of tag at end of any closing tag).
- content string The tag contents (between the opening and closing tags). Will be empty string if self-closing.
- attributes Object Object containing all the tag attributes as key/value pairs. Will also index attributes as numbered items, so you can reference the order of attributes too. Attributes without a key and value can be referenced via the numbered items. This means you can parse attributes according to position as well as property name.
Tag attributes are accessible via the attributes property of the tag parameter.
parser.add('CONTENT', tag=>{ // add handler for CONTENT tag
return new Promise((resolve, reject)=>{ // Use the id attribute of the tag to do a lookup-up
mydatabase.query('SELECT * FROM content WHERE ID=' + tag.attributes.id, (err, rows, fields)=>{
if (err) return reject(err);
resolve(rows[0].content);
});
});
});
let sampleText = '[[CONTENT id=45]]';
parser.parse(sampleText).then(parsedText=>{
console.log(parsedText);
});
Attributes can be quoted via single or double qotes. Quotes can be missed out entirely if the attribute value has no spaces in it. So [[CONTENT id=45]], [[CONTENT id='45']] and [[CONTENT id="45"]] are all the same.
Attributes are also accessible via their position. This allows for simple positional attributes to be used. Sometimes you might want to allow a really concise tag.
To modify the above example:
parser.add('CONTENT', tag=>{ // add handler for CONTENT tag
return new Promise((resolve, reject)=>{ // Use the id attribute of the tag to do a lookup-up
mydatabase.query('SELECT * FROM content WHERE ID=' + tag.attributes[1], (err, rows, fields)=>{
if (err) return reject(err);
resolve(rows[0].content);
});
});
});
let sampleText = '[[CONTENT 45]]';
parser.parse(sampleText).then(parsedText=>{
console.log(parsedText);
});
Tag positions start at 1. These positional attributes can be quoted, so the following are all the same: [[CONTENT 45]], [[CONTENT '45']] and [[CONTENT "45"]].
If a position is occupied by a key/value pair then an object is returned for that index with the single property and value. This means you could in theory mix'n'match postional attributes and key/value pairs.
Tags are automatically self-closing but you can supply tag content by adding a closing tag. For example:
parser.add('HELLO', tag=>{ // add a handler for the 'HELLO' tag
return 'HELLO WORLD: ' + tag.content
});
let sampleText = '[[HELLO]] !!![[/HELLO]]';
parser.parse(sampleText, req).then(parsedText=>{
console.log(parsedText); // will log 'HELLO WORLD !!!!'
});
Any parameters passed to the parse method after the string to parse are passed onto the handlers. So for example:
parser.add('HELLO', (tag, req)=>{ // add a handler for the 'HELLO' tag
return 'HELLO WORLD: ' + req.path;
});
parser.parse(sampleText, req).then(parsedText=>{
console.log(parsedText);
});
This can be extreamly useful if you need to pass application data around or even a http request object.
If you try to add a handler for tag that already exists an error is thrown. You can overide this by passing true as the 3rd parameter.
parser.add('HELLO', tag=>{ // add a handler for the 'HELLO' tag
return 'HELLO WORLD';
});
parser.add('HELLO', tag=>{ // add a handler for the 'HELLO' tag
return 'HELLO WORLD!';
}, true); // Allow this to overwrite the previous one.
You can test for a handler by using the has method.
console.log('Does the passer have a handler assigned for the "HELLO" tag?', parser.has('HELLO'));
You can delete a handler by calling the delete method.
parser.delete('HELLO'); // Delete the handler for the HELLO tag.
You can also get an assigned handler if you need to manipulate it somehow.
parser.get('HELLO');
You can also create more generic handlers, which do not need tag names. These use regular expressions to caputure tags to parse.
These can be extreamly powerful as you can create concise embed codes for adding content.
Here is an example, which adds a pdf link to some content.
parser.add(/^.*\.pdf/, tag=>{ // add a handler for the 'HELLO' tag
return '<a href="/uploads/pdfs/' + tag.attributes[1] + '">' + tag.attributes[2] + '</a>';
});
let sampleText = '[[mydoc.pdf "Interesting Document"]]'
parser.parse(sampleText).then(parsedText=>{
console.log(parsedText); // will log <a href="/uploads/pdfs/mydoc.pdf">Interesting Document</a>
});
Named tags will always run if they are matched to handler. These generic handlers will only fire if a named tag handler is not found.
You can also run a function as the tag selector. These functions should return true or false for tag match. The above example as a functional selector would be:
parser.add(tagContents=>{
return /^.*\.pdf/.test(tagContents);
}, tag=>{ // add a handler for the 'HELLO' tag
return '<a href="/uploads/pdfs/' + tag.attributes[1] + '">' + tag.attributes[2] + '</a>';
});
let sampleText = '[[mydoc.pdf "Interesting Document"]]'
parser.parse(sampleText).then(parsedText=>{
console.log(parsedText); // will log <a href="/uploads/pdfs/mydoc.pdf">Interesting Document</a>
});