using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using mshtml;
namespace jp.bne.jomora.html
{
///
/// MyHTMLDocument の概要の説明です。
///
public class MyHTMLDocument
{
///
/// http://〜を処理するか否か。
///
public const bool ENABLE_HTTP = true;
///
/// 同時に読み込むリンク階層数の規定値。
///
public const int MAX_PARSELEVEL = 3;
///
/// 同時に読み込むリンク階層数の規定値。
///
private int parseLevel;
///
/// HTMLドキュメントに含まれるHTMLオブジェクト。
///
private Hashtable objTable;
///
/// HTMLオブジェクト。
///
private HTMLDocumentClass htmlDocument;
///
/// HTMLオブジェクトの親オブジェクト。
/// HTMLオブジェクトと同じ生存期間が必要。
///
private HTMLDocumentClass parentDocument;
# region constructors
///
/// コンストラクタ。
/// 初期化処理のみ。
///
private MyHTMLDocument()
{
parseLevel = MAX_PARSELEVEL;
objTable = new Hashtable();
}
///
/// コンストラクタ。
/// 下階層のHTMLドキュメント生成時に、クラス内部で利用する。
///
private MyHTMLDocument(int parseLevel, ref Hashtable objTable)
{
this.parseLevel = parseLevel;
this.objTable = objTable;
}
///
/// コンストラクタ。
/// 読み込むHTMLドキュメントを指定する。
///
/// 読み込むHTMLドキュメントのファイルパス
public MyHTMLDocument(string loadPath) : this()
{
this.Load(loadPath);
}
///
/// コンストラクタ。
/// 読み込むHTMLドキュメントと、保存先を指定する。
///
/// 読み込むHTMLドキュメントのファイルパス
/// 保存するHTMLドキュメントのファイルパス
public MyHTMLDocument(string loadPath, string savePath) : this(loadPath)
{
this.Save(savePath);
}
# endregion
///
/// フィールドを削除する。
///
public void Clear()
{
this.objTable.Clear();
this.htmlDocument.clear();
this.parentDocument.clear();
}
///
/// HTMLドキュメントを読み込む
///
///
private void Load(string path)
{
//path = System.Web.HttpUtility.UrlDecode(path);
//if (true)
//if (path.StartsWith("http://") || path.StartsWith("https://"))
//{
parentDocument = new HTMLDocumentClass();
IHTMLDocument2 doc2 = parentDocument;
IHTMLDocument4 doc4 = parentDocument;
doc2.write("");
doc2.close();
htmlDocument = (HTMLDocumentClass)doc4.createDocumentFromUrl(path, null);
/*
}
else
{
htmlDocument = new HTMLDocumentClass();
UCOMIPersistFile persist = htmlDocument as UCOMIPersistFile;
persist.Load(path, 0x00000002 | 0x00000030 | 0x00100000 | 0x00200000);
}
*/
int i = 0;
while (htmlDocument.readyState != "complete")
{
if (++i > 50)
{
htmlDocument = null;
return;
}
System.Threading.Thread.Sleep(70);
System.Windows.Forms.Application.DoEvents();
//Console.WriteLine(myObject.readyState);
}
//htmlDocument.close();
}
///
/// HTMLドキュメントを保存する。
///
/// 保存するHTMLドキュメントのファイルパス
public void Save(string path)
{
// 関連オブジェクト保存用のディレクトリ
string dirPath = Path.GetDirectoryName(path);
string dirName = ".";
if (this.parseLevel == MAX_PARSELEVEL)
{
dirName = GetHTMLObjectFolderName(path);
dirPath = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + dirName;
if (!Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
}
# region 関連オブジェクトの走査と保存
// img
foreach (IHTMLImgElement image in htmlDocument.images)
{
string oldPath = GetCanonicalName(image.src);
//Console.WriteLine("oldPath : " + oldPath);
if (!Path.HasExtension(oldPath))
{
continue;
}
if (!objTable.ContainsKey(oldPath))
{
string newFileName = MyHTMLDocument.GetNewPath(dirPath, Path.GetFileName(oldPath));
string newPath = dirPath + Path.DirectorySeparatorChar + newFileName;
//Console.WriteLine("newPath : " + newPath);
this.objTable.Add(oldPath, newPath);
bool result = CopyFileWithURILocation(oldPath, newPath);
if (result)
{
image.src = dirName + Path.DirectorySeparatorChar + newFileName;
}
}
else
{
image.src = (objTable[oldPath] as string).Replace(Path.GetDirectoryName(path), ".");
}
}
// link
foreach (IHTMLLinkElement link in htmlDocument.getElementsByTagName("link"))
{
string oldPath = GetCanonicalName(new Uri(new Uri(htmlDocument.URLUnencoded, true), link.href, true).AbsoluteUri);
//Console.WriteLine("oldPath : " + oldPath);
if (Path.GetExtension(oldPath) != ".css")
{
continue;
}
if (!objTable.ContainsKey(oldPath))
{
string newFileName = MyHTMLDocument.GetNewPath(dirPath, Path.GetFileName(oldPath));
string newPath = dirPath + Path.DirectorySeparatorChar + newFileName;
//Console.WriteLine("newPath : " + newPath);
this.objTable.Add(oldPath, newPath);
bool result = CopyFileWithURILocation(oldPath, newPath);
if (result)
{
link.href = dirName + Path.DirectorySeparatorChar + newFileName;
}
}
else
{
link.href = (objTable[oldPath] as string).Replace(Path.GetDirectoryName(path), ".");
}
}
// embed
foreach (IHTMLEmbedElement embed in htmlDocument.embeds)
{
string oldPath = GetCanonicalName(new Uri(new Uri(htmlDocument.URLUnencoded, true), embed.src, true).AbsoluteUri);
//Console.WriteLine("oldPath : " + oldPath);
if (oldPath.Length == 0)
{
continue;
}
if (!objTable.ContainsKey(oldPath))
{
string newFileName = MyHTMLDocument.GetNewPath(dirPath, Path.GetFileName(oldPath));
string newPath = dirPath + Path.DirectorySeparatorChar + newFileName;
//Console.WriteLine("newPath : " + newPath);
this.objTable.Add(oldPath, newPath);
bool result = CopyFileWithURILocation(oldPath, newPath);
if (result)
{
embed.src = dirName + Path.DirectorySeparatorChar + newFileName;
}
}
else
{
embed.src = (objTable[oldPath] as string).Replace(Path.GetDirectoryName(path), ".");
}
}
// a
foreach (IHTMLAnchorElement link in htmlDocument.getElementsByTagName("a"))
{
if (this.parseLevel == 0)
{
break;
}
string oldPath = GetCanonicalName(link.href);
//Console.Write("oldPath : " + oldPath + " ");
if (oldPath.Length == 0)
{
continue;
}
if (!objTable.ContainsKey(oldPath))
{
string newFileName = MyHTMLDocument.GetNewPath(dirPath, Path.GetFileName(oldPath));
string newPath = dirPath + Path.DirectorySeparatorChar + newFileName;
//Console.WriteLine("newPath : " + newPath);
this.objTable.Add(oldPath, newPath);
string ext = Path.GetExtension(oldPath).ToLower();
if (ext == ".htm" || ext == ".html")
{
if (ENABLE_HTTP || !oldPath.StartsWith("http"))
{
//a.href がhtmlだったら…
MyHTMLDocument childDocument = new MyHTMLDocument(this.parseLevel - 1, ref this.objTable);
childDocument.Load(oldPath);
//Console.WriteLine("childDocument.Save(newPath); " + newPath);
childDocument.Save(newPath);
link.href = dirName + Path.DirectorySeparatorChar + newFileName;
}
}
else
{
bool result = CopyFileWithURILocation(oldPath, newPath);
if (result)
{
link.href = dirName + Path.DirectorySeparatorChar + newFileName;
}
}
}
else
{
link.href = (objTable[oldPath] as string).Replace(Path.GetDirectoryName(path), ".");
}
}
# endregion
htmlDocument.close();
parentDocument.close();
// 最後にHTMLドキュメントを保存する。
/* この方法で保存すると、変更内容が反映されない(++
UCOMIPersistFile persist = htmlDocument as UCOMIPersistFile;
persist.Save(path, true);
*/
// 苦肉の保存策
using (StreamWriter writer = new StreamWriter(path, false, System.Text.Encoding.GetEncoding(htmlDocument.charset)))
{
writer.WriteLine(htmlDocument.documentElement.outerHTML);
}
}
#region static methods
///
/// ファイル名の正規化(?)をする。
/// ファイル名の後に、#,?,&などの記号でパラメータが書かれていた場合、
/// それら以降の文字列を排除する。
///
/// 元のファイル名
/// 修正されたファイル名
public static string GetCanonicalName(string fileName)
{
if (fileName == null || fileName.Length == 0)
{
return "";
}
int index = fileName.IndexOfAny(new char[]{'#','?','&'});
if (index != -1)
{
fileName = fileName.Substring(0, index);
}
return fileName;
}
///
/// 関連オブジェクトの保存先ファイル名を生成する。
/// すでに同じ名前のファイルが存在するならば、ファイル名を変更する。
///
/// 保存先ディレクトリのパス
/// オリジナルファイル名
///
public static string GetNewPath(string dirName, string fileName)
{
string newPath = dirName + Path.DirectorySeparatorChar + fileName;
while (File.Exists(newPath))
{
/*
fileName = Path.GetFileNameWithoutExtension(fileName) + "-" + Path.GetExtension(fileName);
newPath = dirName + Path.DirectorySeparatorChar + fileName;
*/
string fileNameNoExt = Path.GetFileNameWithoutExtension(fileName);
fileName = fileNameNoExt + "(1)" + Path.GetExtension(fileName);
if (fileNameNoExt.EndsWith(")"))
{
int index = fileNameNoExt.LastIndexOf('(');
if (index != -1)
{
string numStr = fileNameNoExt.Substring(index + 1, fileNameNoExt.Length - index - 2);
try
{
int i = int.Parse(numStr);
fileName = fileNameNoExt.Substring(0, index) + "(" + ++i + ")" + Path.GetExtension(fileName);
}
catch (System.FormatException) {}
}
}
newPath = dirName + Path.DirectorySeparatorChar + fileName;
}
return fileName;
}
///
/// URIでファイルを指定して、ローカルファイルとして保存する。
/// file://〜、http://〜 に対応。ftp://〜 には非対応。
///
/// 保存するコンテンツ
/// 保存先のファイルパス
/// 正常に取得、保存できたらtrue。できなかったらfalse。
public static bool CopyFileWithURILocation(string uri, string newpath)
{
if (!(uri.StartsWith("file://") || (uri.StartsWith("http") && ENABLE_HTTP)))
{
return false;
}
try
{
WebRequest req = WebRequest.Create(uri);
req.Method = "GET";
//req.Timeout = 5000;
using (WebResponse res = req.GetResponse())
{
//Console.WriteLine("データを読み取っています...");
req.ContentType = res.ContentType;
using (Stream stream = res.GetResponseStream())
using (FileStream fs = new FileStream(newpath, FileMode.Create))
{
//Console.WriteLine("length : " + stream.Length);
int b;
while ((b = stream.ReadByte()) != -1)
{
fs.WriteByte((byte)b);
}
/* httpではseekをサポートしないようですので、以下は利用不能。
byte[] bytes = new byte[stream.Length];
long result = stream.Read(bytes, 0, (int)stream.Length);
fs.Write(bytes, 0, bytes.Length);
*/
}
}
return true;
}
catch (WebException webexp)
{
System.Diagnostics.Trace.WriteLine(webexp.Message);
}
catch (Exception exp)
{
System.Diagnostics.Trace.WriteLine(exp);
}
return false;
}
///
/// 関連オブジェクトの保存先ディレクトリパスを取得する。
///
/// 元となるHTMLドキュメントのファイルパス(例:C:\test\sample.htm)
/// 関連オブジェクトの保存先ディレクトリパス(例:sample.files)
public static string GetHTMLObjectFolderName(string filepath)
{
return Path.GetFileNameWithoutExtension(filepath) + ".files";
}
///
/// HTMLファイルの削除時に、関連フォルダも削除する。
/// 関連フォルダが存在しない場合は、何もしない。
///
/// HTMLファイルパス
public static void Delete(string filepath)
{
try
{
string dirpath = Path.GetDirectoryName(filepath) + Path.DirectorySeparatorChar + GetHTMLObjectFolderName(filepath);
if (Directory.Exists(dirpath))
{
Directory.Delete(dirpath, true);
}
}
catch (Exception exp)
{
System.Diagnostics.Trace.WriteLine(exp.Message);
}
File.Delete(filepath);
}
///
/// HTMLファイルの移動時に、関連フォルダも移動する。
/// 関連フォルダが存在しない場合は、何もしない。
///
/// HTMLファイルパス
public static void Move(string oldFilepath, string newFilepath)
{
try
{
string dirpath = Path.GetDirectoryName(oldFilepath) + Path.DirectorySeparatorChar + GetHTMLObjectFolderName(oldFilepath);
if (Directory.Exists(dirpath))
{
Directory.Move(dirpath, Path.GetDirectoryName(newFilepath) + Path.DirectorySeparatorChar + GetHTMLObjectFolderName(newFilepath));
}
}
catch (Exception exp)
{
System.Diagnostics.Trace.WriteLine(exp.Message);
}
//File.Move(oldFilepath, newFilepath);
string text = "";
using (StreamReader reader = new StreamReader(oldFilepath, System.Text.Encoding.Default)) {
text = reader.ReadToEnd();
}
using (StreamWriter writer = new StreamWriter(newFilepath, false, System.Text.Encoding.Default)) {
writer.Write(text.Replace(GetHTMLObjectFolderName(oldFilepath), GetHTMLObjectFolderName(newFilepath)));
}
File.SetAttributes(newFilepath, File.GetAttributes(oldFilepath));
// delete
File.Delete(oldFilepath);
}
///
/// HTMLファイルの複製時に、関連フォルダも複製する。
/// 関連フォルダが存在しない場合は、何もしない。
///
/// HTMLファイルパス
public static void Copy(string oldFilepath, string newFilepath) {
try {
string dirpath = Path.GetDirectoryName(oldFilepath) + Path.DirectorySeparatorChar + GetHTMLObjectFolderName(oldFilepath);
if (Directory.Exists(dirpath)) {
CopyDirectory(dirpath, Path.GetDirectoryName(newFilepath) + Path.DirectorySeparatorChar + GetHTMLObjectFolderName(newFilepath));
}
}
catch (Exception exp) {
System.Diagnostics.Trace.WriteLine(exp.Message);
}
string text = "";
using (StreamReader reader = new StreamReader(oldFilepath, System.Text.Encoding.Default)) {
text = reader.ReadToEnd();
}
using (StreamWriter writer = new StreamWriter(newFilepath, false, System.Text.Encoding.Default)) {
writer.Write(text.Replace(GetHTMLObjectFolderName(oldFilepath), GetHTMLObjectFolderName(newFilepath)));
}
File.SetAttributes(newFilepath, File.GetAttributes(oldFilepath));
}
///
/// ディレクトリを再帰的にコピーする汎用メソッド。
///
/// コピーするディレクトリ
/// コピー先のディレクトリ
public static void CopyDirectory(string sourceDirName, string destDirName) {
//コピー先のディレクトリがないときは作る
if (!Directory.Exists(destDirName)) {
Directory.CreateDirectory(destDirName);
//属性もコピー
File.SetAttributes(destDirName,
File.GetAttributes(sourceDirName));
}
//コピー先のディレクトリ名の末尾に"\"をつける
if (destDirName[destDirName.Length - 1] !=
Path.DirectorySeparatorChar)
destDirName = destDirName + Path.DirectorySeparatorChar;
//コピー元のディレクトリにあるファイルをコピー
string[] files = Directory.GetFiles(sourceDirName);
foreach (string file in files)
File.Copy(file,
destDirName + Path.GetFileName(file), true);
//コピー元のディレクトリにあるディレクトリについて、
//再帰的に呼び出す
string[] dirs = Directory.GetDirectories(sourceDirName);
foreach (string dir in dirs)
CopyDirectory(dir, destDirName + Path.GetFileName(dir));
}
#endregion
}
}