//xml 简单xml解析 import string.encoding; namespace string; var push = ..table.push; var pop = ..table.pop; var insert = ..table.insert; var sub = sub; var trim = trim; var find = find; var match = match; var format = format; var lower = lower; var cmp = cmp; var createElementMeta; var ncrDecode = function(html){ html = ..string.replace(html,"&#x(\x+);",function(c){ return ..string.unescape( ..string.format("\U%06x",tonumber(c,16) ) ); } ); html = ..string.replace(html,"&#(\d+);",function(c){ return ..string.unescape( ..string.format("\U%06x",tonumber(c,10) ) ); } ); return ( ..string.replace(html,"&[a-zA-Z]+;",{ [" "] = ' ';["<"] = '<';[">"] = '>'; ["&"] = '&';["""] = '"';["'"] = "'";["“"] = '“';["”"] = '”';["”"] = '”'; ["—"] = '—';["€"] = "€";["¢"] = '¢';["£"] = '£';["¥"] = '¥'; ["⁄"] = "/";["»"] = "▼";["×"] = "×";["&division;"] = "÷"; ["∑"] = "∑";["¤cy;"] = "¤"; ["¦"] = "|";["§"] = "§";["¨"] = "¨";["¯"] = "ˉ";["°"] = "°";["±"] = "±";["´"] = "′"; ["µ"] = "μ";["·"] = "·";["º"] = "o";["©"] = "©";["√"] = '√';["↑"] = "↑";["↓"] = "↓";["←"] = "←";["→"] = "→"; ["↔"] = "↔";["®"] = "®";["#"] = "#"; } ) ); } var nodePrototype = { enumNodes = function(p,findTagName){ for(index,tagName,childCount,xNode in owner.eachChild() ){ if(findTagName && 0!=..string.cmp(findTagName,tagName)) continue; var ret,ret2 = p(owner,index,tagName,childCount,xNode) if( ret !== null ) return ret,ret2; } for(index,tagName,childCount,xNode in owner.eachChild() ){ var ret,ret2 = xNode.enumNodes(p,findTagName); if( ret !== null ) return ret,ret2; } }; eachQuery = function(opt){ var eles = owner.queryEles(opt); var i,v; return function(){ i,v = ..table.next(eles,i); return v; } }; queryEles = function(opt){ var cmpMatch = ..string.cmpMatch; var innerText = opt.innerText opt.innerText = null; var attr; var result = {} var ok,ele = owner.enumNodes( function(parentElement,index,tagName,childCount,xNode){ for(k,v in opt){ attr = xNode[k] if( ( (k == "tagName") || (k == "id") || (k == "name") ) && ( !..string.match(v,"\p")) ){ if( ( attr!=v ) && ( 0 != cmp( attr,v ) ) ) return; } elseif( ( attr!=v ) && ( ! cmpMatch( attr,v ) ) ){ return; } } if( innerText ){ var t = xNode.innerText(); if( ( t!=innerText ) && ( ! cmpMatch( t,innerText ) ) ) return; } push( result,xNode ) } ); return result; }; queryEle = function(opt){ return owner.queryEles(opt)[1]; }; getEle = function(v){ return owner.queryEles(id=v)[1]; }; getEles = function(v){ return owner.queryEles(name=v); }; getAttribute = function(a){ if( owner[a] ) return owner[a]; for(k,v in owner){ if( ( cmp(k,a)==0) && ( type(v) == "string") ) return v; } }; eachAttribute = function(){ var root = owner; var keys = ..table.map(owner,lambda(v,k)(type(v)=="string" &&( k!="tagName")&&( k!="cdata")&& (k!="text"))? v : null) var k,v; return function(ind){ k,v = ..table.next(keys,k); return k,v; } }; eachChild = function(tagName){ var root = owner; if( tagName ) root = owner[tagName]; var len = #root; return function(ind){ ind++; if(ind<=len){ var node = root[ind]; return ind,node.tagName,#node,node; } }, ,0 }; innerText = function(){ var children = {}; for(i=1;#owner;1){ if( !owner[i].tagName && owner[i].text ) push(children,owner[i].text ); elseif( !owner[i].tagName && owner[i].cdata ) push(children,owner[i].cdata ); else push(children,owner[i].innerText() ) } var s = ..string.join( children,""); return ncrDecode(s); }; innerXml = function(indent,line='\r\n'){ var children = {}; var html; var m; if( indent ) { if( type(indent) != "string" ) indent = ""; } else { indent = null; } for(i=1;#owner;1){ html = owner[i].outerXml(indent,line); if(indent) html = indent ++ html; push( children,html ); } var s = ..string.join( children,line); return s; }; outerXml = function(indent,line='\r\n'){ if( indent ) { if( type(indent) != "string" ) indent = ""; } else { indent = null; } var prop = {""}; var tv; if( ( !owner.tagName ) ){ if( owner.text ){ var xml = owner.text; return xml; } if( owner.cdata ) return ''; } elseif( cmp(owner.tagName,"!DOCTYPE") == 0 ){ return format('<%s%s>', owner.tagName, owner.text ) } for k,v in ..table.eachName(owner,,{"version";"encoding"}){ if( k =="tagName" ) continue; var tv = type(v); if( tv != "string" && tv!="number" && tv !="boolean" ) continue ; v = tostring(v); v = replace(v,'@<',"<"); v = replace(v,'@>',">"); v = replace(v,'@"',"""); v = replace(v,"@'","'"); push(prop, k + '="' + v + '"' ); } prop = #prop > 1 ? ..string.join( prop," ") : "" ; if( ! owner.tagName ){ return owner.innerXml(indent,line); } var xmlRootNode = owner@.xmlRootNode; var xmlElementType = xmlRootNode ? xmlRootNode@.xmlElementType; var innerXml = owner.innerXml(indent ? indent + " ",line); if( owner.tagName[1] == '?'# ){ return format('<%s%s?>', owner.tagName,prop ) } elseif(#innerXml){ if( #innerXml > 255 || find(innerXml,"[\r\n]") ){ return format('<%s%s>%s%s%s%s',owner.tagName , prop,line,innerXml,line,indent:"",owner.tagName ) } else { return format('<%s%s>%s',owner.tagName , prop,..string.trim(innerXml),owner.tagName ) } } elseif( xmlElementType ? ( (1/*void*/!=xmlElementType[owner.tagName] ) ) ){ return format('<%s%s>', owner.tagName , prop,owner.tagName ) } else { return format('<%s%s/>', owner.tagName, prop ) } } getParent = function(){ return owner@.xmlParentNode; }; remove = function(){ var parent = owner@.xmlParentNode; if(!parent) return; if(owner.tagName && ( owner.tagName != "tagName") ){ var t = type(parent[owner.tagName]); if( t == type.table ){ for(i=1;#t;1){ if( t[i] == owner ) { ..table.remove(t,i) break; } } }; } for(i=1;#parent;1){ if( parent[i] == owner ) { ..table.remove(parent,i) break; } } return true; }; pushXml = function(html,pos){ var r; var nodes = ..string.xml(html,owner@.xmlElementType,owner@.optionalTags); for(i=1;#nodes;1){r = owner.pushElement(nodes[i],pos);} return r; }; pushElement = function(ele,pos){ var method = pos === null? push : insert; if(ele.tagName && ( ele.tagName != "tagName") ){ var t = type(owner[ele.tagName]); if( t == type.null ){ owner[ele.tagName] = {}; method( owner[ele.tagName],ele,pos ); } elseif( t == type.table ){ method( owner[ele.tagName],ele,pos ); }; } method( owner,ele,pos ); if(!ele@)ele@ = createElementMeta(owner,owner@.xmlRootNode); else { ele@.xmlParentNode = owner; ele@.xmlRootNode = owner@.xmlRootNode; } return ele; } } createElementMeta = function(parent,root){ return { xmlParentNode = parent; xmlRootNode = ( parent ? parent.xmlRootNode ) : root ; _get = nodePrototype; _tostring = nodePrototype.outerXml; _readonly = false; _type = "object"; } } var function parseProperties(ele,str) { if(!#str) return; replace( str + " ", "([\w_\-\::]+)\s*(<=\s*""[^""]+"">|<=\s*'[^']+'>|<=\s*\S+>|\s)", function (w, a, b){ if( w = "tagName" ) return; a = ..string.trimleft(a,"="); a = ..string.trimleft(a); if( a[1] == '"'# ) a = ..string.trim(a,'"'); elseif( a[1] == '\''# ) a = ..string.trim(a,'\''); ele[w] = a } ) } class xml{ ctor( xmlstr,xmlElementType,optionalTags ){ { if(!xmlstr) error("XML文本参数不能为空",2); this.encoding = ..string.match(xmlstr,"^\s*\<\?xml[^\>]+?encoding\s*=\s*([^\s\?\>]+)"); if(this.encoding){ this.encoding = ..string.trim(this.encoding,`'"`); this.codepage = ..string.encoding.getCodepage(this.encoding); if( this.codepage && this.codepage != 65001 ) xmlstr = ..string.fromto(xmlstr,this.codepage,65001); } this@ = createElementMeta(); this@.xmlRootNode = this; this@.xmlElementType = xmlElementType; this@.optionalTags = optionalTags; var nodeStack = {}; var topNode = this; push(nodeStack, topNode); var strfrom = 1; var tagfrom,tagto,slash_front ,tagName ,properties,slash_back = find(xmlstr, elementPattern, strfrom); while(tagfrom) { var innerText = sub(xmlstr, strfrom, tagfrom-1) var innerText = trim(innerText); if( #innerText ){ if(#topNode && topNode[#topNode].text && (!topNode[#topNode].tagName) ){ topNode[#topNode].text = topNode[#topNode].text + innerText; } else{ push(topNode, { text = innerText;@(createElementMeta(topNode)) } ); }; } if( tagfrom && xmlstr[tagfrom+1]=='!'# ) { if( sub(xmlstr,tagfrom,tagfrom+8 ) == "@>", tagfrom+9) if(cdata){ topNode.pushElement({cdata=cdata}) strfrom = tagfrom + 12 + #cdata tagfrom,tagto,slash_front ,tagName,properties, slash_back = find(xmlstr, elementPattern , strfrom); continue; } } else{ var tagfrom2,tagto2 = find(xmlstr,"^\<\!\-\-.*?\-\-\>" , tagfrom); if( tagto2 ){ strfrom = tagto2+1 tagfrom,tagto,slash_front ,tagName,properties, slash_back = find(xmlstr, elementPattern , strfrom ); continue; } elseif( cmp( tagName,"!DOCTYPE") == 0 ){ var tagfrom2,tagto2,text2 = find(xmlstr, "\<\!<@@DOCTYPE@>\s*(\P+%\[\])\>", strfrom); if(tagfrom != tagfrom2 ){ tagfrom2,tagto2,text2 = find(xmlstr, `\<\!<@@DOCTYPE@>\s*(\P+%"")\>`, strfrom); if(tagfrom != tagfrom2 ){ tagfrom2,tagto2,text2 = find(xmlstr, `\<\!<@@DOCTYPE@>\s*(\P+%""\P*%"")\>`, strfrom); if(tagfrom != tagfrom2 ){ tagfrom2 = tagfrom; tagto2 = tagto; text2 = ..string.slice(xmlstr,tagfrom + #"!DOCTYPE ",tagto-1); } } } var tab = { tagName = tagName;text = text2 } topNode.pushElement(tab); strfrom = tagto2+1 tagfrom,tagto,slash_front ,tagName,properties, slash_back = find(xmlstr, elementPattern , strfrom ); continue; } else { var tab = { tagName=tagName }; parseProperties( tab,properties); topNode.pushElement(tab) strfrom = tagto+1 tagfrom,tagto,slash_front ,tagName,properties, slash_back = find(xmlstr, elementPattern , strfrom ); continue; } } if(!tagfrom) { break; } } var elementType; if(xmlElementType && tagName){ elementType = xmlElementType[lower(tagName)]; if( elementType == 1/*void*/ ){ if( slash_front == "/"){ strfrom = tagto+1; tagfrom,tagto,slash_front,tagName,properties, slash_back = find(xmlstr, elementPattern , strfrom); continue; } slash_back = "/"; } } if( #slash_back ){ // if(optionalTags){ var tags = optionalTags[lower(tagName)]; if(tags){ if(#nodeStack>1){ var lastNode = nodeStack[#nodeStack]; if(lastNode && lastNode.tagName && tags[lower(lastNode.tagName)]){ var ele = pop(nodeStack); topNode = nodeStack[#nodeStack]; topNode.pushElement( ele ); } } } } var tab = { tagName=tagName; } parseProperties( tab,properties); topNode.pushElement( tab ) } elseif slash_front != "/" { if(optionalTags){ var tags = optionalTags[lower(tagName)]; if(tags){ if(#nodeStack>1){ var lastNode = nodeStack[#nodeStack]; if(lastNode && lastNode.tagName && tags[lower(lastNode.tagName)]){ var ele = pop(nodeStack); topNode = nodeStack[#nodeStack]; topNode.pushElement( ele ); } } } } // topNode = { tagName=tagName;@(createElementMeta(,this)) } parseProperties( topNode,properties); push(nodeStack, topNode); } else { // var lastNode = nodeStack[#nodeStack]; if( lastNode && lastNode.tagName != tagName ){ var matchIdx; for(i=#nodeStack;2;-1){ if( nodeStack[i].tagName == tagName ){ matchIdx = i; } } if( !matchIdx && ( cmp(nodeStack[#nodeStack].tagName,tagName) == 0 ) ){ var ele = pop(nodeStack) topNode = nodeStack[#nodeStack] topNode.pushElement( ele ) } else { if(!matchIdx){ for(i=#nodeStack;2;-1){ if( cmp( nodeStack[i].tagName,tagName) == 0 ){ matchIdx = i; } } } if( !matchIdx ){ var tab = { tagName = tagName } topNode.pushElement( tab ) } else{ for(i=#nodeStack;matchIdx+1;-1){ var ele = pop(nodeStack) topNode = nodeStack[#nodeStack] topNode.pushElement( ele ) } var ele = pop(nodeStack) topNode = nodeStack[#nodeStack] if(!topNode) break; topNode.pushElement( ele ) } } } else { var ele = pop(nodeStack) topNode = nodeStack[#nodeStack] topNode.pushElement( ele ) } if #nodeStack < 1 { break; } } strfrom = tagto+1 if( elementType == 2/*raw*/ && (slash_front!= "/") && (slash_back!= "/") ){ slash_back = null; properties = null; tagfrom,tagto,tagName = find(xmlstr, "\<(<@![CDATA[@>|<\/[\?!\w_\-\::]+>)(.*?)\>" , strfrom); if( tagName == "![CDATA[" ) continue; if(tagName){ slash_front = "/"; tagName = ..string.right(tagName,-2); } continue; } tagfrom,tagto,slash_front,tagName,properties,slash_back = find(xmlstr, elementPattern , strfrom); } var innerText = sub(xmlstr, strfrom) var innerText = trim(innerText); if( #innerText ){ push(nodeStack[#nodeStack], { text = innerText ;@(createElementMeta(nodeStack[#nodeStack],this));} ) } if #nodeStack> 1 { for(i=#nodeStack;2;-1){ var ele = pop(nodeStack); topNode = nodeStack[#nodeStack]; topNode.pushElement( ele ); } } } }; } xml.ncrDecode = ncrDecode; xml.ncr = xml.ncrDecode; //@Deprecated xml.elementPattern = "\<([\/]?)([\d\s]?![\?!\w_\-\::]+)(.*?)([\/\?]?)\>"; //xml 只规定了 5 个实体字体,HTML 4 则规定了 252 个 xml.escape = function(v){ if(v) return ..string.replace(v,`[\&\<\>\"\']`,{`&`:'&',`<`:"<",`>`:">",`"`:""",`'`:"'"}) } xml.ncrEncode = function(str,pattern){ return (..string.replace(str,pattern || ":|.",function(c){ var u = ..string.charCodeAt(c,1); var r = "&#"+u+";" return #r<5 ? u : "&#x"+..string.format("%x",u)+";" })); } /**intellisense() string.xml = 简单 XML 解析器。\n严格的校验 XML 语法正确性不是本模块的设计目标,\n所以此解析器尽可能兼容错误,兼容了 HTML,SGML 的部分规则:\n\n1. 不要求存在根标签 \n2. 尝试自动修正不配对的标记 \n3. 在有必要的时候尝试忽略大小写。 string.xml(__/*xml字符串*/) = 创建 XML 对象。\n此对象不支持 table.tostring , console.dump 函数,不能跨线程传递。\n\n严格的校验XML语法正确性不是本模块的义务和目标,\n所以,此解析器尽可能兼容错误,兼容了HTML,SGML的部分规则:\n1、不要求存在根标签 \n2、尝试自动修正不配对的标记 \n3、在有必要的时候尝试忽略大小写 string.xml() = !string_xml. !string_xml.eachChild(标签名) = @for(index,tagName,childCount,xNode in ??.eachChild(__/*标签名,区分大小写*/) ){ io.print( index,tagName,childCount,xNode ) } !string_xml.eachAttribute() = @for( k,v in ??.eachAttribute() ){ __/*遍历节点所有属性,k为属性名,v为属性值*/ } !string_xml.outerXml() = 转换为XML文本,\n如果参数@1为true则缩进格式化返回的xml文本,\n可选在参数@2中自定义换行符 !string_xml.innerXml() = 将子节点转换XML文本,\n如果参数@1为true则缩进格式化返回的xml文本,\n可选在参数@2中自定义换行符 !string_xml.innerText() = 返回节点内部文本或CDATA文本 !string_xml.getEle("__") = 获取指定id的节点对象,\n参数指定节点id,忽略大小写,支持模式匹配规则 !string_xml.getEles("__") = 获取所有指定相同name的节点对象,返回数组,\n即使找不到节点,也会返回空数组,\n参数指定节点name,忽略大小写,支持模式匹配规则 !string_xml.queryEles(.( 查询参数表) = 搜索节点对象,该函数返回的是一个数组,\n即使找不到节点,此函数也会返回一个空数组,\n\n参数@1指定一个表对象,\n该参数表可包含一个或多个键值,用于匹配节点的属性值,\n可使用tagName属性指定节点的标签名,\n可使用parent属性指定开始查询节点的父节点,parent可以是ID或者节点对象。\n属性值使用 string.cmpMatch函数进行比对,\n等价于调用string.cmp函数进行忽略大小写的比较,\n并且在失败后调用 string.match函数使用模式匹配语法进行比较\n\n注意在匹配节点属性时有几个例外:\nparent属性不使用模式匹配进行比对,\ntagName,id,name属性如果匹配值不含标点则使用忽略大小写的完全比对(禁用模式匹配和部分匹配) !string_xml.eachQuery(查询参数表) = @for ele in ??.eachQuery(tagName="img"__/*搜索并遍历节点对象,\n\n参数@1指定一个表对象,\n该参数表可包含一个或多个键值,用于匹配节点的属性值,\n可使用tagName属性指定节点的标签名,\n可使用parent属性指定开始查询节点的父节点,parent可以是ID或者节点对象。\n属性值使用 string.cmpMatch函数进行比对,\n等价于调用string.cmp函数进行忽略大小写的比较,\n并且在失败后调用 string.match函数使用模式匹配语法进行比较\n\n注意在匹配节点属性时有几个例外:\nparent属性不使用模式匹配进行比对,\ntagName,id,name属性如果匹配值不含标点则使用忽略大小写的完全比对(禁用模式匹配和部分匹配)*/){\n \n}\n!string_xml. !string_xml.queryEle(.(查询参数表) = 搜索节点对象,参数@1指定一个表对象,\n\n该参数表可包含一个或多个键值,用于匹配节点的属性值,\n可使用tagName属性指定节点的标签名,\n可使用parent属性指定开始查询节点的父节点,parent可以是ID或者节点对象。\n属性值使用 string.cmpMatch函数进行比对,\n等价于调用string.cmp函数进行忽略大小写的比较,\n并且在失败后调用 string.match函数使用模式匹配语法进行比较\n\n注意在匹配节点属性时有几个例外:\nparent属性不使用模式匹配进行比对,\ntagName,id,name属性如果匹配值不含标点则使用忽略大小写的完全比对(禁用模式匹配和部分匹配) !string_xml.queryEles() = !string_xml. !string_xml.enumNodes(枚举函数,搜索标签名) = @.enumNodes( \n function(parentElement,index,tagName,childCount,xNode){ \n \n },__/*可选指定要查找的标签名(tagName),忽略大小写\n回调函数返回非 null 值时退出枚举,并返回回调函数的所有返回值*/\n) !string_xml.tagName = 节点标签名 !string_xml.getAttribute(__) = 取属性值,\n参数中指定属性名字,属性名忽略大小写,\n如果通过下标或成员操作符直接获取属性时区分大小写的 !string_xml.eachChild() = !string_xml. !string_xml.encoding = 源文本字符集 !string_xml.codepage = 源文本代码页 !string_xml.? = 属性或子节点数组\n!string_xml. !string_xml.pushElement(节点属性表) = @.pushElement(\n tagName = \n cdata = \n text = __/*注意text,cdata,tagName三个属性只能指定一个,\n可选使用参数@2指定新增节点的位置,不指定则添加到子节点数组尾部,\n返回新增的节点对象*/\n) !string_xml.pushXml(__) = 将字符串参数@1中指定的XML添加到当前节点的子节点,\n可选用参数@2指定新节点的位置,不指定则添加到子节点数组尾部,\n返回新增XML中最后一个根节点 !string_xml.pushElement() = !string_xml. !string_xml.pushXml() = !string_xml. !string_xml.getParent() = 获取父节点 !string_xml.remove() = 如果存在父节点,在父节点中移除此节点 string.xml.ncrEncode(.(str,pattern) = 将字符串参数 @str 中以 @pattern 指定模式串所匹配的字符进行 NCR 编码。\n参数 @pattern 可用模式匹配语法指定要编码的字符,省略则默认为 ":|."。\n返回编码后的字符串。 string.xml.ncrDecode(__) = NCR 编码字符、HTML 实体字符还原为 UTF8 文本 string.xml.escape(__) = 将字符串中的 < > " ' & 等 XML 标记字符编码为实体字符(entity)。\n如果参数为非 null 值则返回转换后的字符串。\n参数只能是 null 或字符串、buffer end intellisense**/ /**details(使用说明) string.xml 每个 XML 节点对象都是一个表对象(table), 这些 XML 节点对象如果有 tagName 属性表示标记名 - 那他就是一个普通节点。 如果没有 tagName 属性而是拥有 text 或 cdata属性 - 那就说明它是一个 text 文本节点或者是 cdata 节点。 string.xml 提供以下四种类型的节点: 1. 拥有标签名( tagName 属性)的普通 XML 元素 普通 XML 节点使用 tagName 属性表示 XML 标记名, 在 string.xml 中 "tagName"属于保留的 XML 属性,其他属性使用此属性名将会被自动忽略。 > XML 声明节点的 tagName 为"?xml" 2. 顶层根节点 string.xml 总是虚拟出一个空的没有 tagName 属性的顶层根节点, 这个根节点是文档里的 XML 真实根节点的父节点。 3. 文本节点 文本节点使用 text 属性表示文本,无 tagName 属性,无其他属性。 4. CDATA节点 CDATA 节点使用 cdata 属性表示数据, 无 tagName 属性,无其他属性 注释节点被自动忽略不会存为节点对象。 end details**/