From 9f9ce6595b08032a602981f82940ca113cff8211 Mon Sep 17 00:00:00 2001 From: "jeremy@jermolene.com" Date: Wed, 3 Feb 2021 15:13:56 +0000 Subject: [PATCH] Make it easier to subclass the wikitext parser with a custom rule set We can now pass arrays of rule classes to the parser constructor, overriding the rules that would normally be used by the parser. This allows us to create custom variants of the wikitext parser with their own content type. It could also provide a basis for a new Markdown parser based on our existing wikitext parser but with new rules. --- boot/boot.js | 21 ++++--- core/modules/parsers/wikiparser/wikiparser.js | 58 +++++++++++++------ .../new/ParserSubclassingMechanism.tid | 42 ++++++++++++++ 3 files changed, 95 insertions(+), 26 deletions(-) create mode 100644 editions/dev/tiddlers/new/ParserSubclassingMechanism.tid diff --git a/boot/boot.js b/boot/boot.js index 353e5e0f6..fbac37d77 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -892,6 +892,19 @@ $tw.modules.applyMethods = function(moduleType,targetObject) { return targetObject; }; +/* +Return a class created from a modules. The module should export the properties to be added to those of the optional base class +*/ +$tw.modules.createClassFromModule = function(moduleExports,baseClass) { + var newClass = function() {}; + if(baseClass) { + newClass.prototype = new baseClass(); + newClass.prototype.constructor = baseClass; + } + $tw.utils.extend(newClass.prototype,moduleExports); + return newClass; +}; + /* Return an array of classes created from the modules of a specified type. Each module should export the properties to be added to those of the optional base class */ @@ -899,13 +912,7 @@ $tw.modules.createClassesFromModules = function(moduleType,subType,baseClass) { var classes = Object.create(null); $tw.modules.forEachModuleOfType(moduleType,function(title,moduleExports) { if(!subType || moduleExports.types[subType]) { - var newClass = function() {}; - if(baseClass) { - newClass.prototype = new baseClass(); - newClass.prototype.constructor = baseClass; - } - $tw.utils.extend(newClass.prototype,moduleExports); - classes[moduleExports.name] = newClass; + classes[moduleExports.name] = $tw.modules.createClassFromModule(moduleExports,baseClass); } }); return classes; diff --git a/core/modules/parsers/wikiparser/wikiparser.js b/core/modules/parsers/wikiparser/wikiparser.js index dbeed9de2..e6f860a91 100644 --- a/core/modules/parsers/wikiparser/wikiparser.js +++ b/core/modules/parsers/wikiparser/wikiparser.js @@ -25,6 +25,14 @@ Attributes are stored as hashmaps of the following objects: /*global $tw: false */ "use strict"; +/* +type: content type of text +text: text to be parsed +options: see below: + parseAsInline: true to parse text as inline instead of block + wiki: reference to wiki to use + _canonical_uri: optional URI of content if text is missing or empty +*/ var WikiParser = function(type,text,options) { this.wiki = options.wiki; var self = this; @@ -33,19 +41,6 @@ var WikiParser = function(type,text,options) { this.loadRemoteTiddler(options._canonical_uri); text = $tw.language.getRawString("LazyLoadingWarning"); } - // Initialise the classes if we don't have them already - if(!this.pragmaRuleClasses) { - WikiParser.prototype.pragmaRuleClasses = $tw.modules.createClassesFromModules("wikirule","pragma",$tw.WikiRuleBase); - this.setupRules(WikiParser.prototype.pragmaRuleClasses,"$:/config/WikiParserRules/Pragmas/"); - } - if(!this.blockRuleClasses) { - WikiParser.prototype.blockRuleClasses = $tw.modules.createClassesFromModules("wikirule","block",$tw.WikiRuleBase); - this.setupRules(WikiParser.prototype.blockRuleClasses,"$:/config/WikiParserRules/Block/"); - } - if(!this.inlineRuleClasses) { - WikiParser.prototype.inlineRuleClasses = $tw.modules.createClassesFromModules("wikirule","inline",$tw.WikiRuleBase); - this.setupRules(WikiParser.prototype.inlineRuleClasses,"$:/config/WikiParserRules/Inline/"); - } // Save the parse text this.type = type || "text/vnd.tiddlywiki"; this.source = text || ""; @@ -54,13 +49,38 @@ var WikiParser = function(type,text,options) { this.configTrimWhiteSpace = false; // Set current parse position this.pos = 0; - // Instantiate the pragma parse rules - this.pragmaRules = this.instantiateRules(this.pragmaRuleClasses,"pragma",0); - // Instantiate the parser block and inline rules - this.blockRules = this.instantiateRules(this.blockRuleClasses,"block",0); - this.inlineRules = this.instantiateRules(this.inlineRuleClasses,"inline",0); - // Parse any pragmas + // Start with empty output this.tree = []; + // Assemble the rule classes we're going to use + var pragmaRuleClasses, blockRuleClasses, inlineRuleClasses; + if(options.rules) { + pragmaRuleClasses = options.rules.pragma; + blockRuleClasses = options.rules.block; + inlineRuleClasses = options.rules.inline; + } else { + // Setup the rule classes if we don't have them already + if(!this.pragmaRuleClasses) { + WikiParser.prototype.pragmaRuleClasses = $tw.modules.createClassesFromModules("wikirule","pragma",$tw.WikiRuleBase); + this.setupRules(WikiParser.prototype.pragmaRuleClasses,"$:/config/WikiParserRules/Pragmas/"); + } + pragmaRuleClasses = this.pragmaRuleClasses; + if(!this.blockRuleClasses) { + WikiParser.prototype.blockRuleClasses = $tw.modules.createClassesFromModules("wikirule","block",$tw.WikiRuleBase); + this.setupRules(WikiParser.prototype.blockRuleClasses,"$:/config/WikiParserRules/Block/"); + } + blockRuleClasses = this.blockRuleClasses; + if(!this.inlineRuleClasses) { + WikiParser.prototype.inlineRuleClasses = $tw.modules.createClassesFromModules("wikirule","inline",$tw.WikiRuleBase); + this.setupRules(WikiParser.prototype.inlineRuleClasses,"$:/config/WikiParserRules/Inline/"); + } + inlineRuleClasses = this.inlineRuleClasses; + } + // Instantiate the pragma parse rules + this.pragmaRules = this.instantiateRules(pragmaRuleClasses,"pragma",0); + // Instantiate the parser block and inline rules + this.blockRules = this.instantiateRules(blockRuleClasses,"block",0); + this.inlineRules = this.instantiateRules(inlineRuleClasses,"inline",0); + // Parse any pragmas var topBranch = this.parsePragmas(); // Parse the text into inline runs or blocks if(options.parseAsInline) { diff --git a/editions/dev/tiddlers/new/ParserSubclassingMechanism.tid b/editions/dev/tiddlers/new/ParserSubclassingMechanism.tid new file mode 100644 index 000000000..c4aab295e --- /dev/null +++ b/editions/dev/tiddlers/new/ParserSubclassingMechanism.tid @@ -0,0 +1,42 @@ +created: 20210203150855206 +modified: 20210203150855206 +title: ParserSubclassingMechanism + +!! Introduction + +The wikitext parser subclassing mechanism makes it possible for custom parsers to use the wiki text parsing engine, but to customise the rules that are used. + +!! Example + +Here is an example of a subclass of the checkbox widget that adds logging to the event handler: + +```js +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var WikiParser = require("$:/core/modules/parsers/wikiparser/wikiparser.js")["text/vnd.tiddlywiki"], + HtmlParser = $tw.modules.createClassFromModule(require("$:/core/modules/parsers/wikiparser/rules/html.js"),$tw.WikiRuleBase), + EntityParser = $tw.modules.createClassFromModule(require("$:/core/modules/parsers/wikiparser/rules/entity.js"),$tw.WikiRuleBase); + +var MyCustomWikiParser = function(type,text,options) { + var parser = new WikiParser(type,text,$tw.utils.extend({},options,{ + // Force the parser to parse in inline mode + parseAsInline: true, + // Specify which rules will be used + rules: { + pragma: [], + block: [], + inline: [HtmlParser,EntityParser] + } + })); + this.tree = parser.tree; + this.prototype = parser.prototype; +}; + +exports["text/vnd.my-custom-type"] = MyCustomWikiParser; + +})(); +```