using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.IO; using System.Threading; namespace PriFormat { internal enum SearchState { Pending, Searching, Found, NotFound } internal sealed class SearchTask { public PriPath Path { get; } public BaseResources Result { get; set; } public SearchState State { get; set; } public SearchTask (PriPath path) { Path = path; State = SearchState.Pending; } } public sealed class PriReader: IDisposable { private PriFile _pri; private Stream _stream; private readonly bool _fromFile; private readonly object _lock = new object (); private readonly Dictionary _tasks = new Dictionary (); private Thread _searchThread; private bool _searchRunning; private bool _disposed; private readonly AutoResetEvent _searchWakeup = new AutoResetEvent (false); private PriReader (string filePath) { _fromFile = true; _stream = new FileStream (filePath, FileMode.Open, FileAccess.Read, FileShare.Read); _pri = PriFile.Parse (_stream); } private PriReader (Stream stream) { _fromFile = false; _stream = stream; _pri = PriFile.Parse (stream); } public static PriReader Open (string filePath) => new PriReader (filePath); public static PriReader Open (Stream stream) => new PriReader (stream); public void AddSearch (IEnumerable resourceNames) { if (resourceNames == null) return; bool added = false; lock (_lock) { foreach (var name in resourceNames) { if (string.IsNullOrEmpty (name)) continue; var path = new PriResourceIdentifier (name).Path; if (!_tasks.ContainsKey (path)) { _tasks [path] = new SearchTask (path); added = true; } } } if (added) EnsureSearchThread (); } public void AddSearch (string resname) { AddSearch (new string [] { resname }); } private void EnsureSearchThread () { lock (_lock) { if (_disposed) return; if (_searchRunning) return; _searchRunning = true; _searchThread = new Thread (SearchThreadProc); _searchThread.IsBackground = true; _searchThread.Start (); } } private void SearchThreadProc () { try { while (true) { if (_disposed) return; bool hasPending = false; lock (_lock) { foreach (var task in _tasks.Values) { if (task.State == SearchState.Pending) { hasPending = true; break; } } } if (!hasPending) { // 没任务了,休眠等待唤醒 _searchWakeup.WaitOne (200); continue; } // 真正跑一次搜索 RunSearch (TimeSpan.FromSeconds (10)); } } finally { lock (_lock) { _searchRunning = false; _searchThread = null; } } } public BaseResources GetValue (string resourceName) { if (string.IsNullOrEmpty (resourceName)) return null; var path = new PriResourceIdentifier (resourceName).Path; SearchTask task; lock (_lock) { if (!_tasks.TryGetValue (path, out task)) { task = new SearchTask (path); _tasks [path] = task; EnsureSearchThread (); } } // 已有结果 if (task.State == SearchState.Found) return task.Result; // 等待搜索完成 while (true) { if (_disposed) return null; lock (_lock) { if (task.State == SearchState.Found) return task.Result; if (task.State == SearchState.NotFound) return null; } Thread.Sleep (50); } } public void RunSearch (TimeSpan timeout) { var begin = DateTime.Now; foreach (var rmsRef in _pri.PriDescriptorSection.ResourceMapSections) { var rms = _pri.GetSectionByRef (rmsRef); if (rms == null || rms.HierarchicalSchemaReference != null) continue; var decision = _pri.GetSectionByRef (rms.DecisionInfoSection); foreach (var candidateSet in rms.CandidateSets.Values) { if (DateTime.Now - begin > timeout) return; var item = _pri.GetResourceMapItemByRef (candidateSet.ResourceMapItem); var fullName = item.FullName.Trim ('\\'); var parts = fullName.Split (new [] { '\\' }, StringSplitOptions.RemoveEmptyEntries); var itemPath = new PriPath (fullName, true); SearchTask task; lock (_lock) { if (!_tasks.TryGetValue (itemPath, out task)) continue; if (task.State != SearchState.Pending) continue; task.State = SearchState.Searching; } var result = ReadCandidate (candidateSet, decision); lock (_lock) { task.Result = result; task.State = result != null ? SearchState.Found : SearchState.NotFound; } } } lock (_lock) { foreach (var kv in _tasks) { if (kv.Value.State == SearchState.Pending) { kv.Value.State = SearchState.NotFound; } } } if (DateTime.Now - begin > timeout) return; } private BaseResources ReadCandidate ( CandidateSet candidateSet, DecisionInfoSection decisionInfo) { string value = System.String.Empty; int restype = 0; // 0 string, 1 file Dictionary strdict = new Dictionary (); Dictionary filedict = new Dictionary (); foreach (var candidate in candidateSet.Candidates) { if (candidate.SourceFile != null) { } else { var byteSpan = new ByteSpan (); if (candidate.DataItem != null) byteSpan = _pri.GetDataItemByRef (candidate.DataItem); else byteSpan = candidate.Data; _stream.Seek (byteSpan.Offset, SeekOrigin.Begin); var binaryReader = new BinaryReader (_stream, Encoding.Default); { var data = binaryReader.ReadBytes ((int)byteSpan.Length); switch (candidate.Type) { case ResourceValueType.AsciiPath: case ResourceValueType.AsciiString: value = Encoding.ASCII.GetString (data).TrimEnd ('\0'); break; case ResourceValueType.Utf8Path: case ResourceValueType.Utf8String: value = Encoding.UTF8.GetString (data).TrimEnd ('\0'); break; case ResourceValueType.Path: case ResourceValueType.String: value = Encoding.Unicode.GetString (data).TrimEnd ('\0'); break; case ResourceValueType.EmbeddedData: value = Convert.ToBase64String (data); break; } } } var qualifierSet = decisionInfo.QualifierSets [candidate.QualifierSet]; var qualis = new Dictionary (); foreach (var quali in qualifierSet.Qualifiers) { var qtype = quali.Type; var qvalue = quali.Value; qualis.Add (qtype, qvalue); } if (qualis.ContainsKey (QualifierType.Language)) { restype = 0; strdict.Add (new StringQualifier (qualis [QualifierType.Language].ToString ()), value); } else { restype = 1; if (qualis.ContainsKey (QualifierType.Scale)) { var cons = qualis.ContainsKey (QualifierType.Contrast) ? qualis [QualifierType.Contrast].ToString () : "None"; Contrast cs = Contrast.None; switch (cons?.Trim ()?.ToLower ()) { case "white": cs = Contrast.White; break; case "black": cs = Contrast.Black; break; case "high": cs = Contrast.High; break; case "low": cs = Contrast.Low; break; case "none": cs = Contrast.None; break; } filedict.Add (new FileQualifier (Convert.ToInt32 (qualis [QualifierType.Scale]), cs), value); } } } if (strdict.Count > 0 && filedict.Count > 0) { if (strdict.Count >= filedict.Count) return new StringResources (strdict, true); else return new FileResources (filedict, true); } else if (strdict.Count > 0) return new StringResources (strdict, true); else if (filedict.Count > 0) return new FileResources (filedict, true); return new StringResources (); } public void Dispose () { _disposed = true; _searchWakeup.Set (); lock (_lock) { _tasks.Clear (); } _pri?.Dispose (); _pri = null; if (_fromFile) _stream?.Dispose (); _stream = null; } public string Resource (string resname) { return GetValue (resname)?.SuitableValue ?? ""; } public Dictionary Resources (IEnumerable list) { var ret = new Dictionary (); AddSearch (list); foreach (var item in list) ret [item] = Resource (item); return ret; } public string Path (string resname) => Resource (resname); public Dictionary Paths (IEnumerable resnames) => Resources (resnames); public string String (string resname) => Resource (resname); public Dictionary Strings (IEnumerable resnames) => Resources (resnames); } public sealed class PriReaderBundle: IDisposable { private sealed class PriInst { // 0b01 = lang, 0b10 = scale, 0b11 = both public readonly byte Type; public PriReader Reader; public PriInst (byte type, Stream stream) { Type = (byte)(type & 0x03); Reader = PriReader.Open (stream); } public bool IsValid { get { return (Type & 0x03) != 0 && Reader != null; } } } private readonly List _priFiles = new List (3); private readonly Dictionary _mapPri = new Dictionary (); // ----------------------------- // Set // ----------------------------- // type: 1 = language, 2 = scale, 3 = both public bool Set (byte type, Stream priStream) { byte realType = (byte)(type & 0x03); if (realType == 0 || priStream == null) return false; PriInst inst; if (_mapPri.TryGetValue (realType, out inst)) { inst.Reader.Dispose (); inst.Reader = PriReader.Open (priStream); } else { inst = new PriInst (realType, priStream); _priFiles.Add (inst); _mapPri [realType] = inst; } return true; } // 如果你外部仍然是 IStream / IntPtr,这里假定你已有封装成 Stream 的工具 public bool Set (byte type, IntPtr priStream) { if (priStream == IntPtr.Zero) return false; Stream stream = StreamHelper.FromIStream (priStream); return Set (type, stream); } // ----------------------------- // 内部路由 // ----------------------------- private PriReader Get (byte type, bool mustReturn) { type = (byte)(type & 0x03); PriInst inst; if (_mapPri.TryGetValue (type, out inst)) return inst.Reader; // fallback: both if (type != 0x03 && _mapPri.TryGetValue (0x03, out inst)) return inst.Reader; if (mustReturn && _priFiles.Count > 0) return _priFiles [0].Reader; return null; } private static bool IsMsResourcePrefix (string s) { try { return MSRUriHelper.IsMsResourceUri (s) || MSRUriHelper.IsFullMsResourceUri (s) || MSRUriHelper.IsRelativeMsResourceUri (s); } catch { return MSRUriHelper.IsMsResourceUri (s); } } // ----------------------------- // AddSearch // ----------------------------- public void AddSearch (IEnumerable arr) { if (arr == null) return; List langRes = new List (); List scaleRes = new List (); foreach (string it in arr) { if (string.IsNullOrEmpty (it)) continue; if (IsMsResourcePrefix (it)) langRes.Add (it); else scaleRes.Add (it); } PriReader langPri = Get (1, true); PriReader scalePri = Get (2, true); if (langPri != null && langRes.Count > 0) langPri.AddSearch (langRes); if (scalePri != null && scaleRes.Count > 0) scalePri.AddSearch (scaleRes); } public void AddSearch (string resName) { if (string.IsNullOrEmpty (resName)) return; if (IsMsResourcePrefix (resName)) { PriReader langPri = Get (1, true); if (langPri != null) langPri.AddSearch (resName); } else { PriReader scalePri = Get (2, true); if (scalePri != null) scalePri.AddSearch (resName); } } // ----------------------------- // Resource / Path / String // ----------------------------- public string Resource (string resName) { if (string.IsNullOrEmpty (resName)) return string.Empty; PriReader reader; if (IsMsResourcePrefix (resName)) reader = Get (1, true); else reader = Get (2, true); if (reader == null) return string.Empty; var res = reader.GetValue (resName); return res != null ? res.ToString () : string.Empty; } public Dictionary Resources (IEnumerable resNames) { if (resNames == null) throw new ArgumentNullException ("resNames"); AddSearch (resNames); Dictionary result = new Dictionary (); foreach (string name in resNames) result [name] = Resource (name); return result; } public string Path (string resName) { return Resource (resName); } public Dictionary Paths (IEnumerable resNames) { return Resources (resNames); } public string String (string resName) { return Resource (resName); } public Dictionary Strings (IEnumerable resNames) { return Resources (resNames); } public void Dispose () { foreach (var inst in _priFiles) { inst.Reader.Dispose (); } _mapPri.Clear (); _priFiles.Clear (); } } public abstract class BaseQualifier { } public class StringQualifier: BaseQualifier, IEquatable, IEquatable { public string LocaleName { get; private set; } public int LCID => CultureInfo.GetCultureInfo (LocaleName).LCID; public StringQualifier (string localeName) { LocaleName = localeName; } public StringQualifier (int lcid) { LocaleName = CultureInfo.GetCultureInfo (lcid).Name; } public override string ToString () { return $"String Qualifier: {LocaleName} ({LCID})"; } public bool Equals (StringQualifier other) { var ca = new CultureInfo (this.LocaleName); var cb = new CultureInfo (other.LocaleName); return string.Equals (ca.Name, cb.Name, StringComparison.OrdinalIgnoreCase); } public bool Equals (string other) { return this.Equals (new StringQualifier (other)); } public override int GetHashCode () => CultureInfo.GetCultureInfo (LocaleName).Name.GetHashCode (); } public enum Contrast { None, Black, White, High, Low }; public class FileQualifier: BaseQualifier, IEquatable, IEquatable, IEquatable> { public Contrast Contrast { get; private set; } = Contrast.None; public int Scale { get; private set; } = 0; public FileQualifier (int scale, Contrast contrast = Contrast.None) { Scale = scale; this.Contrast = contrast; } public override string ToString () { return $"File Qualifier: Scale {Scale}, Contrast {this.Contrast}"; } public bool Equals (FileQualifier other) { return this.Contrast == other.Contrast && this.Scale == other.Scale; } public bool Equals (int other) { return this.Scale == other && this.Contrast == Contrast.None; } public bool Equals (Tuple other) { return this.Contrast == other.Item2 && this.Scale == other.Item1; } public override int GetHashCode () { unchecked { int hash = 17; hash = hash * 31 + Scale.GetHashCode (); hash = hash * 31 + Contrast.GetHashCode (); return hash; } } } public abstract class BaseResources: IDisposable { public virtual void Dispose () { } public virtual string SuitableValue { get; } = String.Empty; public virtual Dictionary AllValue { get; } = new Dictionary (); /// /// 表示是否寻找过,如果真则不用再次寻找。 /// public bool IsFind { get; set; } } public class StringResources: BaseResources, IDictionary, IDictionary { private Dictionary dict = new Dictionary (); public string this [string key] { get { return dict [new StringQualifier (key)]; } set { } } public string this [StringQualifier key] { get { return dict [key]; } set { } } public int Count => dict.Count; public bool IsReadOnly => true; public ICollection Keys => dict.Keys; public ICollection Values => dict.Values; ICollection IDictionary.Keys => dict.Keys.Select (k => k.LocaleName).ToList (); public void Add (KeyValuePair item) { } public void Add (KeyValuePair item) { } public void Add (string key, string value) { } public void Add (StringQualifier key, string value) { } public void Clear () { } public bool Contains (KeyValuePair item) { string value; if (TryGetValue (item.Key, out value)) return value == item.Value; return false; } public bool Contains (KeyValuePair item) => dict.Contains (item); public bool ContainsKey (string key) { foreach (var kv in dict) { if (string.Equals (kv.Key.LocaleName, key, StringComparison.OrdinalIgnoreCase)) return true; } return false; } public bool ContainsKey (StringQualifier key) => dict.ContainsKey (key); public void CopyTo (KeyValuePair [] array, int arrayIndex) { if (array == null) throw new ArgumentNullException ("array"); if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException ("arrayIndex"); if (array.Length - arrayIndex < dict.Count) throw new ArgumentException ("The destination array is not large enough."); foreach (var kv in dict) { array [arrayIndex++] = new KeyValuePair ( kv.Key.LocaleName, kv.Value); } } public void CopyTo (KeyValuePair [] array, int arrayIndex) { if (array == null) throw new ArgumentNullException ("array"); if (arrayIndex < 0 || arrayIndex > array.Length) throw new ArgumentOutOfRangeException ("arrayIndex"); if (array.Length - arrayIndex < dict.Count) throw new ArgumentException ("The destination array is not large enough."); foreach (var kv in dict) { array [arrayIndex++] = new KeyValuePair ( kv.Key, kv.Value); } } public IEnumerator> GetEnumerator () => dict.GetEnumerator (); public bool Remove (KeyValuePair item) { return false; } public bool Remove (string key) => false; public bool Remove (KeyValuePair item) => false; public bool Remove (StringQualifier key) { return false; } public bool TryGetValue (string key, out string value) { foreach (var kv in dict) { if (string.Equals (kv.Key.LocaleName, key, StringComparison.OrdinalIgnoreCase)) { value = kv.Value; return true; } } value = null; return false; } public bool TryGetValue (StringQualifier key, out string value) => dict.TryGetValue (key, out value); IEnumerator> IEnumerable>.GetEnumerator () { foreach (var kv in dict) { yield return new KeyValuePair (kv.Key.LocaleName, kv.Value); } } IEnumerator IEnumerable.GetEnumerator () => dict.GetEnumerator (); internal static bool LocaleNameEqualsIgnoreRegion (string a, string b) { var ca = new CultureInfo (a); var cb = new CultureInfo (b); return string.Equals (ca.TwoLetterISOLanguageName, cb.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase); } public static string GetCoincidentValue (Dictionary d, string localeName) { if (d == null) return null; foreach (var kv in d) { if (kv.Key.LocaleName?.Trim ()?.ToLower () == localeName?.Trim ()?.ToLower ()) return kv.Value; } var targetLang = new StringQualifier (localeName); foreach (var kv in d) { if (kv.Key.LCID == targetLang.LCID) return kv.Value; } foreach (var kv in d) { if (LocaleNameEqualsIgnoreRegion (kv.Key.LocaleName, localeName)) return kv.Value; } return String.Empty; } public static string GetSuitableValue (Dictionary d) { var ret = GetCoincidentValue (d, LocaleExt.GetComputerLocaleCode ()); if (String.IsNullOrEmpty (ret)) ret = GetCoincidentValue (d, "en-us"); if (String.IsNullOrEmpty (ret) && d.Count > 0) ret = d.ElementAt (0).Value; return ret; } public override string SuitableValue => GetSuitableValue (dict); public override Dictionary AllValue => dict.ToDictionary (kv => (BaseQualifier)kv.Key, kv => kv.Value); public override void Dispose () { dict.Clear (); dict = null; base.Dispose (); } ~StringResources () { Dispose (); } public StringResources (Dictionary _dict, bool isfind = true) { dict = _dict; IsFind = isfind; } public StringResources (Dictionary _dict, bool isfind = true) { dict = _dict.ToDictionary (kv => new StringQualifier (kv.Key), kv => kv.Value); IsFind = isfind; } public StringResources () { IsFind = false; } } public class FileResources: BaseResources, IDictionary { private Dictionary dict = new Dictionary (); public string this [FileQualifier key] { get { return dict [key]; } set { } } public int Count => dict.Count; public bool IsReadOnly => false; public ICollection Keys => dict.Keys; public ICollection Values => dict.Values; public void Add (KeyValuePair item) { } public void Add (FileQualifier key, string value) { } public void Clear () { } public bool Contains (KeyValuePair item) => dict.Contains (item); public bool ContainsKey (FileQualifier key) => dict.ContainsKey (key); public void CopyTo (KeyValuePair [] array, int arrayIndex) { } public IEnumerator> GetEnumerator () => dict.GetEnumerator (); public bool Remove (KeyValuePair item) => false; public bool Remove (FileQualifier key) => false; public bool TryGetValue (FileQualifier key, out string value) => dict.TryGetValue (key, out value); IEnumerator IEnumerable.GetEnumerator () => dict.GetEnumerator (); public static string GetCoincidentValue (Dictionary d, int scale, Contrast contrast = Contrast.None) { var td = d.OrderBy (k => k.Key.Contrast).ThenBy (k => k.Key.Scale); foreach (var kv in td) { if (kv.Key.Contrast == contrast) { if (kv.Key.Scale >= scale) return kv.Value; } } foreach (var kv in td) { if (kv.Key.Contrast == Contrast.None) if (kv.Key.Scale >= scale) return kv.Value; } foreach (var kv in td) { if (kv.Key.Contrast == Contrast.Black) if (kv.Key.Scale >= scale) return kv.Value; } foreach (var kv in td) { if (kv.Key.Scale >= scale) return kv.Value; } if (d.Count > 0) return d.ElementAt (0).Value; return String.Empty; } public static string GetSuitableValue (Dictionary d, Contrast contrast = Contrast.None) => GetCoincidentValue (d, UIExt.DPI, contrast); public override string SuitableValue => GetSuitableValue (dict); public override Dictionary AllValue => dict.ToDictionary (kv => (BaseQualifier)kv.Key, kv => kv.Value); public override void Dispose () { dict.Clear (); dict = null; base.Dispose (); } ~FileResources () { Dispose (); } public FileResources (Dictionary _dict, bool isfind = true) { dict = _dict; IsFind = isfind; } public FileResources (Dictionary, string> _dict, bool isfind = true) { dict = _dict.ToDictionary (kv => new FileQualifier (kv.Key.Item1, kv.Key.Item2), kv => kv.Value); IsFind = isfind; } public FileResources () { IsFind = false; } } }