300 lines
9.2 KiB
C#
Executable File
300 lines
9.2 KiB
C#
Executable File
/* LabItem.cs
|
||
* part of zaaReloaded2
|
||
*
|
||
* Copyright 2015 Daniel Kraus
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
using System;
|
||
using System.Globalization;
|
||
using System.Text.RegularExpressions;
|
||
|
||
namespace zaaReloaded2.Models
|
||
{
|
||
/// <summary>
|
||
/// Represents a single laboratory item (e.g., sodium or creatinine).
|
||
/// </summary>
|
||
public class LabItem
|
||
{
|
||
#region Properties
|
||
|
||
/// <summary>
|
||
/// Gets or sets the original name of the item (as known by Lauris).
|
||
/// </summary>
|
||
public string Name { get; set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets the unit of the item (as known by Lauris).
|
||
/// </summary>
|
||
public string Unit { get; set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets the value of the item. This may be a number or a string.
|
||
/// </summary>
|
||
public string Value { get; set; }
|
||
|
||
public double NumericalValue
|
||
{
|
||
get
|
||
{
|
||
if (IsNumerical)
|
||
{
|
||
return _numericalValue;
|
||
}
|
||
else
|
||
{
|
||
throw new InvalidOperationException(
|
||
String.Format("Value '{0}' is not numerical.", Value));
|
||
}
|
||
}
|
||
private set
|
||
{
|
||
_numericalValue = value;
|
||
}
|
||
}
|
||
|
||
public bool IsNumerical
|
||
{
|
||
get
|
||
{
|
||
return (Double.TryParse(Value,
|
||
NumberStyles.Any,
|
||
CultureInfo.InvariantCulture,
|
||
out _numericalValue));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets or sets the normal value of the item. Need not be set. This is
|
||
/// used for items with nominal values (as opposed to numbers).
|
||
/// </summary>
|
||
public string Normal { get; set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets the lower limit of normal.
|
||
/// </summary>
|
||
public double LowerLimit
|
||
{
|
||
get
|
||
{
|
||
return _lowerLimit;
|
||
}
|
||
set
|
||
{
|
||
_lowerLimit = value;
|
||
HasLowerLimit = true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Is true if the item has a lower limit of normal.
|
||
/// </summary>
|
||
public bool HasLowerLimit { get; private set; }
|
||
|
||
/// <summary>
|
||
/// Gets or sets the upper limit of normal.
|
||
/// </summary>
|
||
public double UpperLimit
|
||
{
|
||
get
|
||
{
|
||
return _upperLimit;
|
||
}
|
||
set
|
||
{
|
||
_upperLimit = value;
|
||
HasUpperLimit = true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Is true if the item has an upper limit of normal.
|
||
/// </summary>
|
||
public bool HasUpperLimit { get; private set; }
|
||
|
||
/// <summary>
|
||
/// Is true if <see cref="Value"/> is normal.
|
||
/// </summary>
|
||
public bool IsNormal
|
||
{
|
||
get
|
||
{
|
||
if (HasLimits)
|
||
{
|
||
if (HasLowerLimit && HasUpperLimit)
|
||
{
|
||
return (LowerLimit <= NumericalValue &&
|
||
NumericalValue <= UpperLimit);
|
||
}
|
||
else
|
||
{
|
||
if (HasLowerLimit)
|
||
{
|
||
return (LowerLimit <= NumericalValue);
|
||
}
|
||
return (NumericalValue <= UpperLimit);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return (Value == Normal);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Is true if the item has lower and/or upper limits. Is false if the
|
||
/// item does not have limits but a <see cref="Normal"/> value.
|
||
/// </summary>
|
||
public bool HasLimits
|
||
{
|
||
get { return (HasLowerLimit || HasUpperLimit); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// The original Lauris string from which this lab item was created.
|
||
/// </summary>
|
||
public string Lauris { get; private set; }
|
||
|
||
/// <summary>
|
||
/// The canonical name of this item, or the Lauris <see cref="Name"/>
|
||
/// of the item if no canonical name is known.
|
||
/// </summary>
|
||
public string CanonicalName
|
||
{
|
||
get
|
||
{
|
||
if (!string.IsNullOrEmpty(_canonicalName))
|
||
{
|
||
return _canonicalName;
|
||
}
|
||
else
|
||
{
|
||
return Name;
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Constructors
|
||
|
||
/// <summary>
|
||
/// Creates an empty LabItem object.
|
||
/// </summary>
|
||
public LabItem() { }
|
||
|
||
/// <summary>
|
||
/// Creates a LabItem object from a given Lauris output.
|
||
/// </summary>
|
||
/// <param name="laurisString">Lauris output to parse.</param>
|
||
public LabItem(string laurisString)
|
||
:this()
|
||
{
|
||
Lauris = laurisString;
|
||
ParseLauris();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Private methods
|
||
|
||
/// <summary>
|
||
/// Parses the original Lauris string contained in
|
||
/// <see cref="Lauris"/>.
|
||
/// </summary>
|
||
private void ParseLauris()
|
||
{
|
||
// Examples of Lauris output strings:
|
||
// "Natrium: 139 [135 - 145] mmol/l"
|
||
// "HDL - Cholesterin: 45 [>= 35] mg/dl"
|
||
// "GOT (ASAT): 303.0 [<= 50] U/l; "
|
||
// "Niedermol. Heparin (Anti-Xa): 0.99 U/ml;"
|
||
// "HBs-Antigen: neg. ;"
|
||
// "Erythrozyten (U): + [negativ]"
|
||
Match match;
|
||
Regex numericalRegex = new Regex(
|
||
@"(?<name>[^:]+):\s*(?<value>[\d.]+)\s*(?<limits>\[[^\]]+])?\s*(?<unit>[^;]+)?");
|
||
Regex categoricalRegex = new Regex(
|
||
@"(?<name>[^:]+):\s*(?<value>[^[;]+)\s*(\[(?<normal>[^\]]+)])?");
|
||
if (numericalRegex.IsMatch(Lauris))
|
||
{
|
||
match = numericalRegex.Match(Lauris);
|
||
ParseLimits(match);
|
||
}
|
||
else
|
||
{
|
||
match = categoricalRegex.Match(Lauris);
|
||
Normal = match.Groups["normal"].Value.Trim();
|
||
}
|
||
if (match != null)
|
||
{
|
||
Name = match.Groups["name"].Value.Trim();
|
||
Value = match.Groups["value"].Value.Trim();
|
||
Unit = match.Groups["unit"].Value.Trim();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Parses a string containing value limits. The string must be like
|
||
/// "[3.5 - 5]", "[>= 50]", or "[<= 100]".
|
||
/// </summary>
|
||
/// <param name="match">Match object that should contain a group "limits".</param>
|
||
void ParseLimits(Match match)
|
||
{
|
||
if (match.Groups["limits"].Success)
|
||
{
|
||
Regex limitRegex = new Regex(@"\[(?<limit1>[\d.]+)?\s*(?<operator>\S+)\s*(?<limit2>[\d.]+)?]");
|
||
Match limitMatch = limitRegex.Match(match.Groups["limits"].Value);
|
||
if (limitMatch.Groups["limit1"].Success && limitMatch.Groups["limit2"].Success)
|
||
{
|
||
// Use InvariantCulture because Lauris always outputs dots as decimal separator
|
||
LowerLimit = Double.Parse(limitMatch.Groups["limit1"].Value,
|
||
CultureInfo.InvariantCulture);
|
||
UpperLimit = Double.Parse(limitMatch.Groups["limit2"].Value,
|
||
CultureInfo.InvariantCulture);
|
||
}
|
||
else
|
||
{
|
||
switch (limitMatch.Groups["operator"].Value.Trim())
|
||
{
|
||
case "<=":
|
||
UpperLimit = Double.Parse(limitMatch.Groups["limit2"].Value,
|
||
CultureInfo.InvariantCulture);
|
||
break;
|
||
case ">=":
|
||
LowerLimit = Double.Parse(limitMatch.Groups["limit2"].Value,
|
||
CultureInfo.InvariantCulture);
|
||
break;
|
||
default:
|
||
throw new InvalidOperationException(
|
||
String.Format("Unknown operator in {0}",
|
||
match.Groups["limits"].Value));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Fields
|
||
|
||
string _canonicalName;
|
||
double _numericalValue;
|
||
double _lowerLimit;
|
||
double _upperLimit;
|
||
|
||
#endregion
|
||
}
|
||
}
|