|
@@ -0,0 +1,616 @@
|
|
|
+//[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
|
|
|
+//[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
|
|
|
+//[5] Name ::= NameStartChar (NameChar)*
|
|
|
+var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]///\u10000-\uEFFFF
|
|
|
+var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]");
|
|
|
+var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$');
|
|
|
+//var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
|
|
|
+//var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',')
|
|
|
+
|
|
|
+//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE
|
|
|
+//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE
|
|
|
+var S_TAG = 0;//tag name offerring
|
|
|
+var S_ATTR = 1;//attr name offerring
|
|
|
+var S_ATTR_SPACE=2;//attr name end and space offer
|
|
|
+var S_EQ = 3;//=space?
|
|
|
+var S_ATTR_NOQUOT_VALUE = 4;//attr value(no quot value only)
|
|
|
+var S_ATTR_END = 5;//attr value end and no space(quot end)
|
|
|
+var S_TAG_SPACE = 6;//(attr value end || tag end ) && (space offer)
|
|
|
+var S_TAG_CLOSE = 7;//closed el<el />
|
|
|
+
|
|
|
+function XMLReader(){
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+XMLReader.prototype = {
|
|
|
+ parse:function(source,defaultNSMap,entityMap){
|
|
|
+ var domBuilder = this.domBuilder;
|
|
|
+ domBuilder.startDocument();
|
|
|
+ _copy(defaultNSMap ,defaultNSMap = {})
|
|
|
+ parse(source,defaultNSMap,entityMap,
|
|
|
+ domBuilder,this.errorHandler);
|
|
|
+ domBuilder.endDocument();
|
|
|
+ }
|
|
|
+}
|
|
|
+function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){
|
|
|
+ function fixedFromCharCode(code) {
|
|
|
+ // String.prototype.fromCharCode does not supports
|
|
|
+ // > 2 bytes unicode chars directly
|
|
|
+ if (code > 0xffff) {
|
|
|
+ code -= 0x10000;
|
|
|
+ var surrogate1 = 0xd800 + (code >> 10)
|
|
|
+ , surrogate2 = 0xdc00 + (code & 0x3ff);
|
|
|
+
|
|
|
+ return String.fromCharCode(surrogate1, surrogate2);
|
|
|
+ } else {
|
|
|
+ return String.fromCharCode(code);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ function entityReplacer(a){
|
|
|
+ var k = a.slice(1,-1);
|
|
|
+ if(k in entityMap){
|
|
|
+ return entityMap[k];
|
|
|
+ }else if(k.charAt(0) === '#'){
|
|
|
+ return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x')))
|
|
|
+ }else{
|
|
|
+ errorHandler.error('entity not found:'+a);
|
|
|
+ return a;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ function appendText(end){//has some bugs
|
|
|
+ if(end>start){
|
|
|
+ var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer);
|
|
|
+ locator&&position(start);
|
|
|
+ domBuilder.characters(xt,0,end-start);
|
|
|
+ start = end
|
|
|
+ }
|
|
|
+ }
|
|
|
+ function position(p,m){
|
|
|
+ while(p>=lineEnd && (m = linePattern.exec(source))){
|
|
|
+ lineStart = m.index;
|
|
|
+ lineEnd = lineStart + m[0].length;
|
|
|
+ locator.lineNumber++;
|
|
|
+ //console.log('line++:',locator,startPos,endPos)
|
|
|
+ }
|
|
|
+ locator.columnNumber = p-lineStart+1;
|
|
|
+ }
|
|
|
+ var lineStart = 0;
|
|
|
+ var lineEnd = 0;
|
|
|
+ var linePattern = /.*(?:\r\n?|\n)|.*$/g
|
|
|
+ var locator = domBuilder.locator;
|
|
|
+
|
|
|
+ var parseStack = [{currentNSMap:defaultNSMapCopy}]
|
|
|
+ var closeMap = {};
|
|
|
+ var start = 0;
|
|
|
+ while(true){
|
|
|
+ try{
|
|
|
+ var tagStart = source.indexOf('<',start);
|
|
|
+ if(tagStart<0){
|
|
|
+ if(!source.substr(start).match(/^\s*$/)){
|
|
|
+ var doc = domBuilder.doc;
|
|
|
+ var text = doc.createTextNode(source.substr(start));
|
|
|
+ doc.appendChild(text);
|
|
|
+ domBuilder.currentElement = text;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if(tagStart>start){
|
|
|
+ appendText(tagStart);
|
|
|
+ }
|
|
|
+ switch(source.charAt(tagStart+1)){
|
|
|
+ case '/':
|
|
|
+ var end = source.indexOf('>',tagStart+3);
|
|
|
+ var tagName = source.substring(tagStart+2,end);
|
|
|
+ var config = parseStack.pop();
|
|
|
+ if(end<0){
|
|
|
+
|
|
|
+ tagName = source.substring(tagStart+2).replace(/[\s<].*/,'');
|
|
|
+ //console.error('#@@@@@@'+tagName)
|
|
|
+ errorHandler.error("end tag name: "+tagName+' is not complete:'+config.tagName);
|
|
|
+ end = tagStart+1+tagName.length;
|
|
|
+ }else if(tagName.match(/\s</)){
|
|
|
+ tagName = tagName.replace(/[\s<].*/,'');
|
|
|
+ errorHandler.error("end tag name: "+tagName+' maybe not complete');
|
|
|
+ end = tagStart+1+tagName.length;
|
|
|
+ }
|
|
|
+ //console.error(parseStack.length,parseStack)
|
|
|
+ //console.error(config);
|
|
|
+ var localNSMap = config.localNSMap;
|
|
|
+ var endMatch = config.tagName == tagName;
|
|
|
+ var endIgnoreCaseMach = endMatch || config.tagName&&config.tagName.toLowerCase() == tagName.toLowerCase()
|
|
|
+ if(endIgnoreCaseMach){
|
|
|
+ domBuilder.endElement(config.uri,config.localName,tagName);
|
|
|
+ if(localNSMap){
|
|
|
+ for(var prefix in localNSMap){
|
|
|
+ domBuilder.endPrefixMapping(prefix) ;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(!endMatch){
|
|
|
+ errorHandler.fatalError("end tag name: "+tagName+' is not match the current start tagName:'+config.tagName );
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ parseStack.push(config)
|
|
|
+ }
|
|
|
+
|
|
|
+ end++;
|
|
|
+ break;
|
|
|
+ // end elment
|
|
|
+ case '?':// <?...?>
|
|
|
+ locator&&position(tagStart);
|
|
|
+ end = parseInstruction(source,tagStart,domBuilder);
|
|
|
+ break;
|
|
|
+ case '!':// <!doctype,<![CDATA,<!--
|
|
|
+ locator&&position(tagStart);
|
|
|
+ end = parseDCC(source,tagStart,domBuilder,errorHandler);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ locator&&position(tagStart);
|
|
|
+ var el = new ElementAttributes();
|
|
|
+ var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
|
|
|
+ //elStartEnd
|
|
|
+ var end = parseElementStartPart(source,tagStart,el,currentNSMap,entityReplacer,errorHandler);
|
|
|
+ var len = el.length;
|
|
|
+
|
|
|
+
|
|
|
+ if(!el.closed && fixSelfClosed(source,end,el.tagName,closeMap)){
|
|
|
+ el.closed = true;
|
|
|
+ if(!entityMap.nbsp){
|
|
|
+ errorHandler.warning('unclosed xml attribute');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(locator && len){
|
|
|
+ var locator2 = copyLocator(locator,{});
|
|
|
+ //try{//attribute position fixed
|
|
|
+ for(var i = 0;i<len;i++){
|
|
|
+ var a = el[i];
|
|
|
+ position(a.offset);
|
|
|
+ a.locator = copyLocator(locator,{});
|
|
|
+ }
|
|
|
+ //}catch(e){console.error('@@@@@'+e)}
|
|
|
+ domBuilder.locator = locator2
|
|
|
+ if(appendElement(el,domBuilder,currentNSMap)){
|
|
|
+ parseStack.push(el)
|
|
|
+ }
|
|
|
+ domBuilder.locator = locator;
|
|
|
+ }else{
|
|
|
+ if(appendElement(el,domBuilder,currentNSMap)){
|
|
|
+ parseStack.push(el)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if(el.uri === 'http://www.w3.org/1999/xhtml' && !el.closed){
|
|
|
+ end = parseHtmlSpecialContent(source,end,el.tagName,entityReplacer,domBuilder)
|
|
|
+ }else{
|
|
|
+ end++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }catch(e){
|
|
|
+ errorHandler.error('element parse error: '+e)
|
|
|
+ //errorHandler.error('element parse error: '+e);
|
|
|
+ end = -1;
|
|
|
+ //throw e;
|
|
|
+ }
|
|
|
+ if(end>start){
|
|
|
+ start = end;
|
|
|
+ }else{
|
|
|
+ //TODO: 这里有可能sax回退,有位置错误风险
|
|
|
+ appendText(Math.max(tagStart,start)+1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+function copyLocator(f,t){
|
|
|
+ t.lineNumber = f.lineNumber;
|
|
|
+ t.columnNumber = f.columnNumber;
|
|
|
+ return t;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
|
|
|
+ * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
|
|
|
+ */
|
|
|
+function parseElementStartPart(source,start,el,currentNSMap,entityReplacer,errorHandler){
|
|
|
+ var attrName;
|
|
|
+ var value;
|
|
|
+ var p = ++start;
|
|
|
+ var s = S_TAG;//status
|
|
|
+ while(true){
|
|
|
+ var c = source.charAt(p);
|
|
|
+ switch(c){
|
|
|
+ case '=':
|
|
|
+ if(s === S_ATTR){//attrName
|
|
|
+ attrName = source.slice(start,p);
|
|
|
+ s = S_EQ;
|
|
|
+ }else if(s === S_ATTR_SPACE){
|
|
|
+ s = S_EQ;
|
|
|
+ }else{
|
|
|
+ //fatalError: equal must after attrName or space after attrName
|
|
|
+ throw new Error('attribute equal must after attrName');
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case '\'':
|
|
|
+ case '"':
|
|
|
+ if(s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE
|
|
|
+ ){//equal
|
|
|
+ if(s === S_ATTR){
|
|
|
+ errorHandler.warning('attribute value must after "="')
|
|
|
+ attrName = source.slice(start,p)
|
|
|
+ }
|
|
|
+ start = p+1;
|
|
|
+ p = source.indexOf(c,start)
|
|
|
+ if(p>0){
|
|
|
+ value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer);
|
|
|
+ el.add(attrName,value,start-1);
|
|
|
+ s = S_ATTR_END;
|
|
|
+ }else{
|
|
|
+ //fatalError: no end quot match
|
|
|
+ throw new Error('attribute value no end \''+c+'\' match');
|
|
|
+ }
|
|
|
+ }else if(s == S_ATTR_NOQUOT_VALUE){
|
|
|
+ value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer);
|
|
|
+ //console.log(attrName,value,start,p)
|
|
|
+ el.add(attrName,value,start);
|
|
|
+ //console.dir(el)
|
|
|
+ errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!');
|
|
|
+ start = p+1;
|
|
|
+ s = S_ATTR_END
|
|
|
+ }else{
|
|
|
+ //fatalError: no equal before
|
|
|
+ throw new Error('attribute value must after "="');
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case '/':
|
|
|
+ switch(s){
|
|
|
+ case S_TAG:
|
|
|
+ el.setTagName(source.slice(start,p));
|
|
|
+ case S_ATTR_END:
|
|
|
+ case S_TAG_SPACE:
|
|
|
+ case S_TAG_CLOSE:
|
|
|
+ s =S_TAG_CLOSE;
|
|
|
+ el.closed = true;
|
|
|
+ case S_ATTR_NOQUOT_VALUE:
|
|
|
+ case S_ATTR:
|
|
|
+ case S_ATTR_SPACE:
|
|
|
+ break;
|
|
|
+ //case S_EQ:
|
|
|
+ default:
|
|
|
+ throw new Error("attribute invalid close char('/')")
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ''://end document
|
|
|
+ //throw new Error('unexpected end of input')
|
|
|
+ errorHandler.error('unexpected end of input');
|
|
|
+ if(s == S_TAG){
|
|
|
+ el.setTagName(source.slice(start,p));
|
|
|
+ }
|
|
|
+ return p;
|
|
|
+ case '>':
|
|
|
+ switch(s){
|
|
|
+ case S_TAG:
|
|
|
+ el.setTagName(source.slice(start,p));
|
|
|
+ case S_ATTR_END:
|
|
|
+ case S_TAG_SPACE:
|
|
|
+ case S_TAG_CLOSE:
|
|
|
+ break;//normal
|
|
|
+ case S_ATTR_NOQUOT_VALUE://Compatible state
|
|
|
+ case S_ATTR:
|
|
|
+ value = source.slice(start,p);
|
|
|
+ if(value.slice(-1) === '/'){
|
|
|
+ el.closed = true;
|
|
|
+ value = value.slice(0,-1)
|
|
|
+ }
|
|
|
+ case S_ATTR_SPACE:
|
|
|
+ if(s === S_ATTR_SPACE){
|
|
|
+ value = attrName;
|
|
|
+ }
|
|
|
+ if(s == S_ATTR_NOQUOT_VALUE){
|
|
|
+ errorHandler.warning('attribute "'+value+'" missed quot(")!!');
|
|
|
+ el.add(attrName,value.replace(/&#?\w+;/g,entityReplacer),start)
|
|
|
+ }else{
|
|
|
+ if(currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !value.match(/^(?:disabled|checked|selected)$/i)){
|
|
|
+ errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!')
|
|
|
+ }
|
|
|
+ el.add(value,value,start)
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case S_EQ:
|
|
|
+ throw new Error('attribute value missed!!');
|
|
|
+ }
|
|
|
+// console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
|
|
|
+ return p;
|
|
|
+ /*xml space '\x20' | #x9 | #xD | #xA; */
|
|
|
+ case '\u0080':
|
|
|
+ c = ' ';
|
|
|
+ default:
|
|
|
+ if(c<= ' '){//space
|
|
|
+ switch(s){
|
|
|
+ case S_TAG:
|
|
|
+ el.setTagName(source.slice(start,p));//tagName
|
|
|
+ s = S_TAG_SPACE;
|
|
|
+ break;
|
|
|
+ case S_ATTR:
|
|
|
+ attrName = source.slice(start,p)
|
|
|
+ s = S_ATTR_SPACE;
|
|
|
+ break;
|
|
|
+ case S_ATTR_NOQUOT_VALUE:
|
|
|
+ var value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer);
|
|
|
+ errorHandler.warning('attribute "'+value+'" missed quot(")!!');
|
|
|
+ el.add(attrName,value,start)
|
|
|
+ case S_ATTR_END:
|
|
|
+ s = S_TAG_SPACE;
|
|
|
+ break;
|
|
|
+ //case S_TAG_SPACE:
|
|
|
+ //case S_EQ:
|
|
|
+ //case S_ATTR_SPACE:
|
|
|
+ // void();break;
|
|
|
+ //case S_TAG_CLOSE:
|
|
|
+ //ignore warning
|
|
|
+ }
|
|
|
+ }else{//not space
|
|
|
+//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE
|
|
|
+//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE
|
|
|
+ switch(s){
|
|
|
+ //case S_TAG:void();break;
|
|
|
+ //case S_ATTR:void();break;
|
|
|
+ //case S_ATTR_NOQUOT_VALUE:void();break;
|
|
|
+ case S_ATTR_SPACE:
|
|
|
+ var tagName = el.tagName;
|
|
|
+ if(currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !attrName.match(/^(?:disabled|checked|selected)$/i)){
|
|
|
+ errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead2!!')
|
|
|
+ }
|
|
|
+ el.add(attrName,attrName,start);
|
|
|
+ start = p;
|
|
|
+ s = S_ATTR;
|
|
|
+ break;
|
|
|
+ case S_ATTR_END:
|
|
|
+ errorHandler.warning('attribute space is required"'+attrName+'"!!')
|
|
|
+ case S_TAG_SPACE:
|
|
|
+ s = S_ATTR;
|
|
|
+ start = p;
|
|
|
+ break;
|
|
|
+ case S_EQ:
|
|
|
+ s = S_ATTR_NOQUOT_VALUE;
|
|
|
+ start = p;
|
|
|
+ break;
|
|
|
+ case S_TAG_CLOSE:
|
|
|
+ throw new Error("elements closed character '/' and '>' must be connected to");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }//end outer switch
|
|
|
+ //console.log('p++',p)
|
|
|
+ p++;
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * @return true if has new namespace define
|
|
|
+ */
|
|
|
+function appendElement(el,domBuilder,currentNSMap){
|
|
|
+ var tagName = el.tagName;
|
|
|
+ var localNSMap = null;
|
|
|
+ //var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
|
|
|
+ var i = el.length;
|
|
|
+ while(i--){
|
|
|
+ var a = el[i];
|
|
|
+ var qName = a.qName;
|
|
|
+ var value = a.value;
|
|
|
+ var nsp = qName.indexOf(':');
|
|
|
+ if(nsp>0){
|
|
|
+ var prefix = a.prefix = qName.slice(0,nsp);
|
|
|
+ var localName = qName.slice(nsp+1);
|
|
|
+ var nsPrefix = prefix === 'xmlns' && localName
|
|
|
+ }else{
|
|
|
+ localName = qName;
|
|
|
+ prefix = null
|
|
|
+ nsPrefix = qName === 'xmlns' && ''
|
|
|
+ }
|
|
|
+ //can not set prefix,because prefix !== ''
|
|
|
+ a.localName = localName ;
|
|
|
+ //prefix == null for no ns prefix attribute
|
|
|
+ if(nsPrefix !== false){//hack!!
|
|
|
+ if(localNSMap == null){
|
|
|
+ localNSMap = {}
|
|
|
+ //console.log(currentNSMap,0)
|
|
|
+ _copy(currentNSMap,currentNSMap={})
|
|
|
+ //console.log(currentNSMap,1)
|
|
|
+ }
|
|
|
+ currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
|
|
|
+ a.uri = 'http://www.w3.org/2000/xmlns/'
|
|
|
+ domBuilder.startPrefixMapping(nsPrefix, value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var i = el.length;
|
|
|
+ while(i--){
|
|
|
+ a = el[i];
|
|
|
+ var prefix = a.prefix;
|
|
|
+ if(prefix){//no prefix attribute has no namespace
|
|
|
+ if(prefix === 'xml'){
|
|
|
+ a.uri = 'http://www.w3.org/XML/1998/namespace';
|
|
|
+ }if(prefix !== 'xmlns'){
|
|
|
+ a.uri = currentNSMap[prefix || '']
|
|
|
+
|
|
|
+ //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var nsp = tagName.indexOf(':');
|
|
|
+ if(nsp>0){
|
|
|
+ prefix = el.prefix = tagName.slice(0,nsp);
|
|
|
+ localName = el.localName = tagName.slice(nsp+1);
|
|
|
+ }else{
|
|
|
+ prefix = null;//important!!
|
|
|
+ localName = el.localName = tagName;
|
|
|
+ }
|
|
|
+ //no prefix element has default namespace
|
|
|
+ var ns = el.uri = currentNSMap[prefix || ''];
|
|
|
+ domBuilder.startElement(ns,localName,tagName,el);
|
|
|
+ //endPrefixMapping and startPrefixMapping have not any help for dom builder
|
|
|
+ //localNSMap = null
|
|
|
+ if(el.closed){
|
|
|
+ domBuilder.endElement(ns,localName,tagName);
|
|
|
+ if(localNSMap){
|
|
|
+ for(prefix in localNSMap){
|
|
|
+ domBuilder.endPrefixMapping(prefix)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ el.currentNSMap = currentNSMap;
|
|
|
+ el.localNSMap = localNSMap;
|
|
|
+ //parseStack.push(el);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+}
|
|
|
+function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){
|
|
|
+ if(/^(?:script|textarea)$/i.test(tagName)){
|
|
|
+ var elEndStart = source.indexOf('</'+tagName+'>',elStartEnd);
|
|
|
+ var text = source.substring(elStartEnd+1,elEndStart);
|
|
|
+ if(/[&<]/.test(text)){
|
|
|
+ if(/^script$/i.test(tagName)){
|
|
|
+ //if(!/\]\]>/.test(text)){
|
|
|
+ //lexHandler.startCDATA();
|
|
|
+ domBuilder.characters(text,0,text.length);
|
|
|
+ //lexHandler.endCDATA();
|
|
|
+ return elEndStart;
|
|
|
+ //}
|
|
|
+ }//}else{//text area
|
|
|
+ text = text.replace(/&#?\w+;/g,entityReplacer);
|
|
|
+ domBuilder.characters(text,0,text.length);
|
|
|
+ return elEndStart;
|
|
|
+ //}
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return elStartEnd+1;
|
|
|
+}
|
|
|
+function fixSelfClosed(source,elStartEnd,tagName,closeMap){
|
|
|
+ //if(tagName in closeMap){
|
|
|
+ var pos = closeMap[tagName];
|
|
|
+ if(pos == null){
|
|
|
+ //console.log(tagName)
|
|
|
+ pos = source.lastIndexOf('</'+tagName+'>')
|
|
|
+ if(pos<elStartEnd){//忘记闭合
|
|
|
+ pos = source.lastIndexOf('</'+tagName)
|
|
|
+ }
|
|
|
+ closeMap[tagName] =pos
|
|
|
+ }
|
|
|
+ return pos<elStartEnd;
|
|
|
+ //}
|
|
|
+}
|
|
|
+function _copy(source,target){
|
|
|
+ for(var n in source){target[n] = source[n]}
|
|
|
+}
|
|
|
+function parseDCC(source,start,domBuilder,errorHandler){//sure start with '<!'
|
|
|
+ var next= source.charAt(start+2)
|
|
|
+ switch(next){
|
|
|
+ case '-':
|
|
|
+ if(source.charAt(start + 3) === '-'){
|
|
|
+ var end = source.indexOf('-->',start+4);
|
|
|
+ //append comment source.substring(4,end)//<!--
|
|
|
+ if(end>start){
|
|
|
+ domBuilder.comment(source,start+4,end-start-4);
|
|
|
+ return end+3;
|
|
|
+ }else{
|
|
|
+ errorHandler.error("Unclosed comment");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ //error
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ if(source.substr(start+3,6) == 'CDATA['){
|
|
|
+ var end = source.indexOf(']]>',start+9);
|
|
|
+ domBuilder.startCDATA();
|
|
|
+ domBuilder.characters(source,start+9,end-start-9);
|
|
|
+ domBuilder.endCDATA()
|
|
|
+ return end+3;
|
|
|
+ }
|
|
|
+ //<!DOCTYPE
|
|
|
+ //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
|
|
|
+ var matchs = split(source,start);
|
|
|
+ var len = matchs.length;
|
|
|
+ if(len>1 && /!doctype/i.test(matchs[0][0])){
|
|
|
+ var name = matchs[1][0];
|
|
|
+ var pubid = len>3 && /^public$/i.test(matchs[2][0]) && matchs[3][0]
|
|
|
+ var sysid = len>4 && matchs[4][0];
|
|
|
+ var lastMatch = matchs[len-1]
|
|
|
+ domBuilder.startDTD(name,pubid && pubid.replace(/^(['"])(.*?)\1$/,'$2'),
|
|
|
+ sysid && sysid.replace(/^(['"])(.*?)\1$/,'$2'));
|
|
|
+ domBuilder.endDTD();
|
|
|
+
|
|
|
+ return lastMatch.index+lastMatch[0].length
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+function parseInstruction(source,start,domBuilder){
|
|
|
+ var end = source.indexOf('?>',start);
|
|
|
+ if(end){
|
|
|
+ var match = source.substring(start,end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);
|
|
|
+ if(match){
|
|
|
+ var len = match[0].length;
|
|
|
+ domBuilder.processingInstruction(match[1], match[2]) ;
|
|
|
+ return end+2;
|
|
|
+ }else{//error
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @param source
|
|
|
+ */
|
|
|
+function ElementAttributes(source){
|
|
|
+
|
|
|
+}
|
|
|
+ElementAttributes.prototype = {
|
|
|
+ setTagName:function(tagName){
|
|
|
+ if(!tagNamePattern.test(tagName)){
|
|
|
+ throw new Error('invalid tagName:'+tagName)
|
|
|
+ }
|
|
|
+ this.tagName = tagName
|
|
|
+ },
|
|
|
+ add:function(qName,value,offset){
|
|
|
+ if(!tagNamePattern.test(qName)){
|
|
|
+ throw new Error('invalid attribute:'+qName)
|
|
|
+ }
|
|
|
+ this[this.length++] = {qName:qName,value:value,offset:offset}
|
|
|
+ },
|
|
|
+ length:0,
|
|
|
+ getLocalName:function(i){return this[i].localName},
|
|
|
+ getLocator:function(i){return this[i].locator},
|
|
|
+ getQName:function(i){return this[i].qName},
|
|
|
+ getURI:function(i){return this[i].uri},
|
|
|
+ getValue:function(i){return this[i].value}
|
|
|
+// ,getIndex:function(uri, localName)){
|
|
|
+// if(localName){
|
|
|
+//
|
|
|
+// }else{
|
|
|
+// var qName = uri
|
|
|
+// }
|
|
|
+// },
|
|
|
+// getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
|
|
|
+// getType:function(uri,localName){}
|
|
|
+// getType:function(i){},
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+function split(source,start){
|
|
|
+ var match;
|
|
|
+ var buf = [];
|
|
|
+ var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
|
|
|
+ reg.lastIndex = start;
|
|
|
+ reg.exec(source);//skip <
|
|
|
+ while(match = reg.exec(source)){
|
|
|
+ buf.push(match);
|
|
|
+ if(match[1])return buf;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+exports.XMLReader = XMLReader;
|
|
|
+
|