sax.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. define(function(require, exports, module) {
  2. //[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]
  3. //[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
  4. //[5] Name ::= NameStartChar (NameChar)*
  5. 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
  6. var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\u00B7\u0300-\u036F\\ux203F-\u2040]");
  7. var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$');
  8. //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/
  9. //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(',')
  10. //S_TAG, S_ATTR, S_EQ, S_V
  11. //S_ATTR_S, S_E, S_S, S_C
  12. var S_TAG = 0;//tag name offerring
  13. var S_ATTR = 1;//attr name offerring
  14. var S_ATTR_S=2;//attr name end and space offer
  15. var S_EQ = 3;//=space?
  16. var S_V = 4;//attr value(no quot value only)
  17. var S_E = 5;//attr value end and no space(quot end)
  18. var S_S = 6;//(attr value end || tag end ) && (space offer)
  19. var S_C = 7;//closed el<el />
  20. function XMLReader(){
  21. }
  22. XMLReader.prototype = {
  23. parse:function(source,defaultNSMap,entityMap){
  24. var domBuilder = this.domBuilder;
  25. domBuilder.startDocument();
  26. _copy(defaultNSMap ,defaultNSMap = {})
  27. parse(source,defaultNSMap,entityMap,
  28. domBuilder,this.errorHandler);
  29. domBuilder.endDocument();
  30. }
  31. }
  32. function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){
  33. function fixedFromCharCode(code) {
  34. // String.prototype.fromCharCode does not supports
  35. // > 2 bytes unicode chars directly
  36. if (code > 0xffff) {
  37. code -= 0x10000;
  38. var surrogate1 = 0xd800 + (code >> 10)
  39. , surrogate2 = 0xdc00 + (code & 0x3ff);
  40. return String.fromCharCode(surrogate1, surrogate2);
  41. } else {
  42. return String.fromCharCode(code);
  43. }
  44. }
  45. function entityReplacer(a){
  46. var k = a.slice(1,-1);
  47. if(k in entityMap){
  48. return entityMap[k];
  49. }else if(k.charAt(0) === '#'){
  50. return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x')))
  51. }else{
  52. errorHandler.error('entity not found:'+a);
  53. return a;
  54. }
  55. }
  56. function appendText(end){//has some bugs
  57. var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer);
  58. locator&&position(start);
  59. domBuilder.characters(xt,0,end-start);
  60. start = end
  61. }
  62. function position(start,m){
  63. while(start>=endPos && (m = linePattern.exec(source))){
  64. startPos = m.index;
  65. endPos = startPos + m[0].length;
  66. locator.lineNumber++;
  67. //console.log('line++:',locator,startPos,endPos)
  68. }
  69. locator.columnNumber = start-startPos+1;
  70. }
  71. var startPos = 0;
  72. var endPos = 0;
  73. var linePattern = /.+(?:\r\n?|\n)|.*$/g
  74. var locator = domBuilder.locator;
  75. var parseStack = [{currentNSMap:defaultNSMapCopy}]
  76. var closeMap = {};
  77. var start = 0;
  78. while(true){
  79. var i = source.indexOf('<',start);
  80. if(i<0){
  81. if(!source.substr(start).match(/^\s*$/)){
  82. var doc = domBuilder.document;
  83. var text = doc.createTextNode(source.substr(start));
  84. doc.appendChild(text);
  85. domBuilder.currentElement = text;
  86. }
  87. return;
  88. }
  89. if(i>start){
  90. appendText(i);
  91. }
  92. switch(source.charAt(i+1)){
  93. case '/':
  94. var end = source.indexOf('>',i+3);
  95. var tagName = source.substring(i+2,end);
  96. var config;
  97. if (parseStack.length > 1) {
  98. config = parseStack.pop();
  99. } else {
  100. errorHandler.fatalError("end tag name not found for: "+tagName);
  101. break;
  102. }
  103. var localNSMap = config.localNSMap;
  104. if(config.tagName != tagName){
  105. errorHandler.fatalError("end tag name: " + tagName + " does not match the current start tagName: "+config.tagName );
  106. }
  107. domBuilder.endElement(config.uri,config.localName,tagName);
  108. if(localNSMap){
  109. for(var prefix in localNSMap){
  110. domBuilder.endPrefixMapping(prefix) ;
  111. }
  112. }
  113. end++;
  114. break;
  115. // end elment
  116. case '?':// <?...?>
  117. locator&&position(i);
  118. end = parseInstruction(source,i,domBuilder);
  119. break;
  120. case '!':// <!doctype,<![CDATA,<!--
  121. locator&&position(i);
  122. end = parseDCC(source,i,domBuilder,errorHandler);
  123. break;
  124. default:
  125. try{
  126. locator&&position(i);
  127. var el = new ElementAttributes();
  128. //elStartEnd
  129. var end = parseElementStartPart(source,i,el,entityReplacer,errorHandler);
  130. var len = el.length;
  131. //position fixed
  132. if(len && locator){
  133. var backup = copyLocator(locator,{});
  134. for(var i = 0;i<len;i++){
  135. var a = el[i];
  136. position(a.offset);
  137. a.offset = copyLocator(locator,{});
  138. }
  139. copyLocator(backup,locator);
  140. }
  141. if(!el.closed && fixSelfClosed(source,end,el.tagName,closeMap)){
  142. el.closed = true;
  143. if(!entityMap.nbsp){
  144. errorHandler.warning('unclosed xml attribute');
  145. }
  146. }
  147. appendElement(el,domBuilder,parseStack);
  148. if(el.uri === 'http://www.w3.org/1999/xhtml' && !el.closed){
  149. end = parseHtmlSpecialContent(source,end,el.tagName,entityReplacer,domBuilder)
  150. }else{
  151. end++;
  152. }
  153. }catch(e){
  154. errorHandler.error('element parse error: '+e);
  155. end = -1;
  156. }
  157. }
  158. if(end<0){
  159. //TODO: 这里有可能sax回退,有位置错误风险
  160. appendText(i+1);
  161. }else{
  162. start = end;
  163. }
  164. }
  165. }
  166. function copyLocator(f,t){
  167. t.lineNumber = f.lineNumber;
  168. t.columnNumber = f.columnNumber;
  169. return t;
  170. }
  171. /**
  172. * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack);
  173. * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
  174. */
  175. function parseElementStartPart(source,start,el,entityReplacer,errorHandler){
  176. var attrName;
  177. var value;
  178. var p = ++start;
  179. var s = S_TAG;//status
  180. while(true){
  181. var c = source.charAt(p);
  182. switch(c){
  183. case '=':
  184. if(s === S_ATTR){//attrName
  185. attrName = source.slice(start,p);
  186. s = S_EQ;
  187. }else if(s === S_ATTR_S){
  188. s = S_EQ;
  189. }else{
  190. //fatalError: equal must after attrName or space after attrName
  191. throw new Error('attribute equal must after attrName');
  192. }
  193. break;
  194. case '\'':
  195. case '"':
  196. if(s === S_EQ){//equal
  197. start = p+1;
  198. p = source.indexOf(c,start)
  199. if(p>0){
  200. value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer);
  201. el.add(attrName,value,start-1);
  202. s = S_E;
  203. }else{
  204. //fatalError: no end quot match
  205. throw new Error('attribute value no end \''+c+'\' match');
  206. }
  207. }else if(s == S_V){
  208. value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer);
  209. //console.log(attrName,value,start,p)
  210. el.add(attrName,value,start);
  211. //console.dir(el)
  212. errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!');
  213. start = p+1;
  214. s = S_E
  215. }else{
  216. //fatalError: no equal before
  217. throw new Error('attribute value must after "="');
  218. }
  219. break;
  220. case '/':
  221. switch(s){
  222. case S_TAG:
  223. el.setTagName(source.slice(start,p));
  224. case S_E:
  225. case S_S:
  226. case S_C:
  227. s = S_C;
  228. el.closed = true;
  229. case S_V:
  230. case S_ATTR:
  231. case S_ATTR_S:
  232. break;
  233. //case S_EQ:
  234. default:
  235. throw new Error("attribute invalid close char('/')")
  236. }
  237. break;
  238. case ''://end document
  239. //throw new Error('unexpected end of input')
  240. errorHandler.error('unexpected end of input');
  241. case '>':
  242. switch(s){
  243. case S_TAG:
  244. el.setTagName(source.slice(start,p));
  245. case S_E:
  246. case S_S:
  247. case S_C:
  248. break;//normal
  249. case S_V://Compatible state
  250. case S_ATTR:
  251. value = source.slice(start,p);
  252. if(value.slice(-1) === '/'){
  253. el.closed = true;
  254. value = value.slice(0,-1)
  255. }
  256. case S_ATTR_S:
  257. if(s === S_ATTR_S){
  258. value = attrName;
  259. }
  260. if(s == S_V){
  261. errorHandler.warning('attribute "'+value+'" missed quot(")!!');
  262. el.add(attrName,value.replace(/&#?\w+;/g,entityReplacer),start)
  263. }else{
  264. errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!')
  265. el.add(value,value,start)
  266. }
  267. break;
  268. case S_EQ:
  269. throw new Error('attribute value missed!!');
  270. }
  271. // console.log(tagName,tagNamePattern,tagNamePattern.test(tagName))
  272. return p;
  273. /*xml space '\x20' | #x9 | #xD | #xA; */
  274. case '\u0080':
  275. c = ' ';
  276. default:
  277. if(c<= ' '){//space
  278. switch(s){
  279. case S_TAG:
  280. el.setTagName(source.slice(start,p));//tagName
  281. s = S_S;
  282. break;
  283. case S_ATTR:
  284. attrName = source.slice(start,p)
  285. s = S_ATTR_S;
  286. break;
  287. case S_V:
  288. var value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer);
  289. errorHandler.warning('attribute "'+value+'" missed quot(")!!');
  290. el.add(attrName,value,start)
  291. case S_E:
  292. s = S_S;
  293. break;
  294. //case S_S:
  295. //case S_EQ:
  296. //case S_ATTR_S:
  297. // void();break;
  298. //case S_C:
  299. //ignore warning
  300. }
  301. }else{//not space
  302. //S_TAG, S_ATTR, S_EQ, S_V
  303. //S_ATTR_S, S_E, S_S, S_C
  304. switch(s){
  305. //case S_TAG:void();break;
  306. //case S_ATTR:void();break;
  307. //case S_V:void();break;
  308. case S_ATTR_S:
  309. errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead!!')
  310. el.add(attrName,attrName,start);
  311. start = p;
  312. s = S_ATTR;
  313. break;
  314. case S_E:
  315. errorHandler.warning('attribute space is required"'+attrName+'"!!')
  316. case S_S:
  317. s = S_ATTR;
  318. start = p;
  319. break;
  320. case S_EQ:
  321. s = S_V;
  322. start = p;
  323. break;
  324. case S_C:
  325. throw new Error("elements closed character '/' and '>' must be connected to");
  326. }
  327. }
  328. }
  329. p++;
  330. }
  331. }
  332. /**
  333. * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
  334. */
  335. function appendElement(el,domBuilder,parseStack){
  336. var tagName = el.tagName;
  337. var localNSMap = null;
  338. var currentNSMap = parseStack[parseStack.length-1].currentNSMap;
  339. var i = el.length;
  340. while(i--){
  341. var a = el[i];
  342. var qName = a.qName;
  343. var value = a.value;
  344. var nsp = qName.indexOf(':');
  345. if(nsp>0){
  346. var prefix = a.prefix = qName.slice(0,nsp);
  347. var localName = qName.slice(nsp+1);
  348. var nsPrefix = prefix === 'xmlns' && localName
  349. }else{
  350. localName = qName;
  351. prefix = null
  352. nsPrefix = qName === 'xmlns' && ''
  353. }
  354. //can not set prefix,because prefix !== ''
  355. a.localName = localName ;
  356. //prefix == null for no ns prefix attribute
  357. if(nsPrefix !== false){//hack!!
  358. if(localNSMap == null){
  359. localNSMap = {}
  360. //console.log(currentNSMap,0)
  361. _copy(currentNSMap,currentNSMap={})
  362. //console.log(currentNSMap,1)
  363. }
  364. currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value;
  365. a.uri = 'http://www.w3.org/2000/xmlns/'
  366. domBuilder.startPrefixMapping(nsPrefix, value)
  367. }
  368. }
  369. var i = el.length;
  370. while(i--){
  371. a = el[i];
  372. var prefix = a.prefix;
  373. if(prefix){//no prefix attribute has no namespace
  374. if(prefix === 'xml'){
  375. a.uri = 'http://www.w3.org/XML/1998/namespace';
  376. }if(prefix !== 'xmlns'){
  377. a.uri = currentNSMap[prefix]
  378. //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)}
  379. }
  380. }
  381. }
  382. var nsp = tagName.indexOf(':');
  383. if(nsp>0){
  384. prefix = el.prefix = tagName.slice(0,nsp);
  385. localName = el.localName = tagName.slice(nsp+1);
  386. }else{
  387. prefix = null;//important!!
  388. localName = el.localName = tagName;
  389. }
  390. //no prefix element has default namespace
  391. var ns = el.uri = currentNSMap[prefix || ''];
  392. domBuilder.startElement(ns,localName,tagName,el);
  393. //endPrefixMapping and startPrefixMapping have not any help for dom builder
  394. //localNSMap = null
  395. if(el.closed){
  396. domBuilder.endElement(ns,localName,tagName);
  397. if(localNSMap){
  398. for(prefix in localNSMap){
  399. domBuilder.endPrefixMapping(prefix)
  400. }
  401. }
  402. }else{
  403. el.currentNSMap = currentNSMap;
  404. el.localNSMap = localNSMap;
  405. parseStack.push(el);
  406. }
  407. }
  408. function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){
  409. if(/^(?:script|textarea)$/i.test(tagName)){
  410. var elEndStart = source.indexOf('</'+tagName+'>',elStartEnd);
  411. var text = source.substring(elStartEnd+1,elEndStart);
  412. if(/[&<]/.test(text)){
  413. if(/^script$/i.test(tagName)){
  414. //if(!/\]\]>/.test(text)){
  415. //lexHandler.startCDATA();
  416. domBuilder.characters(text,0,text.length);
  417. //lexHandler.endCDATA();
  418. return elEndStart;
  419. //}
  420. }//}else{//text area
  421. text = text.replace(/&#?\w+;/g,entityReplacer);
  422. domBuilder.characters(text,0,text.length);
  423. return elEndStart;
  424. //}
  425. }
  426. }
  427. return elStartEnd+1;
  428. }
  429. function fixSelfClosed(source,elStartEnd,tagName,closeMap){
  430. //if(tagName in closeMap){
  431. var pos = closeMap[tagName];
  432. if(pos == null){
  433. //console.log(tagName)
  434. pos = closeMap[tagName] = source.lastIndexOf('</'+tagName+'>')
  435. }
  436. return pos<elStartEnd;
  437. //}
  438. }
  439. function _copy(source,target){
  440. for(var n in source){target[n] = source[n]}
  441. }
  442. function parseDCC(source,start,domBuilder,errorHandler){//sure start with '<!'
  443. var next= source.charAt(start+2)
  444. switch(next){
  445. case '-':
  446. if(source.charAt(start + 3) === '-'){
  447. var end = source.indexOf('-->',start+4);
  448. //append comment source.substring(4,end)//<!--
  449. if(end>start){
  450. domBuilder.comment(source,start+4,end-start-4);
  451. return end+3;
  452. }else{
  453. errorHandler.error("Unclosed comment");
  454. return -1;
  455. }
  456. }else{
  457. //error
  458. return -1;
  459. }
  460. default:
  461. if(source.substr(start+3,6) == 'CDATA['){
  462. var end = source.indexOf(']]>',start+9);
  463. domBuilder.startCDATA();
  464. domBuilder.characters(source,start+9,end-start-9);
  465. domBuilder.endCDATA()
  466. return end+3;
  467. }
  468. //<!DOCTYPE
  469. //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)
  470. var matchs = split(source,start);
  471. var len = matchs.length;
  472. if(len>1 && /!doctype/i.test(matchs[0][0])){
  473. var name = matchs[1][0];
  474. var pubid = len>3 && /^public$/i.test(matchs[2][0]) && matchs[3][0]
  475. var sysid = len>4 && matchs[4][0];
  476. var lastMatch = matchs[len-1]
  477. domBuilder.startDTD(name,pubid && pubid.replace(/^(['"])(.*?)\1$/,'$2'),
  478. sysid && sysid.replace(/^(['"])(.*?)\1$/,'$2'));
  479. domBuilder.endDTD();
  480. return lastMatch.index+lastMatch[0].length
  481. }
  482. }
  483. return -1;
  484. }
  485. function parseInstruction(source,start,domBuilder){
  486. var end = source.indexOf('?>',start);
  487. if(end){
  488. var match = source.substring(start,end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/);
  489. if(match){
  490. var len = match[0].length;
  491. domBuilder.processingInstruction(match[1], match[2]) ;
  492. return end+2;
  493. }else{//error
  494. return -1;
  495. }
  496. }
  497. return -1;
  498. }
  499. /**
  500. * @param source
  501. */
  502. function ElementAttributes(source){
  503. }
  504. ElementAttributes.prototype = {
  505. setTagName:function(tagName){
  506. if(!tagNamePattern.test(tagName)){
  507. throw new Error('invalid tagName:'+tagName)
  508. }
  509. this.tagName = tagName
  510. },
  511. add:function(qName,value,offset){
  512. if(!tagNamePattern.test(qName)){
  513. throw new Error('invalid attribute:'+qName)
  514. }
  515. this[this.length++] = {qName:qName,value:value,offset:offset}
  516. },
  517. length:0,
  518. getLocalName:function(i){return this[i].localName},
  519. getOffset:function(i){return this[i].offset},
  520. getQName:function(i){return this[i].qName},
  521. getURI:function(i){return this[i].uri},
  522. getValue:function(i){return this[i].value}
  523. // ,getIndex:function(uri, localName)){
  524. // if(localName){
  525. //
  526. // }else{
  527. // var qName = uri
  528. // }
  529. // },
  530. // getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))},
  531. // getType:function(uri,localName){}
  532. // getType:function(i){},
  533. }
  534. function _set_proto_(thiz,parent){
  535. thiz.__proto__ = parent;
  536. return thiz;
  537. }
  538. if(!(_set_proto_({},_set_proto_.prototype) instanceof _set_proto_)){
  539. _set_proto_ = function(thiz,parent){
  540. function p(){};
  541. p.prototype = parent;
  542. p = new p();
  543. for(parent in thiz){
  544. p[parent] = thiz[parent];
  545. }
  546. return p;
  547. }
  548. }
  549. function split(source,start){
  550. var match;
  551. var buf = [];
  552. var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g;
  553. reg.lastIndex = start;
  554. reg.exec(source);//skip <
  555. while(match = reg.exec(source)){
  556. buf.push(match);
  557. if(match[1])return buf;
  558. }
  559. }
  560. return XMLReader;
  561. });