-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Pushing StorageLib to the repo, to fulfill the functionality.
- Loading branch information
Showing
11 changed files
with
614 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
0.0.1.0 | ||
1.0.0.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
global using Xunit; |
Oops, something went wrong.