sax.js 18 KB

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