2012-12-13 21:34:31 +00:00
|
|
|
/*\
|
2012-12-20 16:41:06 +00:00
|
|
|
title: $:/core/modules/parsers/wikiparser/rules/html.js
|
2012-12-13 21:34:31 +00:00
|
|
|
type: application/javascript
|
2012-12-20 16:02:03 +00:00
|
|
|
module-type: wikirule
|
2012-12-13 21:34:31 +00:00
|
|
|
|
|
|
|
Wiki rule for HTML elements and widgets. For example:
|
|
|
|
|
|
|
|
{{{
|
|
|
|
<aside>
|
|
|
|
This is an HTML5 aside element
|
|
|
|
</aside>
|
|
|
|
|
2012-12-30 13:55:19 +00:00
|
|
|
<$slider target="MyTiddler">
|
2012-12-13 21:34:31 +00:00
|
|
|
This is a widget invocation
|
2012-12-30 13:55:19 +00:00
|
|
|
</$slider>
|
2012-12-13 21:34:31 +00:00
|
|
|
|
|
|
|
}}}
|
|
|
|
|
|
|
|
\*/
|
|
|
|
(function(){
|
|
|
|
|
|
|
|
/*jslint node: true, browser: true */
|
|
|
|
/*global $tw: false */
|
|
|
|
"use strict";
|
|
|
|
|
2012-12-14 13:31:47 +00:00
|
|
|
exports.name = "html";
|
2012-12-30 17:21:38 +00:00
|
|
|
exports.types = {inline: true, block: true};
|
2012-12-14 13:31:47 +00:00
|
|
|
|
2012-12-14 15:44:19 +00:00
|
|
|
exports.init = function(parser) {
|
|
|
|
this.parser = parser;
|
2013-06-15 14:12:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.findNextMatch = function(startPos) {
|
|
|
|
// Find the next tag
|
|
|
|
this.nextTag = this.findNextTag(this.parser.source,startPos,{
|
|
|
|
requireLineBreak: this.is.block
|
|
|
|
});
|
|
|
|
return this.nextTag ? this.nextTag.start : undefined;
|
2012-12-13 21:34:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Parse the most recent match
|
|
|
|
*/
|
2012-12-14 13:31:47 +00:00
|
|
|
exports.parse = function() {
|
2013-06-15 14:12:05 +00:00
|
|
|
// Retrieve the most recent match so that recursive calls don't overwrite it
|
|
|
|
var tag = this.nextTag;
|
|
|
|
this.nextTag = null;
|
|
|
|
// Advance the parser position to past the tag
|
|
|
|
this.parser.pos = tag.end;
|
2014-02-20 21:42:31 +00:00
|
|
|
// Check for an immediately following double linebreak
|
2014-08-03 08:43:20 +00:00
|
|
|
var hasLineBreak = !tag.isSelfClosing && !!$tw.utils.parseTokenRegExp(this.parser.source,this.parser.pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
|
2013-06-15 14:12:05 +00:00
|
|
|
// Set whether we're in block mode
|
|
|
|
tag.isBlock = this.is.block || hasLineBreak;
|
|
|
|
// Parse the body if we need to
|
|
|
|
if(!tag.isSelfClosing && $tw.config.htmlVoidElements.indexOf(tag.tag) === -1) {
|
2021-01-29 14:57:30 +00:00
|
|
|
var reEndString = "</" + $tw.utils.escapeRegExp(tag.tag) + ">";
|
2012-12-30 17:21:38 +00:00
|
|
|
if(hasLineBreak) {
|
2013-06-15 14:12:05 +00:00
|
|
|
tag.children = this.parser.parseBlocks(reEndString);
|
2012-12-13 21:34:31 +00:00
|
|
|
} else {
|
2021-01-29 14:57:30 +00:00
|
|
|
var reEnd = new RegExp("(" + reEndString + ")","mg");
|
|
|
|
tag.children = this.parser.parseInlineRun(reEnd,{eatTerminator: true});
|
2012-12-13 21:34:31 +00:00
|
|
|
}
|
2013-06-15 14:12:05 +00:00
|
|
|
}
|
|
|
|
// Return the tag
|
|
|
|
return [tag];
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2014-04-17 11:00:32 +00:00
|
|
|
Look for an HTML tag. Returns null if not found, otherwise returns {type: "element", name:, attributes: [], isSelfClosing:, start:, end:,}
|
2013-06-15 14:12:05 +00:00
|
|
|
*/
|
|
|
|
exports.parseTag = function(source,pos,options) {
|
|
|
|
options = options || {};
|
|
|
|
var token,
|
|
|
|
node = {
|
|
|
|
type: "element",
|
|
|
|
start: pos,
|
|
|
|
attributes: {}
|
|
|
|
};
|
|
|
|
// Define our regexps
|
2013-07-14 22:13:42 +00:00
|
|
|
var reTagName = /([a-zA-Z0-9\-\$]+)/g;
|
2013-06-15 14:12:05 +00:00
|
|
|
// Skip whitespace
|
2014-04-17 11:00:32 +00:00
|
|
|
pos = $tw.utils.skipWhiteSpace(source,pos);
|
2013-06-15 14:12:05 +00:00
|
|
|
// Look for a less than sign
|
2014-04-17 11:00:32 +00:00
|
|
|
token = $tw.utils.parseTokenString(source,pos,"<");
|
2013-06-15 14:12:05 +00:00
|
|
|
if(!token) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
pos = token.end;
|
|
|
|
// Get the tag name
|
2014-04-17 11:00:32 +00:00
|
|
|
token = $tw.utils.parseTokenRegExp(source,pos,reTagName);
|
2013-06-15 14:12:05 +00:00
|
|
|
if(!token) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
node.tag = token.match[1];
|
2018-01-16 22:13:40 +00:00
|
|
|
if(node.tag.slice(1).indexOf("$") !== -1) {
|
|
|
|
return null;
|
|
|
|
}
|
2014-05-14 07:51:08 +00:00
|
|
|
if(node.tag.charAt(0) === "$") {
|
|
|
|
node.type = node.tag.substr(1);
|
|
|
|
}
|
2013-06-15 14:12:05 +00:00
|
|
|
pos = token.end;
|
2016-10-12 12:17:53 +00:00
|
|
|
// Check that the tag is terminated by a space, / or >
|
|
|
|
if(!$tw.utils.parseWhiteSpace(source,pos) && !(source.charAt(pos) === "/") && !(source.charAt(pos) === ">") ) {
|
|
|
|
return null;
|
|
|
|
}
|
2013-06-15 14:12:05 +00:00
|
|
|
// Process attributes
|
2014-04-17 11:00:32 +00:00
|
|
|
var attribute = $tw.utils.parseAttribute(source,pos);
|
2013-06-15 14:12:05 +00:00
|
|
|
while(attribute) {
|
|
|
|
node.attributes[attribute.name] = attribute;
|
|
|
|
pos = attribute.end;
|
|
|
|
// Get the next attribute
|
2014-04-17 11:00:32 +00:00
|
|
|
attribute = $tw.utils.parseAttribute(source,pos);
|
2013-06-15 14:12:05 +00:00
|
|
|
}
|
|
|
|
// Skip whitespace
|
2014-04-17 11:00:32 +00:00
|
|
|
pos = $tw.utils.skipWhiteSpace(source,pos);
|
2013-06-15 14:12:05 +00:00
|
|
|
// Look for a closing slash
|
2014-04-17 11:00:32 +00:00
|
|
|
token = $tw.utils.parseTokenString(source,pos,"/");
|
2013-06-15 14:12:05 +00:00
|
|
|
if(token) {
|
|
|
|
pos = token.end;
|
|
|
|
node.isSelfClosing = true;
|
|
|
|
}
|
|
|
|
// Look for a greater than sign
|
2014-04-17 11:00:32 +00:00
|
|
|
token = $tw.utils.parseTokenString(source,pos,">");
|
2013-06-15 14:12:05 +00:00
|
|
|
if(!token) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
pos = token.end;
|
|
|
|
// Check for a required line break
|
|
|
|
if(options.requireLineBreak) {
|
2014-08-03 08:43:20 +00:00
|
|
|
token = $tw.utils.parseTokenRegExp(source,pos,/([^\S\n\r]*\r?\n(?:[^\S\n\r]*\r?\n|$))/g);
|
2013-06-15 14:12:05 +00:00
|
|
|
if(!token) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Update the end position
|
|
|
|
node.end = pos;
|
|
|
|
return node;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.findNextTag = function(source,pos,options) {
|
|
|
|
// A regexp for finding candidate HTML tags
|
|
|
|
var reLookahead = /<([a-zA-Z\-\$]+)/g;
|
|
|
|
// Find the next candidate
|
|
|
|
reLookahead.lastIndex = pos;
|
|
|
|
var match = reLookahead.exec(source);
|
|
|
|
while(match) {
|
|
|
|
// Try to parse the candidate as a tag
|
|
|
|
var tag = this.parseTag(source,match.index,options);
|
|
|
|
// Return success
|
2014-05-14 07:51:08 +00:00
|
|
|
if(tag && this.isLegalTag(tag)) {
|
2013-06-15 14:12:05 +00:00
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
// Look for the next match
|
|
|
|
reLookahead.lastIndex = match.index + 1;
|
|
|
|
match = reLookahead.exec(source);
|
|
|
|
}
|
|
|
|
// Failed
|
|
|
|
return null;
|
2012-12-13 21:34:31 +00:00
|
|
|
};
|
|
|
|
|
2013-10-23 11:36:34 +00:00
|
|
|
exports.isLegalTag = function(tag) {
|
2014-05-14 07:51:08 +00:00
|
|
|
// Widgets are always OK
|
|
|
|
if(tag.type !== "element") {
|
2013-10-23 11:36:34 +00:00
|
|
|
return true;
|
2014-05-14 07:51:08 +00:00
|
|
|
// If it's an HTML tag that starts with a dash then it's not legal
|
|
|
|
} else if(tag.tag.charAt(0) === "-") {
|
2013-10-23 11:36:34 +00:00
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
// Otherwise it's OK
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-12-13 21:34:31 +00:00
|
|
|
})();
|