Skip to content

Commit

Permalink
Pushing StorageLib to the repo, to fulfill the functionality.
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinRuf committed Oct 18, 2023
1 parent f7d8259 commit 800f0a6
Show file tree
Hide file tree
Showing 11 changed files with 614 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Publish Application
on:
push:
branches:
- advanced-conversations
- main
paths:
- "PrimitiveChatBot/VERSION"

Expand Down
2 changes: 1 addition & 1 deletion PrimitiveChatBot/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.1.0
1.0.0.0
33 changes: 33 additions & 0 deletions StorageLib/Diagram.cd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="FullSignature">
<Class Name="StorageLib.Message">
<Position X="5" Y="0.5" Width="4.25" />
<TypeIdentifier>
<HashCode>AAAAAAAABACAAAAAAAAEAIAAAAAAAQAAAQAAAgQAABA=</HashCode>
<FileName>Message.cs</FileName>
</TypeIdentifier>
</Class>
<Class Name="StorageLib.Storage">
<Position X="0.5" Y="0.5" Width="4.25" />
<TypeIdentifier>
<HashCode>ACAAAAAAAAAAAAAAIAAAAAQAgAAAAAAAEACAAwAAAAA=</HashCode>
<FileName>Storage.cs</FileName>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="StorageLib.Exceptions.ImportFormatException">
<Position X="5" Y="4.25" Width="4.25" />
<TypeIdentifier>
<HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAAAAA=</HashCode>
<FileName>Exceptions\ImportFormatException.cs</FileName>
</TypeIdentifier>
</Class>
<Enum Name="StorageLib.KeywordDetection">
<Position X="1.75" Y="4.5" Width="3" />
<TypeIdentifier>
<HashCode>AAAAAAAAAEAAAAAAAIAAAAAAAAAEAAgAAAAAAAAAAAA=</HashCode>
<FileName>Message.cs</FileName>
</TypeIdentifier>
</Enum>
<Font Name="Segoe UI" Size="9" />
</ClassDiagram>
21 changes: 21 additions & 0 deletions StorageLib/Exceptions/ImportFormatException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StorageLib.Exceptions
{
public class ImportFormatException : Exception
{
public string Caption { get; } = "JSON Ungültig Formatiert!";
public string Text { get; } = "Jede nachricht muss ein Schlüsselwort und eine Antwort enthalten.";

public ImportFormatException(): base() { }

public ImportFormatException(string text): base()
{
Text = text;
}
}
}
138 changes: 138 additions & 0 deletions StorageLib/Message.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StorageLib
{
/// <summary>
/// Possible Detection Types for a Message
/// </summary>
public enum KeywordDetection
{
MatchFull,
MatchPartial,
MatchFullCaseSensitive,
MatchPartialCaseSensitive,
}

/// <summary>
/// Represents a keyword-awnser pair
/// </summary>
public class Message
{
/// <summary>
/// A unique identifier for the message
/// Allows for conversation hopping
/// </summary>
public string Slug { get; set; }

/// <summary>
/// The keyword to be contained/matched to display the matching anwser
/// </summary>
public string Keyword { get; set; }

/// <summary>
/// The anwser for the keyword
/// </summary>
public string Answer { get; set; }

/// <summary>
/// The way the keyword should be matched
/// </summary>
public KeywordDetection Type { get; set; } = KeywordDetection.MatchPartial;

/// <summary>
/// Childrens for this conversation
/// </summary>
public List<Message> Children { get; set; } = new List<Message>();

/// <summary>
/// Basic constructor for a Message
/// </summary>
/// <param name="keyword">The keyword for a message to get triggered</param>
/// <param name="anwser">The anwser the bot should give</param>
public Message(string keyword, string anwser)
{
Keyword = keyword;
Answer = anwser;
Slug = $"{keyword}_tree";
}

/// <summary>
/// Overflow constructor for a Message
/// </summary>
/// <param name="keyword">The keyword to match for the message to get triggered</param>
/// <param name="anwser">The anwser the bot should give</param>
/// <param name="type">The type of detection to use when trying to match a messge</param>
public Message(string keyword, string anwser, KeywordDetection type) : this(keyword, anwser)
{
Type = type;
}

/// <summary>
/// Basic empty constructor for JSON Seria
/// </summary>
public Message() : this(string.Empty, string.Empty) { }

/// <summary>
/// Find a message by its slug (will check all children as well)
/// </summary>
/// <param name="slug">The slug to search for</param>
/// <returns>The message to return</returns>
public Message? FindMessageBySlug(string slug)
{
if (Slug == slug) return this;

foreach (var child in Children)
{
var found = child.FindMessageBySlug(slug);
if (found != null) return found;
}

return null;
}

/// <summary>
/// Check if the conversation tree has messages left
/// </summary>
/// <returns></returns>
public bool HasNextKeywords()
{
return Children.Count > 0;
}

/// <summary>
/// Get the next possible Keywords
/// </summary>
/// <returns></returns>
public string[] GetNextKeywords()
{
return Children.Select(w => w.Keyword).ToArray();
}

/// <summary>
/// Validate the message to be correct and usable by the program
/// </summary>
/// <returns>True if the message is valid</returns>
public bool Validate()
{
if (string.IsNullOrWhiteSpace(Keyword) || string.IsNullOrWhiteSpace(Answer))
{
return false;
}
if (Children.Count > 0)
{
foreach (var child in Children)
{
if (!child.Validate())
{
return false;
}
}
}
return true;
}
}
}
168 changes: 168 additions & 0 deletions StorageLib/Storage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using Microsoft.Win32;
using Newtonsoft.Json;
using StorageLib.Exceptions;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Xml.Serialization;

namespace StorageLib
{
/// <summary>
/// Storage handling for keyword-awnser pairs
/// </summary>
public class Storage : INotifyPropertyChanged
{

/// <summary>
/// Keyword - Awnser pairs
/// </summary>
public List<Message> Messages
{
get => _messages;
set
{
_messages = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Count));
}
}
private List<Message> _messages = new List<Message>();

/// <summary>
/// Count accessor
/// </summary>
public int Count => _count(Messages);

public Storage() { }

public event PropertyChangedEventHandler? PropertyChanged;

/// <summary>
/// Simplify the usage of OnPropertyChanged
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

/// <summary>
/// Receive a message by keyword
/// </summary>
/// <param name="keyword">The keyword identifying a message</param>
/// <param name="fallback">The default message for non-found keywords</param>
/// <returns>The found Message</returns>
public Message? GetMessage(string keyword, Message? tree = null)
{
// Filter the array for the keyword
// Will use the Observable collection if no Message passed, the tree else.
Message[] messages = (tree?.Children.ToImmutableArray() ?? Messages.ToImmutableArray()).Where(m =>
{
switch (m.Type)
{
case KeywordDetection.MatchFullCaseSensitive:
return m.Keyword.Trim() == keyword.Trim();
case KeywordDetection.MatchPartialCaseSensitive:
return keyword.Trim().Contains(m.Keyword.Trim());
case KeywordDetection.MatchFull:
return m.Keyword.ToLower().Trim() == keyword.ToLower().Trim();
// MatchPartial is the default
default:
return keyword.ToLower().Trim().Contains(m.Keyword.ToLower().Trim());
}
}).ToArray();
// Return the default since no match for the keyword was found
if (messages.Length == 0) return null;
return messages.First();
}

/// <summary>
/// Get the top level keywords
/// </summary>
/// <returns>The keywords existing</returns>
public string[] GetTopLevelKeywords()
{
return Messages.Select(m => m.Keyword).ToArray();
}

/// <summary>
/// Allow a file path to be prodived for Import, it will be converted to a stream and use the Stream Import
/// </summary>
/// <param name="filePath">Path to the file to Import</param>
public void Import(string filePath)
{
using (Stream stream = File.OpenRead(filePath))
{
Import(stream);
}
}

/// <summary>
/// Import from a stream to allow compatibility with embedded ressources
/// </summary>
/// <param name="stream">The Stream to read</param>
/// <exception cref="ImportFormatException">Thrown on invalid Data</exception>
public void Import(Stream stream)
{
using (StreamReader fileStream = new StreamReader(stream))
using (JsonTextReader reader = new JsonTextReader(fileStream))
{
JsonSerializer serializer = new JsonSerializer();
try
{
List<Message>? importedMessages = serializer.Deserialize<List<Message>>(reader);
if (importedMessages == null || !importedMessages.Any())
{
throw new ImportFormatException();
}

foreach (var message in importedMessages)
{
if (!message.Validate())
{
throw new ImportFormatException(
$"Jede nachricht muss ein Schlüsselwort und eine Antwort enthalten.\n\nFehlerhalft:" +
$"\nSchlüsselwort: {(message.Keyword.Length > 0 ? message.Keyword : "????????????????")}" +
$"\nAntwort: {(message.Answer.Length > 0 ? message.Answer : "????????????????")}");
}
}

Messages = importedMessages;
}
catch (Exception ex) when (ex is JsonReaderException || ex is JsonSerializationException)
{
throw new InvalidDataException("Provided file does not contain valid JSON.");
}
}
}

/// <summary>
/// Count the current Keywords in the Storage
/// </summary>
/// <param name="messages">The message object to count the Keywords for</param>
/// <returns>The count of all keywords</returns>
private int _count(List<Message> messages)
{
int count = 0;
foreach (var message in messages)
{
count++;
if (message.Children != null)
{
count += _count(message.Children);
}
}
return count;
}
}
}
13 changes: 13 additions & 0 deletions StorageLib/StorageLib.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions StorageLibTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
Loading

0 comments on commit 800f0a6

Please sign in to comment.