První commit — WPF aplikace pro generování výkresových rohových razítek (.NET 10, SkiaSharp, ACadSharp, ClosedXML) včetně xUnit testů, DXF šablony, log a dokumentace. Build výstupy (bin/, obj/, .vs/) jsou ignorovány. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
123 lines
5.5 KiB
C#
123 lines
5.5 KiB
C#
using System.Collections.Concurrent;
|
||
using System.Reflection;
|
||
using Rozpisky.Models;
|
||
|
||
namespace Rozpisky.Dxf;
|
||
|
||
/// <summary>
|
||
/// Sestaví slovník „tag DXF → hodnota" pro jednu rozpisku z údajů projektu, řádku přílohy a
|
||
/// sdílených číselníků. Mapování (které pole programu plní který atribut) <b>není v kódu</b> –
|
||
/// čte se z dodávaného <c>mapovani.json</c> vedle .exe přes <see cref="MapovaniStore"/>, hodnoty
|
||
/// se dotahují reflexí. Klíče jsou kanonické (bez suffixu _NNN) – párování řeší
|
||
/// <see cref="DxfTemplate.NormalizeTag"/>.
|
||
/// </summary>
|
||
public static class RozpiskaValues
|
||
{
|
||
public static Dictionary<string, string> Build(Projekt p, Priloha r, IEnumerable<Revize> revize, Ciselniky c)
|
||
{
|
||
// Tabulky pro dohledání (číslo revize → řádek, název firmy → firma, označení → část).
|
||
var revLookup = ToLookup(revize, x => x.Cislo);
|
||
var firmaLookup = ToLookup(c.Firmy, f => f.Nazev);
|
||
var castLookup = ToLookup(c.Casti, x => x.Oznaceni);
|
||
|
||
var v = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (var m in MapovaniStore.Mapovani)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(m.Tag) || string.IsNullOrWhiteSpace(m.Zdroj)) continue;
|
||
var val = Resolve(m.Zdroj, p, r, revLookup, firmaLookup, castLookup);
|
||
if (string.IsNullOrEmpty(val))
|
||
{
|
||
// Pole, která dle manuálu nesmí zůstat prázdná (kap. 4.1.7), dostanou „—"; ostatní se přeskočí.
|
||
if (!string.IsNullOrEmpty(m.Vychozi))
|
||
v[DxfTemplate.NormalizeTag(m.Tag)] = m.Vychozi;
|
||
continue;
|
||
}
|
||
val = ApplyTransform(m.Transform, val);
|
||
if (!string.IsNullOrEmpty(m.Prefix)) val = m.Prefix + val;
|
||
v[DxfTemplate.NormalizeTag(m.Tag)] = val;
|
||
}
|
||
|
||
// Strojový 47-poziční kód (K1–K47) – „kód pod rozpiskou". Negeneruje se přes mapování
|
||
// (skládá se z více polí), ale algoritmicky; každá pozice = jedna buňka K1..K47.
|
||
// Desky objektu mají pevný střed _XX_X_000 (typ/dílčí/pořadí se neberou z řádku).
|
||
string kod = r.JeDesky ? KodPrilohy.SestavDesky(p, r) : KodPrilohy.Sestav(p, r);
|
||
for (int i = 0; i < kod.Length && i < KodPrilohy.Delka; i++)
|
||
v[DxfTemplate.NormalizeTag($"K{i + 1}")] = kod[i].ToString();
|
||
|
||
return v;
|
||
}
|
||
|
||
/// <summary>Vyhodnotí <c>Zdroj</c> z mapování na konkrétní hodnotu. Tvary:
|
||
/// <c>Projekt.X</c>, <c>Priloha.Y</c>, a lookup <c>Revize:R1.Datum</c> / <c>Firma:ZhotovitelCasti.Adresa</c>
|
||
/// / <c>Cast:OznaceniCasti.Nazev</c> (klíč z vlastnosti přílohy → řádek číselníku → cílová vlastnost).</summary>
|
||
private static string? Resolve(string zdroj, Projekt p, Priloha r,
|
||
IReadOnlyDictionary<string, Revize> revLookup,
|
||
IReadOnlyDictionary<string, Firma> firmaLookup,
|
||
IReadOnlyDictionary<string, Cast> castLookup)
|
||
{
|
||
int colon = zdroj.IndexOf(':');
|
||
if (colon > 0)
|
||
{
|
||
var kind = zdroj[..colon];
|
||
var rest = zdroj[(colon + 1)..]; // "ZhotovitelCasti.Adresa"
|
||
int dot = rest.IndexOf('.');
|
||
if (dot <= 0) return null;
|
||
var klic = GetProp(r, rest[..dot]); // hodnota klíče z přílohy
|
||
var cilProp = rest[(dot + 1)..];
|
||
if (string.IsNullOrWhiteSpace(klic)) return null;
|
||
return kind switch
|
||
{
|
||
"Revize" => revLookup.TryGetValue(klic, out var rev) ? GetProp(rev, cilProp) : null,
|
||
"Firma" => firmaLookup.TryGetValue(klic, out var f) ? GetProp(f, cilProp) : null,
|
||
"Cast" => castLookup.TryGetValue(klic, out var ca) ? GetProp(ca, cilProp) : null,
|
||
_ => null,
|
||
};
|
||
}
|
||
|
||
int sep = zdroj.IndexOf('.');
|
||
if (sep <= 0) return null;
|
||
object? obj = zdroj[..sep] switch
|
||
{
|
||
"Projekt" => p,
|
||
"Priloha" => r,
|
||
_ => null,
|
||
};
|
||
return obj is null ? null : GetProp(obj, zdroj[(sep + 1)..]);
|
||
}
|
||
|
||
/// <summary>Pojmenované úpravy hodnoty. <c>FormatA4</c>: čistě číselný počet → „4 x A4".</summary>
|
||
private static string ApplyTransform(string? transform, string val) => transform switch
|
||
{
|
||
"FormatA4" => IsNumeric(val) ? $"{val.Trim()} x A4" : val,
|
||
_ => val,
|
||
};
|
||
|
||
private static bool IsNumeric(string s)
|
||
{
|
||
s = s.Trim();
|
||
return s.Length > 0 && s.All(char.IsDigit);
|
||
}
|
||
|
||
/// <summary>Slovník klíč→první výskyt (case-insensitive), prázdné klíče ignoruje.</summary>
|
||
private static Dictionary<string, T> ToLookup<T>(IEnumerable<T> src, Func<T, string> key)
|
||
{
|
||
var d = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (var x in src)
|
||
{
|
||
var k = key(x);
|
||
if (!string.IsNullOrWhiteSpace(k)) d.TryAdd(k, x);
|
||
}
|
||
return d;
|
||
}
|
||
|
||
// Cache PropertyInfo – Build se volá pro každý řádek (rozpisku) při exportu.
|
||
private static readonly ConcurrentDictionary<(Type, string), PropertyInfo?> PropCache = new();
|
||
|
||
private static string? GetProp(object obj, string name)
|
||
{
|
||
var pi = PropCache.GetOrAdd((obj.GetType(), name),
|
||
key => key.Item1.GetProperty(key.Item2, BindingFlags.Public | BindingFlags.Instance));
|
||
return pi?.GetValue(obj) as string;
|
||
}
|
||
}
|