Skip to content

Instantly share code, notes, and snippets.

@think49
Created June 4, 2011 07:38
Show Gist options
  • Save think49/1007702 to your computer and use it in GitHub Desktop.
Save think49/1007702 to your computer and use it in GitHub Desktop.
xquery.js : 入力された XPath 文字列を元にノードを生成します。
/**
* 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