Add project files.
This commit is contained in:
78
Parser/ASTNode.cs
Normal file
78
Parser/ASTNode.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Parser;
|
||||
|
||||
public record ASTNode
|
||||
{
|
||||
public string Accept(IAccepter compiler)
|
||||
{
|
||||
return ((string)compiler.GetType()
|
||||
.GetMethod("Visit", BindingFlags.Public | BindingFlags.Instance, new[] { this.GetType() })!
|
||||
.Invoke(compiler, new[] { this })!)!;
|
||||
}
|
||||
|
||||
public static string PrintNode(ASTNode node)
|
||||
{
|
||||
// print type name and string representation of all its fields
|
||||
var type = node.GetType();
|
||||
var fields = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(type.Name);
|
||||
sb.Append("(");
|
||||
int i = 0;
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (i++ > 0) sb.Append(",");
|
||||
var value = field.GetValue(node);
|
||||
if (value is ASTNode valueNode)
|
||||
{
|
||||
sb.Append(PrintNode(valueNode));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(value);
|
||||
}
|
||||
|
||||
// if field is list - print all its elements
|
||||
if (field.PropertyType.IsGenericType && field.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
|
||||
{
|
||||
var list = (IList)field.GetValue(node);
|
||||
sb.Append("[");
|
||||
int j = 0;
|
||||
sb.AppendLine();
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (j++ > 0)
|
||||
{
|
||||
sb.Append(",");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
if (item is ASTNode itemNode)
|
||||
{
|
||||
sb.Append(PrintNode(itemNode));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sb.Append("]");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(")");
|
||||
return sb.ToString();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAccepter
|
||||
{
|
||||
}
|
||||
22
Parser/Parser.csproj
Normal file
22
Parser/Parser.csproj
Normal file
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="bar.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="kitchen.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="receipt.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
11
Parser/Program.cs
Normal file
11
Parser/Program.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
using System.Text.Json;
|
||||
using Parser;
|
||||
|
||||
var bar = File.ReadAllText("kitchen.txt");
|
||||
var parser = new Parser.RecipeTranslator();
|
||||
var ast = parser.ParseReceipt(bar);
|
||||
Console.WriteLine(ASTNode.PrintNode(ast));
|
||||
Console.ReadLine();
|
||||
|
||||
209
Parser/ReceiptTranslator.cs
Normal file
209
Parser/ReceiptTranslator.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
namespace Parser;
|
||||
|
||||
|
||||
public record KitchenReceipt(string Location, string Date, string Owner, string Tisch, List<KitchenItem> items) :ASTNode;
|
||||
public record KitchenItem(string Name) : ASTNode;
|
||||
public record KitchenProduct(int Amount, string Name):KitchenItem(Name);
|
||||
public record KitchenGang(string Name) :KitchenItem(Name);
|
||||
|
||||
public record FinalReceipt(string Location, string Phone, string URL, string Date, List<FinalReceiptItem> items, string total, string MWST, string Comment, string Thanks) : ASTNode;
|
||||
|
||||
public record FinalReceiptItem(string Name):ASTNode;
|
||||
public record FinalReceiptProduct(string Name, int Amount, string Price, string Total) : FinalReceiptItem(Name);
|
||||
public record FinalReceiptGang(string Name) : FinalReceiptItem(Name);
|
||||
|
||||
public class RecipeTranslator
|
||||
{
|
||||
public ASTNode ParseReceipt(string receiptText)
|
||||
{
|
||||
// Determine receipt type based on content
|
||||
bool isFinalReceipt = receiptText.Contains("http") ||
|
||||
receiptText.Contains("+41") ||
|
||||
receiptText.Contains("Summe CHF") ||
|
||||
receiptText.Contains("MWST") ||
|
||||
receiptText.Contains("Thank you");
|
||||
|
||||
bool isKitchenReceipt = receiptText.Contains("TISCH:");
|
||||
|
||||
if (isKitchenReceipt && !isFinalReceipt)
|
||||
{
|
||||
return ParseKitchenReceipt(receiptText);
|
||||
}
|
||||
else if (isFinalReceipt)
|
||||
{
|
||||
return ParseFinalReceipt(receiptText);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to kitchen receipt if unclear
|
||||
return ParseKitchenReceipt(receiptText);
|
||||
}
|
||||
}
|
||||
|
||||
public static FinalReceipt ParseFinalReceipt(string receiptText)
|
||||
{
|
||||
var lines = receiptText.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(l => l.Trim())
|
||||
.ToList();
|
||||
|
||||
// Extract header information
|
||||
string location = lines[0];
|
||||
string phone = lines[1];
|
||||
string url = lines[2];
|
||||
|
||||
// Find and extract date
|
||||
var dateLine = lines.FirstOrDefault(l => l.StartsWith("Datum:"));
|
||||
string date = dateLine?.Replace("Datum:", "").Trim() ?? "";
|
||||
|
||||
// Find total line
|
||||
var totalLine = lines.FirstOrDefault(l => l.Contains("Summe CHF :"));
|
||||
string total = totalLine?.Split(':').Last().Trim() ?? "";
|
||||
|
||||
// Find MWST line (comes after "TOTAL MWST" header)
|
||||
var mwstLineIndex = lines.FindIndex(l => l.StartsWith("TOTAL") && l.Contains("MWST"));
|
||||
string mwst = mwstLineIndex >= 0 && mwstLineIndex + 1 < lines.Count
|
||||
? lines[mwstLineIndex + 1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault() ?? ""
|
||||
: "";
|
||||
|
||||
// Extract comment (line after MWST values)
|
||||
string comment = mwstLineIndex >= 0 && mwstLineIndex + 2 < lines.Count
|
||||
? lines[mwstLineIndex + 2]
|
||||
: "";
|
||||
|
||||
// Extract thank you message (last non-separator line)
|
||||
string thanks = lines.LastOrDefault(l => !l.Contains("--")) ?? "";
|
||||
|
||||
// Parse items
|
||||
var items = new List<FinalReceiptItem>();
|
||||
var startIndex = lines.FindIndex(l => l.StartsWith("Datum:")) + 2; // Skip date and separator
|
||||
var endIndex = lines.FindIndex(l => l.Contains("Summe CHF"));
|
||||
|
||||
for (int i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
|
||||
// Skip separator lines and empty lines
|
||||
if (line.Contains("---") || string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
|
||||
// Check if line contains item data (has * separator)
|
||||
if (line.Contains("*"))
|
||||
{
|
||||
// Parse format: "Name Amount * Price Total"
|
||||
var parts = line.Split('*');
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var leftPart = parts[0].Trim();
|
||||
var rightPart = parts[1].Trim();
|
||||
|
||||
// Extract name and amount from left part
|
||||
var leftTokens = leftPart.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var amount = int.Parse(leftTokens.Last());
|
||||
var name = string.Join(" ", leftTokens.Take(leftTokens.Length - 1));
|
||||
|
||||
// Extract price and total from right part
|
||||
var rightTokens = rightPart.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var price = rightTokens.Length > 0 ? rightTokens[0] : "";
|
||||
var itemTotal = rightTokens.Length > 1 ? rightTokens[1] : "";
|
||||
|
||||
items.Add(new FinalReceiptProduct(name, amount, price, itemTotal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new FinalReceipt(location, phone, url, date, items, total, mwst, comment, thanks);
|
||||
}
|
||||
|
||||
public static KitchenReceipt ParseKitchenReceipt(string receiptText)
|
||||
{
|
||||
var lines = receiptText.Split('\n', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(l => l.Trim())
|
||||
.ToList();
|
||||
|
||||
int currentIndex = 0;
|
||||
|
||||
// Skip optional "*** KOPIE ***" header
|
||||
if (lines[currentIndex].Contains("***"))
|
||||
{
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
// Extract location (can be any phrase)
|
||||
string location = lines[currentIndex];
|
||||
currentIndex++;
|
||||
|
||||
// Skip separator line
|
||||
while (currentIndex < lines.Count && lines[currentIndex].Contains("---"))
|
||||
{
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
// Extract date line
|
||||
string date = lines[currentIndex];
|
||||
currentIndex++;
|
||||
|
||||
// Extract owner
|
||||
string owner="";
|
||||
do
|
||||
{
|
||||
if (owner!="")
|
||||
{
|
||||
owner += "/n";
|
||||
}
|
||||
owner += lines[currentIndex];
|
||||
currentIndex++;
|
||||
} while (!lines[currentIndex].ToLower().Contains("tisch:"));
|
||||
|
||||
|
||||
// Extract table (extract text after "TISCH:")
|
||||
string tisch = lines[currentIndex].Replace("TISCH:", "").Trim();
|
||||
currentIndex++;
|
||||
|
||||
// Skip empty lines and separators until we reach items
|
||||
while (currentIndex < lines.Count &&
|
||||
(string.IsNullOrWhiteSpace(lines[currentIndex]) || lines[currentIndex].Contains("-")))
|
||||
{
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
// Parse items
|
||||
var items = new List<KitchenItem>();
|
||||
while (currentIndex < lines.Count)
|
||||
{
|
||||
var line = lines[currentIndex];
|
||||
|
||||
// Stop at separator or end
|
||||
if (line.Contains("---") || string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ( line.ToLower().Contains(". gang"))
|
||||
{
|
||||
items.Add(new KitchenGang(line.Trim()));
|
||||
currentIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse format: "1x Espresso" or " 1x Espresso"
|
||||
var trimmedLine = line.Trim();
|
||||
if (trimmedLine.Contains("x"))
|
||||
{
|
||||
var parts = trimmedLine.Split('x', 2);
|
||||
if (parts.Length == 2 && int.TryParse(parts[0].Trim(), out int amount))
|
||||
{
|
||||
var itemName = parts[1].Trim();
|
||||
items.Add(new KitchenProduct(amount, itemName));
|
||||
}
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
return new KitchenReceipt(location, date, owner, tisch, items);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
11
Parser/bar.txt
Normal file
11
Parser/bar.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
*** KOPIE ***
|
||||
BAR
|
||||
---------------------------------
|
||||
17-Dec-25 11:15 Nr.:5
|
||||
Roger Deuber
|
||||
TISCH: BISTRO 1
|
||||
|
||||
---------------------------------
|
||||
1x Espresso
|
||||
1x Kaffee Crème
|
||||
---------------------------------
|
||||
16
Parser/kitchen.txt
Normal file
16
Parser/kitchen.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
*** KOPIE ***
|
||||
WARME KÜCHE
|
||||
---------------------------------
|
||||
18-Dec-25 08:59 Nr.:5
|
||||
Roger Deuber
|
||||
TISCH: PALETT1
|
||||
|
||||
---------------------------------
|
||||
1. Gang
|
||||
1x Apéroplatten
|
||||
für 1 Person
|
||||
2. Gang
|
||||
1x Pizza quattro formaggi
|
||||
3. Gang
|
||||
1x Schoggichueche
|
||||
---------------------------------
|
||||
19
Parser/receipt.txt
Normal file
19
Parser/receipt.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Gaumenfreuden
|
||||
+41438104848
|
||||
https://gaumen-freuden.ch
|
||||
------------------------------------------
|
||||
Datum: 01.07.2025 15:41:34
|
||||
------------------------------------------
|
||||
Der Glücklichmacher 1 * 16,50 16,50
|
||||
(klein)
|
||||
Gartenfreunde 1 * 16,50 16,50
|
||||
S' Zähni 1 * 10,00 10,00
|
||||
Apéroplatten 1 * 15,50 15,50
|
||||
|
||||
Summe CHF : 58,50
|
||||
------------------------------------------
|
||||
TOTAL MWST
|
||||
58,50 0
|
||||
Nicht mehrwertsteuerpflichtig
|
||||
------------------------------------------
|
||||
Thank you for your order!
|
||||
Reference in New Issue
Block a user