jQuery(JavaScript)だけでサイトマップを自動生成とか〜

サンプルはこちら→W7工廠分室|トップページ
以前ファイル内容を閲覧できるものを作ったのですが(jQueryとPHPとその他色々を使ってディレクトリ構造をツリー表示とか内容表示みたいな〜 - w7工廠
肝心のサイトマップがないw
どうせならPHP使いたくないし。ということで作ってみました。
サンプルではul/li構造のままで、CSSで飾り付けしていないので見づらいかもしれません。
Googleサイトマップ(XML)じゃないですよ〜。


仕様
・最初のページを指定するとそのページのリンク(a要素href属性)を再帰で辿ってタイトルを取得していきます。
(リンクしているのに存在しないファイルは無視して次のファイルを探します。)
・「ページ名|タイトル」の場合などはページ名だけにすることもできます。
欠点
ajax同期処理でリンク先ファイルをすべて読み込むので重いです。→簡易なチェックとか作成向けかもしれません
・順序は上にあるもの・先に読み込んだもの順です。
・トップページと同じ位置に置かないといけない(sitemapフォルダに・・・ということができません)
制限事項
・a要素href属性の除外判定で下記のものは無視するようになっています。
1.リンクで上階層へ移動(通常、上に移動するのは既出のページと判断)
2.ドメイン名を含む(他サイトへの移動をしないため)
3.ジャンプ(同じページを表示しないため)
4.JavaScript
5.メールアドレスを含む
6.html/PHP以外
その他
Firefox,Chrome,IE10で動作します。(今のところ他はチェックしていません)
・ローカルではXAMPPなどでサーバー環境を用意してください。
・リンクがフォルダ名だけの場合、index.htmlを検索するようにしています。



■バージョン1.9(2012-05-23)
とりあえず一時完成版です。
修正点
・別フォルダ-同レベル階層の場合の処理を実装
ディレクトリ名を表示・非表示もできるようにしました。(45行目)
実装していないもの
ドメイン名/ジャンプ/メールアドレス/#や?を含むものは省きます
その他
・ul/li構造のインデント処理っていらないんですけどね(Firebug他ほとんど自動調整してくれますし)
・getListメソッドでテキスト処理なくても、その都度jQueryで差し込んで行ってもよかったのですが、テキスト処理を考えてみたかったので。

■バージョン1.8(2012-05-23)
修正点
・ul/li構造生成部分を修正しました。
・URL取得とURLチェックを統一しました。
不具合
・全バージョンと同じ
・フォルダ名が異なり、同レベル階層の場合の処理を実装していません。(1/1-1/1-1.htmlと2/2-1/2-1.htmlみたいなもの)

■バージョン1.7(2012-05-22)
修正点
・タイトル区切りを有りにしていて、区切りがないものはそのまま表示するように変更
・ファイル内容取得時にファイルが無いなどのエラーで中断しないようにした。
(設定でエラーメッセージで表示することは可能)
ディレクトリの深さに応じたチェックをする(謎)
不具合
・バージョン1.6よりはましだが、別階層の同じ構造の際に上記チェックが仇となる(謎)

■バージョン1.6(2012-05-21)
不具合
・フォルダ同階層かつ意味合いで下階層の場合、同列に並んで見た目が変になります(サンプルではサイトマップ自体w)
・いろんなサイトでチェックしてないので、これから出ると思います。
・ちょっとul/li構造が違うみたいです。
その他
・$.ajaxの段階でxml扱いして、titleやa要素href属性に絞って取得することもできるようですが、途中で挫折してしまいました。
JavaScript(仕様を無視しすぎ?・・・次回v2.0にて)

$.ajax({
	async:false,
	url:filename,
	dataType:"xml",//htmlなのにxmlで・・・<meta />のエラーがでますw
	type:"POST",
	success:function(data){
		res={
			title:$("html head title",data).text(),
			hrefs:$("html body a[href]",data)//ここでもっと条件を絞れそう・・・
		}
	}
});
//これらからnew String(res.title)やnew String(res.hrefs[i])とすればいいみたい・・・



■設置方法
・jq_makeSiteMap.jsとサイトマップを表示するためのフォルダ(div id="siteMap")を準備します。
・タイトルが「タイトル|サイト名」などで区切られている場合はgetTitleメソッドを修正してください。


XHTML

<head>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
	<script type="text/javascript" src="lib/jq_makeSiteMap.js"></script>
</head>
<body>
	<div id="siteMap"></div>
</body>

Javascript (jq_makeSiteMap.js)

/* jq_makeSiteMap.js 2012-05-21→2012-05-23 */
$(function(){
	var msm=new MakeSiteMap("index.html");
	$("#siteMap").html(msm.getList());
});

function MakeSiteMap(targetFile){
	this.lists=[];
	this.setList({dir:"",file:targetFile});
}
MakeSiteMap.prototype={
	getList:function(){//サイトマップ取得
		var im="\t";
		var indent=im;
		var html="<ul>\n";
		var before={deep:0,dir:"",file:""};
		for(var i=0;i<this.lists.length;i++){
			var current=this.lists[i];
			var anchor='<a href="'+current.dir+current.file+'" target="_blank">'+current.title+'</a>';
			var bds=before.dir.split("/");
			var cds=current.dir.split("/");
			var len;
			if(before.deep<=current.deep){
				len=before.deep;
			}else{
				len=current.deep;
			}
			var cnt=0;
			for(var j=0;j<len;j++){
				if(bds[j]==""){cnt=j;break;}
				if(cds[j]==""){cnt=j;break;}
				if(bds[j]==cds[j]){cnt++;continue;}
			}
			var blen=before.deep-cnt;
			for(var m=0;m<blen;m++){
				indent=indent.slice(0,(-1)*im.length);
				html+=indent+"</ul>\n";
				indent=indent.slice(0,(-1)*im.length);
				html+=indent+"</li>\n";
			}
			var clen=current.deep-cnt;
			for(var n=0;n<clen;n++){
				html+=indent+"<li>"+cds[n+cnt]+"\n";//ディレクトリ名非表示はhtml+=indent+"<li>\n";
				indent=indent+im;
				html+=indent+"<ul>\n";
				indent=indent+im;
			}
			html+=indent+"<li>"+anchor+"</li>\n";
			before=current;
		}
		indent=indent.slice(0,(-1)*im.length);
		html+=indent+"</ul>\n";
		return html;
	},
	getTitle:function(html){//タイトル抽出
		var separator="";//タイトルの区切り文字(タイトル|サイト名など、""で区切りを検出しない)
		var pagetitle="LEFT";//上記の場合、左右のどちらがタイトルか(LEFT/RIGHT)
		var titletag=html.match(/(<title>)(.+)(<\/title>)/gi);
		if(!titletag){return;}
		var titletext=titletag[0].replace(/(<title>)|(<\/title>)/gi,"");
		if(!separator){return titletext;}
		var title=titletext.split(separator);
		switch(pagetitle){
			case "RIGHT":if(!title[1]){return title[0]}else{return title[1];};break;
			case "LEFT" :return title[0];break;
		}
	},
	getUrls:function(html){//<a>のhrefからURL抽出・チェック除外処理
		var anchors=html.match(/(<a[^>]*>)|(<\/a>)/gi);//a要素抽出
		if(!anchors){return false;}
		var urls=[];
		for(var i=0;i<anchors.length;i++){
			var href=anchors[i].match(/(href=")(.+)("{1})/gi);//href属性抽出
			if(!href){continue;}
			var url=href[0].split('"')[1];
			if(url.charAt(0)=="/"){url=url.slice(1);}   //「/」から始まる場合
			if(url.slice(0,2)=="./"){url=url.slice(2);} //「./」から始まる場合
			if(url.slice(0,3)=="../"){continue;}        // 「../」上階層へ移動するもの
			if(url.slice(0,4)=="http"){continue;}       //ドメイン名を含むもの
			if(url.slice(0,3)=="ftp"){continue;}
			if(url.match(/#/)){continue;}               //ジャンプ
			if(url.match(/\?/)){continue;} 
			if(url.match(/javascript:/gi)){continue;}   //javascriptのもの
			if(url.match(/@/)){continue;}               //メールアドレスを含むもの
			var flg=true;
			for(var j=0;j<urls.length;j++){             //ページ内で既出のもの
				if(url==urls[j]){flg=false;break;}
			}
			if(!flg){continue;}
			urls.push(url);
		}
		return urls;
	},
	loadFile:function(filename){//ファイル内容を取得(同期処理)
		var res;
		$.ajax({
			async:false,
			url:filename,
			dataType:"text",
			type:"POST",
			success:function(data){res=data;},
			error:function(r,s,e){return;}
		});
		return res;
	},
	setLink:function(tdir,url){//ディレクトリとファイル名に分割
		var defname="index.html";//ファイル名記述省略の場合(デフォルト:index.html)
		var ext,dir,file;
		ext=url.split("/").pop().split(".")[1];
		if(!ext){//ファイル名なし
			file=defname;
			dir=url;
		}else if((ext.slice(0,3)=="htm") | (ext.slice(0,3)=="php")){//処理するファイルhtml/php
			file=url.split("/").pop();
			dir=url.slice(0,(-1)*(file.length));
		}else{//処理しないファイル(画像など)
			return false;
		}
		return {dir:tdir+dir,file:file};
	},
	setList:function(target){//メイン
		//targetをリストに加える
		var targetHtml=this.loadFile(target.dir+target.file);
		if(!targetHtml){return false;}
		var deep=target.dir.split("/").length-1;
		this.lists.push({
			deep:deep,
			dir:target.dir,
			file:target.file,
			title:this.getTitle(targetHtml)
		});
		//target内<a href>からリンクを取得し、targetに設定(再帰)
		var urls=this.getUrls(targetHtml);
		for(var i=0;i<urls.length;i++){
			var nextTarget=this.setLink(target.dir,urls[i]);
			if(!nextTarget){continue;}
			var flg=true;
			for(var j=0;j<this.lists.length;j++){//サイト内で既出のもの
				if((nextTarget.dir+nextTarget.file)==(this.lists[j].dir+this.lists[j].file)){flg=false;break;}
			}
			if(!flg){continue;}
			this.setList(nextTarget);
		}
		return;
	}
}