Created
June 4, 2011 07:38
-
-
Save think49/1007702 to your computer and use it in GitHub Desktop.
xquery.js : 入力された XPath 文字列を元にノードを生成します。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* xquery.js | |
* xQuery generates a node based on a XPath character string. | |
* | |
* @version 1.0.1 | |
* @author think49 | |
* @url https://gist.github.com/1007702 | |
* @license http://www.opensource.org/licenses/mit-license.php (The MIT License) | |
* @see <a href="http://www.w3.org/TR/xpath/">XML Path Language (XPath)</a> | |
*/ | |
var xQuery = function(x){ | |
if(!x){ return false; } | |
return $xQuery.xpath2node(x); | |
}; | |
// ----------- ノード操作 | |
/* 孫ノードを追加(firstChildの孫) */ | |
HTMLElement.prototype.appendGrandChild = function(child){ | |
var target = this; | |
while(target.firstChild && target.firstChild.nodeType == 1){ | |
target = target.firstChild; | |
} | |
target.appendChild(child); | |
}; | |
/* 子ノードをfirstChild手前に追加 */ | |
HTMLElement.prototype.appendFirstChild = function(child){ | |
var target = this; | |
if(this.firstChild){ | |
target.insertBefore(child,target.firstChild); | |
} else { | |
target.appendChild(child); | |
} | |
}; | |
/* 子ノードリストをfirstChild手前に追加 (appendFirstChildに依存) */ | |
HTMLElement.prototype.appendFirstChilds = function(childs){ | |
for(var i=childs.length-1,min=-1; i>min; i--){ | |
console.info(i); | |
this.appendFirstChild(childs[i]); | |
} | |
}; | |
/* 子ノードリストを追加 */ | |
HTMLElement.prototype.appendChilds = function(childs){ | |
var i=childs.length-1; | |
this.appendChild(childs[i]); | |
i--; | |
var target = this.lastChild; | |
while(i > -1){ | |
this.insertBefore(childs[i], target); | |
target = target.previousSibling; | |
i--; | |
} | |
}; | |
/* 対象ノードを削除 */ | |
HTMLElement.prototype.removeNode = function(){ | |
return this.parentNode.removeChild(this); | |
} | |
/* getElementsByClassName */ | |
/* | |
*ソース | |
高速なgetElementsByClassName - 素人がプログラミングを勉強するブログ | |
http://d.hatena.ne.jp/javascripter/20080805/1217912008 | |
*/ | |
/* | |
HTMLElement.prototype.getElementsByClassName=function(className) { | |
var classNames = className.split(/\s+/), | |
var all = this.getElementsByTagName('*'), | |
memo={}, | |
ret=[]; | |
for(var i=0, max = all.length, elm, c, flag; i<l; i++){ | |
elm = all[i]; | |
iname = elem.className; | |
if(!(iname in memo)) | |
memo[iname] = " "+iname.toLowerCase()+" "; | |
for (var j=0,jname; j<names.length; j++) { | |
jname = names[j]; | |
if (!(jname in memo)){ | |
memo[jname] = " "+jname.toLowerCase()+" "; | |
} | |
if(flag = memo[iname].indexOf(memo[jname]) != -1){ | |
continue; | |
} else{ | |
break; | |
} | |
} | |
if(flag)ret[ret.length]=elem; | |
} | |
return ret; | |
}; | |
*/ | |
// ----------- 配列操作 | |
/* 配列の要素削除 (値を指定) */ | |
Array.prototype.delete = function(value) { | |
for(var i = this.length; i>-1; i--){ | |
if(this[i] == value){ | |
this.splice(i,1); | |
} | |
} | |
}; | |
// ----------- 文字列処理 | |
/* replaceCallback関数 */ | |
$xQuery.replaceCallback = function(str, r, func){ | |
if(r.test(str)){ | |
return RegExp.leftContext + func(RegExp.$1) + RegExp.rightContext; | |
} else { | |
return str; | |
} | |
}; | |
// ----------- 変換 | |
$xQuery.xpathRegExp = function(){ | |
var base_reg = { | |
'quote':'"((?:""|[^"])+)(")', | |
'apostrophe':"'((?:'|[^'])+)(')" | |
}; | |
var reg = { | |
'element': '([a-z]+\d*)(?=[.#/[]|$)', | |
'id': '#([a-zA-Z][\\w\\-]+)(?=[.#/[]|$)', | |
'className': '\\.([a-zA-Z][\\w\\-]+)(?=[.#/[]|$)', | |
'attribute_quot': '\\[@([a-z]+)=' + base_reg['quote'] + '\\](?=[.#/[]|$)', | |
'attribute_apos': '\\[@([a-z]+)=' + base_reg['apostrophe'] + '\\](?=[.#/[]|$)', | |
'text_quot': '\\[text\\(\\)=' + base_reg['quote'] + '\\]', | |
'text_apos': '\\[text\\(\\)=' + base_reg['apostrophe'] + '\\]', | |
'textonly_quot': '^text\\(\\)=' + base_reg['quote'] + '$', | |
'textonly_apos': '^text\\(\\)=' + base_reg['apostrophe'] + '$' | |
}; | |
return reg; | |
} | |
/* XPath->Node */ | |
$xQuery.xpath2node = function(x){ | |
/* バックスラッシュ、ダブルクオートをエスケープ */ | |
function EscapeQuot(str, quote){ | |
return str.replace(new RegExp(quote + '(' + quote + ')', 'g'), '$1'); | |
} | |
var elm, bak, xnode = document.createElement('x'); | |
var reg = this.xpathRegExp(); | |
if(new RegExp(reg['textonly_quot']).test(x) || new RegExp(reg['textonly_apos']).test(x)){ | |
xnode.appendGrandChild(document.createTextNode(EscapeQuot(RegExp.$1, RegExp.$2))); | |
return xnode.firstChild;; | |
} | |
x.match(/^/); // RegExp.rightContextの初期化 | |
while(RegExp.rightContext){ | |
RegExp.rightContext.match(/^\/+/); | |
if(new RegExp(reg['element']).test(RegExp.rightContext)){ | |
elm = document.createElement(RegExp.$1); // RegExp.lastMatch == RegExp.$& (Opera NG?) | |
while(new RegExp('^(?!\/|$)').test(RegExp.rightContext)){ | |
if(new RegExp('^'+reg['id']).test(RegExp.rightContext)){ | |
// console.info("IDセレクタにマッチしました。\nID: \""+RegExp.$1+'"') | |
elm.id = RegExp.$1; | |
} else if(new RegExp('^'+reg['className']).test(RegExp.rightContext)) { | |
// console.info("クラスセレクタにマッチしました。\nクラス: \""+RegExp.$1+'"') | |
elm.className += ' ' + RegExp.$1; | |
} else if(new RegExp('^'+reg['attribute_quot']).test(RegExp.rightContext) || new RegExp(reg['attribute_apos']).test(RegExp.rightContext)) { | |
bak = RegExp.rightContext; // RegExp.rightContextをバックアップ | |
if(RegExp.$1 == 'style' && document.documentElement.getAttribute("style") == document.documentElement.style){ // IEのsetAttributeバグ対策 | |
elm.style.cssText = EscapeQuot(RegExp.$2, RegExp.$3); | |
} else { | |
elm.setAttribute(RegExp.$1, EscapeQuot(RegExp.$2, RegExp.$3)); | |
} | |
// console.info("属性値をセットしました。\n属性名: \""+RegExp.$1+"\"\n属性値: \""+RegExp.$2+'"') | |
bak.match(/^/); // RegExp.rightContextロールバック | |
} else if(new RegExp('^'+reg['text_quot']).test(RegExp.rightContext) || new RegExp('^'+reg['text_apos']).test(RegExp.rightContext)) { | |
bak = RegExp.rightContext; // RegExp.rightContextをバックアップ | |
// console.info("テキストノードにマッチしました。\nテキスト値 = \"" + EscapeQuot(RegExp.$1, RegExp.$2) + '"'); | |
elm.appendGrandChild(document.createTextNode(EscapeQuot(RegExp.$1, RegExp.$2))); // ここでRegExp.rightContextが変化する | |
bak.match(/^/); // RegExp.rightContextロールバック | |
} else { | |
console.error("例外エラー: 要素ノードの後に不正な文字列があります。\n未消費文字列 = \""+RegExp.rightContext+'"') | |
return false; | |
} | |
// console.info("要素ノードに付属するノードチェックが一巡しました。\n未消費文字列 = \""+RegExp.rightContext+'"'); | |
} | |
xnode.appendGrandChild(elm); | |
} else { | |
console.error("例外エラー: XPathは「テキストノードのみ」「要素ノードから始まる」のいずれかである必要があります。\n未消費文字列 = \""+RegExp.rightContext+'"'); | |
return false; | |
} | |
} | |
return xnode.firstChild; | |
}; | |
/* Node->HTML */ | |
$xQuery.node2html = function(node){ | |
var reg = new RegExp('(<\/?[a-zA-Z]*[A-Z][a-zA-Z]*\d*)(?=[ >])'); | |
var xnode = document.createElement('x'); | |
xnode.appendChild(node); | |
var html = xnode.innerHTML; | |
while(reg.test(html)){ | |
html = this.replaceCallback(html, reg, function(str){return str.toLowerCase();}); | |
} | |
return html; | |
}; | |
/* XPath->HTML */ | |
$xQuery.xpath2html = function(x){ return this.node2html(this.xpath2node(x)); }; | |
/* CSV->配列 */ | |
$xQuery.csv2array = function(csv, delimiter){ | |
var a = new Array(); | |
var i = 0, j = 0, bak; | |
var reg = { | |
'plain':'^([^\r\n' + delimiter + ']*)(?=[\r\n' + delimiter + ']|$)', | |
'quote':'^"((?:[^"]*"")*[^"]*)"(?=[\r\n' + delimiter + ']|$)', | |
'delimiter':'^' + delimiter | |
}; | |
csv = csv.toString(); | |
csv.match(/^/); // RegExp.rightContextの初期化 | |
while(RegExp.rightContext){ | |
a[i] = new Array(); | |
j = 0; | |
if(new RegExp(reg['quote']).test(RegExp.rightContext)){ | |
bak = RegExp.rightContext; // RegExp.rightContextをバックアップ | |
a[i][j] = RegExp.$1.replace(/""/g, '"'); | |
bak.match(/^/); // RegExp.rightContextロールバック | |
j++; | |
} else if(new RegExp(reg['plain']).test(RegExp.rightContext)){ | |
a[i][j] = RegExp.$1; | |
j++; | |
} | |
// to next column | |
while(new RegExp(reg['delimiter']).test(RegExp.rightContext)){ | |
if(new RegExp(reg['quote']).test(RegExp.rightContext)){ | |
bak = RegExp.rightContext; // RegExp.rightContextをバックアップ | |
a[i][j] = RegExp.$1.replace(/""/g, '"'); | |
bak.match(/^/); // RegExp.rightContextロールバック | |
j++; | |
} else if(new RegExp(reg['plain']).test(RegExp.rightContext)){ | |
a[i][j] = RegExp.$1; | |
j++; | |
} | |
} | |
if(new RegExp('^$').test(RegExp.rightContext)){ break; } // EOF | |
RegExp.rightContext.match(/^\r\n|[\r\n]/); // to next line | |
i++; | |
} | |
return a; | |
} | |
/* 配列->table要素ノード */ | |
$xQuery.array2tableNode = function(a, option){ | |
function createTr(cells, c){ | |
var tr = cp['tr'].cloneNode(true), cell; | |
// console.info(tr); | |
for(var row=0,max=cells.length; row<max; row++){ | |
cell = c.cloneNode(true); | |
cell.appendChild(document.createTextNode(cells[row])); | |
tr.appendChild(cell); | |
// console.info(tr); | |
} | |
/* | |
for(var row in cells){ | |
cell = c.cloneNode(true); | |
cell.appendChild(document.createTextNode(cells[row])); | |
tr.appendChild(cell); | |
} | |
*/ | |
return tr; | |
} | |
// Copy Node | |
var cp = { | |
'table': document.createElement('table'), | |
'tr': document.createElement('tr'), | |
'td': document.createElement('td') | |
}; | |
var table = cp['table'].cloneNode(true); | |
if(option['xpath']){ | |
var x = option['xpath'].replace(/^\s+|\s+$/g, '').split(/\r\n|[\r\n]/); | |
// console.info("x = ["+x+"]"); | |
var base_reg = this.xpathRegExp(); | |
var reg = { | |
'table':'^table(?:' + base_reg['id'] + '|' + base_reg['className'] + '|' + base_reg['attribute_quot'] + '|' + base_reg['attribute_apos'] + '|' + base_reg['text_quot'] + '|' + base_reg['text_apos'] + ')*$', | |
'caption':'^caption(?:' + base_reg['text_quot'] + '|' + base_reg['text_apos'] + ')$', | |
'col':'^col(?:' + base_reg['id'] + '|' + base_reg['className'] + '|' + base_reg['attribute_quot'] + '|' + base_reg['attribute_apos'] + '|' + base_reg['text_quot'] + '|' + base_reg['text_apos'] + ')*$', | |
'tr':'^tr(?:' + base_reg['id'] + '|' + base_reg['className'] + '|' + base_reg['attribute_quot'] + '|' + base_reg['attribute_apos'] + '|' + base_reg['text_quot'] + '|' + base_reg['text_apos'] + ')*$', | |
'th':'^th(?:' + base_reg['id'] + '|' + base_reg['className'] + '|' + base_reg['attribute_quot'] + '|' + base_reg['attribute_apos'] + '|' + base_reg['text_quot'] + '|' + base_reg['text_apos'] + ')*$', | |
'td':'^td(?:' + base_reg['id'] + '|' + base_reg['className'] + '|' + base_reg['attribute_quot'] + '|' + base_reg['attribute_apos'] + '|' + base_reg['text_quot'] + '|' + base_reg['text_apos'] + ')*$', | |
}; | |
// console.info('reg = '+reg['caption']); | |
for(var i=0,max=x.length; i<max; i++){ | |
for(var j in reg){ | |
// console.info("--- check ---\nx["+i+"] = "+x[i]+"\nreg["+j+"] = "+reg[j]); | |
if(new RegExp(reg[j]).test(x[i])){ | |
if(j != 'col'){ | |
cp[j] = $xQuery(x[i]); | |
} else { | |
if(!cp[j]){ cp[j] = document.createElement('x'); } | |
cp[j].appendChild($xQuery(x[i])); | |
} | |
// console.info("--- matched! ---\ncp["+j+"] = \""+cp[j]+"\""); | |
break; | |
} | |
} | |
} | |
/* | |
for(var i=0,max=x.length; i<max; i++){ | |
for(var j in reg){ | |
// console.info("--- check ---\nx["+i+"] = "+x[i]+"\nreg["+j+"] = "+reg[j]); | |
if(new RegExp(reg[j]).test(x[i])){ | |
cp[j] = $xQuery(x[i]); | |
// console.info("--- matched! ---\ncp["+j+"] = \""+cp[j]+"\""); | |
break; | |
} | |
} | |
} | |
*/ | |
} | |
var table = cp['table'].cloneNode(true), i=0; | |
if(cp['caption']){ | |
table.appendChild(cp['caption'].cloneNode(true)); | |
} | |
if(cp['col']){ | |
table.appendChilds(cp['col'].childNodes); | |
} | |
if(cp['th']){ | |
table.appendChild(createTr(a.shift(), cp['th'].cloneNode(true))); | |
} | |
for(var line = 0, max = a.length ; line < max ; line++){ | |
table.appendChild(createTr(a[line], cp['td'].cloneNode(true))); | |
} | |
return table; | |
} | |
/* CSV->Table要素ノード */ | |
$xQuery.csv2tableNode = function(csv, option){ | |
return this.array2tableNode(this.csv2array(csv, option['delimiter']), option); | |
} | |
/* CSV->TableHTML */ | |
$xQuery.csv2tableHtml = function(csv, option){ | |
return this.node2html(this.csv2tableNode(csv, option)); | |
} | |
// ----------- String拡張 | |
String.prototype.trim = function() { | |
return this.replace(/^\s+|\s+$/g, ''); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment