From 27fd67142f499df89f5d23ab70ecec02bc6ffcc7 Mon Sep 17 00:00:00 2001 From: Alan Date: Mon, 19 Dec 2022 14:57:47 +0800 Subject: [PATCH] Fix #7504 - include rtf parser in bootstrap editor --- buildSDK/dependancy_bootstrap.txt | 11 + roojs-bootstrap-debug.js | 494 ++++++++++++++++++++++++++++++ roojs-bootstrap.js | 37 +++ 3 files changed, 542 insertions(+) diff --git a/buildSDK/dependancy_bootstrap.txt b/buildSDK/dependancy_bootstrap.txt index dc19c70ca4..cfa194648d 100644 --- a/buildSDK/dependancy_bootstrap.txt +++ b/buildSDK/dependancy_bootstrap.txt @@ -127,6 +127,16 @@ Roo.bootstrap.form.CheckBox Roo.bootstrap.form.Radio Roo.bootstrap.form.SecurePass + +Roo.rtf.namespace +Roo.rtf.Hex +Roo.rtf.Paragraph +Roo.rtf.Span +Roo.rtf.Group +Roo.rtf.Document +Roo.rtf.Ctrl +Roo.rtf.Parser + Roo.htmleditor.namespace Roo.htmleditor.Filter Roo.htmleditor.FilterAttributes @@ -145,6 +155,7 @@ Roo.htmleditor.TidyWriter Roo.htmleditor.TidyEntities Roo.htmleditor.KeyEnter + Roo.htmleditor.Block Roo.htmleditor.BlockFigure Roo.htmleditor.BlockTable diff --git a/roojs-bootstrap-debug.js b/roojs-bootstrap-debug.js index c639897318..add16b4037 100644 --- a/roojs-bootstrap-debug.js +++ b/roojs-bootstrap-debug.js @@ -26030,7 +26030,501 @@ Roo.extend(Roo.bootstrap.form.SecurePass, Roo.bootstrap.form.Input, { return this.IsLongEnough(pwd, 6) || !this.IsLongEnough(pwd, 0); } +});Roo.rtf = {}; // namespace +Roo.rtf.Hex = function(hex) +{ + this.hexstr = hex; +}; +Roo.rtf.Paragraph = function(opts) +{ + this.content = []; ///??? is that used? +};Roo.rtf.Span = function(opts) +{ + this.value = opts.value; +}; + +Roo.rtf.Group = function(parent) +{ + // we dont want to acutally store parent - it will make debug a nightmare.. + this.content = []; + this.cn = []; + + + +}; + +Roo.rtf.Group.prototype = { + ignorable : false, + content: false, + cn: false, + addContent : function(node) { + // could set styles... + this.content.push(node); + }, + addChild : function(cn) + { + this.cn.push(cn); + }, + // only for images really... + toDataURL : function() + { + var mimetype = false; + switch(true) { + case this.content.filter(function(a) { return a.value == 'pngblip' } ).length > 0: + mimetype = "image/png"; + break; + case this.content.filter(function(a) { return a.value == 'jpegblip' } ).length > 0: + mimetype = "image/jpeg"; + break; + default : + return 'about:blank'; // ?? error? + } + + + var hexstring = this.content[this.content.length-1].value; + + return 'data:' + mimetype + ';base64,' + btoa(hexstring.match(/\w{2}/g).map(function(a) { + return String.fromCharCode(parseInt(a, 16)); + }).join("")); + } + +}; +// this looks like it's normally the {rtf{ .... }} +Roo.rtf.Document = function() +{ + // we dont want to acutally store parent - it will make debug a nightmare.. + this.rtlch = []; + this.content = []; + this.cn = []; + +}; +Roo.extend(Roo.rtf.Document, Roo.rtf.Group, { + addChild : function(cn) + { + this.cn.push(cn); + switch(cn.type) { + case 'rtlch': // most content seems to be inside this?? + case 'listtext': + case 'shpinst': + this.rtlch.push(cn); + return; + default: + this[cn.type] = cn; + } + + }, + + getElementsByType : function(type) + { + var ret = []; + this._getElementsByType(type, ret, this.cn, 'rtf'); + return ret; + }, + _getElementsByType : function (type, ret, search_array, path) + { + search_array.forEach(function(n,i) { + if (n.type == type) { + n.path = path + '/' + n.type + ':' + i; + ret.push(n); + } + if (n.cn.length > 0) { + this._getElementsByType(type, ret, n.cn, path + '/' + n.type+':'+i); + } + },this); + } + }); + +Roo.rtf.Ctrl = function(opts) +{ + this.value = opts.value; + this.param = opts.param; +}; +/** + * + * + * based on this https://github.com/iarna/rtf-parser + * it's really only designed to extract pict from pasted RTF + * + * usage: + * + * var images = new Roo.rtf.Parser().parse(a_string).filter(function(g) { return g.type == 'pict'; }); + * + * + */ + + + + + +Roo.rtf.Parser = function(text) { + //super({objectMode: true}) + this.text = ''; + this.parserState = this.parseText; + + // these are for interpeter... + this.doc = {}; + ///this.parserState = this.parseTop + this.groupStack = []; + this.hexStore = []; + this.doc = false; + + this.groups = []; // where we put the return. + + for (var ii = 0; ii < text.length; ++ii) { + ++this.cpos; + + if (text[ii] === '\n') { + ++this.row; + this.col = 1; + } else { + ++this.col; + } + this.parserState(text[ii]); + } + + + +}; +Roo.rtf.Parser.prototype = { + text : '', // string being parsed.. + controlWord : '', + controlWordParam : '', + hexChar : '', + doc : false, + group: false, + groupStack : false, + hexStore : false, + + + cpos : 0, + row : 1, // reportin? + col : 1, // + + + push : function (el) + { + var m = 'cmd'+ el.type; + if (typeof(this[m]) == 'undefined') { + Roo.log('invalid cmd:' + el.type); + return; + } + this[m](el); + //Roo.log(el); + }, + flushHexStore : function() + { + if (this.hexStore.length < 1) { + return; + } + var hexstr = this.hexStore.map( + function(cmd) { + return cmd.value; + }).join(''); + + this.group.addContent( new Roo.rtf.Hex( hexstr )); + + + this.hexStore.splice(0) + + }, + + cmdgroupstart : function() + { + this.flushHexStore(); + if (this.group) { + this.groupStack.push(this.group); + } + // parent.. + if (this.doc === false) { + this.group = this.doc = new Roo.rtf.Document(); + return; + + } + this.group = new Roo.rtf.Group(this.group); + }, + cmdignorable : function() + { + this.flushHexStore(); + this.group.ignorable = true; + }, + cmdendparagraph : function() + { + this.flushHexStore(); + this.group.addContent(new Roo.rtf.Paragraph()); + }, + cmdgroupend : function () + { + this.flushHexStore(); + var endingGroup = this.group; + + + this.group = this.groupStack.pop(); + if (this.group) { + this.group.addChild(endingGroup); + } + + + + var doc = this.group || this.doc; + //if (endingGroup instanceof FontTable) { + // doc.fonts = endingGroup.table + //} else if (endingGroup instanceof ColorTable) { + // doc.colors = endingGroup.table + //} else if (endingGroup !== this.doc && !endingGroup.get('ignorable')) { + if (endingGroup.ignorable === false) { + //code + this.groups.push(endingGroup); + // Roo.log( endingGroup ); + } + //Roo.each(endingGroup.content, function(item)) { + // doc.addContent(item); + //} + //process.emit('debug', 'GROUP END', endingGroup.type, endingGroup.get('ignorable')) + //} + }, + cmdtext : function (cmd) + { + this.flushHexStore(); + if (!this.group) { // an RTF fragment, missing the {\rtf1 header + //this.group = this.doc + return; // we really don't care about stray text... + } + this.group.addContent(new Roo.rtf.Span(cmd)); + }, + cmdcontrolword : function (cmd) + { + this.flushHexStore(); + if (!this.group.type) { + this.group.type = cmd.value; + return; + } + this.group.addContent(new Roo.rtf.Ctrl(cmd)); + // we actually don't care about ctrl words... + return ; + /* + var method = 'ctrl$' + cmd.value.replace(/-(.)/g, (_, char) => char.toUpperCase()) + if (this[method]) { + this[method](cmd.param) + } else { + if (!this.group.get('ignorable')) process.emit('debug', method, cmd.param) + } + */ + }, + cmdhexchar : function(cmd) { + this.hexStore.push(cmd); + }, + cmderror : function(cmd) { + throw cmd.value; + }, + + /* + _flush (done) { + if (this.text !== '\u0000') this.emitText() + done() + } + */ + + + parseText : function(c) + { + if (c === '\\') { + this.parserState = this.parseEscapes; + } else if (c === '{') { + this.emitStartGroup(); + } else if (c === '}') { + this.emitEndGroup(); + } else if (c === '\x0A' || c === '\x0D') { + // cr/lf are noise chars + } else { + this.text += c; + } + }, + + parseEscapes: function (c) + { + if (c === '\\' || c === '{' || c === '}') { + this.text += c; + this.parserState = this.parseText; + } else { + this.parserState = this.parseControlSymbol; + this.parseControlSymbol(c); + } + }, + parseControlSymbol: function(c) + { + if (c === '~') { + this.text += '\u00a0'; // nbsp + this.parserState = this.parseText + } else if (c === '-') { + this.text += '\u00ad'; // soft hyphen + } else if (c === '_') { + this.text += '\u2011'; // non-breaking hyphen + } else if (c === '*') { + this.emitIgnorable(); + this.parserState = this.parseText; + } else if (c === "'") { + this.parserState = this.parseHexChar; + } else if (c === '|') { // formula cacter + this.emitFormula(); + this.parserState = this.parseText; + } else if (c === ':') { // subentry in an index entry + this.emitIndexSubEntry(); + this.parserState = this.parseText; + } else if (c === '\x0a') { + this.emitEndParagraph(); + this.parserState = this.parseText; + } else if (c === '\x0d') { + this.emitEndParagraph(); + this.parserState = this.parseText; + } else { + this.parserState = this.parseControlWord; + this.parseControlWord(c); + } + }, + parseHexChar: function (c) + { + if (/^[A-Fa-f0-9]$/.test(c)) { + this.hexChar += c; + if (this.hexChar.length >= 2) { + this.emitHexChar(); + this.parserState = this.parseText; + } + return; + } + this.emitError("Invalid character \"" + c + "\" in hex literal."); + this.parserState = this.parseText; + + }, + parseControlWord : function(c) + { + if (c === ' ') { + this.emitControlWord(); + this.parserState = this.parseText; + } else if (/^[-\d]$/.test(c)) { + this.parserState = this.parseControlWordParam; + this.controlWordParam += c; + } else if (/^[A-Za-z]$/.test(c)) { + this.controlWord += c; + } else { + this.emitControlWord(); + this.parserState = this.parseText; + this.parseText(c); + } + }, + parseControlWordParam : function (c) { + if (/^\d$/.test(c)) { + this.controlWordParam += c; + } else if (c === ' ') { + this.emitControlWord(); + this.parserState = this.parseText; + } else { + this.emitControlWord(); + this.parserState = this.parseText; + this.parseText(c); + } + }, + + + + + emitText : function () { + if (this.text === '') { + return; + } + this.push({ + type: 'text', + value: this.text, + pos: this.cpos, + row: this.row, + col: this.col + }); + this.text = '' + }, + emitControlWord : function () + { + this.emitText(); + if (this.controlWord === '') { + // do we want to track this - it seems just to cause problems. + //this.emitError('empty control word'); + } else { + this.push({ + type: 'controlword', + value: this.controlWord, + param: this.controlWordParam !== '' && Number(this.controlWordParam), + pos: this.cpos, + row: this.row, + col: this.col + }); + } + this.controlWord = ''; + this.controlWordParam = ''; + }, + emitStartGroup : function () + { + this.emitText(); + this.push({ + type: 'groupstart', + pos: this.cpos, + row: this.row, + col: this.col + }); + }, + emitEndGroup : function () + { + this.emitText(); + this.push({ + type: 'groupend', + pos: this.cpos, + row: this.row, + col: this.col + }); + }, + emitIgnorable : function () + { + this.emitText(); + this.push({ + type: 'ignorable', + pos: this.cpos, + row: this.row, + col: this.col + }); + }, + emitHexChar : function () + { + this.emitText(); + this.push({ + type: 'hexchar', + value: this.hexChar, + pos: this.cpos, + row: this.row, + col: this.col + }); + this.hexChar = '' + }, + emitError : function (message) + { + this.emitText(); + this.push({ + type: 'error', + value: message, + row: this.row, + col: this.col, + char: this.cpos //, + //stack: new Error().stack + }); + }, + emitEndParagraph : function () { + this.emitText(); + this.push({ + type: 'endparagraph', + pos: this.cpos, + row: this.row, + col: this.col + }); + } + +} ; Roo.htmleditor = {}; /** diff --git a/roojs-bootstrap.js b/roojs-bootstrap.js index d8f444087f..2524b1a4d5 100644 --- a/roojs-bootstrap.js +++ b/roojs-bootstrap.js @@ -1110,6 +1110,43 @@ pt.innerHTML=this.meterLabel+' '+this.pwdStrengths[B];this.errorMsg='';retu }var B=new Array(new this.CharacterSetChecks(this.kCapitalLetter),new this.CharacterSetChecks(this.kSmallLetter),new this.CharacterSetChecks(this.kDigit),new this.CharacterSetChecks(this.kPunctuation));for(var C=0;C0:A="image/png";break;case this.content.filter(function(a){return a.value=='jpegblip'}).length>0:A="image/jpeg";break;default:return 'about:blank';}var B=this.content[this.content.length-1].value;return 'data:'+A+';base64,'+btoa(B.match(/\w{2}/g).map(function(a){return String.fromCharCode(parseInt(a,16)); +}).join(""));}}; +// Roo/rtf/Document.js +Roo.rtf.Document=function(){this.rtlch=[];this.content=[];this.cn=[];};Roo.extend(Roo.rtf.Document,Roo.rtf.Group,{addChild:function(cn){this.cn.push(cn);switch(cn.type){case 'rtlch':case 'listtext':case 'shpinst':this.rtlch.push(cn);return;default:this[cn.type]=cn; +}},getElementsByType:function(A){var B=[];this._getElementsByType(A,B,this.cn,'rtf');return B;},_getElementsByType:function(A,B,C,D){C.forEach(function(n,i){if(n.type==A){n.path=D+'/'+n.type+':'+i;B.push(n);}if(n.cn.length>0){this._getElementsByType(A,B,n.cn,D+'/'+n.type+':'+i); +}},this);}}); +// Roo/rtf/Ctrl.js +Roo.rtf.Ctrl=function(A){this.value=A.value;this.param=A.param;}; +// Roo/rtf/Parser.js +Roo.rtf.Parser=function(A){this.text='';this.parserState=this.parseText;this.doc={};this.groupStack=[];this.hexStore=[];this.doc=false;this.groups=[];for(var ii=0;ii=2){this.emitHexChar();this.parserState=this.parseText;}return;}this.emitError("Invalid character \""+c+"\" in hex literal."); +this.parserState=this.parseText;},parseControlWord:function(c){if(c===' '){this.emitControlWord();this.parserState=this.parseText;}else if(/^[-\d]$/.test(c)){this.parserState=this.parseControlWordParam;this.controlWordParam+=c;}else if(/^[A-Za-z]$/.test(c)){this.controlWord+=c; +}else{this.emitControlWord();this.parserState=this.parseText;this.parseText(c);}},parseControlWordParam:function(c){if(/^\d$/.test(c)){this.controlWordParam+=c;}else if(c===' '){this.emitControlWord();this.parserState=this.parseText;}else{this.emitControlWord(); +this.parserState=this.parseText;this.parseText(c);}},emitText:function(){if(this.text===''){return;}this.push({type:'text',value:this.text,pos:this.cpos,row:this.row,col:this.col});this.text=''},emitControlWord:function(){this.emitText();if(this.controlWord===''){} +else{this.push({type:'controlword',value:this.controlWord,param:this.controlWordParam!==''&&Number(this.controlWordParam),pos:this.cpos,row:this.row,col:this.col});}this.controlWord='';this.controlWordParam='';},emitStartGroup:function(){this.emitText();this.push({type:'groupstart',pos:this.cpos,row:this.row,col:this.col} +);},emitEndGroup:function(){this.emitText();this.push({type:'groupend',pos:this.cpos,row:this.row,col:this.col});},emitIgnorable:function(){this.emitText();this.push({type:'ignorable',pos:this.cpos,row:this.row,col:this.col});},emitHexChar:function(){this.emitText(); +this.push({type:'hexchar',value:this.hexChar,pos:this.cpos,row:this.row,col:this.col});this.hexChar=''},emitError:function(A){this.emitText();this.push({type:'error',value:A,row:this.row,col:this.col,char:this.cpos});},emitEndParagraph:function(){this.emitText(); +this.push({type:'endparagraph',pos:this.cpos,row:this.row,col:this.col});}}; // Roo/htmleditor/namespace.js Roo.htmleditor={}; // Roo/htmleditor/Filter.js -- 2.39.2