Annotation of kupuMPIWG/common/kupucontentfilters.js, revision 1.1.1.1

1.1       dwinter     1: /*****************************************************************************
                      2:  *
                      3:  * Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
                      4:  *
                      5:  * This software is distributed under the terms of the Kupu
                      6:  * License. See LICENSE.txt for license text. For a list of Kupu
                      7:  * Contributors see CREDITS.txt.
                      8:  *
                      9:  *****************************************************************************/
                     10: 
                     11: // $Id: kupucontentfilters.js 12162 2005-05-10 15:53:10Z guido $
                     12: 
                     13: 
                     14: //----------------------------------------------------------------------------
                     15: // 
                     16: // ContentFilters
                     17: //
                     18: //  These are (or currently 'this is') filters for HTML cleanup and 
                     19: //  conversion. Kupu filters should be classes that should get registered to
                     20: //  the editor using the registerFilter method with 2 methods: 'initialize'
                     21: //  and 'filter'. The first will be called with the editor as its only
                     22: //  argument and the latter with a reference to the ownerdoc (always use 
                     23: //  that to create new nodes and such) and the root node of the HTML DOM as 
                     24: //  its arguments.
                     25: //
                     26: //----------------------------------------------------------------------------
                     27: 
                     28: function NonXHTMLTagFilter() {
                     29:     /* filter out non-XHTML tags*/
                     30:     
                     31:     // A mapping from element name to whether it should be left out of the 
                     32:     // document entirely. If you want an element to reappear in the resulting 
                     33:     // document *including* it's contents, add it to the mapping with a 1 value.
                     34:     // If you want an element not to appear but want to leave it's contents in 
                     35:     // tact, add it to the mapping with a 0 value. If you want an element and
                     36:     // it's contents to be removed from the document, don't add it.
                     37:     if (arguments.length) {
                     38:         // allow an optional filterdata argument
                     39:         this.filterdata = arguments[0];
                     40:     } else {
                     41:         // provide a default filterdata dict
                     42:         this.filterdata = {'html': 1,
                     43:                             'body': 1,
                     44:                             'head': 1,
                     45:                             'title': 1,
                     46:                             
                     47:                             'a': 1,
                     48:                             'abbr': 1,
                     49:                             'acronym': 1,
                     50:                             'address': 1,
                     51:                             'b': 1,
                     52:                             'base': 1,
                     53:                             'blockquote': 1,
                     54:                             'br': 1,
                     55:                             'caption': 1,
                     56:                             'cite': 1,
                     57:                             'code': 1,
                     58:                             'col': 1,
                     59:                             'colgroup': 1,
                     60:                             'dd': 1,
                     61:                             'dfn': 1,
                     62:                             'div': 1,
                     63:                             'dl': 1,
                     64:                             'dt': 1,
                     65:                             'em': 1,
                     66:                             'h1': 1,
                     67:                             'h2': 1,
                     68:                             'h3': 1,
                     69:                             'h4': 1,
                     70:                             'h5': 1,
                     71:                             'h6': 1,
                     72:                             'h7': 1,
                     73:                             'i': 1,
                     74:                             'img': 1,
                     75:                             'kbd': 1,
                     76:                             'li': 1,
                     77:                             'link': 1,
                     78:                             'meta': 1,
                     79:                             'ol': 1,
                     80:                             'p': 1,
                     81:                             'pre': 1,
                     82:                             'q': 1,
                     83:                             'samp': 1,
                     84:                             'script': 1,
                     85:                             'span': 1,
                     86:                             'strong': 1,
                     87:                             'style': 1,
                     88:                             'sub': 1,
                     89:                             'sup': 1,
                     90:                             'table': 1,
                     91:                             'tbody': 1,
                     92:                             'td': 1,
                     93:                             'tfoot': 1,
                     94:                             'th': 1,
                     95:                             'thead': 1,
                     96:                             'tr': 1,
                     97:                             'ul': 1,
                     98:                             'u': 1,
                     99:                             'var': 1,
                    100: 
                    101:                             // even though they're deprecated we should leave
                    102:                             // font tags as they are, since Kupu sometimes
                    103:                             // produces them itself.
                    104:                             'font': 1,
                    105:                             'center': 0
                    106:                             };
                    107:     };
                    108:                         
                    109:     this.initialize = function(editor) {
                    110:         /* init */
                    111:         this.editor = editor;
                    112:     };
                    113: 
                    114:     this.filter = function(ownerdoc, htmlnode) {
                    115:         return this._filterHelper(ownerdoc, htmlnode);
                    116:     };
                    117: 
                    118:     this._filterHelper = function(ownerdoc, node) {
                    119:         /* filter unwanted elements */
                    120:         if (node.nodeType == 3) {
                    121:             return ownerdoc.createTextNode(node.nodeValue);
                    122:         } else if (node.nodeType == 4) {
                    123:             return ownerdoc.createCDATASection(node.nodeValue);
                    124:         };
                    125:         // create a new node to place the result into
                    126:         // XXX this can be severely optimized by doing stuff inline rather 
                    127:         // than on creating new elements all the time!
                    128:         var newnode = ownerdoc.createElement(node.nodeName);
                    129:         // copy the attributes
                    130:         for (var i=0; i < node.attributes.length; i++) {
                    131:             var attr = node.attributes[i];
                    132:             newnode.setAttribute(attr.nodeName, attr.nodeValue);
                    133:         };
                    134:         for (var i=0; i < node.childNodes.length; i++) {
                    135:             var child = node.childNodes[i];
                    136:             var nodeType = child.nodeType;
                    137:             var nodeName = child.nodeName.toLowerCase();
                    138:             if (nodeType == 3 || nodeType == 4) {
                    139:                 newnode.appendChild(this._filterHelper(ownerdoc, child));
                    140:             };
                    141:             if (nodeName in this.filterdata && this.filterdata[nodeName]) {
                    142:                 newnode.appendChild(this._filterHelper(ownerdoc, child));
                    143:             } else if (nodeName in this.filterdata) {
                    144:                 for (var j=0; j < child.childNodes.length; j++) {
                    145:                     newnode.appendChild(this._filterHelper(ownerdoc, child.childNodes[j]));
                    146:                 };
                    147:             };
                    148:         };
                    149:         return newnode;
                    150:     };
                    151: };
                    152: 
                    153: //-----------------------------------------------------------------------------
                    154: //
                    155: // XHTML validation support
                    156: //
                    157: // This class is the XHTML 1.0 transitional DTD expressed as Javascript
                    158: // data structures.
                    159: //
                    160: function XhtmlValidation(editor) {
                    161:     // Support functions
                    162:     this.Set = function(ary) {
                    163:         if (typeof(ary)==typeof('')) ary = [ary];
                    164:         if (ary instanceof Array) {
                    165:             for (var i = 0; i < ary.length; i++) {
                    166:                 this[ary[i]] = 1;
                    167:             }
                    168:         }
                    169:         else {
                    170:             for (var v in ary) { // already a set?
                    171:                 this[v] = 1;
                    172:             }
                    173:         }
                    174:     }
                    175: 
                    176:     this._exclude = function(array, exceptions) {
                    177:         var ex;
                    178:         if (exceptions.split) {
                    179:             ex = exceptions.split("|");
                    180:         } else {
                    181:             ex = exceptions;
                    182:         }
                    183:         var exclude = new this.Set(ex);
                    184:         var res = [];
                    185:         for (var k=0; k < array.length;k++) {
                    186:             if (!exclude[array[k]]) res.push(array[k]);
                    187:         }
                    188:         return res;
                    189:     }
                    190:     this.setAttrFilter = function(attributes, filter) {
                    191:         for (var j = 0; j < attributes.length; j++) {
                    192:             var attr = attributes[j];
                    193:             this.attrFilters[attr] = filter || this._defaultCopyAttribute;
                    194:         }
                    195:     }
                    196: 
                    197:     this.setTagAttributes = function(tags, attributes) {
                    198:         for (var j = 0; j < tags.length; j++) {
                    199:             this.tagAttributes[tags[j]] = attributes;
                    200:         }
                    201:     }
                    202: 
                    203:     // define some new attributes for existing tags
                    204:     this.includeTagAttributes = function(tags, attributes) {
                    205:         for (var j = 0; j < tags.length; j++) {
                    206:             var tag = tags[j];
                    207:             this.tagAttributes[tag] = this.tagAttributes[tag].concat(attributes);
                    208:         }
                    209:     }
                    210: 
                    211:     this.excludeTagAttributes = function(tags, attributes) {
                    212:         var bad = new this.Set(attributes);
                    213:         var tagset = new this.Set(tags);
                    214:         for (var tag in tagset) {
                    215:             var val = this.tagAttributes[tag];
                    216:             for (var i = val.length; i >= 0; i--) {
                    217:                 if (bad[val[i]]) {
                    218:                     val = val.concat(); // Copy
                    219:                     val.splice(i,1);
                    220:                 }
                    221:             }
                    222:             this.tagAttributes[tag] = val;
                    223:         }
                    224:     }
                    225: 
                    226:     this.excludeTags = function(badtags) {
                    227:         if (typeof(badtags)==typeof('')) badtags = [badtags];
                    228:         for (var i = 0; i < badtags.length; i++) {
                    229:             delete this.tagAttributes[badtags[i]];
                    230:         }
                    231:     }
                    232: 
                    233:     this.excludeAttributes = function(badattrs) {
                    234:         this.excludeTagAttributes(this.tagAttributes, badattrs);
                    235:         for (var i = 0; i < badattrs.length; i++) {
                    236:             delete this.attrFilters[badattrs[i]];
                    237:         }
                    238:     }
                    239:     if (editor.getBrowserName()=="IE") {
                    240:         this._getTagName = function(htmlnode) {
                    241:             var nodename = htmlnode.nodeName.toLowerCase();
                    242:             if (htmlnode.scopeName && htmlnode.scopeName != "HTML") {
                    243:                 nodename = htmlnode.scopeName+':'+nodename;
                    244:             }
                    245:             return nodename;
                    246:         }
                    247:     } else {
                    248:         this._getTagName = function(htmlnode) {
                    249:             return htmlnode.nodeName.toLowerCase();
                    250:         }
                    251:     };
                    252: 
                    253:     // Supporting declarations
                    254:     this.elements = new function(validation) {
                    255:         // A list of all attributes
                    256:         this.attributes = [
                    257:             'abbr','accept','accept-charset','accesskey','action','align','alink',
                    258:             'alt','archive','axis','background','bgcolor','border','cellpadding',
                    259:             'cellspacing','char','charoff','charset','checked','cite','class',
                    260:             'classid','clear','code','codebase','codetype','color','cols','colspan',
                    261:             'compact','content','coords','data','datetime','declare','defer','dir',
                    262:             'disabled','enctype','face','for','frame','frameborder','headers',
                    263:             'height','href','hreflang','hspace','http-equiv','id','ismap','label',
                    264:             'lang','language','link','longdesc','marginheight','marginwidth',
                    265:             'maxlength','media','method','multiple','name','nohref','noshade','nowrap',
                    266:             'object','onblur','onchange','onclick','ondblclick','onfocus','onkeydown',
                    267:             'onkeypress','onkeyup','onload','onmousedown','onmousemove','onmouseout',
                    268:             'onmouseover','onmouseup','onreset','onselect','onsubmit','onunload',
                    269:             'profile','prompt','readonly','rel','rev','rows','rowspan','rules',
                    270:             'scheme','scope','scrolling','selected','shape','size','span','src',
                    271:             'standby','start','style','summary','tabindex','target','text','title',
                    272:             'type','usemap','valign','value','valuetype','vlink','vspace','width',
                    273:             'xml:lang','xml:space','xmlns'];
                    274: 
                    275:         // Core attributes
                    276:         this.coreattrs = ['id', 'title', 'style', 'class'];
                    277:         this.i18n = ['lang', 'dir', 'xml:lang'];
                    278:         // All event attributes are here but commented out so we don't
                    279:         // have to remove them later.
                    280:         this.events = []; // 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup'.split('|');
                    281:         this.focusevents = []; // ['onfocus','onblur']
                    282:         this.loadevents = []; // ['onload', 'onunload']
                    283:         this.formevents = []; // ['onsubmit','onreset']
                    284:         this.inputevents = [] ; // ['onselect', 'onchange']
                    285:         this.focus = ['accesskey', 'tabindex'].concat(this.focusevents);
                    286:         this.attrs = [].concat(this.coreattrs, this.i18n, this.events);
                    287: 
                    288:         // entities
                    289:         this.special_extra = ['object','applet','img','map','iframe'];
                    290:         this.special_basic=['br','span','bdo'];
                    291:         this.special = [].concat(this.special_basic, this.special_extra);
                    292:         this.fontstyle_extra = ['big','small','font','basefont'];
                    293:         this.fontstyle_basic = ['tt','i','b','u','s','strike'];
                    294:         this.fontstyle = [].concat(this.fontstyle_basic, this.fontstyle_extra);
                    295:         this.phrase_extra = ['sub','sup'];
                    296:         this.phrase_basic=[
                    297:                           'em','strong','dfn','code','q',
                    298:                           'samp','kbd','var', 'cite','abbr','acronym'];
                    299:         this.inline_forms = ['input','select','textarea','label','button'];
                    300:         this.misc_inline = ['ins','del'];
                    301:         this.misc = ['noscript'].concat(this.misc_inline);
                    302:         this.inline = ['a'].concat(this.special, this.fontstyle, this.phrase, this.inline_forms);
                    303: 
                    304:         this.Inline = ['#PCDATA'].concat(this.inline, this.misc_inline);
                    305: 
                    306:         this.heading = ['h1','h2','h3','h4','h5','h6'];
                    307:         this.lists = ['ul','ol','dl','menu','dir'];
                    308:         this.blocktext = ['pre','hr','blockquote','address','center','noframes'];
                    309:         this.block = ['p','div','isindex','fieldset','table'].concat(
                    310:                      this.heading, this.lists, this.blocktext);
                    311: 
                    312:         this.Flow = ['#PCDATA','form'].concat(this.block, this.inline);
                    313:     }(this);
                    314: 
                    315:     this._commonsetting = function(self, names, value) {
                    316:         for (var n = 0; n < names.length; n++) {
                    317:             self[names[n]] = value;
                    318:         }
                    319:     }
                    320:     
                    321:     // The tagAttributes class returns all valid attributes for a tag,
                    322:     // e.g. a = this.tagAttributes.head
                    323:     // a.head -> [ 'lang', 'xml:lang', 'dir', 'id', 'profile' ]
                    324:     this.tagAttributes = new function(el, validation) {
                    325:         this.title = el.i18n.concat('id');
                    326:         this.html = this.title.concat('xmlns');
                    327:         this.head = this.title.concat('profile');
                    328:         this.base = ['id', 'href', 'target'];
                    329:         this.meta =  this.title.concat('http-equiv','name','content', 'scheme');
                    330:         this.link = el.attrs.concat('charset','href','hreflang','type', 'rel','rev','media','target');
                    331:         this.style = this.title.concat('type','media','title', 'xml:space');
                    332:         this.script = ['id','charset','type','language','src','defer', 'xml:space'];
                    333:         this.iframe = [
                    334:                       'longdesc','name','src','frameborder','marginwidth',
                    335:                       'marginheight','scrolling','align','height','width'].concat(el.coreattrs);
                    336:         this.body = ['background','bgcolor','text','link','vlink','alink'].concat(el.attrs, el.loadevents);
                    337:         validation._commonsetting(this,
                    338:                                   ['p','div'].concat(el.heading),
                    339:                                   ['align'].concat(el.attrs));
                    340:         this.dl = this.dir = this.menu = el.attrs.concat('compact');
                    341:         this.ul = this.menu.concat('type');
                    342:         this.ol = this.ul.concat('start');
                    343:         this.li = el.attrs.concat('type','value');
                    344:         this.hr = el.attrs.concat('align','noshade','size','width');
                    345:         this.pre = el.attrs.concat('width','xml:space');
                    346:         this.blockquote = this.q = el.attrs.concat('cite');
                    347:         this.ins = this.del = this.blockquote.concat('datetime');
                    348:         this.a = el.attrs.concat(el.focus,'charset','type','name','href','hreflang','rel','rev','shape','coords','target');
                    349:         this.bdo = el.coreattrs.concat(el.events, 'lang','xml:lang','dir');
                    350:         this.br = el.coreattrs.concat('clear');
                    351:         validation._commonsetting(this,
                    352:                                   ['noscript','noframes','dt', 'dd', 'address','center','span','em', 'strong', 'dfn','code',
                    353:                                   'samp','kbd','var','cite','abbr','acronym','sub','sup','tt',
                    354:                                   'i','b','big','small','u','s','strike', 'fieldset'],
                    355:                                   el.attrs);
                    356: 
                    357:         this.basefont = ['id','size','color','face'];
                    358:         this.font = el.coreattrs.concat(el.i18n, 'size','color','face');
                    359:         this.object = el.attrs.concat('declare','classid','codebase','data','type','codetype','archive','standby','height','width','usemap','name','tabindex','align','border','hspace','vspace');
                    360:         this.param = ['id','name','value','valuetype','type'];
                    361:         this.applet = el.coreattrs.concat('codebase','archive','code','object','alt','name','width','height','align','hspace','vspace');
                    362:         this.img = el.attrs.concat('src','alt','name','longdesc','height','width','usemap','ismap','align','border','hspace','vspace');
                    363:         this.map = this.title.concat('title','name', 'style', 'class', el.events);
                    364:         this.area = el.attrs.concat('shape','coords','href','nohref','alt','target', el.focus);
                    365:         this.form = el.attrs.concat('action','method','name','enctype',el.formevents,'accept','accept-charset','target');
                    366:         this.label = el.attrs.concat('for','accesskey', el.focusevents);
                    367:         this.input = el.attrs.concat('type','name','value','checked','disabled','readonly','size','maxlength','src','alt','usemap',el.input,'accept','align', el.focus);
                    368:         this.select = el.attrs.concat('name','size','multiple','disabled','tabindex', el.focusevents,el.input);
                    369:         this.optgroup = el.attrs.concat('disabled','label');
                    370:         this.option = el.attrs.concat('selected','disabled','label','value');
                    371:         this.textarea = el.attrs.concat('name','rows','cols','disabled','readonly', el.inputevents, el.focus);
                    372:         this.legend = el.attrs.concat('accesskey','align');
                    373:         this.button = el.attrs.concat('name','value','type','disabled',el.focus);
                    374:         this.isindex = el.coreattrs.concat('prompt', el.i18n);
                    375:         this.table = el.attrs.concat('summary','width','border','frame','rules','cellspacing','cellpadding','align','bgcolor');
                    376:         this.caption = el.attrs.concat('align');
                    377:         this.col = this.colgroup = el.attrs.concat('span','width','align','char','charoff','valign');
                    378:         this.thead =  el.attrs.concat('align','char','charoff','valign');
                    379:         this.tfoot = this.tbody = this.thead;
                    380:         this.tr = this.thead.concat('bgcolor');
                    381:         this.td = this.th = this.tr.concat('abbr','axis','headers','scope','rowspan','colspan','nowrap','width','height');
                    382:     }(this.elements, this);
                    383: 
                    384:     // State array. For each tag identifies what it can contain.
                    385:     // I'm not attempting to check the order or number of contained
                    386:     // tags (yet).
                    387:     this.States = new function(el, validation) {
                    388: 
                    389:         var here = this;
                    390:         function setStates(tags, value) {
                    391:             var valset = new validation.Set(value);
                    392: 
                    393:             for (var i = 0; i < tags.length; i++) {
                    394:                 here[tags[i]] = valset;
                    395:             }
                    396:         }
                    397:         
                    398:         setStates(['html'], ['head','body']);
                    399:         setStates(['head'], ['title','base','script','style', 'meta','link','object','isindex']);
                    400:         setStates([
                    401:             'base', 'meta', 'link', 'hr', 'param', 'img', 'area', 'input',
                    402:             'br', 'basefont', 'isindex', 'col',
                    403:             ], []);
                    404: 
                    405:         setStates(['title','style','script','option','textarea'], ['#PCDATA']);
                    406:         setStates([ 'noscript', 'iframe', 'noframes', 'body', 'div',
                    407:             'li', 'dd', 'blockquote', 'center', 'ins', 'del', 'td', 'th',
                    408:             ], el.Flow);
                    409: 
                    410:         setStates(el.heading, el.Inline);
                    411:         setStates([ 'p', 'dt', 'address', 'span', 'bdo', 'caption',
                    412:             'em', 'strong', 'dfn','code','samp','kbd','var',
                    413:             'cite','abbr','acronym','q','sub','sup','tt','i',
                    414:             'b','big','small','u','s','strike','font','label',
                    415:             'legend'], el.Inline);
                    416: 
                    417:         setStates(['ul', 'ol', 'menu', 'dir', 'ul', ], ['li']);
                    418:         setStates(['dl'], ['dt','dd']);
                    419:         setStates(['pre'], validation._exclude(el.Inline, "img|object|applet|big|small|sub|sup|font|basefont"));
                    420:         setStates(['a'], validation._exclude(el.Inline, "a"));
                    421:         setStates(['applet', 'object'], ['#PCDATA', 'param','form'].concat(el.block, el.inline, el.misc));
                    422:         setStates(['map'], ['form', 'area'].concat(el.block, el.misc));
                    423:         setStates(['form'], validation._exclude(el.Flow, ['form']));
                    424:         setStates(['select'], ['optgroup','option']);
                    425:         setStates(['optgroup'], ['option']);
                    426:         setStates(['fieldset'], ['#PCDATA','legend','form'].concat(el.block,el.inline,el.misc));
                    427:         setStates(['button'], validation._exclude(el.Flow, ['a','form','iframe'].concat(el.inline_forms)));
                    428:         setStates(['table'], ['caption','col','colgroup','thead','tfoot','tbody','tr']);
                    429:         setStates(['thead', 'tfoot', 'tbody'], ['tr']);
                    430:         setStates(['colgroup'], ['col']);
                    431:         setStates(['tr'], ['th','td']);
                    432:     }(this.elements, this);
                    433: 
                    434:     // Permitted elements for style.
                    435:     this.styleWhitelist = new this.Set(['text-align', 'list-style-type', 'float']);
                    436:     this.classBlacklist = new this.Set(['MsoNormal', 'MsoTitle', 'MsoHeader', 'MsoFootnoteText',
                    437:         'Bullet1', 'Bullet2']);
                    438: 
                    439:     this.classFilter = function(value) {
                    440:         var classes = value.split(' ');
                    441:         var filtered = [];
                    442:         for (var i = 0; i < classes.length; i++) {
                    443:             var c = classes[i];
                    444:             if (c && !this.classBlacklist[c]) {
                    445:                 filtered.push(c);
                    446:             }
                    447:         }
                    448:         return filtered.join(' ');
                    449:     }
                    450:     this._defaultCopyAttribute = function(name, htmlnode, xhtmlnode) {
                    451:         var val = htmlnode.getAttribute(name);
                    452:         if (val) xhtmlnode.setAttribute(name, val);
                    453:     }
                    454:     // Set up filters for attributes.
                    455:     this.attrFilters = new function(validation, editor) {
                    456:         var attrs = validation.elements.attributes;
                    457:         for (var i=0; i < attrs.length; i++) {
                    458:             this[attrs[i]] = validation._defaultCopyAttribute;
                    459:         }
                    460:         this['class'] = function(name, htmlnode, xhtmlnode) {
                    461:             var val = htmlnode.getAttribute('class');
                    462:             if (val) val = validation.classFilter(val);
                    463:             if (val) xhtmlnode.setAttribute('class', val);
                    464:         }
                    465:         // allow a * wildcard to make all attributes valid in the filter
                    466:         // note that this is pretty slow on IE
                    467:         this['*'] = function(name, htmlnode, xhtmlnode) {
                    468:             for (var i=0; i < htmlnode.attributes.length; i++) {
                    469:                 var attr = htmlnode.attributes[i];
                    470:                 if (attr.value !== null && attr.value !== undefined) {
                    471:                     xhtmlnode.setAttribute(attr.name, attr.value);
                    472:                 };
                    473:             };
                    474:         };
                    475:         if (editor.getBrowserName()=="IE") {
                    476:             this['class'] = function(name, htmlnode, xhtmlnode) {
                    477:                 var val = htmlnode.className;
                    478:                 if (val) val = validation.classFilter(val);
                    479:                 if (val) xhtmlnode.setAttribute('class', val);
                    480:             }
                    481:             this['http-equiv'] = function(name, htmlnode, xhtmlnode) {
                    482:                 var val = htmlnode.httpEquiv;
                    483:                 if (val) xhtmlnode.setAttribute('http-equiv', val);
                    484:             }
                    485:             this['xml:lang'] = this['xml:space'] = function(name, htmlnode, xhtmlnode) {
                    486:                 try {
                    487:                     var val = htmlnode.getAttribute(name);
                    488:                     if (val) xhtmlnode.setAttribute(name, val);
                    489:                 } catch(e) {
                    490:                 }
                    491:             }
                    492:         }
                    493:         this.rowspan = this.colspan = function(name, htmlnode, xhtmlnode) {
                    494:             var val = htmlnode.getAttribute(name);
                    495:             if (val && val != '1') xhtmlnode.setAttribute(name, val);
                    496:         }
                    497:         this.style = function(name, htmlnode, xhtmlnode) {
                    498:             var val = htmlnode.style.cssText;
                    499:             if (val) {
                    500:                 var styles = val.split(/; */);
                    501:                 for (var i = styles.length; i >= 0; i--) if (styles[i]) {
                    502:                     var parts = /^([^:]+): *(.*)$/.exec(styles[i]);
                    503:                     var name = parts[1].toLowerCase();
                    504:                     if (validation.styleWhitelist[name]) {
                    505:                         styles[i] = name+': '+parts[2];
                    506:                     } else {
                    507:                         styles.splice(i,1); // delete
                    508:                     }
                    509:                 }
                    510:                 if (styles[styles.length-1]) styles.push('');
                    511:                 val = styles.join('; ').strip();
                    512:             }
                    513:             if (val) xhtmlnode.setAttribute('style', val);
                    514:         }
                    515:     }(this, editor);
                    516: 
                    517:     // Exclude unwanted tags.
                    518:     this.excludeTags(['center']);
                    519: 
                    520:     if (editor.config && editor.config.htmlfilter) {
                    521:         this.filterStructure = editor.config.htmlfilter.filterstructure;
                    522:         
                    523:         var exclude = editor.config.htmlfilter;
                    524:         if (exclude.a)
                    525:             this.excludeAttributes(exclude.a);
                    526:         if (exclude.t)
                    527:             this.excludeTags(exclude.t);
                    528:         if (exclude.c) {
                    529:             var c = exclude.c;
                    530:             if (!c.length) c = [c];
                    531:             for (var i = 0; i < c.length; i++) {
                    532:                 this.excludeTagAttributes(c[i].t, c[i].a);
                    533:             }
                    534:         }
                    535:         if (exclude.style) {
                    536:             var s = exclude.style;
                    537:             for (var i = 0; i < s.length; i++) {
                    538:                 this.styleWhitelist[s[i]] = 1;
                    539:             }
                    540:         }
                    541:         if (exclude['class']) {
                    542:             var c = exclude['class'];
                    543:             for (var i = 0; i < c.length; i++) {
                    544:                 this.classBlacklist[c[i]] = 1;
                    545:             }
                    546:         }
                    547:     };
                    548: 
                    549:     // Copy all valid attributes from htmlnode to xhtmlnode.
                    550:     this._copyAttributes = function(htmlnode, xhtmlnode, valid) {
                    551:         if (valid.contains('*')) {
                    552:             // allow all attributes on this tag
                    553:             this.attrFilters['*'](name, htmlnode, xhtmlnode);
                    554:             return;
                    555:         };
                    556:         for (var i = 0; i < valid.length; i++) {
                    557:             var name = valid[i];
                    558:             var filter = this.attrFilters[name];
                    559:             if (filter) filter(name, htmlnode, xhtmlnode);
                    560:         }
                    561:     }
                    562: 
                    563:     this._convertToSarissaNode = function(ownerdoc, htmlnode, xhtmlparent) {
                    564:         return this._convertNodes(ownerdoc, htmlnode, xhtmlparent, new this.Set(['html']));
                    565:     };
                    566:     
                    567:     this._convertNodes = function(ownerdoc, htmlnode, xhtmlparent, permitted) {
                    568:         var name, parentnode = xhtmlparent;
                    569:         var nodename = this._getTagName(htmlnode);
                    570:         var nostructure = !this.filterstructure;
                    571: 
                    572:         // TODO: This permits valid tags anywhere. it should use the state
                    573:         // table in xhtmlvalid to only permit tags where the XHTML DTD
                    574:         // says they are valid.
                    575:         var validattrs = this.tagAttributes[nodename];
                    576:         if (validattrs && (nostructure || permitted[nodename])) {
                    577:             try {
                    578:                 var xhtmlnode = ownerdoc.createElement(nodename);
                    579:                 parentnode = xhtmlnode;
                    580:             } catch (e) { };
                    581: 
                    582:             if (validattrs && xhtmlnode)
                    583:                 this._copyAttributes(htmlnode, xhtmlnode, validattrs);
                    584:         }
                    585: 
                    586:         var kids = htmlnode.childNodes;
                    587:         var permittedChildren = this.States[parentnode.tagName] || permitted;
                    588: 
                    589:         if (kids.length == 0) {
                    590:             if (htmlnode.text && htmlnode.text != "" &&
                    591:                 (nostructure || permittedChildren['#PCDATA'])) {
                    592:                 var text = htmlnode.text;
                    593:                 var tnode = ownerdoc.createTextNode(text);
                    594:                 parentnode.appendChild(tnode);
                    595:             }
                    596:         } else {
                    597:             for (var i = 0; i < kids.length; i++) {
                    598:                 var kid = kids[i];
                    599: 
                    600:                 if (kid.parentNode !== htmlnode) {
                    601:                     if (kid.tagName == 'BODY') {
                    602:                         if (nodename != 'html') continue;
                    603:                     } else if (kid.parentNode.tagName === htmlnode.tagName) {
                    604:                         continue; // IE bug: nodes appear multiple places
                    605:                     }
                    606:                 }
                    607:                 
                    608:                 if (kid.nodeType == 1) {
                    609:                     var newkid = this._convertNodes(ownerdoc, kid, parentnode, permittedChildren);
                    610:                     if (newkid != null) {
                    611:                         parentnode.appendChild(newkid);
                    612:                     };
                    613:                 } else if (kid.nodeType == 3) {
                    614:                     if (nostructure || permittedChildren['#PCDATA'])
                    615:                         parentnode.appendChild(ownerdoc.createTextNode(kid.nodeValue));
                    616:                 } else if (kid.nodeType == 4) {
                    617:                     if (nostructure || permittedChildren['#PCDATA'])
                    618:                         parentnode.appendChild(ownerdoc.createCDATASection(kid.nodeValue));
                    619:                 }
                    620:             }
                    621:         } 
                    622:         return xhtmlnode;
                    623:     };
                    624: }
                    625: 
                    626: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>