UsefullCode.net


Visual Studio 2005/2008/2010やandroid SDK/NDKでの開発者向けに便利なソースコードを提供
This site provide you with useful source codes under 'USEFULLCODE license'.

第03回 subscribeをダウンロードする

2016年04月12日 11:12
0件のコメント

今回はsubscriberへ配信された内容を元に記事をダウンロードします。

気象庁防災情報XMLではsubscriberへ以下のようなfeedが配信されます。
entryの数はfeedによって異なり、実際の記事内容はlinkとしてURLが記載されています。
今回はこのlinkをダウンロードします。

なお、linkのURLは配信から数時間だけ有効で、それを過ぎると404エラーでダウンロードできなくなります。

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
<title>JMAXML publishing feed</title>
<subtitle>this feed is published by JMA</subtitle>
<updated>2016-04-06T17:45:01+09:00</updated>
<id>urn:uuid:d38e0e80-12ba-3236-b10f-256b78a08995</id>
<link href="http://www.jma.go.jp/" rel="related"/>
<link href="http://xml.kishou.go.jp/feed/other.xml" rel="self"/>
<rights>Published by Japan Meteorological Agency</rights>

<entry>
<title>地方海上警報</title>
<id>urn:uuid:e89f7227-becb-3cdf-b7f8-c665434602d4</id>
<updated>2016-04-06T08:44:37Z</updated>
<author><name>鹿児島地方気象台</name></author>
<link href="http://xml.kishou.go.jp/data/e89f7227-becb-3cdf-b7f8-c665434602d4.xml" type="application/xml"/>
<content type="text">【鹿児島海上気象】</content>
</entry>
</feed>
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
<title>JMAXML publishing feed</title>
<subtitle>this feed is published by JMA</subtitle>
<updated>2016-04-06T17:46:01+09:00</updated>
<id>urn:uuid:d38e0e80-12ba-3236-b10f-256b78a08995</id>
<link href="http://www.jma.go.jp/" rel="related"/>
<link href="http://xml.kishou.go.jp/feed/other.xml" rel="self"/>
<rights>Published by Japan Meteorological Agency</rights>

<entry>
<title>地方海上警報</title>
<id>urn:uuid:37eb566f-9585-36d6-8972-d9473d134bf0</id>
<updated>2016-04-06T08:45:25Z</updated>
<author><name>沖縄気象台</name></author>
<link href="http://xml.kishou.go.jp/data/37eb566f-9585-36d6-8972-d9473d134bf0.xml" type="application/xml"/>
<content type="text">【沖縄海上気象】</content>
</entry>
<entry>
<title>生物季節観測</title>
<id>urn:uuid:a43b264a-cefc-3495-9f81-1b315359cbb7</id>
<updated>2016-04-06T08:45:32Z</updated>
<author><name>甲府地方気象台</name></author>
<link href="http://xml.kishou.go.jp/data/a43b264a-cefc-3495-9f81-1b315359cbb7.xml" type="application/xml"/>
<content type="text">【生物季節観測】</content>
</entry>
</feed>



前回は↑のファイルが保存されるようにしました。
今回はこのファイルを読み込んで、linkを抽出、それをファイルへダウンロードします。

まずはプロジェクトを作成します。
前回のソリューションを開き、ソリューションウインドウの「JmaForecast」ソリューションを右クリックして現れたメニューから「追加」にある「新しいプロジェクト」を選択し、
「Visual C#」の「コンソールアプリケーション」を追加します。
ここでプロジェクト名は「jmadownload」、.NETのバージョンは「.NET Framework 4」としました。
さらに「プロジェクト」メニューにある「jmadownloadのプロパティ」を選択し、構成「Release」にたいして、デバッグ」タブにある「Visual Studioホスティングプロセスを有効にする」のチェックをはずしておきます。

これでダウンロード処理を用意し、さらにsubscriber側にjmadownload.exeを呼ぶ処理を追加します。
そして出来上がったexeを2つともサーバーへアップロードすればokです。

■Program.cs(jmadownload.exe)
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Xml;

namespace jmadownload
{
	/// <summary>
	/// 気象庁からpush配信されたxmlデータに記載されているリンクをダウンロードする
	/// 
	/// 呼び出すときにxmlへのファイルパスを渡すこと
	/// </summary>
	class Program
	{
		/// <summary>
		/// argsにファイル名を渡すこと!
		/// </summary>
		static void Main(string[] args)
		{
			if (args.Length == 0)
			{
#if DEBUG
				//デバッグ時のテスト用
				args = new string[1];
				//args[0] = @"..\..\635955615261813750.txt";		//サンプルxml
				args[0] = @"..\..\635955616026657500.txt";			//サンプルxml
#else
				return;
#endif
			}


			try
			{
				List<Entry> listEntry = new List<Entry>();

				//xmlファイルからエントリーリストを取得
				ListUpEntry(args[0], listEntry);

				bool bDownloaded = true;

				foreach (Entry entry in listEntry)
				{
					if (entry.strLink == "" || entry.strTitle == "")
						continue;
					if (entry.strLink.IndexOf(@"http://xml.kishou.go.jp/data/") != 0)		//気象庁のURL以外はダウンロードしない
						continue;

					string strFile;
					string strRootFolder;

					strRootFolder = Path.GetDirectoryName(args[0]) + @"\";

					PrepareFolerFile(entry, strRootFolder, out strFile);					//保存ファイル名の決定/保存先フォルダ作成

					if (strFile == "")
						continue;


					//すでにファイルがあるならダウンロードしない
					if (File.Exists(strFile))
						continue;

					//ブロッキングでダウンロード					try
					{
						bool bSuccess = false;
						for (int i = 0; i < 10; i++)		//ダウンロード試行回数は10回
						{
							using (MemoryStream ms = new MemoryStream())
							{
								bool ret;
								Stream strm = (Stream)ms;

								//メモリストリームへダウンロード								ret = DownloadStream(entry.strLink, ref strm);
								if (ret == false)
									continue;

								//ファイルへ保存
								using (FileStream fs = new FileStream(strFile, FileMode.Create, FileAccess.Write))
								{
									ms.CopyTo(fs);
									fs.Flush();
								}
								bSuccess = true;
								break;		//ダウンロード成功!
							}
						}
						if (bSuccess == false)
							bDownloaded = false;
					}
					catch (Exception)
					{
					}
				}

				if (bDownloaded)
					File.Delete(args[0]);
			}
			catch (Exception)
			{
			}
		}



		/// <summary>
		/// URLをStreamへダウンロードする
		/// </summary>
		static bool DownloadStream(string strURL, ref Stream outStream)
		{
			if (outStream == null)
				outStream = new MemoryStream();

			try
			{
				WebRequest request = WebRequest.Create(strURL);

				request.Method = "GET";

				using (WebResponse response = request.GetResponse())
				using (Stream dataStream = response.GetResponseStream())
				{
					dataStream.CopyTo(outStream);
				}
				if (outStream.CanSeek)
					outStream.Seek(0, SeekOrigin.Begin);
				return true;
			}
			catch (Exception)
			{
			}
			return false;
		}




		class Entry
		{
			public string strTitle
			{
				get { return _strTitle; }
				set { _strTitle = value; }
			}
			string _strTitle = "";

			public string strID
			{
				get { return _strID; }
				set { _strID = value; }
			}
			string _strID = "";

			public DateTime dtUpdated
			{
				get { return _dtUpdated; }
				set { _dtUpdated = value; }
			}
			DateTime _dtUpdated = DateTime.MinValue;

			public string strAuthorName
			{
				get { return _strAuthorName; }
				set { _strAuthorName = value; }
			}
			string _strAuthorName = "";

			public string strLink
			{
				get { return _strLink; }
				set { _strLink = value; }
			}
			string _strLink = "";

			public string strContent
			{
				get { return _strContent; }
				set { _strContent = value; }
			}
			string _strContent = "";



			public bool SetUpdated(string strDate)
			{
				bool ret;
				DateTime dtDate;

				ret = StringToDateTime(strDate, out dtDate);
				if (ret)
					_dtUpdated = dtDate;

				return ret;
			}

		}


		/// <summary>
		/// エントリーの列挙
		/// 
		/// 指定されたファイルパスのサブスクライブxmlを読み込み、中にあるエントリー情報をlistEntryへ追加する
		/// 失敗したらマイナス1
		/// 成功したら取得できたエントリー数を返す
		/// </summary>
		static int ListUpEntry(string strSubscribeXMLFile, List<Entry> listEntry)
		{
			//XML例。取得するのは/feed/entry
			//
			//<?xml version="1.0" encoding="utf-8"?>
			//<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
			//<title>JMAXML publishing feed</title>
			//<subtitle>this feed is published by JMA</subtitle>
			//<updated>2016-04-07T19:01:01+09:00</updated>
			//<id>urn:uuid:f57b5866-0c8c-3c92-9aff-10a715cdf48b</id>
			//<link href="http://www.jma.go.jp/" rel="related"/>
			//<link href="http://xml.kishou.go.jp/feed/extra.xml" rel="self"/>
			//<rights>Published by Japan Meteorological Agency</rights>
			//
			//<entry>
			//<title>気象特別警報・警報・注意報</title>
			//<id>urn:uuid:0e212e5b-fb6f-3409-a8e9-d96854a4346c</id>
			//<updated>2016-04-07T10:00:44Z</updated>
			//<author><name>岡山地方気象台</name></author>
			//<link href="http://xml.kishou.go.jp/data/0e212e5b-fb6f-3409-a8e9-d96854a4346c.xml" type="application/xml"/>
			//<content type="text">【岡山県気象警報・注意報】注意報を解除します。</content>
			//</entry>
			//<entry>
			//<title>気象警報・注意報</title>
			//<id>urn:uuid:1c90ac60-8ab6-3d80-b619-a1b7d9be6b71</id>
			//<updated>2016-04-07T10:00:44Z</updated>
			//<author><name>岡山地方気象台</name></author>
			//<link href="http://xml.kishou.go.jp/data/1c90ac60-8ab6-3d80-b619-a1b7d9be6b71.xml" type="application/xml"/>
			//<content type="text">【岡山県気象警報・注意報】注意報を解除します。</content>
			//</entry>
			//</feed>
			//

			try
			{
				int nCount = 0;

				using (XmlTextReader reader = new XmlTextReader(strSubscribeXMLFile))
				{
					//ネームスペースを無視
					reader.Namespaces = false;

					XmlDocument xml = new XmlDocument();
					xml.Load(reader);


					//エントリーの選択
					XmlNodeList entries = xml.DocumentElement.SelectNodes(@"/feed/entry");

					foreach (XmlNode entry in entries)
					{
						Entry item = new Entry();

						XmlNode node;

						node = entry.SelectSingleNode("title");
						if (node != null)
							item.strTitle = node.InnerText;

						node = entry.SelectSingleNode("id");
						if (node != null)
							item.strID = node.InnerText;

						node = entry.SelectSingleNode("author/name");
						if (node != null)
							item.strAuthorName = node.InnerText;

						node = entry.SelectSingleNode("updated");
						if (node != null)
							item.SetUpdated(node.InnerText);


						node = entry.SelectSingleNode("link");
						if (node != null)
						{
							XmlAttribute type = node.Attributes["type"];
							if (type != null && type.Value == @"application/xml")
							{
								XmlAttribute href = node.Attributes["href"];
								if (href != null)
									item.strLink = href.Value;
							}
						}

						node = entry.SelectSingleNode("content");
						if (node != null)
							item.strContent = node.InnerText;

						//タイトルがなかったら無視
						if (item.strTitle == "")
							continue;

						nCount++;
						listEntry.Add(item);
					}
				}

				return nCount;
			}
			catch (Exception)
			{
				return -1;
			}
		}



		/// <summary>
		/// ファイル名として不適な文字を置き換える
		/// </summary>
		static string ReplaceInvalidChar(string strText)
		{
			char[] pcbInvalid = Path.GetInvalidFileNameChars();


			//IDをファイル名にする
			// →ファイル名に使えない文字を除去
			{
				//ありがちな文字は決め打ちで全角に
				strText = strText.Replace('*', '*');
				strText = strText.Replace('\\', '¥');
				strText = strText.Replace(':', ':');
				strText = strText.Replace('<', '<');
				strText = strText.Replace('>', '>');
				strText = strText.Replace('?', '?');
				strText = strText.Replace('|', '|');

				//使えない文字除去
				foreach (char c in pcbInvalid)
				{
					strText = strText.Replace(c.ToString(), "");
				}
			}

			return strText;
		}



		/// <summary>
		/// フォルダーを作成して、保存すべきファイルパスを返す
		/// 
		/// 保存先はrootfolderの下にyyyymmddフォルダの下、
		/// ファイル名は「タイトル_ID.txt」
		/// </summary>
		static bool PrepareFolerFile(Entry entry, string strRootFolder, out string strFilePath)
		{
			strFilePath = "";
			if (entry == null || entry.strTitle == "" || entry.dtUpdated == DateTime.MinValue || strRootFolder == "")
				return false;

			try
			{
				strFilePath = ReplaceInvalidChar(entry.strTitle) + "_" + ReplaceInvalidChar(entry.strID) + ".txt";

				string strDate = string.Format("{0:0000}{1:00}{2:00}", entry.dtUpdated.Year, entry.dtUpdated.Month, entry.dtUpdated.Day);

				if (strRootFolder.Substring(strRootFolder.Length - 1) != @"\")
					strRootFolder += @"\";

				string strFolder = strRootFolder + strDate + @"\";
				Directory.CreateDirectory(strFolder);

				strFilePath = strFolder + strFilePath;

				return true;
			}
			catch (Exception)
			{
			}
			return false;
		}




		/// <summary>
		/// 「yyyy/mm/dd」「yyyymmdd」「yyyy/mm/dd hh:mm」「yyyy/mm/dd hh:mm:ss」をDateTimeにする
		/// 
		///「2016-04-07T19:01:01+09:00」の形式対応(時差がプラス9時間でなければそれに合わせて変換後返す)
		///「2016-04-07T10:00:44Z」(UTC日時)の形式対応(プラス9時間して返す)
		/// 
		/// yyyymmddの区切りは「年月日」「/」「:」「-」に対応
		/// hhmmssの区切りは「時分秒」「:」に対応
		/// カッコで囲まれた曜日表記に対応 ex. 「(日)」「(水)」「(Wed)」「(sat.)」
		/// </summary>
		public static bool StringToDateTime(string strDate, out DateTime dtDate)
		{
			//「yyyy年mm月dd日」				→「yyyy/mm/dd」
			//「yyyy年mm月dd日hh時mm分」		→「yyyy/mm/dd hh:mm」
			//「yyyy年mm月dd日hh時mm分」		→「yyyy/mm/dd hh:mm」
			//「yyyy年mm月dd日 hh時mm分」		→「yyyy/mm/dd hh:mm」
			//「yyyy年mm月dd日hh時mm分ss秒」	→「yyyy/mm/dd hh:mm:ss」
			//「yyyy年mm月dd日 hh時mm分ss秒」	→「yyyy/mm/dd hh:mm:ss」
			//「yyyy年mm月dd日hh:mm」			→「yyyy/mm/dd hh:mm」
			//「yyyy年mm月dd日 hh:mm」			→「yyyy/mm/dd hh:mm」
			//「yyyy年mm月dd日hh:mm:ss」		→「yyyy/mm/dd hh:mm:ss」
			//「yyyy年mm月dd日 hh:mm:ss」		→「yyyy/mm/dd hh:mm:ss」
			{
				//カッコで囲まれた曜日の除去
				{
					//カッコを全角に統一
					strDate = strDate.Replace("(", "(");
					strDate = strDate.Replace(")", ")");

					//カッコを1つのスペースに変換
					Regex regex = new Regex(@"((.*))");
					MatchCollection matchCol = regex.Matches(strDate);
					if (matchCol.Count > 0)
						strDate = strDate.Replace(matchCol[0].Groups[1].Value, " ");
				}

				strDate = strDate.Replace(" ", " ");	//全角スペースの半角化
				strDate = strDate.Replace("年", "/");
				strDate = strDate.Replace("月", "/");
				strDate = strDate.Replace("日 ", " ");	//後ろにスペースのある「日」はそのまま除去
				strDate = strDate.Replace("時", ":");
				strDate = strDate.Replace("分", ":");
				strDate = strDate.Replace("秒", "");

				//2つ以上のスペースを1つに変換
				while (strDate.IndexOf("  ") >= 0)
				{
					strDate = strDate.Replace("  ", " ");
				}
				//前後のスペースを除去
				{
					strDate = strDate.TrimStart();
					strDate = strDate.TrimEnd();
				}

				//以下の4パターンを考慮して「日」を除去する
				//「yyyy/mm/dd日」→「yyyy/mm/dd」
				//「yyyy/mm/dd日hh:mm」→「yyyy/mm/dd hh:mm」
				//「yyyy/mm/dd日hh:mm:ss」→「yyyy/mm/dd hh:mm:ss」
				//「yyyy/mm/dd日 hh:mm:ss」→「yyyy/mm/dd hh:mm:ss」
				if (strDate.IndexOf("日") > 0)
				{
					strDate = strDate.Replace("日 ", "");			//スペース除去
					Regex regex = new Regex(@"(\d+)日(\d+)");
					MatchCollection matchCol = regex.Matches(strDate);
					if (matchCol.Count > 0)
						strDate = strDate.Replace("日", " ");
					else
						strDate = strDate.Replace("日", "");
				}
			}



			//「yyyy/mm/dd」「yyyy/mm/d」「yyyy/m/dd」「yyyy/m/d」
			if (strDate.Length == 10 || strDate.Length == 9 || strDate.Length == 8)
			{
				Regex regex = new Regex(@"(\d{4})[-/:](\d+)[-/:](\d+)");
				MatchCollection matchCol = regex.Matches(strDate);
				for (int i = 0; i < matchCol.Count; i++)
				{
					if (matchCol[i].Groups.Count == 4)
					{
						try
						{
							dtDate = new DateTime(Int32.Parse(matchCol[i].Groups[1].Value), Int32.Parse(matchCol[i].Groups[2].Value), Int32.Parse(matchCol[i].Groups[3].Value));
							return true;
						}
						catch (Exception)
						{
						}
					}
				}
			}

			//「yyyymmdd」
			if (strDate.Length == 8)
			{
				Regex regex = new Regex(@"(\d{4})(\d{2})(\d{2})");
				MatchCollection matchCol = regex.Matches(strDate);
				for (int i = 0; i < matchCol.Count; i++)
				{
					if (matchCol[i].Groups.Count == 4)
					{
						try
						{
							dtDate = new DateTime(Int32.Parse(matchCol[i].Groups[1].Value), Int32.Parse(matchCol[i].Groups[2].Value), Int32.Parse(matchCol[i].Groups[3].Value));
							return true;
						}
						catch (Exception)
						{
						}
					}
				}
			}

			//「yyyy/mm/dd hh:mm」「yyyy/mm/d hh:mm」「yyyy/m/dd hh:mm」「yyyy/m/d hh:mm」「yyyy/m/d h:mm」
			if (strDate.Length == 16 || strDate.Length == 15 || strDate.Length == 14 || strDate.Length == 13)
			{
				Regex regex = new Regex(@"(\d{4})[-/:](\d+)[-/:](\d+) (\d+):(\d{2})");
				MatchCollection matchCol = regex.Matches(strDate);
				for (int i = 0; i < matchCol.Count; i++)
				{
					if (matchCol[i].Groups.Count == 6)
					{
						try
						{
							dtDate = new DateTime(Int32.Parse(matchCol[i].Groups[1].Value), Int32.Parse(matchCol[i].Groups[2].Value), Int32.Parse(matchCol[i].Groups[3].Value)
									, Int32.Parse(matchCol[i].Groups[4].Value), Int32.Parse(matchCol[i].Groups[5].Value), 0);
							return true;
						}
						catch (Exception)
						{
						}
					}
				}
			}

			//「yyyy/mm/dd hh:mm:ss」「yyyy/m/dd hh:mm:ss」「yyyy/mm/d hh:mm:ss」「yyyy/m/d hh:mm:ss」「yyyy/m/d h:mm:ss」
			if (strDate.Length == 19 || strDate.Length == 18 || strDate.Length == 17 || strDate.Length == 16)
			{
				Regex regex = new Regex(@"(\d{4})[-/:](\d+)[-/:](\d+) (\d+):(\d{2}):(\d{2})");
				MatchCollection matchCol = regex.Matches(strDate);
				for (int i = 0; i < matchCol.Count; i++)
				{
					if (matchCol[i].Groups.Count == 7)
					{
						try
						{
							dtDate = new DateTime(Int32.Parse(matchCol[i].Groups[1].Value), Int32.Parse(matchCol[i].Groups[2].Value), Int32.Parse(matchCol[i].Groups[3].Value)
									, Int32.Parse(matchCol[i].Groups[4].Value), Int32.Parse(matchCol[i].Groups[5].Value), Int32.Parse(matchCol[i].Groups[6].Value));
							return true;
						}
						catch (Exception)
						{
						}
					}
				}
			}


			//「2016-04-07T19:01:01+09:00」の形式
			//
			//「2016-04-07T19:01:01」がオフセット+09:00(日本時間)という意味。戻り値は「2016-04-07 19:01:01」
			//「2016-04-07T19:01:01+00:00」の場合、オフセット+00:00(UTC)という意味。戻り値はJST変換して「2016-04-08 04:01:01」
			if (strDate.Length == 25)
			{
				//Parseですませちゃう
				try
				{
					dtDate = DateTime.Parse(strDate);
					return true;
				}
				catch (Exception)
				{
				}
			}

			//「2016-04-07T10:00:44Z」の形式
			//
			//↑はタイムオフセットゼロ=UTCという意味。日本時間にするため時差9時間プラスして返す
			if (strDate.Length == 20)
			{
				//Parseですませちゃう
				try
				{
					dtDate = DateTime.Parse(strDate);
					return true;
				}
				catch (Exception)
				{
				}
			}

			//Parseしてみる
			try
			{
				dtDate = DateTime.Parse(strDate);
				return true;
			}
			catch (Exception)
			{
			}


			dtDate = DateTime.MinValue;
			return false;
		}

	}
}

次> 最終>>
第02回 subscriberを構築する
第04回 府県天気予報XMLを整形する
トップページに戻る
issei.