diff --git a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/BDVideoLibraryManagerXF.Android.csproj b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/BDVideoLibraryManagerXF.Android.csproj
index 89a739e..dbc29c9 100644
--- a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/BDVideoLibraryManagerXF.Android.csproj
+++ b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/BDVideoLibraryManagerXF.Android.csproj
@@ -17,7 +17,7 @@
Resources
Assets
false
- v12.0
+ v13.0
true
true
Xamarin.Android.Net.AndroidClientHandler
@@ -69,7 +69,7 @@
-
+
diff --git a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Properties/AndroidManifest.xml b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Properties/AndroidManifest.xml
index bd548d9..60a94ed 100644
--- a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Properties/AndroidManifest.xml
+++ b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Properties/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Resources/Resource.designer.cs b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Resources/Resource.designer.cs
index 9878531..9748ad4 100644
--- a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Resources/Resource.designer.cs
+++ b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Resources/Resource.designer.cs
@@ -14,7 +14,7 @@ namespace BDVideoLibraryManagerXF.Droid
{
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.1.0.5")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "13.2.1.111")]
public partial class Resource
{
diff --git a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Resources/values/styles.xml b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Resources/values/styles.xml
index ccb544d..5a5e5a3 100644
--- a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Resources/values/styles.xml
+++ b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.Android/Resources/values/styles.xml
@@ -1,19 +1,18 @@
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.UWP/BDVideoLibraryManagerXF.UWP.csproj b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.UWP/BDVideoLibraryManagerXF.UWP.csproj
index ba81d65..4d79737 100644
--- a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.UWP/BDVideoLibraryManagerXF.UWP.csproj
+++ b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.UWP/BDVideoLibraryManagerXF.UWP.csproj
@@ -146,7 +146,7 @@
-
+
diff --git a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.iOS/BDVideoLibraryManagerXF.iOS.csproj b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.iOS/BDVideoLibraryManagerXF.iOS.csproj
index 5adc1d2..87b4de4 100644
--- a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.iOS/BDVideoLibraryManagerXF.iOS.csproj
+++ b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.iOS/BDVideoLibraryManagerXF.iOS.csproj
@@ -124,7 +124,7 @@
-
+
diff --git a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.csproj b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.csproj
index 7a5bff4..930add0 100644
--- a/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.csproj
+++ b/src/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF/BDVideoLibraryManagerXF.csproj
@@ -30,9 +30,9 @@
-
+
-
+
diff --git a/src/VideoLibraryManagerCommon/Library.cs b/src/VideoLibraryManagerCommon/Library.cs
index baaa9dc..af504db 100644
--- a/src/VideoLibraryManagerCommon/Library.cs
+++ b/src/VideoLibraryManagerCommon/Library.cs
@@ -12,416 +12,415 @@
namespace VideoLibraryManagerCommon.Library
{
- public class Library
- {
- public DiskBD[] Contents { get; private set; }
-
- public Library(DiskBD[] contents)
- {
- this.Contents = contents;
- }
-
- public TimeSpan TimeSpan
- {
- get
- {
- var total = new TimeSpan(0);
- foreach (var item in Contents)
- {
- total += item.TimeSpan;
- }
- return total;
- }
- }
-
- public string[] Genres
- {
- get
- {
- if (_Genres != null) return _Genres;
- var result = new List();
- foreach (var disk in this.Contents)
- {
- foreach (var video in disk.Contents)
- {
- if (string.IsNullOrWhiteSpace(video.ProgramGenre)) continue;
- foreach (var genre in video.ProgramGenre.Split(' '))
- {
- if (!result.Contains(genre)) result.Add(genre);
- var mainGenre = genre.Split(' ')[0];
- if (!result.Contains(mainGenre)) result.Add(mainGenre);
- }
- }
- }
- _Genres = result.ToArray();
- Array.Sort(_Genres);
- return _Genres;
- }
- }
- private string[] _Genres;
- }
-
- public class DiskVideoPairList : IList, INotifyPropertyChanged, System.Collections.Specialized.INotifyCollectionChanged
- {
- private List Contents = new List();
-
- public event PropertyChangedEventHandler PropertyChanged;
- public event NotifyCollectionChangedEventHandler CollectionChanged;
-
- private void OnPropertyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
- private void OnCollectionChanged(NotifyCollectionChangedAction Action) { CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(Action)); }
-
- public TimeSpan TimeSpan
- {
- get
- {
- var total = new TimeSpan(0);
- foreach (var item in Contents)
- {
- total += item.Video.Length;
- }
- return total;
- }
- }
-
- public void SetContents(IEnumerable dp)
- {
- this.Contents = dp.ToList();
- OnCollectionChanged(NotifyCollectionChangedAction.Reset);
- OnPropertyChanged(nameof(TimeSpan));
- }
-
- #region IList interface
- public DiskVideoPair this[int index]
- {
- get
- {
- return ((IList)Contents)[index];
- }
-
- set
- {
- ((IList)Contents)[index] = value;
- }
- }
-
- public int Count
- {
- get
- {
- return ((IList)Contents).Count;
- }
- }
-
- public bool IsReadOnly
- {
- get
- {
- return ((IList)Contents).IsReadOnly;
- }
- }
-
- public void Add(DiskVideoPair item)
- {
- ((IList)Contents).Add(item);
- }
-
- public void Clear()
- {
- ((IList)Contents).Clear();
- }
-
- public bool Contains(DiskVideoPair item)
- {
- return ((IList)Contents).Contains(item);
- }
-
- public void CopyTo(DiskVideoPair[] array, int arrayIndex)
- {
- ((IList)Contents).CopyTo(array, arrayIndex);
- }
-
- public IEnumerator GetEnumerator()
- {
- return ((IList)Contents).GetEnumerator();
- }
-
- public int IndexOf(DiskVideoPair item)
- {
- return ((IList)Contents).IndexOf(item);
- }
-
- public void Insert(int index, DiskVideoPair item)
- {
- ((IList)Contents).Insert(index, item);
- }
-
- public bool Remove(DiskVideoPair item)
- {
- return ((IList)Contents).Remove(item);
- }
-
- public void RemoveAt(int index)
- {
- ((IList)Contents).RemoveAt(index);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return ((IList)Contents).GetEnumerator();
- }
- #endregion
- }
- public class DiskVideoPair
- {
- public VideoBD Video { get; private set; }
- public DiskBD Disk { get; private set; }
- public DiskVideoPair(DiskBD disk, VideoBD video)
- {
- this.Video = video;
- this.Disk = disk;
- }
- }
-
-
- public class DiskBD : IEnumerable
- {
- public string DiskTitle { get; private set; }
- public string DiskName { get; private set; }
- public VideoBD[] Contents { get; private set; }
-
- public TimeSpan TimeSpan
- {
- get
- {
- var total = new TimeSpan(0);
- foreach (var item in Contents)
- {
- total += item.Length;
- }
- return total;
- }
- }
-
- public DiskBD()
- {
- DiskTitle = "";
- DiskName = "";
- Contents = new VideoBD[0];
- }
-
- public DiskBD(string title, string name, VideoBD[] contents)
- {
- DiskTitle = title;
- DiskName = name;
- Contents = contents;
- }
-
- public DiskBD(TextReader tr, string DiskName)
- {
- this.DiskName = DiskName;
-
- var parser = new CsvHelper.CsvParser(tr);
- parser.Configuration.HasHeaderRecord = false;
- DiskTitle = parser.Read()[0];
- var result = new Queue();
- while (true)
- {
- var line = parser.Read();
- if (line == null) { Contents = result.ToArray(); return; }
- result.Enqueue(new VideoBD(line));
- }
- }
-
- public IEnumerator GetEnumerator()
- {
- return ((IEnumerable)Contents).GetEnumerator();
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return ((IEnumerable)Contents).GetEnumerator();
- }
- }
-
-
- public class VideoBD
- {
- public DateTime RecordDateTime { get; set; }
- public TimeSpan Length { get; set; }
- public string ChannelName { get; set; }
- public int ChannelNumber { get; set; }
- public string BroadcastType { get; set; }
- public string ProgramTitle { get; set; }
- public string ProgramTitleNormalized { get { return _ProgramTitleNormalized ?? (_ProgramTitleNormalized = NormalizeText(ProgramTitle)); } }
- private string _ProgramTitleNormalized;
- public string ProgramDetail { get; set; }
- public string ProgramDetailNormalized { get { return _ProgramDetailNormalized ?? (_ProgramDetailNormalized = NormalizeText(ProgramDetail)); } }
- private string _ProgramDetailNormalized;
- public string ProgramGenre { get; set; }
-
- private LinkedText[] _Links = null;
- public LinkedText[] Links => _Links ?? LinkedText.GetFromText(ProgramDetail, ProgramDetailNormalized);
-
- public static string NormalizeText(string s)
- {
- s = s.ToLower().Replace('―', '-').Replace('ー', '-').Replace("・", "").Replace(' ', ' ').Replace(":", ":");
- s = Regex.Replace(s, "[0-9]", p => ((char)(p.Value[0] - '0' + '0')).ToString());
- s = Regex.Replace(s, "[a-z]", p => ((char)(p.Value[0] - 'a' + 'a')).ToString());
- s = Regex.Replace(s, "[A-Z]", p => ((char)(p.Value[0] - 'A' + 'A')).ToString());
- return s;
- }
-
- public VideoBD()
- {
- RecordDateTime = DateTime.MaxValue;
- ChannelName = "";
- ChannelNumber = -1;
- BroadcastType = "";
- ProgramTitle = "";
- ProgramDetail = "";
- ProgramGenre = "";
- }
-
- public VideoBD(string[] CsvEntry)
- {
- var date = DateTime.Parse(CsvEntry[3]);
- var time = DateTime.Parse(CsvEntry[4]);
- this.RecordDateTime = date.Date + time.TimeOfDay;
- this.Length = TimeSpan.Parse(CsvEntry[5]);
- this.ChannelName = CsvEntry[9];
- this.ChannelNumber = int.Parse(CsvEntry[10].Substring(0, CsvEntry[10].Length - 2));
- this.BroadcastType = CsvEntry[11];
- this.ProgramTitle = CsvEntry[12];
- this.ProgramDetail = CsvEntry[13];
- this.ProgramGenre = CsvEntry[15];
- }
- }
-
- public class LinkedText
- {
- private const string PatternHttpCharsInside = @"\w!?/+\-_~;.,*&@#$%()'[\]";
- public const string PatternHttpChars = "[" + PatternHttpCharsInside + "]";
- public const string PatternHttpCharsNot = "[^" + PatternHttpCharsInside + "]";
-
- private static Regex _RegexPhone = null;
- public static Regex RegexPhone => _RegexPhone = _RegexPhone ?? new Regex(@"(0\d{1,4})[\-\(](\d{1,4})[\-\)](\d{3,4})", RegexOptions.Compiled);
- //下だと末尾にマッチしなくて対策も面倒なので上にしました。
- //public static Regex RegexPhone => _RegexPhone = _RegexPhone ?? new Regex(@"(?<=[^\d])(0\d{1,4})[\-\(](\d{1,4})[\-\)](\d{3,4})(?=[^\d])", RegexOptions.Compiled);
-
- private static Regex _RegexHttp1 = null;
- public static Regex RegexHttp1 => _RegexHttp1 = _RegexHttp1 ?? new Regex($@"https?://{PatternHttpChars}+", RegexOptions.Compiled);
-
- private static Regex _RegexHttp2 = null;
- public static Regex RegexHttp2 => _RegexHttp2 = _RegexHttp2 ?? new Regex($@"www\.{PatternHttpChars}+", RegexOptions.Compiled);
-
- //private static Regex _RegexHttp3 = null;
- //public static Regex RegexHttp3 => _RegexHttp3 = _RegexHttp3 ?? new Regex($@"{RegexHttpChars}+\.(?:jp|com|gov|net|co|org)(?:/{RegexHttpChars}+|)(?=[^\w!?/+\-_~;.,*&@#$%()'[\]])", RegexOptions.Compiled);
-
- //バックトラックで処理速度が遅くなるのを避けるために二段階に分けました。
- private static Regex _RegexHttp3 = null;
- public static Regex RegexHttp3 => _RegexHttp3 = _RegexHttp3 ?? new Regex($@"\.(?:jp|com|gov|net|co|org)(?:/{PatternHttpChars}+|)(?={PatternHttpCharsNot})", RegexOptions.Compiled);
-
- private static Regex _RegexSearch = null;
- public static Regex RegexSearch => _RegexSearch = _RegexSearch ?? new Regex(@"[「『""]([^」]+)[」』""][でを\s]?検索", RegexOptions.Compiled);
-
- public const string SearchUrl = "https://www.google.com/search?q={0}";
-
-
- //private static Regex _RegexHttp3Pre = null;
- //public static Regex RegexHttp3Pre => _RegexHttp3Pre = _RegexHttp3Pre ?? new Regex($@"\.(?:jp|com|gov|net|co|org)", RegexOptions.Compiled);
-
- public LinkedText(string text, string textFull, LinkedTextType type)
- {
- Type = type;
- Text = text ?? throw new ArgumentNullException(nameof(text));
- TextFull = textFull ?? throw new ArgumentNullException(nameof(textFull));
- }
-
- public LinkedTextType Type { get; set; }
-
- public string Text { get; set; }
-
- public string TextFull { get; set; }
-
- public static LinkedText[] GetFromText(string text, string noramalizedText)
- {
- var result = new List<(LinkedText, int)>();
-
- //System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
- //System.Diagnostics.Debug.WriteLine("_____");
- //sw.Start();
- //sw.Stop();
- //System.Diagnostics.Debug.WriteLine(sw.ElapsedMilliseconds);
- //sw.Restart();
- {
- //var matches = Regex.Matches(text, @"(?:[^\d])(0\d{1,4})\-(\d{1,4})\-(\d{4})(?:[^\d])");
- var matches = RegexPhone.Matches(noramalizedText);
- foreach (Match item in matches)
- {
- if ((item.Groups[1].Value.Length + item.Groups[2].Value.Length) == 5 && item.Groups[3].Length == 4)
- {
- //固定電話
- //https://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/q_and_a.html#q2
- }
- else if (Regex.IsMatch(item.Groups[1].Value, @"0[2-9]0") && item.Groups[2].Value.Length == 4 && item.Groups[3].Length == 4)
- {
- //携帯電話その他
- //010は国際電話に使うようだが、番組情報で出て来る可能性はないだろう。
- }
- else
- {
- //他に0120とか色々あるので結局素通し。
- }
-
- result.Add((new LinkedText(item.Value, item.Value, LinkedTextType.PhoneNumber), item.Index));
- }
- }
- {
- var matches = RegexHttp1.Matches(noramalizedText);
- foreach (Match item in matches)
- {
- if (!item.Value.Contains('.')) continue;
- result.Add((new LinkedText(item.Value, item.Value, LinkedTextType.Http), item.Index));
- }
- }
- {
- var matches = RegexHttp2.Matches(noramalizedText);
- foreach (Match item in matches)
- {
- if (!item.Value.Contains('.')) continue;
- if (result.Any(a => a.Item1.Text.Contains(item.Value))) continue;
- result.Add((new LinkedText(item.Value, $"https://{item.Value}", LinkedTextType.HttpAssumption), item.Index));
- }
- }
- {
- var matches = RegexHttp3.Matches(noramalizedText);
- foreach (Match item in matches)
- {
- var matches2 = Regex.Matches(noramalizedText, $@"{PatternHttpChars}+{Regex.Escape(item.Value)}");
- foreach (Match item2 in matches2)
- {
- if (result.Any(a => a.Item1.Text.Contains(item2.Value))) continue;
- result.Add((new LinkedText(item2.Value, $"https://{item2.Value}", LinkedTextType.HttpAssumption), item2.Index));
- }
- }
- }
- {
- var matches = RegexSearch.Matches(text);
- foreach (Match item in matches)
- {
- var uri = string.Format(SearchUrl, System.Web.HttpUtility.UrlEncode(item.Groups[1].Value));
- if (result.Any(a => a.Item1.Text.Contains(item.Value))) continue;
- result.Add((new LinkedText(item.Value, uri, LinkedTextType.Search), item.Index));
- }
- }
-
- return result.OrderBy(a => a.Item2).Select(a => a.Item1).ToArray();
- }
- }
-
- public enum LinkedTextType
- {
- PhoneNumber, Http, HttpAssumption, Search
- }
+ public class Library
+ {
+ public DiskBD[] Contents { get; private set; }
+
+ public Library(DiskBD[] contents)
+ {
+ this.Contents = contents;
+ }
+
+ public TimeSpan TimeSpan
+ {
+ get
+ {
+ var total = new TimeSpan(0);
+ foreach (var item in Contents)
+ {
+ total += item.TimeSpan;
+ }
+ return total;
+ }
+ }
+
+ public string[] Genres
+ {
+ get
+ {
+ if (_Genres != null) return _Genres;
+ var result = new List();
+ foreach (var disk in this.Contents)
+ {
+ foreach (var video in disk.Contents)
+ {
+ if (string.IsNullOrWhiteSpace(video.ProgramGenre)) continue;
+ foreach (var genre in video.ProgramGenre.Split(' '))
+ {
+ if (!result.Contains(genre)) result.Add(genre);
+ var mainGenre = genre.Split(' ')[0];
+ if (!result.Contains(mainGenre)) result.Add(mainGenre);
+ }
+ }
+ }
+ _Genres = result.ToArray();
+ Array.Sort(_Genres);
+ return _Genres;
+ }
+ }
+ private string[] _Genres;
+ }
+
+ public class DiskVideoPairList : IList, INotifyPropertyChanged, System.Collections.Specialized.INotifyCollectionChanged
+ {
+ private List Contents = new List();
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ private void OnPropertyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
+ private void OnCollectionChanged(NotifyCollectionChangedAction Action) { CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(Action)); }
+
+ public TimeSpan TimeSpan
+ {
+ get
+ {
+ var total = new TimeSpan(0);
+ foreach (var item in Contents)
+ {
+ total += item.Video.Length;
+ }
+ return total;
+ }
+ }
+
+ public void SetContents(IEnumerable dp)
+ {
+ this.Contents = dp.ToList();
+ OnCollectionChanged(NotifyCollectionChangedAction.Reset);
+ OnPropertyChanged(nameof(TimeSpan));
+ }
+
+ #region IList interface
+ public DiskVideoPair this[int index]
+ {
+ get
+ {
+ return ((IList)Contents)[index];
+ }
+
+ set
+ {
+ ((IList)Contents)[index] = value;
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return ((IList)Contents).Count;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return ((IList)Contents).IsReadOnly;
+ }
+ }
+
+ public void Add(DiskVideoPair item)
+ {
+ ((IList)Contents).Add(item);
+ }
+
+ public void Clear()
+ {
+ ((IList)Contents).Clear();
+ }
+
+ public bool Contains(DiskVideoPair item)
+ {
+ return ((IList)Contents).Contains(item);
+ }
+
+ public void CopyTo(DiskVideoPair[] array, int arrayIndex)
+ {
+ ((IList)Contents).CopyTo(array, arrayIndex);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return ((IList)Contents).GetEnumerator();
+ }
+
+ public int IndexOf(DiskVideoPair item)
+ {
+ return ((IList)Contents).IndexOf(item);
+ }
+
+ public void Insert(int index, DiskVideoPair item)
+ {
+ ((IList)Contents).Insert(index, item);
+ }
+
+ public bool Remove(DiskVideoPair item)
+ {
+ return ((IList)Contents).Remove(item);
+ }
+
+ public void RemoveAt(int index)
+ {
+ ((IList)Contents).RemoveAt(index);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IList)Contents).GetEnumerator();
+ }
+ #endregion
+ }
+ public class DiskVideoPair
+ {
+ public VideoBD Video { get; private set; }
+ public DiskBD Disk { get; private set; }
+ public DiskVideoPair(DiskBD disk, VideoBD video)
+ {
+ this.Video = video;
+ this.Disk = disk;
+ }
+ }
+
+
+ public class DiskBD : IEnumerable
+ {
+ public string DiskTitle { get; private set; }
+ public string DiskName { get; private set; }
+ public VideoBD[] Contents { get; private set; }
+
+ public TimeSpan TimeSpan
+ {
+ get
+ {
+ var total = new TimeSpan(0);
+ foreach (var item in Contents)
+ {
+ total += item.Length;
+ }
+ return total;
+ }
+ }
+
+ public DiskBD()
+ {
+ DiskTitle = "";
+ DiskName = "";
+ Contents = new VideoBD[0];
+ }
+
+ public DiskBD(string title, string name, VideoBD[] contents)
+ {
+ DiskTitle = title;
+ DiskName = name;
+ Contents = contents;
+ }
+
+ public DiskBD(TextReader tr, string DiskName)
+ {
+ this.DiskName = DiskName;
+
+ var parser = new CsvHelper.CsvParser(tr, new CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture) { HasHeaderRecord = false });
+ DiskTitle = parser.Read()[0];
+ var result = new Queue();
+ while (true)
+ {
+ var line = parser.Read();
+ if (line == null) { Contents = result.ToArray(); return; }
+ result.Enqueue(new VideoBD(line));
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)Contents).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)Contents).GetEnumerator();
+ }
+ }
+
+
+ public class VideoBD
+ {
+ public DateTime RecordDateTime { get; set; }
+ public TimeSpan Length { get; set; }
+ public string ChannelName { get; set; }
+ public int ChannelNumber { get; set; }
+ public string BroadcastType { get; set; }
+ public string ProgramTitle { get; set; }
+ public string ProgramTitleNormalized { get { return _ProgramTitleNormalized ?? (_ProgramTitleNormalized = NormalizeText(ProgramTitle)); } }
+ private string _ProgramTitleNormalized;
+ public string ProgramDetail { get; set; }
+ public string ProgramDetailNormalized { get { return _ProgramDetailNormalized ?? (_ProgramDetailNormalized = NormalizeText(ProgramDetail)); } }
+ private string _ProgramDetailNormalized;
+ public string ProgramGenre { get; set; }
+
+ private LinkedText[] _Links = null;
+ public LinkedText[] Links => _Links ?? LinkedText.GetFromText(ProgramDetail, ProgramDetailNormalized);
+
+ public static string NormalizeText(string s)
+ {
+ s = s.ToLower().Replace('―', '-').Replace('ー', '-').Replace("・", "").Replace(' ', ' ').Replace(":", ":");
+ s = Regex.Replace(s, "[0-9]", p => ((char)(p.Value[0] - '0' + '0')).ToString());
+ s = Regex.Replace(s, "[a-z]", p => ((char)(p.Value[0] - 'a' + 'a')).ToString());
+ s = Regex.Replace(s, "[A-Z]", p => ((char)(p.Value[0] - 'A' + 'A')).ToString());
+ return s;
+ }
+
+ public VideoBD()
+ {
+ RecordDateTime = DateTime.MaxValue;
+ ChannelName = "";
+ ChannelNumber = -1;
+ BroadcastType = "";
+ ProgramTitle = "";
+ ProgramDetail = "";
+ ProgramGenre = "";
+ }
+
+ public VideoBD(string[] CsvEntry)
+ {
+ var date = DateTime.Parse(CsvEntry[3]);
+ var time = DateTime.Parse(CsvEntry[4]);
+ this.RecordDateTime = date.Date + time.TimeOfDay;
+ this.Length = TimeSpan.Parse(CsvEntry[5]);
+ this.ChannelName = CsvEntry[9];
+ this.ChannelNumber = int.Parse(CsvEntry[10].Substring(0, CsvEntry[10].Length - 2));
+ this.BroadcastType = CsvEntry[11];
+ this.ProgramTitle = CsvEntry[12];
+ this.ProgramDetail = CsvEntry[13];
+ this.ProgramGenre = CsvEntry[15];
+ }
+ }
+
+ public class LinkedText
+ {
+ private const string PatternHttpCharsInside = @"\w!?/+\-_~;.,*&@#$%()'[\]";
+ public const string PatternHttpChars = "[" + PatternHttpCharsInside + "]";
+ public const string PatternHttpCharsNot = "[^" + PatternHttpCharsInside + "]";
+
+ private static Regex _RegexPhone = null;
+ public static Regex RegexPhone => _RegexPhone = _RegexPhone ?? new Regex(@"(0\d{1,4})[\-\(](\d{1,4})[\-\)](\d{3,4})", RegexOptions.Compiled);
+ //下だと末尾にマッチしなくて対策も面倒なので上にしました。
+ //public static Regex RegexPhone => _RegexPhone = _RegexPhone ?? new Regex(@"(?<=[^\d])(0\d{1,4})[\-\(](\d{1,4})[\-\)](\d{3,4})(?=[^\d])", RegexOptions.Compiled);
+
+ private static Regex _RegexHttp1 = null;
+ public static Regex RegexHttp1 => _RegexHttp1 = _RegexHttp1 ?? new Regex($@"https?://{PatternHttpChars}+", RegexOptions.Compiled);
+
+ private static Regex _RegexHttp2 = null;
+ public static Regex RegexHttp2 => _RegexHttp2 = _RegexHttp2 ?? new Regex($@"www\.{PatternHttpChars}+", RegexOptions.Compiled);
+
+ //private static Regex _RegexHttp3 = null;
+ //public static Regex RegexHttp3 => _RegexHttp3 = _RegexHttp3 ?? new Regex($@"{RegexHttpChars}+\.(?:jp|com|gov|net|co|org)(?:/{RegexHttpChars}+|)(?=[^\w!?/+\-_~;.,*&@#$%()'[\]])", RegexOptions.Compiled);
+
+ //バックトラックで処理速度が遅くなるのを避けるために二段階に分けました。
+ private static Regex _RegexHttp3 = null;
+ public static Regex RegexHttp3 => _RegexHttp3 = _RegexHttp3 ?? new Regex($@"\.(?:jp|com|gov|net|co|org)(?:/{PatternHttpChars}+|)(?={PatternHttpCharsNot})", RegexOptions.Compiled);
+
+ private static Regex _RegexSearch = null;
+ public static Regex RegexSearch => _RegexSearch = _RegexSearch ?? new Regex(@"[「『""]([^」]+)[」』""][でを\s]?検索", RegexOptions.Compiled);
+
+ public const string SearchUrl = "https://www.google.com/search?q={0}";
+
+
+ //private static Regex _RegexHttp3Pre = null;
+ //public static Regex RegexHttp3Pre => _RegexHttp3Pre = _RegexHttp3Pre ?? new Regex($@"\.(?:jp|com|gov|net|co|org)", RegexOptions.Compiled);
+
+ public LinkedText(string text, string textFull, LinkedTextType type)
+ {
+ Type = type;
+ Text = text ?? throw new ArgumentNullException(nameof(text));
+ TextFull = textFull ?? throw new ArgumentNullException(nameof(textFull));
+ }
+
+ public LinkedTextType Type { get; set; }
+
+ public string Text { get; set; }
+
+ public string TextFull { get; set; }
+
+ public static LinkedText[] GetFromText(string text, string noramalizedText)
+ {
+ var result = new List<(LinkedText, int)>();
+
+ //System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
+ //System.Diagnostics.Debug.WriteLine("_____");
+ //sw.Start();
+ //sw.Stop();
+ //System.Diagnostics.Debug.WriteLine(sw.ElapsedMilliseconds);
+ //sw.Restart();
+ {
+ //var matches = Regex.Matches(text, @"(?:[^\d])(0\d{1,4})\-(\d{1,4})\-(\d{4})(?:[^\d])");
+ var matches = RegexPhone.Matches(noramalizedText);
+ foreach (Match item in matches)
+ {
+ if ((item.Groups[1].Value.Length + item.Groups[2].Value.Length) == 5 && item.Groups[3].Length == 4)
+ {
+ //固定電話
+ //https://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/q_and_a.html#q2
+ }
+ else if (Regex.IsMatch(item.Groups[1].Value, @"0[2-9]0") && item.Groups[2].Value.Length == 4 && item.Groups[3].Length == 4)
+ {
+ //携帯電話その他
+ //010は国際電話に使うようだが、番組情報で出て来る可能性はないだろう。
+ }
+ else
+ {
+ //他に0120とか色々あるので結局素通し。
+ }
+
+ result.Add((new LinkedText(item.Value, item.Value, LinkedTextType.PhoneNumber), item.Index));
+ }
+ }
+ {
+ var matches = RegexHttp1.Matches(noramalizedText);
+ foreach (Match item in matches)
+ {
+ if (!item.Value.Contains('.')) continue;
+ result.Add((new LinkedText(item.Value, item.Value, LinkedTextType.Http), item.Index));
+ }
+ }
+ {
+ var matches = RegexHttp2.Matches(noramalizedText);
+ foreach (Match item in matches)
+ {
+ if (!item.Value.Contains('.')) continue;
+ if (result.Any(a => a.Item1.Text.Contains(item.Value))) continue;
+ result.Add((new LinkedText(item.Value, $"https://{item.Value}", LinkedTextType.HttpAssumption), item.Index));
+ }
+ }
+ {
+ var matches = RegexHttp3.Matches(noramalizedText);
+ foreach (Match item in matches)
+ {
+ var matches2 = Regex.Matches(noramalizedText, $@"{PatternHttpChars}+{Regex.Escape(item.Value)}");
+ foreach (Match item2 in matches2)
+ {
+ if (result.Any(a => a.Item1.Text.Contains(item2.Value))) continue;
+ result.Add((new LinkedText(item2.Value, $"https://{item2.Value}", LinkedTextType.HttpAssumption), item2.Index));
+ }
+ }
+ }
+ {
+ var matches = RegexSearch.Matches(text);
+ foreach (Match item in matches)
+ {
+ var uri = string.Format(SearchUrl, System.Web.HttpUtility.UrlEncode(item.Groups[1].Value));
+ if (result.Any(a => a.Item1.Text.Contains(item.Value))) continue;
+ result.Add((new LinkedText(item.Value, uri, LinkedTextType.Search), item.Index));
+ }
+ }
+
+ return result.OrderBy(a => a.Item2).Select(a => a.Item1).ToArray();
+ }
+ }
+
+ public enum LinkedTextType
+ {
+ PhoneNumber, Http, HttpAssumption, Search
+ }
}
diff --git a/src/VideoLibraryManagerCommon/VideoLibraryManagerCommon.csproj b/src/VideoLibraryManagerCommon/VideoLibraryManagerCommon.csproj
index b4c09a2..4f24936 100644
--- a/src/VideoLibraryManagerCommon/VideoLibraryManagerCommon.csproj
+++ b/src/VideoLibraryManagerCommon/VideoLibraryManagerCommon.csproj
@@ -1,11 +1,11 @@
-
-
-
- netstandard2.0
-
-
-
-
-
-
-
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+