How to set relations between entities when parsing file? - c#

I succesfully parsed main entity (measure) from excel book, but I have no idea, how to set relations between measure and lookups (Cloudiness, WindDirection WeatherConditions). I also need to generate guids for lookup entities. How should I do this?
using DynamicSun.Common.Extensions;
using DynamicSun.MoscowWeather.Models;
using DynamicSun.MoscowWeather.Models.Constants;
using DynamicSun.MoscowWeather.Models.DTO;
using NPOI.SS.UserModel;
namespace DynamicSun.MoscowWeather.Infrastructure.Services;
public class WeatherExcelService
{
/// <summary>
/// Parse excel document to set of entities.
/// </summary>
public WeatherSheetDto ParseDocument(Stream file)
{
//_parsed = new WeatherSheetDto();
var documentDto = new WeatherSheetDto();
using var reader = new StreamReader(file);
var workbook = WorkbookFactory.Create(file);
for (int i = 0; i < workbook.NumberOfSheets; i++)
{
var sheet = ParseSheet(workbook.GetSheetAt(i));
documentDto.Measures.UnionWith(sheet.Measures);
documentDto.Cloudiness.UnionWith(sheet.Cloudiness);
documentDto.WindDirections.UnionWith(sheet.WindDirections);
documentDto.WeatherConditions.UnionWith(sheet.WeatherConditions);
}
return documentDto;
}
/// <summary>
/// Parse excel list to set of entities.
/// </summary>
public WeatherSheetDto ParseSheet(ISheet sheet)
{
var documentDto = new WeatherSheetDto();
foreach (IRow row in sheet)
{
if (!TryParseRow(row, out var rowDto))
{
continue;
}
documentDto.WindDirections.UnionWith(rowDto.WindDirections);
documentDto.Cloudiness.Add(rowDto.Cloudiness);
documentDto.WeatherConditions.Add(rowDto.WeatherCondition);
documentDto.Measures.Add(rowDto.Measure);
}
return documentDto;
}
/// <summary>
/// Parse excel row to set of entities.
/// </summary>
public bool TryParseRow(IRow row, out WeatherRowDTO? result)
{
try
{
var cloudiness = ParseCloudiness(row);
result = new WeatherRowDTO
{
WindDirections = ParseWindDirections(row),
Cloudiness = cloudiness,
WeatherCondition = ParseWeatherCondition(row),
Measure = ParseMeasure(row),
};
return true;
}
catch (Exception e) when (e is FormatException or InvalidOperationException)
{
result = null;
return false;
}
}
/// <summary>
/// Parse excel row to Measure.
/// </summary>
public Measure ParseMeasure(IRow row)
{
var timeSpan = TimeSpan.Parse(row.GetStringCell(MeasureCells.Time));
return new Measure
{
Id = Guid.NewGuid(),
DateTime = DateTime.Parse(row.GetStringCell(MeasureCells.Date)).Add(timeSpan),
TemperatureCelsius = (decimal) row.GetNumericCell(MeasureCells.TemperatureCelsius),
AirHumidity = (byte) row.GetNumericCell(MeasureCells.AirHumidity),
DewPoint = (decimal) row.GetNumericCell(MeasureCells.DewPoint),
AtmospherePressure = (ushort) row.GetNumericCell(MeasureCells.AtmospherePressure),
WindSpeed = (byte?) row.GetNullableNumericCell(MeasureCells.WindSpeed),
CloudHeight = (ushort?) row.GetNullableNumericCell(MeasureCells.CloudHeight),
AtmosphericVisibility = (byte?) row.GetNullableNumericCell(MeasureCells.AtmosphericVisibility),
// CloudinessId = null,
// ConditionId = null,
};
}
/// <summary>
/// Parse excel row to set of WindDirection.
/// </summary>
public HashSet<WindDirection> ParseWindDirections(IRow row) =>
row
.GetStringCell(MeasureCells.WindDirection)
.Split(',')
.Distinct()
.Select(v =>
new WindDirection
{
Name = v,
})
.ToHashSet();
/// <summary>
/// Parse excel row to Cloudiness.
/// </summary>
public Cloudiness ParseCloudiness(IRow row) =>
new()
{
Value = (byte) row.GetNumericCell(MeasureCells.Cloudiness),
};
/// <summary>
/// Parse excel row to WeatherCondition.
/// </summary>
public WeatherCondition ParseWeatherCondition(IRow row) =>
new()
{
Name = row.GetStringCell(MeasureCells.WeatherCondition),
};
}
Raw excel data
Output
I tried to save already readed data in a variable and search for related items from there. But in this way we have more complicated code and O(n^2) complicity.

Related

.NET MAUI app on Windows platform getting System.IO.FileNotFoundException' in System.Private.CoreLib.dll

I'm working on a .NET MAUI project in the context of MVVM architecture and I created two .txt files (15.txt and 19.txt) inside directory Resources\Raw. I set the files' Build action to MauiAsset. Each of
these two files contain topology for a matrix of color-coded fields.
What were I expecting: to read the text file's content without obtaining System.IO.FileNotFoundException' in System.Private.CoreLib.dll and to be able to use (invoke) the reader method more than once in a successfull manner.
I experience the following behaviour: in case the mentioned exception is not checked under Debug -> Windows -> Exception Setings -> Common Language Runtime Exceptions then the textfile's content is successfully obtained via the reader method as it's evident by looking at the graphical changes on one of my ContentPages (a matrix of fields appears with plenty of differently colored fields), however at the moment of its invokation an System.IO.FileNotFoundException' in System.Private.CoreLib.dllemerges as part of the logs (alerting message is absent).
However, if the aforementioned exception is checked under Debug -> Windows -> Exception Setings -> Common Language Runtime Exceptions
then my application does not even start succesfully.
Inside class GameViewModel:
/// <summary>
/// event handler method for choosing 15 as tablesize and reading appropriate table
/// </summary>
private async void GameViewModel_ButtonSmallTableClick(object? sender, System.EventArgs e)
{
await Navigation.PushAsync(new GamePage
{
BindingContext = _gameViewModel
});
Stream fileStream = await FileSystem.Current.OpenAppPackageFileAsync("15.txt");
await _gameModel.LoadGameAsync(fileStream);
_gameViewModel.GameTableNumRows = 15;
_gameViewModel.GameTableNumCols = 15;
_gameViewModel.GenerateFields();
}
/// <summary>
/// event handler method for choosing 19 as tablesize and reading appropriate table
/// </summary>
private async void GameViewModel_ButtonLargeTableClick(object? sender, System.EventArgs e)
{
await Navigation.PushAsync(new GamePage
{
BindingContext = _gameViewModel
});
Stream fileStream = await FileSystem.Current.OpenAppPackageFileAsync("19.txt");
await _gameModel.LoadGameAsync(fileStream);
_gameViewModel.GameTableNumRows = 19;
_gameViewModel.GameTableNumCols = 19;
_gameViewModel.GenerateFields();
}
Here's the method definition inside class GameModel:
/// <summary>
/// Loading colored matrix.
/// </summary>
/// <param name="fileStream">Type of Stream.</param>
public async Task LoadGameAsync(System.IO.Stream fileStream)
{
if (_dataAccess == null)
throw new InvalidOperationException("No data access is provided.");
var loadResult = await _dataAccess.LoadAsync(fileStream);
_gameTable = loadResult.Item1;
_player = loadResult.Item2;
_guards = loadResult.Item3;
IsWon = false;
}
Finally, here's the persistence-related method:
/// <summary>
/// Loading file
/// </summary>
/// <param name="path">Filepath.</param>
/// <returns>Gametable, Player, list of Guards.</returns>
public async Task<(UInt16[,], Player, List<Guard>)> LoadAsync(String path)
{
try
{
using (StreamReader reader = new StreamReader(path))
{
String[] gameFieldTypes;
String line = await reader.ReadLineAsync() ?? String.Empty;
Int32 tableSize = Int32.Parse(line);
UInt16[,] table = new UInt16[tableSize, tableSize];
Player player = new Player();
List<Guard> guards = new List<Guard>();
for (Int32 i = 0; i < tableSize; i++)
{
line = await reader.ReadLineAsync() ?? String.Empty;
gameFieldTypes = line.Split(' ');
for (Int32 j = 0; j < tableSize; j++)
{
if (gameFieldTypes[j] == "Empty")
{
table[j, i] = 0;
}
if (gameFieldTypes[j] == "Wall")
{
table[j, i] = 1;
}
if (gameFieldTypes[j] == "Guard")
{
table[j, i] = 2;
Guard guard = new Guard();
guard.X = j;
guard.Y = i;
guards.Add(guard);
}
if (gameFieldTypes[j] == "Player")
{
table[j, i] = 3;
player.X = j;
player.Y = i;
}
if (gameFieldTypes[j] == "Exit")
{
table[j, i] = 4;
}
}
}
return (table, player, guards);
}
}
catch
{
throw new GameDataException();
}
}
EDIT:
Here's the emerging exception: System.IO.FileNotFoundException' in System.Private.CoreLib.dll
Could not find file 'C:\WINDOWS\system32\SuspendedGame'.'
Line throwing exception inside method LoadAsync():
using (StreamReader reader = new StreamReader(path))
And here's the class within which the phrase "SuspendedGame" appears.
public class StoredGameBrowserModel
{
private IStore _store; // persistence
/// <summary>
/// Event of container being changed.
/// </summary>
public event EventHandler? StoreChanged;
public StoredGameBrowserModel(IStore store)
{
_store = store;
StoredGames = new List<StoredGameModel>();
}
/// <summary>
/// Query of the list of stored colored-field matrices.
/// </summary>
public List<StoredGameModel> StoredGames { get; private set; }
/// <summary>
/// Updating stored resource files.
/// </summary>
public async Task UpdateAsync()
{
if (_store == null)
return;
StoredGames.Clear();
// loading saved files
foreach (String name in await _store.GetFilesAsync())
{
if (name == "SuspendedGame") // we do not want to load this particular saved file
continue;
StoredGames.Add(new StoredGameModel
{
Name = name,
Modified = await _store.GetModifiedTimeAsync(name)
});
}
// arranging elements according to their date
StoredGames = StoredGames.OrderByDescending(item => item.Modified).ToList();
OnSavesChanged();
}
private void OnSavesChanged()
{
StoreChanged?.Invoke(this, EventArgs.Empty);
}
}
Solution:
inserting the following code at the beginning of the method body of LoadAsync():
if (!String.IsNullOrEmpty(_basePath))
path = Path.Combine(_basePath, path);
Reasoning:
the prefix library path was missing from the content of the method parameter.

Remove html tags from MainBody

Have an issue here where I try to remove all html tags from this line of EPiServer code
#(Html.PropertyFor(m => m.MainBody)
Because this is suppose to be inside a <a>example code here</a>
Whats a good way to solve this when running EPi Server?
First, it is bad practice using XhtmlString this way, that being said we don't always get to choose.
I'm using this which is a modified version of Rob Volk's extension method.
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
public static class HtmlStringExtensions
{
/// <summary>
/// Truncates a string containing HTML to a number of text characters, keeping whole words.
/// The result contains HTML and any tags left open are closed.
/// by Rob Volk with modifications
/// http://robvolk.com/truncate-html-string-c-extension-method/
/// </summary>
/// <param name="html"></param>
/// <param name="maxCharacters"></param>
/// <param name="trailingText"></param>
/// <returns></returns>
public static string TruncateHtmlString(this string html, int maxCharacters, string trailingText)
{
if (string.IsNullOrEmpty(html))
return html;
// find the spot to truncate
// count the text characters and ignore tags
var textCount = 0;
var charCount = 0;
var ignore = false;
var newString = string.Empty;
foreach (char c in html)
{
newString += c;
charCount++;
if (c == '<')
{
ignore = true;
}
else if (!ignore)
{
textCount++;
}
if (c == '>')
{
ignore = false;
}
// stop once we hit the limit
if (textCount >= maxCharacters)
{
break;
}
}
// Truncate the html and keep whole words only
var trunc = new StringBuilder(newString);
//var trunc = new StringBuilder(html.TruncateWords(charCount));
// keep track of open tags and close any tags left open
var tags = new Stack<string>();
var matches = Regex.Matches(trunc.ToString(), // trunc.ToString()
#"<((?<tag>[^\s/>]+)|/(?<closeTag>[^\s>]+)).*?(?<selfClose>/)?\s*>",
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);
foreach (Match match in matches)
{
if (match.Success)
{
var tag = match.Groups["tag"].Value;
var closeTag = match.Groups["closeTag"].Value;
// push to stack if open tag and ignore it if it is self-closing, i.e. <br />
if (!string.IsNullOrEmpty(tag) && string.IsNullOrEmpty(match.Groups["selfClose"].Value))
tags.Push(tag);
// pop from stack if close tag
else if (!string.IsNullOrEmpty(closeTag))
{
// pop the tag to close it.. find the matching opening tag
// ignore any unclosed tags
while (tags.Pop() != closeTag && tags.Count > 0)
{ }
}
}
}
if (html.Length > charCount)
// add the trailing text
trunc.Append(trailingText);
// pop the rest off the stack to close remainder of tags
while (tags.Count > 0)
{
trunc.Append("</");
trunc.Append(tags.Pop());
trunc.Append('>');
}
return trunc.ToString();
}
/// <summary>
/// Truncates a string containing HTML to a number of text characters, keeping whole words.
/// The result contains HTML and any tags left open are closed.
/// </summary>
/// <param name="html"></param>
/// <param name="maxCharacters"></param>
/// <returns></returns>
public static string TruncateHtmlString(this string html, int maxCharacters)
{
return html.TruncateHtmlString(maxCharacters, null);
}
/// <summary>
/// Strips all HTML tags from a string
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string StripHtml(this string html)
{
if (string.IsNullOrEmpty(html))
return html;
return Regex.Replace(html, #"<(.|\n)*?>", string.Empty);
}
}
Implement using the ToHtmlString() from EPiServer.Core
In example
// #using EPiServer.Core
#(Html.PropertyFor(m => m.MainBody.ToHtmlString().TruncateHtmlString(160, "..."))
Why don't you use string backed by TextArea?
[UIHint(UIHint.Textarea)]
[Display(Name = "Main Body")]
public virtual string MainBody { get; set; }
What you trying to do using XhtmlString is not a best practise and it could have so many negative effects on your rendering.

Get certain value in the string from text file

I have this in my text file:
000000000:Carrots:$1.99:214:03/11/2015:03/11/2016:$0.99
000000001:Bananas:$1.99:872:03/11/2015:03/11/2016:$0.99
000000002:Chocolate:$2.99:083:03/11/2015:03/11/2016:$1.99
000000003:Spaghetti:$3.99:376:03/11/2015:03/11/2016:$2.99
000000004:Tomato Sauce:$1.99:437:03/11/2015:03/11/2016:$0.99
000000005:Lettuce:$0.99:279:03/11/2015:03/11/2016:$0.99
000000006:Orange Juice:$2.99:398:03/11/2015:03/11/2016:$1.99
000000007:Potatoes:$2.99:792:03/11/2015:03/11/2016:$1.99
000000008:Celery:$0.99:973:03/11/2015:03/11/2016:$0.99
000000009:Onions:$1.99:763:03/11/2015:03/11/2016:$0.99
000000010:Chicken:$8.99:345:03/11/2015:03/11/2016:$7.99
000000010:Chicken:$8.99:345:03/11/2015:03/11/2016:$7.99
I need to get the value of each of the "quantity" values from the position in bold.
EDIT:
I want to also compare the values that I got and give an error if the quantity is low.
Solution with minimal memory consumption in case of large input data.
In additional: there are not processing of incorrect data in quantity column. To do this just replace int.Parse block;
This is several methods to process file data using LINQ expressions
internal static class MyExtensions
{
/// <exception cref="OutOfMemoryException">There is insufficient memory to allocate a buffer for the returned string. </exception>
/// <exception cref="IOException">An I/O error occurs. </exception>
/// <exception cref="ArgumentException"><paramref name="stream" /> does not support reading. </exception>
/// <exception cref="ArgumentNullException"><paramref name="stream" /> is null. </exception>
public static IEnumerable<string> EnumerateLines(this Stream stream)
{
using (var reader = new StreamReader(stream))
{
do
{
var line = reader.ReadLine();
if (line == null) break;
yield return line;
} while (true);
}
}
/// <exception cref="ArgumentNullException"><paramref name="line"/> is <see langword="null" />.</exception>
public static IEnumerable<string> ChunkLine(this string line)
{
if (line == null) throw new ArgumentNullException("line");
return line.Split(':');
}
/// <exception cref="ArgumentNullException"><paramref name="chuckedData"/> is <see langword="null" />.</exception>
/// <exception cref="ArgumentException">Index should be not negative value</exception>
public static string GetColumnData(this IEnumerable<string> chuckedData, int columnIndex)
{
if (chuckedData == null) throw new ArgumentNullException("chuckedData");
if (columnIndex < 0) throw new ArgumentException("Column index should be >= 0", "columnIndex");
return chuckedData.Skip(columnIndex).FirstOrDefault();
}
}
This is example of usage:
private void button1_Click(object sender, EventArgs e)
{
var values = EnumerateQuantityValues("largefile.txt");
// do whatever you need
}
private IEnumerable<int> EnumerateQuantityValues(string fileName)
{
const int columnIndex = 3;
using (var stream = File.OpenRead(fileName))
{
IEnumerable<int> enumerable = stream
.EnumerateLines()
.Select(x => x.ChunkLine().GetColumnData(columnIndex))
.Select(int.Parse);
foreach (var value in enumerable)
{
yield return value;
}
}
}
just consider if you are managed to get all these lines in string array or list.
you can apply the below code to get the collection of quantity as IEnumerable<string>.
var quantity = arr.Select(c =>
{
var temp = c.Split('$');
if (temp.Length > 1)
{
temp = temp[1].Split(':');
if (temp.Length > 1)
{
return temp[1];
}
}
return null;
}).Where(c => c != null);
UPDATE
Check the Fiddle.
https://dotnetfiddle.net/HqKdeI
you simply need to split the string
string data = #"000000000:Carrots:$1.99:214:03/11/2015:03/11/2016:$0.99
000000001:Bananas:$1.99:872:03/11/2015:03/11/2016:$0.99
000000002:Chocolate:$2.99:083:03/11/2015:03/11/2016:$1.99
000000003:Spaghetti:$3.99:376:03/11/2015:03/11/2016:$2.99
000000004:Tomato Sauce:$1.99:437:03/11/2015:03/11/2016:$0.99
000000005:Lettuce:$0.99:279:03/11/2015:03/11/2016:$0.99
000000006:Orange Juice:$2.99:398:03/11/2015:03/11/2016:$1.99
000000007:Potatoes:$2.99:792:03/11/2015:03/11/2016:$1.99
000000008:Celery:$0.99:973:03/11/2015:03/11/2016:$0.99
000000009:Onions:$1.99:763:03/11/2015:03/11/2016:$0.99
000000010:Chicken:$8.99:345:03/11/2015:03/11/2016:$7.99";
string[] rows = data.split(Environment.Newline.ToCharArray());
foreach(var row in rows)
{
string[] cols = row.Split(':');
var quantity = cols[3];
}
You can use String.Split to do this.
// Read all lines into an array
string[] lines = File.ReadAllLines(#"C:\path\to\your\file.txt");
// Loop through each one
foreach (string line in lines)
{
// Split into an array based on the : symbol
string[] split = line.Split(':');
// Get the column based on index
Console.WriteLine(split[3]);
}
Check out the example code below. The string you care about is named theValueYouWantInTheString.
char[] delimiterChar = { ':' };
string input = #"000000010:Chicken:$8.99:345:03/11/2015:03/11/2016:$7.99";
string[] values = input.Split(delimiterChar);
string theValueYouWantInTheString = values[3];
If you have a problem, use regular expression. Now you have two problems.
Here is a program that uses your input as a txt file. The function GetQuantity returns a list with int that contains the quantity. With this approach you can define more groups to extract information from each line.
namespace RegExptester
{
class Program
{
private static List<int> GetQuantity(string txtFile)
{
string tempLineValue;
Regex regex = new Regex(#"[0-9]*:[a-zA-Z]*:\$[0-9]*\.[0-9]*:([0-9]*).*", RegexOptions.Compiled);
List<int> retValue = new List<int>();
using (StreamReader inputReader = new StreamReader(txtFile))
{
while (null != (tempLineValue = inputReader.ReadLine()))
{
Match match = regex.Match(tempLineValue);
if (match.Success)
{
if(match.Groups.Count == 2)
{
int numberValue;
if (int.TryParse(match.Groups[1].Value, out numberValue))
retValue.Add(numberValue);
}
}
}
}
return retValue;
}
static void Main(string[] args)
{
var tmp = GetQuantity("c:\\tmp\\junk.txt");
}
}
}
Apparently from each line you want the part between the 3th and the 4th colon. Linq can do that for you:
using (var textReader = new StreamReader(fileName))
{
// read all text and divide into lines:
var allText = textReader.ReadToEnd();
var allLines = textReader.Split(new char[] {'\r','\n'}, StringSplitIoptions.RemoveEmptyEntries);
// split each line based on ':', and take the fourth element
var myValues = allLines.Select(line => line.Split(new char[] {':'})
.Skip(3)
.FirstOrDefault();
}
If you want less readability, of course you can concatenate these statements into one line.

C# + CoDeSys Automation Platform SDK -> PLC's variable = VarRefState.NotMonitoredYet

I'm trying to read the variable of the PLC.
In the Automation Platform, I have a plugin who start a my test (that I've write in C#).
When I execute the plugin for the first time, it always give me the same error. But if a execute it again it's good.
I use a List<IOnlineVarRef6> vars to read my variable. My error is that my vars's State is NotMonitoredYet.
Exemple :
private bool CompareValues(TestCase test, List<IOnlineVarRef6> vars)
{
// Stop here the first time
if (vars.First().State == VarRefState.NotMonitoredYet)
return false;
// Execute well the other times
List<Variable> initialVars = test.initialVariables;
for (int i = 0; i < vars.Count(); i++)
{
object initialValue = initialVars.Single(v => v.Name == vars[i].Expression.ToString()).Value;
if (!vars[i].Value.Equals(initialValue))
return false;
}
return true;
}
I think the problem is in the method who get my variable :
/// <summary>
/// Create a variable watch for each of the specified variable in the list
/// </summary>
/// <example>
/// Variable should include object hierarchy up to Device
/// GPX.Diesel_Control.CTRL.PB_CRANK1
/// </example>
public List<IOnlineVarRef6> CreateVariableWatch(List<string> vars)
{
IVarRef2 varref;
IOnlineVarRef6 iov;
IOnlineApplication17 onlineapp = (IOnlineApplication17)onlineMgr.GetApplication(SystemInstances.Engine.Projects.PrimaryProject.ActiveApplication);
List<IOnlineVarRef6> lstVarRef = new List<IOnlineVarRef6>();
foreach (string var in vars)
{
varref = (IVarRef2)SystemInstances.LanguageModelMgr.GetVarReference(var);
iov = (IOnlineVarRef6)onlineMgr.CreateWatch(varref);
lstVarRef.Add(iov);
}
return lstVarRef;
}
I have a method that wait before calling the CompareValues() and it retries 3 times and it wait before trying again :
public void SetIsTestPassed(TestCase test)
{
Thread.Sleep(test.delayedStart);
int retries = test.retries;
do
{
List<IOnlineVarRef6> vars = SetCurrentVars(test);
test.IsTestPassed = CompareValues(test, vars);
if (test.retryDelay > 0 && !test.IsTestPassed)
Thread.Sleep(test.retryDelay);
retries--;
} while (retries != 0 && !test.IsTestPassed);
}
private List<IOnlineVarRef6> SetCurrentVars(TestCase test)
{
OnlineManagerHelper OnlineMgr = new OnlineManagerHelper(true);
return OnlineMgr.CreateVariableWatch(
test.initialVariables
.Select(v => Settings.Default.InstancePath + v.Name)
.ToList());
}

escaping tricky string to CSV format

I have to create a CSV file from webservice output and the CSV file uses quoted strings with comma separator. I cannot change the format...
So if I have a string it becomes a "string"...
If the value has quotes already they are replaced with double quotes.
For example a str"ing becomes "str""ing"...
However, lately my import has been failing because of the following
original input string is: "","word1,word2,..."
every single quote is replaced by double resulting in: """",""word1,word2,...""
then its prefixed and suffixed with quote before written to CVS file: """"",""word1,word2,..."""
As you can see the final result is this:
""""",""word1,word2,..."""
which breaks my import (is sees it as another field)...
I think the issue is appereance of "," in the original input string.
Is there a CVS escape sequence for this scenario?
Update
The reason why above breaks is due to BCP mapping file (BCP utility is used to load CSV file into SQL db) which has terminator defined as "," . So instead of seeing 1 field it sees 2...But I cannot change the mapping file...
I use this code and it has always worked:
/// <summary>
/// Turn a string into a CSV cell output
/// </summary>
/// <param name="str">String to output</param>
/// <returns>The CSV cell formatted string</returns>
public static string StringToCSVCell(string str)
{
bool mustQuote = (str.Contains(",") || str.Contains("\"") || str.Contains("\r") || str.Contains("\n"));
if (mustQuote)
{
StringBuilder sb = new StringBuilder();
sb.Append("\"");
foreach (char nextChar in str)
{
sb.Append(nextChar);
if (nextChar == '"')
sb.Append("\"");
}
sb.Append("\"");
return sb.ToString();
}
return str;
}
Based on Ed Bayiates' answer:
/// <summary>
/// Turn a string into a CSV cell output
/// </summary>
/// <param name="value">String to output</param>
/// <returns>The CSV cell formatted string</returns>
private string ConvertToCsvCell(string value)
{
var mustQuote = value.Any(x => x == ',' || x == '\"' || x == '\r' || x == '\n');
if (!mustQuote)
{
return value;
}
value = value.Replace("\"", "\"\"");
return string.Format("\"{0}\"", value);
}
My penny thought:
String[] lines = new String[] { "\"\",\"word\",word,word2,1,34,5,2,\"details\"" };
for (int j = 0; j < lines.Length; j++)
{
String[] fields=lines[j].Split(',');
for (int i =0; i<fields.Length; i++)
{
if (fields[i].StartsWith("\"") && fields[i].EndsWith("\""))
{
char[] tmp = new char[fields[i].Length-2];
fields[i].CopyTo(1,tmp,0,fields[i].Length-2);
fields[i] =tmp.ToString();
fields[i] = "\""+fields[i].Replace("\"","\"\"")+"\"";
}
else
fields[i] = fields[i].Replace("\"","\"\"");
}
lines[j]=String.Join(",",fields);
}
Based on contribution of "Ed Bayiates" here's an helpful class to buid csv document:
/// <summary>
/// helpful class to build csv document
/// </summary>
public class CsvBuilder
{
/// <summary>
/// create the csv builder
/// </summary>
public CsvBuilder(char csvSeparator)
{
m_csvSeparator = csvSeparator;
}
/// <summary>
/// append a cell
/// </summary>
public void appendCell(string strCellValue)
{
if (m_nCurrentColumnIndex > 0) m_strBuilder.Append(m_csvSeparator);
bool mustQuote = (strCellValue.Contains(m_csvSeparator)
|| strCellValue.Contains('\"')
|| strCellValue.Contains('\r')
|| strCellValue.Contains('\n'));
if (mustQuote)
{
m_strBuilder.Append('\"');
foreach (char nextChar in strCellValue)
{
m_strBuilder.Append(nextChar);
if (nextChar == '"') m_strBuilder.Append('\"');
}
m_strBuilder.Append('\"');
}
else
{
m_strBuilder.Append(strCellValue);
}
m_nCurrentColumnIndex++;
}
/// <summary>
/// end of line, new line
/// </summary>
public void appendNewLine()
{
m_strBuilder.Append(Environment.NewLine);
m_nCurrentColumnIndex = 0;
}
/// <summary>
/// Create the CSV file
/// </summary>
/// <param name="path"></param>
public void save(string path )
{
File.WriteAllText(path, ToString());
}
public override string ToString()
{
return m_strBuilder.ToString();
}
private StringBuilder m_strBuilder = new StringBuilder();
private char m_csvSeparator;
private int m_nCurrentColumnIndex = 0;
}
How to use it:
void exportAsCsv( string strFileName )
{
CsvBuilder csvStringBuilder = new CsvBuilder(';');
csvStringBuilder.appendCell("#Header col 1 : Name");
csvStringBuilder.appendCell("col 2 : Value");
csvStringBuilder.appendNewLine();
foreach (Data data in m_dataSet)
{
csvStringBuilder.appendCell(data.getName());
csvStringBuilder.appendCell(data.getValue());
csvStringBuilder.appendNewLine();
}
csvStringBuilder.save(strFileName);
}
the first step in parsing this is removing the extra added " 's around your string. Once you do this, you should be able to deal with the embedded " as well as the ,'s.
After much deliberation, it was decided that import utility format was needed to be fixed. The escaping of the string was correct (as users indicated) but the format file that import utility used was incorrect and was causing it to break import.
Thanks all and special thanks to #dbt (up vote)

Categories