├── README.textile
├── test
├── combo.css
├── combo.js
├── xhtml_purifier_test.html
└── xhtml_purifier_test.js
├── xhtml_purifier.html
└── xhtml_purifier.js
/README.textile:
--------------------------------------------------------------------------------
1 | h2. Javascript XHTML Purifier
2 |
3 | This script provides a method to cleanup dirty html. It will take a string of dirty and badly formatted html, and return a pretty printed valid XHTML string.
4 |
5 | h2. Usage
6 |
7 | XHTMLPurifier.purify(html_string);
8 |
9 | h2. About the Implementation
10 |
11 | The purifying is based on section 8.2 in the "HTML5 specification"http://www.whatwg.org/specs/web-apps/current-work/#parsing , and implements a subset of the algorithm described there.
12 |
13 | Only a limited set of the permitted HTML5 elements and attributes are permitted, and all other tags/attributes will simply be gone in the resulting XHTML.
14 |
15 | h2. Allowed elements
16 |
17 | * strong (b and all headers will currently be transformed to strong)
18 | * em
19 | * blockquote
20 | * ol
21 | * ul
22 | * li
23 | * p
24 | * pre
25 | * a
26 | * img
27 | * br
28 | * table
29 | * caption
30 | * col
31 | * colgroup
32 | * tbody
33 | * td
34 | * tfoot
35 | * th
36 | * thead
37 | * tr
38 |
39 | All other elements will be stripped from the resulting XHTML, although the inner text will be left intact.
40 |
41 | The script was originally created for use with a Rich Text Editor for a CMS, and purposefully puts very firm limits on what can be included in the resulting XHTML. Since it is based on the HTML5 parsing specification it is very robust when it comes to cleaning up tag soup.
42 |
43 | h2. License
44 |
45 | Copyright © 2008 "Mathias Biilmann Christensen":http://mathias-biilmann.net / "Domestika INTERNET S.L.":http://domestika.com, released under the MIT license (see MIT-LICENSE)
46 |
47 | Includes John Resig's and Erik Arvidsson's HTML Parser, which is used as a tokenizer.
48 |
49 | HTML Parser By John Resig (ejohn.org)
50 | Original code by Erik Arvidsson, Mozilla Public License
51 | http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
52 |
53 |
--------------------------------------------------------------------------------
/test/combo.css:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3 | Code licensed under the BSD License:
4 | http://developer.yahoo.net/yui/license.txt
5 | version: 2.6.0
6 | */
7 | .yui-skin-sam .yui-log{padding:1em;width:31em;background-color:#AAA;color:#000;border:1px solid black;font-family:monospace;font-size:77%;text-align:left;z-index:9000;}.yui-skin-sam .yui-log-container{position:absolute;top:1em;right:1em;}.yui-skin-sam .yui-log input{margin:0;padding:0;font-family:arial;font-size:100%;font-weight:normal;}.yui-skin-sam .yui-log .yui-log-btns{position:relative;float:right;bottom:.25em;}.yui-skin-sam .yui-log .yui-log-hd{margin-top:1em;padding:.5em;background-color:#575757;}.yui-skin-sam .yui-log .yui-log-hd h4{margin:0;padding:0;font-size:108%;font-weight:bold;color:#FFF;}.yui-skin-sam .yui-log .yui-log-bd{width:100%;height:20em;background-color:#FFF;border:1px solid gray;overflow:auto;}.yui-skin-sam .yui-log p{margin:1px;padding:.1em;}.yui-skin-sam .yui-log pre{margin:0;padding:0;}.yui-skin-sam .yui-log pre.yui-log-verbose{white-space:pre-wrap;white-space:-moz-pre-wrap !important;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;}.yui-skin-sam .yui-log .yui-log-ft{margin-top:.5em;}.yui-skin-sam .yui-log .yui-log-ft .yui-log-categoryfilters{}.yui-skin-sam .yui-log .yui-log-ft .yui-log-sourcefilters{width:100%;border-top:1px solid #575757;margin-top:.75em;padding-top:.75em;}.yui-skin-sam .yui-log .yui-log-filtergrp{margin-right:.5em;}.yui-skin-sam .yui-log .info{background-color:#A7CC25;}.yui-skin-sam .yui-log .warn{background-color:#F58516;}.yui-skin-sam .yui-log .error{background-color:#E32F0B;}.yui-skin-sam .yui-log .time{background-color:#A6C9D7;}.yui-skin-sam .yui-log .window{background-color:#F2E886;}
8 | /*
9 | Copyright (c) 2008, Yahoo! Inc. All rights reserved.
10 | Code licensed under the BSD License:
11 | http://developer.yahoo.net/yui/license.txt
12 | version: 2.6.0
13 | */
14 |
15 |
--------------------------------------------------------------------------------
/test/combo.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3 | Code licensed under the BSD License:
4 | http://developer.yahoo.net/yui/license.txt
5 | version: 2.6.0
6 | */
7 | if(typeof YAHOO=="undefined"||!YAHOO){var YAHOO={};}YAHOO.namespace=function(){var A=arguments,E=null,C,B,D;for(C=0;C ";if(!A.isObject(D)){return D+"";}else{if(D instanceof Date||("nodeType" in D&&"tagName" in D)){return D;}else{if(A.isFunction(D)){return E;}}}I=(A.isNumber(I))?I:3;if(A.isArray(D)){K.push("[");for(F=0,H=D.length;F0)?A.dump(D[F],I-1):L);}else{K.push(D[F]);}K.push(J);}if(K.length>1){K.pop();}K.push("]");}else{K.push("{");for(F in D){if(A.hasOwnProperty(D,F)){K.push(F+G);if(A.isObject(D[F])){K.push((I>0)?A.dump(D[F],I-1):L);}else{K.push(D[F]);}K.push(J);}}if(K.length>1){K.pop();}K.push("}");}return K.join("");},substitute:function(S,E,L){var I,H,G,O,P,R,N=[],F,J="dump",M=" ",D="{",Q="}";for(;;){I=S.lastIndexOf(D);if(I<0){break;}H=S.indexOf(Q,I);if(I+1>=H){break;}F=S.substring(I+1,H);O=F;R=null;G=O.indexOf(M);if(G>-1){R=O.substring(G+1);O=O.substring(0,G);}P=E[O];if(L){P=L(O,P,R);}if(A.isObject(P)){if(A.isArray(P)){P=A.dump(P,parseInt(R,10));}else{R=R||"";var K=R.indexOf(J);if(K>-1){R=R.substring(4);}if(P.toString===Object.prototype.toString||K>-1){P=A.dump(P,parseInt(R,10));}else{P=P.toString();}}}else{if(!A.isString(P)&&!A.isNumber(P)){P="~-"+N.length+"-~";N[N.length]=F;}}S=S.substring(0,I)+P+S.substring(H+1);}for(I=N.length-1;I>=0;I=I-1){S=S.replace(new RegExp("~-"+I+"-~"),"{"+N[I]+"}","g");}return S;},trim:function(D){try{return D.replace(/^\s+|\s+$/g,"");}catch(E){return D;}},merge:function(){var G={},E=arguments;for(var F=0,D=E.length;F=this.left&&A.right<=this.right&&A.top>=this.top&&A.bottom<=this.bottom);};YAHOO.util.Region.prototype.getArea=function(){return((this.bottom-this.top)*(this.right-this.left));};YAHOO.util.Region.prototype.intersect=function(E){var C=Math.max(this.top,E.top);var D=Math.min(this.right,E.right);var A=Math.min(this.bottom,E.bottom);var B=Math.max(this.left,E.left);if(A>=C&&D>=B){return new YAHOO.util.Region(C,D,A,B);}else{return null;}};YAHOO.util.Region.prototype.union=function(E){var C=Math.min(this.top,E.top);var D=Math.max(this.right,E.right);var A=Math.max(this.bottom,E.bottom);var B=Math.min(this.left,E.left);return new YAHOO.util.Region(C,D,A,B);};YAHOO.util.Region.prototype.toString=function(){return("Region {"+"top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+"}");};YAHOO.util.Region.getRegion=function(D){var F=YAHOO.util.Dom.getXY(D);var C=F[1];var E=F[0]+D.offsetWidth;var A=F[1]+D.offsetHeight;var B=F[0];return new YAHOO.util.Region(C,E,A,B);};YAHOO.util.Point=function(A,B){if(YAHOO.lang.isArray(A)){B=A[1];A=A[0];}this.x=this.right=this.left=this[0]=A;this.y=this.top=this.bottom=this[1]=B;};YAHOO.util.Point.prototype=new YAHOO.util.Region();YAHOO.register("dom",YAHOO.util.Dom,{version:"2.6.0",build:"1321"});YAHOO.util.CustomEvent=function(D,B,C,A){this.type=D;this.scope=B||window;this.silent=C;this.signature=A||YAHOO.util.CustomEvent.LIST;this.subscribers=[];if(!this.silent){}var E="_YUICEOnSubscribe";if(D!==E){this.subscribeEvent=new YAHOO.util.CustomEvent(E,this,true);}this.lastError=null;};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1;YAHOO.util.CustomEvent.prototype={subscribe:function(B,C,A){if(!B){throw new Error("Invalid callback for subscriber to '"+this.type+"'");}if(this.subscribeEvent){this.subscribeEvent.fire(B,C,A);}this.subscribers.push(new YAHOO.util.Subscriber(B,C,A));},unsubscribe:function(D,F){if(!D){return this.unsubscribeAll();}var E=false;for(var B=0,A=this.subscribers.length;B0){B=I[0];}try{G=M.fn.call(L,B,M.obj);}catch(F){this.lastError=F;if(A){throw F;}}}else{try{G=M.fn.call(L,this.type,I,M.obj);}catch(H){this.lastError=H;if(A){throw H;}}}if(false===G){if(!this.silent){}break;}}}return(G!==false);},unsubscribeAll:function(){for(var A=this.subscribers.length-1;A>-1;A--){this._delete(A);}this.subscribers=[];return A;},_delete:function(A){var B=this.subscribers[A];if(B){delete B.fn;delete B.obj;}this.subscribers.splice(A,1);},toString:function(){return"CustomEvent: "+"'"+this.type+"', "+"scope: "+this.scope;}};YAHOO.util.Subscriber=function(B,C,A){this.fn=B;this.obj=YAHOO.lang.isUndefined(C)?null:C;this.override=A;};YAHOO.util.Subscriber.prototype.getScope=function(A){if(this.override){if(this.override===true){return this.obj;}else{return this.override;}}return A;};YAHOO.util.Subscriber.prototype.contains=function(A,B){if(B){return(this.fn==A&&this.obj==B);}else{return(this.fn==A);}};YAHOO.util.Subscriber.prototype.toString=function(){return"Subscriber { obj: "+this.obj+", override: "+(this.override||"no")+" }";};if(!YAHOO.util.Event){YAHOO.util.Event=function(){var H=false;var I=[];var J=[];var G=[];var E=[];var C=0;var F=[];var B=[];var A=0;var D={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9};var K=YAHOO.env.ua.ie?"focusin":"focus";var L=YAHOO.env.ua.ie?"focusout":"blur";return{POLL_RETRYS:2000,POLL_INTERVAL:20,EL:0,TYPE:1,FN:2,WFN:3,UNLOAD_OBJ:3,ADJ_SCOPE:4,OBJ:5,OVERRIDE:6,CAPTURE:7,lastError:null,isSafari:YAHOO.env.ua.webkit,webkit:YAHOO.env.ua.webkit,isIE:YAHOO.env.ua.ie,_interval:null,_dri:null,DOMReady:false,throwErrors:false,startInterval:function(){if(!this._interval){var M=this;var N=function(){M._tryPreloadAttach();};this._interval=setInterval(N,this.POLL_INTERVAL);}},onAvailable:function(R,O,S,Q,P){var M=(YAHOO.lang.isString(R))?[R]:R;for(var N=0;N-1;Q--){W=(this._removeListener(N[Q],M,V,Y)&&W);}return W;}}if(!V||!V.call){return this.purgeElement(N,false,M);}if("unload"==M){for(Q=J.length-1;Q>-1;Q--){X=J[Q];if(X&&X[0]==N&&X[1]==M&&X[2]==V){J.splice(Q,1);return true;}}return false;}var R=null;var S=arguments[4];if("undefined"===typeof S){S=this._getCacheIndex(N,M,V);}if(S>=0){R=I[S];}if(!N||!R){return false;}if(this.useLegacyEvent(N,M)){var P=this.getLegacyIndex(N,M);var O=E[P];if(O){for(Q=0,T=O.length;Q0&&F.length>0);}var R=[];var T=function(V,W){var U=V;if(W.override){if(W.override===true){U=W.obj;}else{U=W.override;}}W.fn.call(U,W.obj);};var N,M,Q,P,O=[];for(N=0,M=F.length;N-1;N--){Q=F[N];if(!Q||!Q.id){F.splice(N,1);}}this.startInterval();}else{clearInterval(this._interval);this._interval=null;}this.locked=false;},purgeElement:function(Q,R,T){var O=(YAHOO.lang.isString(Q))?this.getEl(Q):Q;var S=this.getListeners(O,T),P,M;if(S){for(P=S.length-1;P>-1;P--){var N=S[P];this._removeListener(O,N.type,N.fn,N.capture);}}if(R&&O&&O.childNodes){for(P=0,M=O.childNodes.length;P-1;O--){N=I[O];if(N){M._removeListener(N[M.EL],N[M.TYPE],N[M.FN],N[M.CAPTURE],O);}}N=null;}G=null;M._simpleRemove(window,"unload",M._unload);},_getScrollLeft:function(){return this._getScroll()[1];},_getScrollTop:function(){return this._getScroll()[0];},_getScroll:function(){var M=document.documentElement,N=document.body;if(M&&(M.scrollTop||M.scrollLeft)){return[M.scrollTop,M.scrollLeft];}else{if(N){return[N.scrollTop,N.scrollLeft];}else{return[0,0];}}},regCE:function(){},_simpleAdd:function(){if(window.addEventListener){return function(O,P,N,M){O.addEventListener(P,N,(M));};}else{if(window.attachEvent){return function(O,P,N,M){O.attachEvent("on"+P,N);};}else{return function(){};}}}(),_simpleRemove:function(){if(window.removeEventListener){return function(O,P,N,M){O.removeEventListener(P,N,(M));};}else{if(window.detachEvent){return function(N,O,M){N.detachEvent("on"+O,M);};}else{return function(){};}}}()};}();(function(){var EU=YAHOO.util.Event;EU.on=EU.addListener;EU.onFocus=EU.addFocusListener;EU.onBlur=EU.addBlurListener;
10 | /* DOMReady: based on work by: Dean Edwards/John Resig/Matthias Miller */
11 | if(EU.isIE){YAHOO.util.Event.onDOMReady(YAHOO.util.Event._tryPreloadAttach,YAHOO.util.Event,true);var n=document.createElement("p");EU._dri=setInterval(function(){try{n.doScroll("left");clearInterval(EU._dri);EU._dri=null;EU._ready();n=null;}catch(ex){}},EU.POLL_INTERVAL);}else{if(EU.webkit&&EU.webkit<525){EU._dri=setInterval(function(){var rs=document.readyState;if("loaded"==rs||"complete"==rs){clearInterval(EU._dri);EU._dri=null;EU._ready();}},EU.POLL_INTERVAL);}else{EU._simpleAdd(document,"DOMContentLoaded",EU._ready);}}EU._simpleAdd(window,"load",EU._load);EU._simpleAdd(window,"unload",EU._unload);EU._tryPreloadAttach();})();}YAHOO.util.EventProvider=function(){};YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(A,C,F,E){this.__yui_events=this.__yui_events||{};
12 | var D=this.__yui_events[A];if(D){D.subscribe(C,F,E);}else{this.__yui_subscribers=this.__yui_subscribers||{};var B=this.__yui_subscribers;if(!B[A]){B[A]=[];}B[A].push({fn:C,obj:F,override:E});}},unsubscribe:function(C,E,G){this.__yui_events=this.__yui_events||{};var A=this.__yui_events;if(C){var F=A[C];if(F){return F.unsubscribe(E,G);}}else{var B=true;for(var D in A){if(YAHOO.lang.hasOwnProperty(A,D)){B=B&&A[D].unsubscribe(E,G);}}return B;}return false;},unsubscribeAll:function(A){return this.unsubscribe(A);},createEvent:function(G,D){this.__yui_events=this.__yui_events||{};var A=D||{};var I=this.__yui_events;if(I[G]){}else{var H=A.scope||this;var E=(A.silent);var B=new YAHOO.util.CustomEvent(G,H,E,YAHOO.util.CustomEvent.FLAT);I[G]=B;if(A.onSubscribeCallback){B.subscribeEvent.subscribe(A.onSubscribeCallback);}this.__yui_subscribers=this.__yui_subscribers||{};var F=this.__yui_subscribers[G];if(F){for(var C=0;C{label} {totalTime}ms (+{elapsedTime}) {localTime}:
{sourceAndDetail}
{message}
",BASIC_TEMPLATE:"{label} {totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}
"});YAHOO.widget.LogReader.prototype={logReaderEnabled:true,width:null,height:null,top:null,left:null,right:null,bottom:null,fontSize:null,footerEnabled:true,verboseOutput:true,entryFormat:null,newestOnTop:true,outputBuffer:100,thresholdMax:500,thresholdMin:100,isCollapsed:false,isPaused:false,draggable:true,toString:function(){return"LogReader instance"+this._sName;},pause:function(){this.isPaused=true;this._timeout=null;this.logReaderEnabled=false;if(this._btnPause){this._btnPause.value="Resume";}},resume:function(){this.isPaused=false;this.logReaderEnabled=true;this._printBuffer();if(this._btnPause){this._btnPause.value="Pause";}},hide:function(){this._elContainer.style.display="none";},show:function(){this._elContainer.style.display="block";},collapse:function(){this._elConsole.style.display="none";if(this._elFt){this._elFt.style.display="none";}this._btnCollapse.value="Expand";this.isCollapsed=true;},expand:function(){this._elConsole.style.display="block";if(this._elFt){this._elFt.style.display="block";}this._btnCollapse.value="Collapse";this.isCollapsed=false;},getCheckbox:function(A){return this._filterCheckboxes[A];},getCategories:function(){return this._categoryFilters;},showCategory:function(B){var D=this._categoryFilters;if(D.indexOf){if(D.indexOf(B)>-1){return ;}}else{for(var A=0;A-1){return ;}}else{for(var B=0;B/g,">");}return"";},_sName:null,_buffer:null,_consoleMsgCount:0,_lastTime:null,_timeout:null,_filterCheckboxes:null,_categoryFilters:null,_sourceFilters:null,_elContainer:null,_elHd:null,_elCollapse:null,_btnCollapse:null,_title:null,_elConsole:null,_elFt:null,_elBtns:null,_elCategoryFilters:null,_elSourceFilters:null,_btnPause:null,_btnClear:null,_initContainerEl:function(B){B=YAHOO.util.Dom.get(B);if(B&&B.tagName&&(B.tagName.toLowerCase()=="div")){this._elContainer=B;YAHOO.util.Dom.addClass(this._elContainer,"yui-log");}else{this._elContainer=document.body.appendChild(document.createElement("div"));YAHOO.util.Dom.addClass(this._elContainer,"yui-log");
20 | YAHOO.util.Dom.addClass(this._elContainer,"yui-log-container");var A=this._elContainer.style;if(this.width){A.width=this.width;}if(this.right){A.right=this.right;}if(this.top){A.top=this.top;}if(this.left){A.left=this.left;A.right="auto";}if(this.bottom){A.bottom=this.bottom;A.top="auto";}if(this.fontSize){A.fontSize=this.fontSize;}if(navigator.userAgent.toLowerCase().indexOf("opera")!=-1){document.body.style+="";}}},_initHeaderEl:function(){var A=this;if(this._elHd){YAHOO.util.Event.purgeElement(this._elHd,true);this._elHd.innerHTML="";}this._elHd=this._elContainer.appendChild(document.createElement("div"));this._elHd.id="yui-log-hd"+this._sName;this._elHd.className="yui-log-hd";this._elCollapse=this._elHd.appendChild(document.createElement("div"));this._elCollapse.className="yui-log-btns";this._btnCollapse=document.createElement("input");this._btnCollapse.type="button";this._btnCollapse.className="yui-log-button";this._btnCollapse.value="Collapse";this._btnCollapse=this._elCollapse.appendChild(this._btnCollapse);YAHOO.util.Event.addListener(A._btnCollapse,"click",A._onClickCollapseBtn,A);this._title=this._elHd.appendChild(document.createElement("h4"));this._title.innerHTML="Logger Console";},_initConsoleEl:function(){if(this._elConsole){YAHOO.util.Event.purgeElement(this._elConsole,true);this._elConsole.innerHTML="";}this._elConsole=this._elContainer.appendChild(document.createElement("div"));this._elConsole.className="yui-log-bd";if(this.height){this._elConsole.style.height=this.height;}},_initFooterEl:function(){var A=this;if(this.footerEnabled){if(this._elFt){YAHOO.util.Event.purgeElement(this._elFt,true);this._elFt.innerHTML="";}this._elFt=this._elContainer.appendChild(document.createElement("div"));this._elFt.className="yui-log-ft";this._elBtns=this._elFt.appendChild(document.createElement("div"));this._elBtns.className="yui-log-btns";this._btnPause=document.createElement("input");this._btnPause.type="button";this._btnPause.className="yui-log-button";this._btnPause.value="Pause";this._btnPause=this._elBtns.appendChild(this._btnPause);YAHOO.util.Event.addListener(A._btnPause,"click",A._onClickPauseBtn,A);this._btnClear=document.createElement("input");this._btnClear.type="button";this._btnClear.className="yui-log-button";this._btnClear.value="Clear";this._btnClear=this._elBtns.appendChild(this._btnClear);YAHOO.util.Event.addListener(A._btnClear,"click",A._onClickClearBtn,A);this._elCategoryFilters=this._elFt.appendChild(document.createElement("div"));this._elCategoryFilters.className="yui-log-categoryfilters";this._elSourceFilters=this._elFt.appendChild(document.createElement("div"));this._elSourceFilters.className="yui-log-sourcefilters";}},_initDragDrop:function(){if(YAHOO.util.DD&&this.draggable&&this._elHd){var A=new YAHOO.util.DD(this._elContainer);A.setHandleElId(this._elHd.id);this._elHd.style.cursor="move";}},_initCategories:function(){this._categoryFilters=[];var C=YAHOO.widget.Logger.categories;for(var A=0;Athis.thresholdMax)){Q=0;}K=(B>Q)?(B-Q):0;for(H=K;H0){C=G.substring(0,D);A=G.substring(D,G.length);}else{C=G;}if(this._isNewSource(C)){this._createNewSource(C);}}var H=new Date();var J=new YAHOO.widget.LogMsg({msg:B,time:H,category:F,source:C,sourceDetail:A});var I=this._stack;var E=this.maxStackEntries;if(E&&!isNaN(E)&&(I.length>=E)){I.shift();}I.push(J);this.newLogEvent.fire(J);if(this._browserConsoleEnabled){this._printToBrowserConsole(J);}return true;}else{return false;}};YAHOO.widget.Logger.reset=function(){this._stack=[];this._startTime=new Date().getTime();this.loggerEnabled=true;this.log("Logger reset");this.logResetEvent.fire();};YAHOO.widget.Logger.getStack=function(){return this._stack;};YAHOO.widget.Logger.getStartTime=function(){return this._startTime;};YAHOO.widget.Logger.disableBrowserConsole=function(){YAHOO.log("Logger output to the function console.log() has been disabled.");this._browserConsoleEnabled=false;};YAHOO.widget.Logger.enableBrowserConsole=function(){this._browserConsoleEnabled=true;YAHOO.log("Logger output to the function console.log() has been enabled.");};YAHOO.widget.Logger.handleWindowErrors=function(){if(!YAHOO.widget.Logger._windowErrorsHandled){if(window.error){YAHOO.widget.Logger._origOnWindowError=window.onerror;}window.onerror=YAHOO.widget.Logger._onWindowError;YAHOO.widget.Logger._windowErrorsHandled=true;YAHOO.log("Logger handling of window.onerror has been enabled.");}else{YAHOO.log("Logger handling of window.onerror had already been enabled.");}};YAHOO.widget.Logger.unhandleWindowErrors=function(){if(YAHOO.widget.Logger._windowErrorsHandled){if(YAHOO.widget.Logger._origOnWindowError){window.onerror=YAHOO.widget.Logger._origOnWindowError;YAHOO.widget.Logger._origOnWindowError=null;}else{window.onerror=null;}YAHOO.widget.Logger._windowErrorsHandled=false;YAHOO.log("Logger handling of window.onerror has been disabled.");}else{YAHOO.log("Logger handling of window.onerror had already been disabled.");}};YAHOO.widget.Logger.categoryCreateEvent=new YAHOO.util.CustomEvent("categoryCreate",this,true);YAHOO.widget.Logger.sourceCreateEvent=new YAHOO.util.CustomEvent("sourceCreate",this,true);YAHOO.widget.Logger.newLogEvent=new YAHOO.util.CustomEvent("newLog",this,true);YAHOO.widget.Logger.logResetEvent=new YAHOO.util.CustomEvent("logReset",this,true);YAHOO.widget.Logger._createNewCategory=function(A){this.categories.push(A);this.categoryCreateEvent.fire(A);};YAHOO.widget.Logger._isNewCategory=function(B){for(var A=0;A0){return YAHOO.lang.substitute(B,{message:A});}else{return A;}},fail:function(A){throw new YAHOO.util.AssertionError(this._formatMessage(A,"Test force-failed."));},areEqual:function(B,C,A){if(B!=C){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Values should be equal."),B,C);}},areNotEqual:function(A,C,B){if(A==C){throw new YAHOO.util.UnexpectedValue(this._formatMessage(B,"Values should not be equal."),A);}},areNotSame:function(A,C,B){if(A===C){throw new YAHOO.util.UnexpectedValue(this._formatMessage(B,"Values should not be the same."),A);}},areSame:function(B,C,A){if(B!==C){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Values should be the same."),B,C);}},isFalse:function(B,A){if(false!==B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be false."),false,B);}},isTrue:function(B,A){if(true!==B){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be true."),true,B);}},isNaN:function(B,A){if(!isNaN(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be NaN."),NaN,B);}},isNotNaN:function(B,A){if(isNaN(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Values should not be NaN."),NaN);}},isNotNull:function(B,A){if(YAHOO.lang.isNull(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Values should not be null."),null);}},isNotUndefined:function(B,A){if(YAHOO.lang.isUndefined(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should not be undefined."),undefined);}},isNull:function(B,A){if(!YAHOO.lang.isNull(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be null."),null,B);}},isUndefined:function(B,A){if(!YAHOO.lang.isUndefined(B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value should be undefined."),undefined,B);}},isArray:function(B,A){if(!YAHOO.lang.isArray(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be an array."),B);}},isBoolean:function(B,A){if(!YAHOO.lang.isBoolean(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a Boolean."),B);}},isFunction:function(B,A){if(!YAHOO.lang.isFunction(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a function."),B);}},isInstanceOf:function(B,C,A){if(!(C instanceof B)){throw new YAHOO.util.ComparisonFailure(this._formatMessage(A,"Value isn't an instance of expected type."),B,C);}},isNumber:function(B,A){if(!YAHOO.lang.isNumber(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a number."),B);}},isObject:function(B,A){if(!YAHOO.lang.isObject(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be an object."),B);}},isString:function(B,A){if(!YAHOO.lang.isString(B)){throw new YAHOO.util.UnexpectedValue(this._formatMessage(A,"Value should be a string."),B);}},isTypeOf:function(A,C,B){if(typeof C!=A){throw new YAHOO.util.ComparisonFailure(this._formatMessage(B,"Value should be of type "+expected+"."),expected,typeof actual);}}};YAHOO.util.AssertionError=function(A){arguments.callee.superclass.constructor.call(this,A);this.message=A;this.name="AssertionError";};YAHOO.lang.extend(YAHOO.util.AssertionError,Error,{getMessage:function(){return this.message;},toString:function(){return this.name+": "+this.getMessage();},valueOf:function(){return this.toString();}});YAHOO.util.ComparisonFailure=function(B,A,C){arguments.callee.superclass.constructor.call(this,B);this.expected=A;this.actual=C;this.name="ComparisonFailure";};YAHOO.lang.extend(YAHOO.util.ComparisonFailure,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nExpected: "+this.expected+" ("+(typeof this.expected)+")\nActual:"+this.actual+" ("+(typeof this.actual)+")";}});YAHOO.util.UnexpectedValue=function(B,A){arguments.callee.superclass.constructor.call(this,B);this.unexpected=A;this.name="UnexpectedValue";};YAHOO.lang.extend(YAHOO.util.UnexpectedValue,YAHOO.util.AssertionError,{getMessage:function(){return this.message+"\nUnexpected: "+this.unexpected+" ("+(typeof this.unexpected)+") ";}});YAHOO.util.ShouldFail=function(A){arguments.callee.superclass.constructor.call(this,A||"This test should fail but didn't.");this.name="ShouldFail";};YAHOO.lang.extend(YAHOO.util.ShouldFail,YAHOO.util.AssertionError);YAHOO.util.ShouldError=function(A){arguments.callee.superclass.constructor.call(this,A||"This test should have thrown an error but didn't.");
29 | this.name="ShouldError";};YAHOO.lang.extend(YAHOO.util.ShouldError,YAHOO.util.AssertionError);YAHOO.util.UnexpectedError=function(A){arguments.callee.superclass.constructor.call(this,"Unexpected error: "+A.message);this.cause=A;this.name="UnexpectedError";this.stack=A.stack;};YAHOO.lang.extend(YAHOO.util.UnexpectedError,YAHOO.util.AssertionError);YAHOO.util.ArrayAssert={contains:function(E,D,B){var C=false;var F=YAHOO.util.Assert;for(var A=0;A0){var B=YAHOO.util.Assert;B.fail(B._formatMessage(A,"Array should be empty."));}},isNotEmpty:function(C,A){if(C.length===0){var B=YAHOO.util.Assert;B.fail(B._formatMessage(A,"Array should not be empty."));}},itemsAreSame:function(D,F,C){var A=Math.max(D.length,F.length);var E=YAHOO.util.Assert;for(var B=0;B=0;B--){if(D[B]===E){F.areEqual(A,B,F._formatMessage(C,"Value exists at index "+B+" but should be at index "+A+"."));return ;}}F.fail(F._formatMessage(C,"Value doesn't exist in array."));}};YAHOO.namespace("util");YAHOO.util.ObjectAssert={propertiesAreEqual:function(D,G,C){var F=YAHOO.util.Assert;var B=[];for(var E in D){B.push(E);}for(var A=0;A0)?M:N;F.fireEvent("on"+J,I);
30 | }else{throw new Error("simulateKeyEvent(): No event simulation framework present.");}}},simulateMouseEvent:function(K,P,H,E,Q,J,G,F,D,B,C,A,O,M,I,L){K=YAHOO.util.Dom.get(K);if(!K){throw new Error("simulateMouseEvent(): Invalid target.");}if(YAHOO.lang.isString(P)){P=P.toLowerCase();switch(P){case"mouseover":case"mouseout":case"mousedown":case"mouseup":case"click":case"dblclick":case"mousemove":break;default:throw new Error("simulateMouseEvent(): Event type '"+P+"' not supported.");}}else{throw new Error("simulateMouseEvent(): Event type must be a string.");}if(!YAHOO.lang.isBoolean(H)){H=true;}if(!YAHOO.lang.isBoolean(E)){E=(P!="mousemove");}if(!YAHOO.lang.isObject(Q)){Q=window;}if(!YAHOO.lang.isNumber(J)){J=1;}if(!YAHOO.lang.isNumber(G)){G=0;}if(!YAHOO.lang.isNumber(F)){F=0;}if(!YAHOO.lang.isNumber(D)){D=0;}if(!YAHOO.lang.isNumber(B)){B=0;}if(!YAHOO.lang.isBoolean(C)){C=false;}if(!YAHOO.lang.isBoolean(A)){A=false;}if(!YAHOO.lang.isBoolean(O)){O=false;}if(!YAHOO.lang.isBoolean(M)){M=false;}if(!YAHOO.lang.isNumber(I)){I=0;}var N=null;if(YAHOO.lang.isFunction(document.createEvent)){N=document.createEvent("MouseEvents");if(N.initMouseEvent){N.initMouseEvent(P,H,E,Q,J,G,F,D,B,C,A,O,M,I,L);}else{N=document.createEvent("UIEvents");N.initEvent(P,H,E);N.view=Q;N.detail=J;N.screenX=G;N.screenY=F;N.clientX=D;N.clientY=B;N.ctrlKey=C;N.altKey=A;N.metaKey=M;N.shiftKey=O;N.button=I;N.relatedTarget=L;}if(L&&!N.relatedTarget){if(P=="mouseout"){N.toElement=L;}else{if(P=="mouseover"){N.fromElement=L;}}}K.dispatchEvent(N);}else{if(YAHOO.lang.isObject(document.createEventObject)){N=document.createEventObject();N.bubbles=H;N.cancelable=E;N.view=Q;N.detail=J;N.screenX=G;N.screenY=F;N.clientX=D;N.clientY=B;N.ctrlKey=C;N.altKey=A;N.metaKey=M;N.shiftKey=O;switch(I){case 0:N.button=1;break;case 1:N.button=4;break;case 2:break;default:N.button=0;}N.relatedTarget=L;K.fireEvent("on"+P,N);}else{throw new Error("simulateMouseEvent(): No event simulation framework present.");}}},fireMouseEvent:function(C,B,A){A=A||{};this.simulateMouseEvent(C,B,A.bubbles,A.cancelable,A.view,A.detail,A.screenX,A.screenY,A.clientX,A.clientY,A.ctrlKey,A.altKey,A.shiftKey,A.metaKey,A.button,A.relatedTarget);},click:function(B,A){this.fireMouseEvent(B,"click",A);},dblclick:function(B,A){this.fireMouseEvent(B,"dblclick",A);},mousedown:function(B,A){this.fireMouseEvent(B,"mousedown",A);},mousemove:function(B,A){this.fireMouseEvent(B,"mousemove",A);},mouseout:function(B,A){this.fireMouseEvent(B,"mouseout",A);},mouseover:function(B,A){this.fireMouseEvent(B,"mouseover",A);},mouseup:function(B,A){this.fireMouseEvent(B,"mouseup",A);},fireKeyEvent:function(B,C,A){A=A||{};this.simulateKeyEvent(C,B,A.bubbles,A.cancelable,A.view,A.ctrlKey,A.altKey,A.shiftKey,A.metaKey,A.keyCode,A.charCode);},keydown:function(B,A){this.fireKeyEvent("keydown",B,A);},keypress:function(B,A){this.fireKeyEvent("keypress",B,A);},keyup:function(B,A){this.fireKeyEvent("keyup",B,A);}};YAHOO.namespace("tool");YAHOO.tool.TestManager={TEST_PAGE_BEGIN_EVENT:"testpagebegin",TEST_PAGE_COMPLETE_EVENT:"testpagecomplete",TEST_MANAGER_BEGIN_EVENT:"testmanagerbegin",TEST_MANAGER_COMPLETE_EVENT:"testmanagercomplete",_curPage:null,_frame:null,_logger:null,_timeoutId:0,_pages:[],_results:null,_handleTestRunnerComplete:function(A){this.fireEvent(this.TEST_PAGE_COMPLETE_EVENT,{page:this._curPage,results:A.results});this._processResults(this._curPage,A.results);this._logger.clearTestRunner();if(this._pages.length){this._timeoutId=setTimeout(function(){YAHOO.tool.TestManager._run();},1000);}else{this.fireEvent(this.TEST_MANAGER_COMPLETE_EVENT,this._results);}},_processResults:function(C,A){var B=this._results;B.passed+=A.passed;B.failed+=A.failed;B.ignored+=A.ignored;B.total+=A.total;B.duration+=A.duration;if(A.failed){B.failedPages.push(C);}else{B.passedPages.push(C);}A.name=C;A.type="page";B[C]=A;},_run:function(){this._curPage=this._pages.shift();this.fireEvent(this.TEST_PAGE_BEGIN_EVENT,this._curPage);this._frame.location.replace(this._curPage);},load:function(){if(parent.YAHOO.tool.TestManager!==this){parent.YAHOO.tool.TestManager.load();}else{if(this._frame){var A=this._frame.YAHOO.tool.TestRunner;this._logger.setTestRunner(A);A.subscribe(A.COMPLETE_EVENT,this._handleTestRunnerComplete,this,true);A.run();}}},setPages:function(A){this._pages=A;},start:function(){if(!this._initialized){this.createEvent(this.TEST_PAGE_BEGIN_EVENT);this.createEvent(this.TEST_PAGE_COMPLETE_EVENT);this.createEvent(this.TEST_MANAGER_BEGIN_EVENT);this.createEvent(this.TEST_MANAGER_COMPLETE_EVENT);if(!this._frame){var A=document.createElement("iframe");A.style.visibility="hidden";A.style.position="absolute";document.body.appendChild(A);this._frame=A.contentWindow||A.contentDocument.parentWindow;}if(!this._logger){this._logger=new YAHOO.tool.TestLogger();}this._initialized=true;}this._results={passed:0,failed:0,ignored:0,total:0,type:"report",name:"YUI Test Results",duration:0,failedPages:[],passedPages:[]};this.fireEvent(this.TEST_MANAGER_BEGIN_EVENT,null);this._run();},stop:function(){clearTimeout(this._timeoutId);}};YAHOO.lang.augmentObject(YAHOO.tool.TestManager,YAHOO.util.EventProvider.prototype);YAHOO.namespace("tool");YAHOO.tool.TestLogger=function(B,A){YAHOO.tool.TestLogger.superclass.constructor.call(this,B,A);this.init();};YAHOO.lang.extend(YAHOO.tool.TestLogger,YAHOO.widget.LogReader,{footerEnabled:true,newestOnTop:false,formatMsg:function(B){var A=B.category;var C=this.html2Text(B.msg);return""+A.toUpperCase()+" "+C+"
";},init:function(){if(YAHOO.tool.TestRunner){this.setTestRunner(YAHOO.tool.TestRunner);}this.hideSource("global");this.hideSource("LogReader");this.hideCategory("warn");this.hideCategory("window");this.hideCategory("time");this.clearConsole();},clearTestRunner:function(){if(this._runner){this._runner.unsubscribeAll();this._runner=null;}},setTestRunner:function(A){if(this._runner){this.clearTestRunner();}this._runner=A;A.subscribe(A.TEST_PASS_EVENT,this._handleTestRunnerEvent,this,true);
31 | A.subscribe(A.TEST_FAIL_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_IGNORE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_SUITE_BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_SUITE_COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_CASE_BEGIN_EVENT,this._handleTestRunnerEvent,this,true);A.subscribe(A.TEST_CASE_COMPLETE_EVENT,this._handleTestRunnerEvent,this,true);},_handleTestRunnerEvent:function(D){var A=YAHOO.tool.TestRunner;var C="";var B="";switch(D.type){case A.BEGIN_EVENT:C="Testing began at "+(new Date()).toString()+".";B="info";break;case A.COMPLETE_EVENT:C="Testing completed at "+(new Date()).toString()+".\nPassed:"+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;case A.TEST_FAIL_EVENT:C=D.testName+": "+D.error.getMessage();B="fail";break;case A.TEST_IGNORE_EVENT:C=D.testName+": ignored.";B="ignore";break;case A.TEST_PASS_EVENT:C=D.testName+": passed.";B="pass";break;case A.TEST_SUITE_BEGIN_EVENT:C="Test suite \""+D.testSuite.name+"\" started.";B="info";break;case A.TEST_SUITE_COMPLETE_EVENT:C="Test suite \""+D.testSuite.name+"\" completed.\nPassed:"+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;case A.TEST_CASE_BEGIN_EVENT:C="Test case \""+D.testCase.name+"\" started.";B="info";break;case A.TEST_CASE_COMPLETE_EVENT:C="Test case \""+D.testCase.name+"\" completed.\nPassed:"+D.results.passed+" Failed:"+D.results.failed+" Total:"+D.results.total;B="info";break;default:C="Unexpected event "+D.type;C="info";}YAHOO.log(C,B,"TestRunner");}});YAHOO.namespace("tool.TestFormat");YAHOO.tool.TestFormat.JSON=function(A){return YAHOO.lang.JSON.stringify(A);};YAHOO.tool.TestFormat.XML=function(C){var A=YAHOO.lang;var B="<"+C.type+" name=\""+C.name.replace(/"/g,""").replace(/'/g,"'")+"\"";if(A.isNumber(C.duration)){B+=" duration=\""+C.duration+"\"";}if(C.type=="test"){B+=" result=\""+C.result+"\" message=\""+C.message+"\">";}else{B+=" passed=\""+C.passed+"\" failed=\""+C.failed+"\" ignored=\""+C.ignored+"\" total=\""+C.total+"\">";for(var D in C){if(A.hasOwnProperty(C,D)&&A.isObject(C[D])&&!A.isArray(C[D])){B+=arguments.callee(C[D]);}}}B+=""+C.type+">";return B;};YAHOO.namespace("tool");YAHOO.tool.TestReporter=function(A,B){this.url=A;this.format=B||YAHOO.tool.TestFormat.XML;this._fields=new Object();this._form=null;this._iframe=null;};YAHOO.tool.TestReporter.prototype={constructor:YAHOO.tool.TestReporter,_convertToISOString:function(A){function B(C){return C<10?"0"+C:C;}return A.getUTCFullYear()+"-"+B(A.getUTCMonth()+1)+"-"+B(A.getUTCDate())+"T"+B(A.getUTCHours())+":"+B(A.getUTCMinutes())+":"+B(A.getUTCSeconds())+"Z";},addField:function(A,B){this._fields[A]=B;},clearFields:function(){this._fields=new Object();},destroy:function(){if(this._form){this._form.parentNode.removeChild(this._form);this._form=null;}if(this._iframe){this._iframe.parentNode.removeChild(this._iframe);this._iframe=null;}this._fields=null;},report:function(A){if(!this._form){this._form=document.createElement("form");this._form.method="post";this._form.style.visibility="hidden";this._form.style.position="absolute";this._form.style.top=0;document.body.appendChild(this._form);if(YAHOO.env.ua.ie){this._iframe=document.createElement("");}else{this._iframe=document.createElement("iframe");this._iframe.name="yuiTestTarget";}this._iframe.src="javascript:false";this._iframe.style.visibility="hidden";this._iframe.style.position="absolute";this._iframe.style.top=0;document.body.appendChild(this._iframe);this._form.target="yuiTestTarget";}this._form.action=this.url;while(this._form.hasChildNodes()){this._form.removeChild(this._form.lastChild);}this._fields.results=this.format(A);this._fields.useragent=navigator.userAgent;this._fields.timestamp=this._convertToISOString(new Date());for(var B in this._fields){if(YAHOO.lang.hasOwnProperty(this._fields,B)&&typeof this._fields[B]!="function"){if(YAHOO.env.ua.ie){input=document.createElement("");}else{input=document.createElement("input");input.name=B;}input.type="hidden";input.value=this._fields[B];this._form.appendChild(input);}}delete this._fields.results;delete this._fields.useragent;delete this._fields.timestamp;if(arguments[1]!==false){this._form.submit();}}};YAHOO.register("yuitest",YAHOO.tool.TestRunner,{version:"2.6.0",build:"1321"});
--------------------------------------------------------------------------------
/test/xhtml_purifier_test.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | xhtml_purifier_test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/xhtml_purifier_test.js:
--------------------------------------------------------------------------------
1 | XHTMLPurifierTestCase = function() {
2 | var assert = YAHOO.util.Assert;
3 |
4 | return new YAHOO.tool.TestCase({
5 | name: "XHTMLPurifier Tests",
6 | setUp: function () {
7 | },
8 |
9 | tearDown: function () {
10 | },
11 |
12 | testTables: function () {
13 | var html = "";
14 | html += "Caption";
15 | html += "Header |
";
16 | html += "Row |
";
17 | html += "
";
18 | assert.areEqual(html, XHTMLPurifier.purify(html).replace(/\s+/g, ''));
19 | },
20 |
21 | testEmptyTableWithPAfter: function () {
22 | var html = " Hola
";
23 | assert.areEqual("\n Hola\n
", XHTMLPurifier.purify(html));
24 | },
25 |
26 | testBadTables: function() {
27 | var html = "";
28 | html += "Caption";
29 | html += " | My Header | ";
30 | html += "Row |
";
31 | html += "
";
32 | //FIXME: Somehow the in is followed by two carriage returns
33 | var expected = "\n \n Caption\n \n";
34 | expected += " \n \n \n My Header\n | \n
\n \n";
35 | expected += " \n \n \n Row\n | \n
\n \n";
36 | expected += "
";
37 |
38 | assert.areEqual(expected, XHTMLPurifier.purify(html));
39 | },
40 |
41 | testTableElementsOutsideTables: function() {
42 | var html ="Hello World! |
";
43 | var expected = "\n Hello World!\n
\n";
44 |
45 | assert.areEqual(expected, XHTMLPurifier.purify(html));
46 | },
47 |
48 | testTableWithTwoTbodysAndTFoot: function() {
49 | var html ="Testing |
Another test |
---|
Testing |
";
50 | var expected = "\n \n \n \n Testing\n | \n
\n \n"+
51 | " \n \n \n Another test\n | \n
\n \n"+
52 | " \n \n \n Testing\n | \n
\n \n
";
53 | assert.areEqual(expected, XHTMLPurifier.purify(html));
54 | },
55 |
56 | testSurroundingPs: function() {
57 | var html = 'this is a test';
58 | assert.areEqual('\n this is a test\n
', XHTMLPurifier.purify(html));
59 | },
60 |
61 | testHTMLWithWeirdWordTags: function() {
62 | var html = '- Hello, World!
';
63 | assert.areEqual('\n - \n Hello, World! \n
\n
', XHTMLPurifier.purify(html));
64 | },
65 |
66 | testHTMLWithBoldTags: function() {
67 | var html = 'Testing some bold and testing';
68 | assert.areEqual('\n Testing some bold and testing\n
', XHTMLPurifier.purify(html));
69 | },
70 |
71 | testHTMLWithItalicsTags: function() {
72 | var html = 'Testing some italics and testing';
73 | assert.areEqual('\n Testing some italics and testing\n
', XHTMLPurifier.purify(html));
74 | },
75 |
76 | testEscapedHTML: function() {
77 | var html = "<script src="something"></script>";
78 | assert.areEqual('\n <script src="something"></script>\n
', XHTMLPurifier.purify(html));
79 | }
80 | });
81 | }();
82 |
83 | YAHOO.util.Event.onDOMReady(function (){
84 | //create the logger
85 | var logger = new YAHOO.tool.TestLogger("testLogger");
86 | YAHOO.tool.TestRunner.add(XHTMLPurifierTestCase);
87 |
88 | //run the tests
89 | YAHOO.tool.TestRunner.run();
90 | });
91 |
92 |
93 |
--------------------------------------------------------------------------------
/xhtml_purifier.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 | xhtml_purifier
8 |
9 |
16 |
17 |
18 |
19 | XHTML Purifier
20 | This is a small test page for the Javascript XHTML Purifier
21 |
22 |
23 |
24 | Clean HTML
25 |
26 |
27 |
--------------------------------------------------------------------------------
/xhtml_purifier.js:
--------------------------------------------------------------------------------
1 | /*
2 | * XHTML Purifier By Mathias Biilmann Christensen
3 | * and Rodrigo Alvarez
4 | * Copyright Domestika 2008
5 | *
6 | */
7 |
8 | this.XHTMLPurifier = function() {
9 | var allowHeaders = true;
10 |
11 | var stack = [];
12 | var active_elements = [];
13 | var root;
14 | var insertion_mode;
15 |
16 | var scope_markers = {'td':true, 'th': true, 'caption':true};
17 | var formatting_elements = {'a':true, 'em':true, 'strong':true};
18 | var tags_with_implied_end = {'li':true, 'p':true};
19 | var allowed_attributes = {
20 | 'all_elements': ['class'],
21 | 'a': ['href', 'title', 'name', 'rel', 'rev', 'type'],
22 | 'blockquote': ['cite'],
23 | 'img': ['src', 'alt', 'title', 'longdesc'],
24 | 'td': ['colspan', 'class'],
25 | 'th': ['colspan', 'class'],
26 | 'tr': ['rowspan', 'class'],
27 | 'table': ['class']
28 | };
29 | var allowed_attributes_as_hash;
30 | var selfClosing = {
31 | br: true,
32 | hr: true,
33 | img: true
34 | };
35 | var dontIndent = {
36 | strong: true,
37 | em: true,
38 | pre: true
39 | };
40 | var indent = false;
41 | var indent_string = " ";
42 | var indentation = function(depth, switchOff) {
43 | if (!indent) return "";
44 | if (switchOff) indent = false;
45 | var result = "\n";
46 | for(var i=0; i";
105 | },
106 | endTag: function() {
107 | return "" + this.name + ">";
108 | },
109 | selfClosingTag: function() {
110 | return "<" + this.name + this.attributeString() + "/>";
111 | },
112 | attributeString: function() {
113 | var string = "";
114 |
115 | var allowed_for_tag = allowed_attributes_as_hash[this.name] || {};
116 | var allowed_for_all = allowed_attributes_as_hash['all_elements'] || {};
117 |
118 | for (var i=0, len=(this.attributes || []).length; i0; i--) {
221 | entry = active_elements[i-1];
222 | if(in_array(stack, entry)) {
223 | break;
224 | }
225 | }
226 | do {
227 | var clone = entry.clone();
228 | current_node().appendChild(clone);
229 | stack.push(clone);
230 | active_elements[i] = clone;
231 | i += 1;
232 | } while(i != active_elements.length)
233 | }
234 |
235 | function has_element_with(arr_of_elements, tagName) {
236 | for(var i = arr_of_elements.length; i>0; i--) {
237 | if(arr_of_elements[i-1].name == tagName) {
238 | return true;
239 | }
240 | }
241 | return false;
242 | }
243 |
244 | function in_scope(tagName) {
245 | return has_element_with(stack, tagName);
246 | }
247 |
248 | function in_table_scope(tagName) {
249 | for(var i = stack.length; i>0; i--) {
250 | var nodeTag = stack[i-1].name;
251 | if(nodeTag == tagName) {
252 | return true;
253 | } else if(nodeTag == 'table' || nodeTag == 'html') {
254 | return false;
255 | }
256 | }
257 | return false;
258 | }
259 |
260 | function insert_html_element_for(tagName, attrs) {
261 | var node = new Node(tagName);
262 | node.attributes = attrs;
263 | current_node().appendChild(node);
264 | stack.push(node);
265 | return node;
266 | }
267 |
268 | function generate_implied_end_tags(exception) {
269 | var tagName = current_node().name;
270 | while(tags_with_implied_end[tagName] && tagName != exception) {
271 | end(tagName);
272 | tagName = current_node().name;
273 | }
274 | }
275 |
276 | function trim_to_1_space(str) {
277 | return str.replace(/^\s+/, ' ').replace(/\s+$/, ' ');
278 | }
279 |
280 | function clear_stack_to_table_context() {
281 | clear_stack_to_context_by_tags(['table', 'html']);
282 | }
283 |
284 | function clear_stack_to_table_body_context() {
285 | clear_stack_to_context_by_tags(['tbody', 'tfoot', 'thead', 'html']);
286 | }
287 |
288 | function clear_stack_to_table_row_context() {
289 | clear_stack_to_context_by_tags(['tr', 'html']);
290 | }
291 |
292 | function clear_stack_to_context_by_tags(tags) {
293 | while(!in_array(tags, current_node().name)) {
294 | stack.pop();
295 | }
296 | }
297 |
298 | function clear_active_elements_to_last_marker() {
299 | do {
300 | var entry = active_elements.pop();
301 | } while(!scope_markers[entry.name]);
302 | }
303 |
304 | function reset_insertion_mode() {
305 | var last = false;
306 | var node;
307 | for (var i = stack.length - 1; i >= 0; i--){
308 | node = stack[i];
309 | if (node == stack[0]) {
310 | last = true;
311 | }
312 | switch(node.name) {
313 | case 'th':
314 | case 'td':
315 | if (!last) {
316 | insertion_mode = InCell;
317 | return;
318 | }
319 | case 'tr':
320 | insertion_mode = InRow;
321 | return;
322 | case 'tbody':
323 | case 'thead':
324 | case 'tfoot':
325 | insertion_mode = InTableBody;
326 | return;
327 | case 'caption':
328 | insertion_mode = InCaption;
329 | return;
330 | case 'colgroup':
331 | insertion_mode = InColumnGroup;
332 | return;
333 | case 'table':
334 | insertion_mode = InTable;
335 | return;
336 | default:
337 | if (last) {
338 | insertion_mode = InBody;
339 | return;
340 | }
341 | }
342 | }
343 | }
344 |
345 | function close_the_cell() {
346 | if (in_table_scope('td')) {
347 | end('td');
348 | } else {
349 | end('th');
350 | }
351 | }
352 |
353 | function start(tagName, attrs, unary) {
354 | insertion_mode.insertion_mode_start(tagName, attrs, unary);
355 | }
356 |
357 | function end(tagName) {
358 | insertion_mode.insertion_mode_end(tagName);
359 | }
360 |
361 | function chars(text) {
362 | if(typeof(text) == 'undefined') {
363 | return;
364 | }
365 | text = text.replace(/\n\s*\n\s*\n*/g,'\n\n').replace(/(^\n\n|\n\n$)/g,'');
366 | var paragraphs = text.split('\n\n');
367 | var trimmedText, textNode;
368 | if(paragraphs.length > 1) {
369 | for(var i in paragraphs) {
370 | start('p');
371 | reconstruct_the_active_formatting_elements();
372 | trimmedText = trim_to_1_space(paragraphs[i]);
373 | current_node().appendChild(new TextNode(trimmedText));
374 | end('p');
375 | }
376 | } else {
377 | if(text.match(/^\s*$/g) && current_node().children.length && current_node().lastChild().name == 'br') {
378 | return;
379 | }
380 | reconstruct_the_active_formatting_elements();
381 | trimmedText = trim_to_1_space(paragraphs[0]);
382 | current_node().appendChild(new TextNode(trimmedText));
383 | }
384 | }
385 |
386 | var InBody = {
387 | insertion_mode_start: function (tagName, attrs, unary) {
388 | var node;
389 | tagName = tagName.toLowerCase();
390 | switch(tagName) {
391 | case 'b':
392 | start('strong');
393 | return;
394 | case 'i':
395 | start('em');
396 | return;
397 | case 'h1':
398 | case 'h2':
399 | case 'h3':
400 | case 'h4':
401 | case 'h5':
402 | case 'h6':
403 | case 'h7':
404 | if(!allowHeaders) {
405 | start('p');
406 | start('strong');
407 | return;
408 | }
409 | case 'blockquote':
410 | case 'ol':
411 | case 'p':
412 | case 'ul':
413 | case 'pre': // Techically PRE shouldn't be in this groups, since newlines should be ignored after a pre tag
414 | if(in_scope('p')) {
415 | end('p');
416 | }
417 | insert_html_element_for(tagName, attrs);
418 | return;
419 | case 'li':
420 | if(in_scope('p')) {
421 | end('p');
422 | }
423 | node = current_node();
424 | while(node.name == 'li') {
425 | stack.pop();
426 | }
427 | insert_html_element_for(tagName, attrs);
428 | return;
429 | case 'a':
430 | for(var i=active_elements.length; i>0; i--) {
431 | if(active_elements[i-1].name == 'a') {
432 | end('a');
433 | active_elements.splice(i-1,1);
434 | }
435 | }
436 | reconstruct_the_active_formatting_elements();
437 | node = insert_html_element_for(tagName, attrs);
438 | active_elements.push(node);
439 | return;
440 | case 'strong':
441 | case 'em':
442 | reconstruct_the_active_formatting_elements();
443 | node = insert_html_element_for(tagName, attrs);
444 | active_elements.push(node);
445 | return;
446 | case 'table':
447 | if (in_scope('p')) {
448 | end('p');
449 | }
450 | insert_html_element_for(tagName, attrs);
451 | insertion_mode = InTable;
452 | return;
453 | case 'br':
454 | case 'img':
455 | reconstruct_the_active_formatting_elements();
456 | // These conditions for BR tags are not part of the HTML5 specification
457 | // but serve to make sure we don't add BR tags to empty elements and
458 | // to make sure we create paragraphs instead of double BRs
459 | if(tagName == 'br') {
460 |
461 | if(current_node().textContent().match(/^\s*$/g)) {
462 | return;
463 | }
464 | if(current_node().children.length && current_node().lastChild().name == 'br') {
465 | current_node().removeChild(current_node().lastChild());
466 | start('p');
467 | return;
468 | }
469 | }
470 | insert_html_element_for(tagName, attrs);
471 | stack.pop();
472 | return;
473 | }
474 | },
475 |
476 | insertion_mode_end: function (tagName) {
477 | if(typeof(tagName) == undefined) {
478 | return;
479 | }
480 | var node;
481 | tagName = tagName.toLowerCase();
482 | switch(tagName) {
483 | case 'b':
484 | end('strong');
485 | return;
486 | case 'i':
487 | end('em');
488 | return;
489 | case 'h1':
490 | case 'h2':
491 | case 'h3':
492 | case 'h4':
493 | case 'h5':
494 | case 'h6':
495 | case 'h7':
496 | if(!allowHeaders) {
497 | end('strong');
498 | end('p');
499 | return;
500 | }
501 | if(in_scope(tagName)) {
502 | generate_implied_end_tags();
503 | do {
504 | node = stack.pop();
505 | } while(node.name != tagName);
506 | }
507 | return;
508 | case 'blockquote':
509 | case 'ol':
510 | case 'ul':
511 | case 'pre': // Techically PRE shouldn't be in this groups, since newlines should be ignored after a pre tag
512 | if(in_scope(tagName)) {
513 | generate_implied_end_tags();
514 | }
515 | if(in_scope(tagName)) {
516 | do {
517 | node = stack.pop();
518 | } while(node.name != tagName);
519 | }
520 | return;
521 | case 'p':
522 | if(in_scope(tagName)) {
523 | generate_implied_end_tags(tagName);
524 | }
525 | var no_p_in_scope = true;
526 | while(in_scope(tagName)) {
527 | no_p_in_scope = false;
528 | node = stack.pop();
529 | }
530 | if(no_p_in_scope) {
531 | start('p',[],false);
532 | end('p');
533 | }
534 | return;
535 | case 'li':
536 | if(in_scope(tagName)) {
537 | generate_implied_end_tags(tagName);
538 | }
539 | if(in_scope(tagName)) {
540 | do {
541 | node = stack.pop();
542 | } while(node.name != tagName);
543 | }
544 | return;
545 | case 'a':
546 | case 'em':
547 | case 'strong':
548 | for(var i=active_elements.length; i>0; i--) {
549 | if(active_elements[i-1].name == tagName) {
550 | node = active_elements[i-1];
551 | break;
552 | }
553 | }
554 | if(typeof(node) == 'undefined' || !in_array(stack, node)) {
555 | return;
556 | }
557 | // Step 2 from the algorithm in the HTML5 spec will never be necessary with the tags we allow
558 | do {
559 | var popped_node = stack.pop();
560 | } while(popped_node != node);
561 | active_elements.splice(i-1, 1);
562 | return;
563 | default:
564 | node = current_node();
565 | if(node.name == tagName) {
566 | generate_implied_end_tags();
567 | while(stack.length > 0 && node != current_node()) {
568 | stack.pop();
569 | }
570 | }
571 | }
572 | }
573 | };
574 |
575 | var InTable = {
576 | insertion_mode_start: function (tagName, attrs, unary) {
577 | tagName = tagName.toLowerCase();
578 | switch(tagName) {
579 | case 'caption':
580 | clear_stack_to_table_context();
581 | active_elements.push(insert_html_element_for(tagName, attrs));
582 | insertion_mode = InCaption;
583 | return;
584 | case 'colgroup':
585 | clear_stack_to_table_context();
586 | insert_html_element_for(tagName, attrs);
587 | insertion_mode = InColumnGroup;
588 | return;
589 | case 'col':
590 | start('colgroup');
591 | start(tagName, attrs, unary);
592 | return;
593 | case 'tbody':
594 | case 'tfoot':
595 | case 'thead':
596 | clear_stack_to_table_context();
597 | insert_html_element_for(tagName, attrs);
598 | insertion_mode = InTableBody;
599 | return;
600 | case 'td':
601 | case 'th':
602 | case 'tr':
603 | start('tbody');
604 | start(tagName, attrs, unary);
605 | return;
606 | }
607 | },
608 |
609 | insertion_mode_end: function (tagName) {
610 | if(typeof(tagName) == undefined) {
611 | return;
612 | }
613 | tagName = tagName.toLowerCase();
614 | switch(tagName) {
615 | case 'table':
616 | if(in_table_scope('table')) {
617 | var node;
618 | do {
619 | node = stack.pop();
620 | } while(node.name != 'table');
621 | }
622 | reset_insertion_mode();
623 | return;
624 | };
625 | }
626 | };
627 |
628 | var InCaption = {
629 | insertion_mode_start: function (tagName, attrs, unary) {
630 | tagName = tagName.toLowerCase();
631 | switch(tagName) {
632 | case 'caption':
633 | case 'col':
634 | case 'colgroup':
635 | case 'tbody':
636 | case 'td':
637 | case 'tfoot':
638 | case 'th':
639 | case 'thead':
640 | case 'tr':
641 | end('caption');
642 | start(tagName);
643 | return;
644 | default:
645 | InBody.insertion_mode_start(tagName, attrs, unary);
646 | return;
647 | };
648 | },
649 |
650 | insertion_mode_end: function(tagName) {
651 | if(typeof(tagName) == undefined) {
652 | return;
653 | }
654 | tagName = tagName.toLowerCase();
655 | switch(tagName) {
656 | case 'caption':
657 | if (in_table_scope('caption')) {
658 | generate_implied_end_tags();
659 | if (current_node().name == 'caption') {
660 | var node;
661 | do {
662 | node = stack.pop();
663 | } while(node.name != 'caption')
664 | clear_active_elements_to_last_marker();
665 | insertion_mode = InTable;
666 | }
667 | }
668 | return;
669 | case "body":
670 | case "col":
671 | case "colgroup":
672 | case "html":
673 | case "tbody":
674 | case "td":
675 | case "tfoot":
676 | case "th":
677 | case "thead":
678 | case "tr":
679 | return;
680 | case 'table':
681 | end('caption');
682 | end('table');
683 | return;
684 | default:
685 | InBody.insertion_mode_end(tagName);
686 | return;
687 | };
688 | }
689 | };
690 |
691 | var InColumnGroup = {
692 | insertion_mode_start: function (tagName, attrs, unary) {
693 | tagName = tagName.toLowerCase();
694 | switch(tagName) {
695 | case 'html':
696 | InBody.insertion_mode_start(tagName, attrs, unary);
697 | return;
698 | case 'col':
699 | insert_html_element_for(tagName, attrs);
700 | stack.pop();
701 | return;
702 | default:
703 | end('colgroup');
704 | start(tagName);
705 | return;
706 | };
707 | },
708 |
709 | insertion_mode_end: function(tagName) {
710 | if(typeof(tagName) == undefined) {
711 | return;
712 | }
713 | tagName = tagName.toLowerCase();
714 | switch(tagName) {
715 | case 'colgroup':
716 | if(current_node().name != 'html') {
717 | stack.pop();
718 | insertion_mode = InTable;
719 | }
720 | return;
721 | case 'col':
722 | return;
723 | default:
724 | end('colgroup');
725 | end(tagName);
726 | return;
727 | };
728 | }
729 | };
730 |
731 | var InTableBody = {
732 | insertion_mode_start: function (tagName, attrs, unary) {
733 | tagName = tagName.toLowerCase();
734 | switch(tagName) {
735 | case 'tr':
736 | clear_stack_to_table_body_context();
737 | insert_html_element_for(tagName, attrs);
738 | insertion_mode = InRow;
739 | return;
740 | case 'th':
741 | case 'td':
742 | start('tr');
743 | start(tagName, attrs, unary);
744 | return;
745 | case "caption":
746 | case "col":
747 | case "colgroup":
748 | case "tbody":
749 | case "tfoot":
750 | case "thead":
751 | if (in_table_scope('tbody') || in_table_scope('thead') || in_table_scope('tfoot')) {
752 | clear_stack_to_table_body_context();
753 | end(current_node().name);
754 | start(tagName, attrs, unary);
755 | }
756 | return;
757 | };
758 | },
759 |
760 | insertion_mode_end: function(tagName) {
761 | if(typeof(tagName) == undefined) {
762 | return;
763 | }
764 | tagName = tagName.toLowerCase();
765 | switch(tagName) {
766 | case 'tbody':
767 | case 'tfoot':
768 | case 'thead':
769 | if (in_table_scope(tagName)) {
770 | clear_stack_to_table_body_context();
771 | stack.pop();
772 | insertion_mode = InTable;
773 | }
774 | return;
775 | case 'table':
776 | if (in_table_scope('tbody') || in_table_scope('thead') || in_table_scope('tfoot')) {
777 | clear_stack_to_table_body_context();
778 | end(current_node().name);
779 | end(tagName, attrs, unary);
780 | }
781 | return;
782 | case "body":
783 | case "caption":
784 | case "col":
785 | case "colgroup":
786 | case "html":
787 | case "td":
788 | case "th":
789 | case "tr":
790 | return;
791 | default:
792 | InTable.insertion_mode_end(tagName);
793 | return;
794 | };
795 | }
796 | };
797 |
798 | var InRow = {
799 | insertion_mode_start: function (tagName, attrs, unary) {
800 | tagName = tagName.toLowerCase();
801 | switch(tagName) {
802 | case 'th':
803 | case 'td':
804 | clear_stack_to_table_row_context();
805 | var node = insert_html_element_for(tagName, attrs);
806 | insertion_mode = InCell;
807 | active_elements.push(node);
808 | return;
809 | case "caption":
810 | case "col":
811 | case "colgroup":
812 | case "tbody":
813 | case "tfoot":
814 | case "thead":
815 | case "tr":
816 | end('tr');
817 | start(tagName, attrs, unary);
818 | return;
819 | default:
820 | InTable.insertion_mode_start(tagName, attrs, unary);
821 | return;
822 | };
823 | },
824 |
825 | insertion_mode_end: function(tagName) {
826 | if(typeof(tagName) == undefined) {
827 | return;
828 | }
829 | tagName = tagName.toLowerCase();
830 | switch(tagName) {
831 | case 'tr':
832 | if (in_table_scope(tagName)) {
833 | clear_stack_to_table_row_context();
834 | stack.pop();
835 | insertion_mode = InTableBody;
836 | }
837 | return;
838 | case 'table':
839 | end('tr');
840 | start(tagName, attrs, unary);
841 | return;
842 | case "tbody":
843 | case "tfoot":
844 | case "thead":
845 | if (in_table_scope(tagName)) {
846 | end('tr');
847 | end(tagName);
848 | }
849 | return;
850 | case "body":
851 | case "caption":
852 | case "col":
853 | case "colgroup":
854 | case "html":
855 | case "td":
856 | case "th":
857 | return;
858 | default:
859 | InTable.insertion_mode_end(tagName);
860 | return;
861 | };
862 | }
863 | };
864 |
865 | var InCell = {
866 | insertion_mode_start: function (tagName, attrs, unary) {
867 | tagName = tagName.toLowerCase();
868 | switch(tagName) {
869 | case "caption":
870 | case "col":
871 | case "colgroup":
872 | case "tbody":
873 | case "td":
874 | case "tfoot":
875 | case "th":
876 | case "thead":
877 | case "tr":
878 | if (in_table_scope('td') || in_table_scope('th')) {
879 | close_the_cell();
880 | start(tagName, attrs, unary);
881 | }
882 | return;
883 | default:
884 | InBody.insertion_mode_start(tagName, attrs, unary);
885 | return;
886 | };
887 | },
888 |
889 | insertion_mode_end: function(tagName) {
890 | if(typeof(tagName) == undefined) {
891 | return;
892 | }
893 | tagName = tagName.toLowerCase();
894 | switch(tagName) {
895 | case "td":
896 | case "th":
897 | if (in_table_scope(tagName)) {
898 | generate_implied_end_tags();
899 | if (current_node().name != tagName) {
900 | return;
901 | }
902 | var node;
903 | do {
904 | node = stack.pop();
905 | } while(node.name != tagName);
906 |
907 | clear_active_elements_to_last_marker();
908 | insertion_mode = InRow;
909 | }
910 | return;
911 | case "body":
912 | case "caption":
913 | case "col":
914 | case "colgroup":
915 | case "html":
916 | return;
917 | case "table":
918 | case "tbody":
919 | case "tfoot":
920 | case "thead":
921 | case "tr":
922 | if (in_table_scope(tagName)) {
923 | close_the_cell();
924 | end(tagName);
925 | }
926 | return;
927 | default:
928 | InBody.insertion_mode_end(tagName);
929 | return;
930 | };
931 | }
932 | };
933 |
934 | return {
935 | purify: function(text) {
936 | init();
937 | insertion_mode = InBody;
938 | HTMLParser(text, {
939 | start: start,
940 | end: end,
941 | chars: chars
942 | });
943 | return root.innerHTML().replace(/^\s+/, '');
944 | }
945 | };
946 | }();
947 |
948 | /*
949 | * HTML Parser By John Resig (ejohn.org)
950 | * Original code by Erik Arvidsson, Mozilla Public License
951 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
952 | *
953 | */
954 |
955 | (function(){
956 |
957 | // Regular Expressions for parsing tags and attributes (modified attribute name matcher, to catch xml:lang)
958 | var startTag = /^<(\w+\:?\w*)((?:\s+[a-zA-Z_:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
959 | endTag = /^<\/(\w+)[^>]*>/,
960 | attr = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
961 |
962 | // Empty Elements - HTML 4.01
963 | var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
964 |
965 | // Block Elements - HTML 4.01
966 | var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
967 |
968 | // Inline Elements - HTML 4.01
969 | var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
970 |
971 | // Elements that you can, intentionally, leave open
972 | // (and which close themselves)
973 | var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
974 |
975 | // Attributes that have their values filled in disabled="disabled"
976 | var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
977 |
978 | // Special Elements (can contain anything)
979 | var special = makeMap("script,style");
980 |
981 | var HTMLParser = this.HTMLParser = function( html, handler ) {
982 | var index, chars, match, stack = [], last = html;
983 | stack.last = function(){
984 | return this[ this.length - 1 ];
985 | };
986 |
987 | while ( html ) {
988 | chars = true;
989 | // Make sure we're not in a script or style element
990 | if ( !stack.last() || !special[ stack.last() ] ) {
991 |
992 | // Comment
993 | if ( html.indexOf("");
995 |
996 | if ( index >= 0 ) {
997 | if ( handler.comment )
998 | handler.comment( html.substring( 4, index ) );
999 | html = html.substring( index + 3 );
1000 | chars = false;
1001 | }
1002 |
1003 | // end tag
1004 | } else if ( html.indexOf("") == 0 ) {
1005 | match = html.match( endTag );
1006 |
1007 | if ( match ) {
1008 | html = html.substring( match[0].length );
1009 | match[0].replace( endTag, parseEndTag );
1010 | chars = false;
1011 | }
1012 |
1013 | // start tag
1014 | } else if ( html.indexOf("<") == 0 ) {
1015 | match = html.match( startTag );
1016 |
1017 | if ( match ) {
1018 | html = html.substring( match[0].length );
1019 | match[0].replace( startTag, parseStartTag );
1020 | chars = false;
1021 | }
1022 | }
1023 |
1024 | if ( chars ) {
1025 | index = html.indexOf("<");
1026 |
1027 | var text = index < 0 ? html : html.substring( 0, index );
1028 | html = index < 0 ? "" : html.substring( index );
1029 |
1030 | if ( handler.chars )
1031 | handler.chars( text );
1032 | }
1033 |
1034 | } else {
1035 | html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function(all, text){
1036 | text = text.replace(//g, "$1")
1037 | .replace(//g, "$1");
1038 |
1039 | if ( handler.chars )
1040 | handler.chars( text );
1041 |
1042 | return "";
1043 | });
1044 |
1045 | parseEndTag( "", stack.last() );
1046 | }
1047 |
1048 | if ( html == last )
1049 | throw "Parse Error: " + html;
1050 | last = html;
1051 | }
1052 |
1053 | // Clean up any remaining tags
1054 | parseEndTag();
1055 |
1056 | function parseStartTag( tag, tagName, rest, unary ) {
1057 | if ( block[ tagName ] ) {
1058 | while ( stack.last() && inline[ stack.last() ] ) {
1059 | parseEndTag( "", stack.last() );
1060 | }
1061 | }
1062 |
1063 | if ( closeSelf[ tagName ] && stack.last() == tagName ) {
1064 | parseEndTag( "", tagName );
1065 | }
1066 |
1067 | unary = empty[ tagName ] || !!unary;
1068 |
1069 | if ( !unary )
1070 | stack.push( tagName );
1071 |
1072 | if ( handler.start ) {
1073 | var attrs = [];
1074 |
1075 | rest.replace(attr, function(match, name) {
1076 | var value = arguments[2] ? arguments[2] :
1077 | arguments[3] ? arguments[3] :
1078 | arguments[4] ? arguments[4] :
1079 | fillAttrs[name] ? name : "";
1080 |
1081 | attrs.push({
1082 | name: name,
1083 | value: value,
1084 | escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
1085 | });
1086 | });
1087 |
1088 | if ( handler.start )
1089 | handler.start( tagName, attrs, unary );
1090 | }
1091 | }
1092 |
1093 | function parseEndTag( tag, tagName ) {
1094 | // If no tag name is provided, clean shop
1095 | if ( !tagName )
1096 | var pos = 0;
1097 |
1098 | // Find the closest opened tag of the same type
1099 | else
1100 | for ( var pos = stack.length - 1; pos >= 0; pos-- )
1101 | if ( stack[ pos ] == tagName )
1102 | break;
1103 |
1104 | if ( pos >= 0 ) {
1105 | // Close all the open elements, up the stack
1106 | for ( var i = stack.length - 1; i >= pos; i-- )
1107 | if ( handler.end )
1108 | handler.end( stack[ i ] );
1109 |
1110 | // Remove the open elements from the stack
1111 | stack.length = pos;
1112 | }
1113 | }
1114 | };
1115 |
1116 | function makeMap(str){
1117 | var obj = {}, items = str.split(",");
1118 | for ( var i = 0; i < items.length; i++ )
1119 | obj[ items[i] ] = true;
1120 | return obj;
1121 | }
1122 | })();
1123 |
--------------------------------------------------------------------------------