using System.Globalization; using System.Text.Json; using System.Text.RegularExpressions; namespace FoodLabeling.Application.Helpers; /// /// Print Log「Expiry Date」:从 fl_label_print_task.PrintInputJson 解析保质期展示文案。 /// 与 App 落库一致:多为整份模板快照(elements[]),到期日在 duration date 类元素的 config.text。 /// public static class ReportsPrintLogExpiryHelper { private static readonly string[] RootExpiryKeys = { "expiryDate", "expiry", "expirationDate", "bestBeforeDate", "useByDate" }; private static readonly Regex ExpiryNamePattern = new( @"(durationdate|duration_date|duration\s*date|expirydate|expiry_date|expiry\s*date|expirationdate|expiration_date|usebydate|use_by)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); private static readonly Regex ExcludeNamePattern = new( @"(currentdate|currenttime|prepped|prepdate|prepon|labelname)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); /// /// 解析保质期展示文本;无法解析时返回「无」。 /// public static string ExtractExpiryText(string? printInputJson) { if (string.IsNullOrWhiteSpace(printInputJson)) { return "无"; } try { using var doc = JsonDocument.Parse(printInputJson); if (doc.RootElement.ValueKind != JsonValueKind.Object) { return "无"; } var root = doc.RootElement; if (TryGetRootExpiryText(root, out var fromRoot)) { return fromRoot; } if (TryGetProperty(root, "elements", out var elements) && elements.ValueKind == JsonValueKind.Array) { if (TryExtractFromElements(elements, out var fromElements)) { return fromElements; } } } catch { return "无"; } return "无"; } private static bool TryGetRootExpiryText(JsonElement root, out string text) { text = "无"; foreach (var prop in root.EnumerateObject()) { var key = prop.Name.Trim(); if (!RootExpiryKeys.Any(k => key.Equals(k, StringComparison.OrdinalIgnoreCase))) { continue; } if (TryFormatJsonValueAsExpiryText(prop.Value, out text)) { return true; } } return false; } private static bool TryExtractFromElements(JsonElement elements, out string text) { text = "无"; var bestScore = 0; string? bestText = null; var bestOrder = int.MinValue; foreach (var el in elements.EnumerateArray()) { if (el.ValueKind != JsonValueKind.Object) { continue; } var score = ScoreExpiryElement(el); if (score <= 0) { continue; } var display = GetElementConfigDisplayText(el); if (string.IsNullOrWhiteSpace(display)) { continue; } var order = TryGetIntProperty(el, "orderNum", "OrderNum") ?? 0; if (score > bestScore || (score == bestScore && order > bestOrder)) { bestScore = score; bestOrder = order; bestText = display.Trim(); } } if (string.IsNullOrWhiteSpace(bestText)) { return false; } text = bestText; return true; } private static int ScoreExpiryElement(JsonElement el) { var nameHint = $"{GetStringProperty(el, "elementName", "ElementName")} {GetStringProperty(el, "inputKey", "InputKey")}" .Trim(); if (string.IsNullOrWhiteSpace(nameHint)) { nameHint = GetStringProperty(el, "id", "Id"); } if (!string.IsNullOrWhiteSpace(nameHint) && ExcludeNamePattern.IsMatch(nameHint)) { return -1; } var typeAdd = GetStringProperty(el, "typeAdd", "TypeAdd") .Trim() .ToLowerInvariant() .Replace(" ", " ", StringComparison.Ordinal); if (ExpiryNamePattern.IsMatch(nameHint)) { return 100; } if (typeAdd.Contains("duration date", StringComparison.Ordinal)) { return 90; } if (TryGetProperty(el, "config", out var cfg) || TryGetProperty(el, "Config", out cfg)) { var native = GetStringProperty(cfg, "nativeSourceType", "NativeSourceType") .Trim() .ToUpperInvariant(); if (native == "DATE" && ExpiryNamePattern.IsMatch(nameHint)) { return 85; } } var type = GetStringProperty(el, "type", "Type").Trim().ToUpperInvariant(); if (type is "DATE" or "DURATION" or "TIME") { if (ExpiryNamePattern.IsMatch(nameHint)) { return 75; } } return 0; } private static string? GetElementConfigDisplayText(JsonElement el) { if (!TryGetProperty(el, "config", out var cfg) && !TryGetProperty(el, "Config", out cfg)) { return null; } foreach (var key in new[] { "text", "Text", "format", "Format" }) { if (cfg.TryGetProperty(key, out var v) && v.ValueKind == JsonValueKind.String) { var s = v.GetString()?.Trim(); if (!string.IsNullOrWhiteSpace(s)) { return s; } } } return null; } private static bool TryFormatJsonValueAsExpiryText(JsonElement v, out string text) { text = "无"; if (v.ValueKind == JsonValueKind.String) { var s = v.GetString()?.Trim(); if (string.IsNullOrWhiteSpace(s)) { return false; } text = s; return true; } if (v.ValueKind == JsonValueKind.Number && v.TryGetInt64(out var unix)) { text = DateTimeOffset.FromUnixTimeSeconds(unix).LocalDateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); return true; } if (v.ValueKind is JsonValueKind.True or JsonValueKind.False or JsonValueKind.Number) { text = v.ToString(); return true; } return false; } private static bool TryGetProperty(JsonElement obj, string name, out JsonElement value) { if (obj.TryGetProperty(name, out value)) { return true; } if (name.Length > 0) { var pascal = char.ToUpperInvariant(name[0]) + name[1..]; if (obj.TryGetProperty(pascal, out value)) { return true; } } value = default; return false; } private static string GetStringProperty(JsonElement obj, params string[] names) { foreach (var name in names) { if (TryGetProperty(obj, name, out var v) && v.ValueKind == JsonValueKind.String) { return v.GetString() ?? string.Empty; } } return string.Empty; } private static int? TryGetIntProperty(JsonElement obj, params string[] names) { foreach (var name in names) { if (!TryGetProperty(obj, name, out var v)) { continue; } if (v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out var n)) { return n; } } return null; } }