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 } }