text: define(function(require, exports, module) {
  
  //[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\\ux203F-\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_V
  //S_ATTR_S,	S_E,	S_S,	S_C
  var S_TAG = 0;//tag name offerring
  var S_ATTR = 1;//attr name offerring 
  var S_ATTR_S=2;//attr name end and space offer
  var S_EQ = 3;//=space?
  var S_V = 4;//attr value(no quot value only)
  var S_E = 5;//attr value end and no space(quot end)
  var S_S = 6;//(attr value end || tag end ) && (space offer)
  var S_C = 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
          var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer);
          locator&&position(start);
          domBuilder.characters(xt,0,end-start);
          start = end
      }
      function position(start,m){
          while(start>=endPos && (m = linePattern.exec(source))){
              startPos = m.index;
              endPos = startPos + m[0].length;
              locator.lineNumber++;
              //console.log('line++:',locator,startPos,endPos)
          }
          locator.columnNumber = start-startPos+1;
      }
      var startPos = 0;
      var endPos = 0;
      var linePattern = /.+(?:\r\n?|\n)|.*$/g
      var locator = domBuilder.locator;
      
      var parseStack = [{currentNSMap:defaultNSMapCopy}]
      var closeMap = {};
      var start = 0;
      while(true){
          var i = source.indexOf('<',start);
          if(i<0){
              if(!source.substr(start).match(/^\s*$/)){
                  var doc = domBuilder.document;
                  var text = doc.createTextNode(source.substr(start));
                  doc.appendChild(text);
                  domBuilder.currentElement = text;
              }
              return;
          }
          if(i>start){
              appendText(i);
          }
          switch(source.charAt(i+1)){
          case '/':
              var end = source.indexOf('>',i+3);
              var tagName = source.substring(i+2,end);
              var config;
              if (parseStack.length > 1) {
                  config = parseStack.pop();
              } else {
                  errorHandler.fatalError("end tag name not found for: "+tagName);
                  break;
              }
              var localNSMap = config.localNSMap;
              
              if(config.tagName != tagName){
                  errorHandler.fatalError("end tag name: " + tagName + " does not match the current start tagName: "+config.tagName );
              }
              domBuilder.endElement(config.uri,config.localName,tagName);
              if(localNSMap){
                  for(var prefix in localNSMap){
                      domBuilder.endPrefixMapping(prefix) ;
                  }
              }
              end++;
              break;
              // end elment
          case '?':// <?...?>
              locator&&position(i);
              end = parseInstruction(source,i,domBuilder);
              break;
          case '!':// <!doctype,<![CDATA,<!--
              locator&&position(i);
              end = parseDCC(source,i,domBuilder,errorHandler);
              break;
          default:
              try{
                  locator&&position(i);
                  
                  var el = new ElementAttributes();
                  
                  //elStartEnd
                  var end = parseElementStartPart(source,i,el,entityReplacer,errorHandler);
                  var len = el.length;
                  //position fixed
                  if(len && locator){
                      var backup = copyLocator(locator,{});
                      for(var i = 0;i<len;i++){
                          var a = el[i];
                          position(a.offset);
                          a.offset = copyLocator(locator,{});
                      }
                      copyLocator(backup,locator);
                  }
                  if(!el.closed && fixSelfClosed(source,end,el.tagName,closeMap)){
                      el.closed = true;
                      if(!entityMap.nbsp){
                          errorHandler.warning('unclosed xml attribute');
                      }
                  }
                  appendElement(el,domBuilder,parseStack);
                  
                  
                  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);
                  end = -1;
              }
  
          }
          if(end<0){
              //TODO: 这里有可能sax回退，有位置错误风险
              appendText(i+1);
          }else{
              start = end;
          }
      }
  }
  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,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_S){
                  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){//equal
                  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_E;
                  }else{
                      //fatalError: no end quot match
                      throw new Error('attribute value no end \''+c+'\' match');
                  }
              }else if(s == S_V){
                  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_E
              }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_E:
              case S_S:
              case S_C:
                  s = S_C;
                  el.closed = true;
              case S_V:
              case S_ATTR:
              case S_ATTR_S:
                  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');
          case '>':
              switch(s){
              case S_TAG:
                  el.setTagName(source.slice(start,p));
              case S_E:
              case S_S:
              case S_C:
                  break;//normal
              case S_V://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_S:
                  if(s === S_ATTR_S){
                      value = attrName;
                  }
                  if(s == S_V){
                      errorHandler.warning('attribute "'+value+'" missed quot(")!!');
                      el.add(attrName,value.replace(/&#?\w+;/g,entityReplacer),start)
                  }else{
                      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_S;
                      break;
                  case S_ATTR:
                      attrName = source.slice(start,p)
                      s = S_ATTR_S;
                      break;
                  case S_V:
                      var value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer);
                      errorHandler.warning('attribute "'+value+'" missed quot(")!!');
                      el.add(attrName,value,start)
                  case S_E:
                      s = S_S;
                      break;
                  //case S_S:
                  //case S_EQ:
                  //case S_ATTR_S:
                  //	void();break;
                  //case S_C:
                      //ignore warning
                  }
              }else{//not space
  //S_TAG,	S_ATTR,	S_EQ,	S_V
  //S_ATTR_S,	S_E,	S_S,	S_C
                  switch(s){
                  //case S_TAG:void();break;
                  //case S_ATTR:void();break;
                  //case S_V:void();break;
                  case S_ATTR_S:
                      errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead!!')
                      el.add(attrName,attrName,start);
                      start = p;
                      s = S_ATTR;
                      break;
                  case S_E:
                      errorHandler.warning('attribute space is required"'+attrName+'"!!')
                  case S_S:
                      s = S_ATTR;
                      start = p;
                      break;
                  case S_EQ:
                      s = S_V;
                      start = p;
                      break;
                  case S_C:
                      throw new Error("elements closed character '/' and '>' must be connected to");
                  }
              }
          }
          p++;
      }
  }
  /**
   * @return end of the elementStartPart(end of elementEndPart for selfClosed el)
   */
  function appendElement(el,domBuilder,parseStack){
      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);
      }
  }
  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 = closeMap[tagName] = source.lastIndexOf('</'+tagName+'>')
      }
      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},
      getOffset:function(i){return this[i].offset},
      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 _set_proto_(thiz,parent){
      thiz.__proto__ = parent;
      return thiz;
  }
  if(!(_set_proto_({},_set_proto_.prototype) instanceof _set_proto_)){
      _set_proto_ = function(thiz,parent){
          function p(){};
          p.prototype = parent;
          p = new p();
          for(parent in thiz){
              p[parent] = thiz[parent];
          }
          return p;
      }
  }
  
  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;
      }
  }
  
  return XMLReader;
  });
  
  
  