in the following code I want to have my OpenFileDialog open in a method until a valid file is selected. This works only conditionally. For some reason it adds a column after the message is displayed. This causes correct data tables to also be read incorrectly if I have previously selected an incorrect file.
public static InputData GetCSVData()
{
InputData InputData = new InputData();
OpenFileDialog OFDReader = new OpenFileDialog();
//Filter OpenFileDialog; show only CSV-Files
OFDReader.Filter = "CSV files|*.csv;";
// check if data contains "Date/Time" .
OFDReader.FileOk += delegate (object s, CancelEventArgs ev)
{
//search for Line to start reader
int LineCounter = 0;
var readertmp = new StreamReader(OFDReader.FileName);
while (true)
{
string LineTmp = readertmp.ReadLine();
string record = "Date/Time";
if (LineTmp.Contains(record))
{ break; }
else if (readertmp.EndOfStream)
{
MessageBox.Show("Data has no DataPoints !", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
ev.Cancel = true;
{ break; }
}
LineCounter++;
}
//read InputData
var reader = new StreamReader(OFDReader.FileName);
for (int i = 0; i < LineCounter; i++)
{
reader.ReadLine();
}
// settings CSVHelper
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ";", // Set delimiter
};
var csv = new CsvReader(reader, config);
var DataRead = new CsvDataReader(csv);
InputData.DataTable.Load(DataRead);
//check for enough columns
int ColumnCounter = 0;
ColumnCounter = InputData.DataTable.Columns.Count;
if (ColumnCounter <= 2)
{
MessageBox.Show("Data has not enough columns!", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
ev.Cancel = true;
}
};
if (OFDReader.ShowDialog() == DialogResult.OK)
{
InputData.FilePath = OFDReader.FileName;
}
return InputData;
}
}
It appears you are making this more complicated than it has to be. For starters it seems odd (at least to me) that you would bother with the FileOK delegate. I do not see what difference it would make if the user is presented with an OpenFileDialog once, twice or many times. Using a single OpenFileDialog for this just seems like a waste of effort.
If the user selects a file and it fails to meet the necessary requirements, then simply open another OpenFileDialog and let the user try again. Doing this in a single dialog is certainly possible, however, “where” else would you use this? It appears this dialog is “specific” to a “certain” type of file, why limit the dialog to the requirement we need. I would think a simple method that loops forever until the user selects a valid file or Cancels the OpenFileDialog would be an easier approach.
With that said, following your code is a little odd. The reason for your issue is that the code is reading the file into the InputData.DataTable regardless if the file FAILS having datapoints OR enough columns. Put a breakpoint on the line…
InputData.DataTable.Load(DataRead);
You will see that the DataTable is filled with the data even if the data has no “DataPoints.” After the above line of code executes the next few lines check to see if the DataTable has 2 or more columns of data. If there are not enough columns, then the code simply pops up a message box indicating this.
This appears straight forward, however, the InputData.DataTable STILL HAS THE DATA even if it was bad. Next time you call the above Load method, it will simply ADD the new table to the existing table. It will add the columns if needed and simply add the rows to the bottom of the existing DataTable. Try opening several BAD files then eventually open the good file and you will see many added columns and rows.
I will assume that you may be under the impression that when you call…
ev.Cancel = true;
That the code stops right there and goes back to the first line in the delegate…
int LineCounter = 0;
… this would not be true. The code continues after ev.Cancel = true; is executed.
This can be seen by the fact that you are getting extra columns and rows every time a BAD file is attempted to be opened. A simple solution is to simply create a “new” InputData object just before you call the load method. Something like…
InputData = new InputData();
InputData.DataTable.Load(DataRead);
This will fix the extra columns issue, however, IF the user selects a BAD file and the error message pops up and the user clicks the OK button to go back to the open file dialog… THEN… IF the user then clicks the open file dialogs “Cancel” button, the BAD file will still be displayed in the grid. I am confident you may not want this behavior.
Without going into detail about some of the other strange aspects of the posted code. I proffer one other possible simpler solution as described in the beginning. Granted, the code below uses multiple OpenFileDialogs, however the user still cannot escape until they pick a valid file or cancel the dialog.
Much of the code below is taken from the existing posted code however, it is structured differently. Initially some variables are created before we stat an endless loop. Specifically, the CsvConfiguration variable config has some added properties set that ignore some code crashing problems when reading the file. I am confident you will want to set up the CsvReader to handle these problems the way you want them to be handled.
Once inside the endless while loop, the code creates a new InputData object, initializes a new OpenFileDialog and sets its properties. Then the code displays the OpenFileDialog and when the dialog returns, the DialogResult result variable is set to the dialogs returned DialogResult.
If the dialog returns OK then the code checks to see if the file is an “empty” file. If the file is empty, a message box is displayed to inform the user, then we branch back up to the begging of the loop. If the dialog result is Cancel, then the code will return a “new” InputData object. The reason for the empty check is that an exception (No header record was found) will be throw on the line…
DataRead = new CsvDataReader(csv);
If the file is empty.
I am confident that there may be some CsvHelper property that I missed that would prevent this “empty” file exception. If there is some better way to check for this “empty” file or prevent the exception, I am open to suggestions.
If the file is NOT empty, we continue by opening the file and go ahead and read its data as intended using the CsvDataReader. The idea is that… IF the file reads correctly without errors and fits the requirements, then we will already have the InputData.DataTable set and all that is left to do is to set its FilePath property and return the InputData object.
Once we have the InputData.DataTable we can check the number of columns in the InputData.DataTable. If the number of columns is less than two (2), then pop up the error message box to the user and loop back to the begging of the while loop.
If the InputData.DataTable meets the two (2) or more columns requirement, then another check is made by looping through all the columns in the data table. If at least ONE (1) column name is “Date/Time” then we are done checking the requirements and simply set the InputData.FileName property and return the InputData object.
If none of the column names in the InputData.DataTable columns is named ”Date/Time,” then again we pop up the error message box and loop back to the begging of the while loop.
It should be noted that if the file fails the number of columns test or the column named Date/Time test… then as with your problem, the InputData.DataTable STILL HAS THE DATA. This is OK here since we will re-initialize a “new” InputData object when we loop back up to the begging of the while loop.
Lastly, you do not show the InputData Class, however it appears to have at least two (2) properties… 1) a string FilePath and 2) a DataTable named DataTable??? this looks odd and is ambiguous… I have renamed my InputData object’s DataTable property to DT. The same “ambiguity” applies to the InputData variable which I have changed to TempInputData.
Since the code may “potentially” create numerous InputData objects each time the user selects a BAD file, I have implemented the IDisposable interface in the InputData Class. This way we can use this Class in a using statement and properly dispose of the unused InputData objects the code creates. I hope I have implemented this correctly.
public class InputData : IDisposable {
public DataTable DT;
public string FilePath;
private bool isDisposed;
public InputData() {
DT = new DataTable();
FilePath = "";
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (isDisposed) {
return;
}
if (disposing) {
DT?.Dispose();
FilePath = null;
}
isDisposed = true;
}
}
private InputData GetInputDataFromSCV() {
InputData TempInputData;
OpenFileDialog OFDReader;
string initialDirectory = #"D:\Test\CSV";
DialogResult result;
CsvConfiguration config = new CsvConfiguration(CultureInfo.InvariantCulture) {
Delimiter = ";",
IgnoreBlankLines = true,
MissingFieldFound = null,
BadDataFound = null
};
CsvReader csv;
CsvDataReader DataRead;
StreamReader readertmp;
FileInfo fi;
while (true) {
using (TempInputData = new InputData()) {
using (OFDReader = new OpenFileDialog()) {
OFDReader.Filter = "CSV files|*.csv;";
OFDReader.InitialDirectory = initialDirectory;
result = OFDReader.ShowDialog();
if (result == DialogResult.OK) {
fi = new FileInfo(OFDReader.FileName);
if (fi.Length != 0) {
using (readertmp = new StreamReader(OFDReader.FileName)) {
csv = new CsvReader(readertmp, config);
DataRead = new CsvDataReader(csv);
TempInputData.DT.Load(DataRead);
if (TempInputData.DT.Columns.Count > 2) {
foreach (DataColumn column in TempInputData.DT.Columns) {
if (column.ColumnName == "Date/Time") {
TempInputData.FilePath = OFDReader.FileName;
return TempInputData;
}
}
// if we get here we know a column named "Date/Time" was NOT found
MessageBox.Show("Data has no DataPoints !", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else {
MessageBox.Show("Data has less than 2 columns?", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
else {
MessageBox.Show("File is empty!", "Wrong Data", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
else {
if (result == DialogResult.Cancel) {
return new InputData();
}
}
}
}
}
}
I hope this makes sense and helps.
Im sorry for the inconvenience. Sometimes I really make it too complicated for myself. I have now solved it as follows:
if (result == DialogResult.Cancel)
{
if (inputDataHistory.Loadcount != 0)
{
TempInputData.FilePath = inputDataHistory.FilePathCache;
TempInputData.LineCounter = inputDataHistory.LinecounterCache;
var reader = new StreamReader(TempInputData.FilePath);
for (int i = 0; i < TempInputData.LineCounter; i++)
{
reader.ReadLine();
}
csv = new CsvReader(reader, config);
DataRead = new CsvDataReader(csv);
TempInputData.DT.Load(DataRead);
TempInputData.IsDisposed = true;
return TempInputData;
}
else
{
return new InputData();
}
I don't know if it is the most efficient solution but I read the key variables into another class before. These are used when canceling to re-read the file before.
Related
I was creating a program in Windows forms and I wanted to save the username and the password in 2 separate files.
One is named: name. The second is named: password.
So first I check if the 2 files are empty but when I run the code it deletes the data on the files.
private void btn1_Click(object sender, EventArgs e)
{
int count = 0;
TextWriter name = new StreamWriter("C:\\Users\\skerd\\AppData\\Local\\Temp\\jvvhbbcp..txt");
TextWriter psswd = new StreamWriter("C:\\Users\\skerd\\AppData\\Local\\Temp\\x1pu0bds..txt");
if (new FileInfo("C:\\Users\\skerd\\AppData\\Local\\Temp\\jvvhbbcp..txt").Length == 0 && new FileInfo("C:\\Users\\skerd\\AppData\\Local\\Temp\\x1pu0bds..txt").Length == 0)
{
if (count < 1)
{
name.Write(txtB_1.Text);
psswd.Write(txtB_2.Text);
name.Close();
psswd.Close();
count++;
}
Close();
}
Let's suppose I insert Carl in the name and Jones in the surname when I restart the program I want that in the files it got in the first one the name and in the second one the surname without deleting anything.
Thanks!
You can load your files content or create new content and then modify your content and then - delete original and replace with new. Although, you could, as additional step, first - rename the original as backup and then save the new and then remove the original backup.
var nameFile = "PATH1";
var pwdFile = "PATH2";
bool nameExists = File.Exists(nameFile);
bool pwdExists = File.Exists(pwdFile);
StringBuilder nameContent = new StringBuilder();
if (nameExists)
nameContent.Append(File.ReadAllText(nameFile));
StringBuilder pwdContent = new StringBuilder();
if (pwdExists)
pwdContent.Append(File.ReadAllText(pwdFile));
// you can potencially do more text manipulations here
nameContent.AppendLine("NEW NAME");
pwdContent.AppendLine("NEW PASSWORD");
if (nameExists)
File.Delete(nameFile);
File.WriteAllText(nameFile, nameContent.ToString());
if (pwdExists)
File.Delete(pwdFile);
File.WriteAllText(pwdFile, pwdContent.ToString());
I am trying to basically create config files. A text file will hold something like:
Name::Adam
Location::Washington
I am trying to grab the first part as the field name (i.e. Name.Text would update the TextBox) then put the second part to that Text. Just not sure where to go or what the best way to build this is. The code below is incomplete because I can't figure out how to update the textboxes.
Thanks for the help!
private void clickImportConfig_ItemClick(object sender, DevExpress.Xpf.Bars.ItemClickEventArgs e)
{
Stream myStream = null;
string fieldUpdate = string.Empty;
string fieldUpdateTo = string.Empty;
try
{
using (myStream)
{
string[] lines = File.ReadAllLines(#"c:\\config.txt");
foreach (string s in lines)
{
var splitted = Regex.Split(s, "::");
fieldUpdate = splitted[0].ToString();
fieldUpdateTo = splitted[1].ToString();
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
I think this is what you're looking for:
private void clickImportConfig_ItemClick(object sender, DevExpress.Xpf.Bars.ItemClickEventArgs e)
{
Stream myStream = null;
string fieldUpdate = string.Empty;
string fieldUpdateTo = string.Empty;
try
{
using (myStream)
{
string[] lines = File.ReadAllLines(#"c:\\config.txt");
foreach (string s in lines)
{
string[] splitted = s.Split(new string[] { "::" }, StringSplitOptions.RemoveEmptyEntries);
fieldUpdate = splitted[0].ToString();
fieldUpdateTo = splitted[1].ToString();
// TextBox textBox = (TextBox)this.FindName(fieldUpdate);
// Or
TextBox textBox = this.FindName(fieldUpdate) as TextBox;
// See below for an explanation
if (textBox != null) // FindName returns null if nothing is found with that name
{
textBox.Text = fieldUpdateTo;
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
}
}
As insane_developer pointed out, you will be better off using the String.Split method (s being the string in this case so written as s.Split()) instead of Regex.Split. It will give you the benefit of removing any empty results from the array. It may also have better performance as Regex is capable of a lot more complicated things, but I haven't tested that so I could be wrong.
You can use the FindName(string name) method to find an element with the specified name. This method returns null if nothing is found and an object if the element is found. This object will need to be cast to the type you are expecting (I.e. TextBox). You can do this in one of the following ways:
TextBox textBox = (TextBox)this.FindName(fieldUpdate);
or
TextBox textBox = this.FindName(fieldUpdate) as TextBox;
The first option will throw an InvalidCastException if FindName returns an object which is not a TextBox. The second option will instead just set the value of textBox to null which will be checked by the if statement and the exception will be avoided. As you are only catching all generic exceptions in this code, an InvalidCastException would show your "Could not read file from disk" message which is not true. So you may want to add an additional catch block to handle any invalid casting.
If you're wondering why you don't just stick to the second option as it solves this problem, then consider this scenario as an example. Lets say in the future you decide for some reason that you want to change all of your TextBox to TextBlock or something else, but forget to come back to change this code, or accidently end up with the name of another type of control in your text file. The second option will set the value of textBox to null and your field(s) won't be updated. But there will be absolutely no errors, leaving you scratching your head and having to debug the problem. The first option would throw an InvalidCastException showing you exactly where the problem is. You could then choose how to handle this problem by either showing another message box or silently writing the error to a log file etc.
You don't need a regular expression, just:
var splitted = s.Split("::", StringSplitOptions.RemoveEmptyEntries);
fieldUpdate = splitted[0];
fieldUpdateTo = splitted[1];
For the rest you have to be more explicit
I am working with C# for the first time and I am facing a strange issue.
I am building my own class for a plugin, but copied parts of the code from an existing class. Basically it's
var sanInput = Console.ReadLine();
alternativeNames = sanInput.Split(',');
sanList = new List<string>(alternativeNames);
but somehow this doesn't work. The debug console says System.Console.ReadLine returned jogi,philipp string, but sanInput keeps null as its value.
Even stranger is the fact, that the next step works "a bit". string.Split returned {string[2]} string[], so it returns an array of [jogi, philipp], but still sanInput, alternativeNamesand sanList stay as null.
How is it possible, that the second line works if sanInput has no value and how can I fix this problem? When I work with the existing class with the same code everything works as expected.
//EDIT:
Looks like a quite complicated issue. Here is the complete method:
public override void HandleMenuResponse(string response, List<Target> targets)
{
if (response == "r")
{
Console.WriteLine("Which hosts do you want to configure? Enter numbers separated by a comma.");
var hostsInput = Console.ReadLine();
int[] hosts = null;
string[] alternativeNames = null;
List<string> sanList = null;
hosts = hostsInput.Split(',').Select(int.Parse).ToArray();
Console.Write("Generating certificates for ");
foreach (int entry in hosts)
{
Console.Write(targets[entry - 1].Host + ", ");
}
Console.Write("\n \n");
foreach (int entry in hosts)
{
int entry2 = entry - 1;
if (Program.Options.San)
{
Console.WriteLine("Enter all Alternative Names for " + targets[entry2].Host + " seperated by a comma:");
// Copied from http://stackoverflow.com/a/16638000
int BufferSize = 16384;
Stream inputStream = Console.OpenStandardInput(BufferSize);
Console.SetIn(new StreamReader(inputStream, Console.InputEncoding, false, BufferSize));
var sanInput = Console.ReadLine();
alternativeNames = sanInput.Split(',');
sanList = new List<string>(alternativeNames);
targets[entry2].AlternativeNames.AddRange(sanList);
}
Auto(targets[entry - 1]);
}
}
if (response == "e")
{
string[] alternativeNames = null;
List<string> sanList = new List<string>();
if (Program.Options.San)
{
Console.WriteLine("Enter all Alternative Names seperated by a comma:");
// Copied from http://stackoverflow.com/a/16638000
int BufferSize = 16384;
Stream inputStream = Console.OpenStandardInput(BufferSize);
Console.SetIn(new StreamReader(inputStream, Console.InputEncoding, false, BufferSize));
var sanInput = Console.ReadLine();
alternativeNames = sanInput.Split(',');
}
if (alternativeNames != null)
{
sanList = new List<string>(alternativeNames);
}
foreach (var entry in targets)
{
Auto(entry);
}
}
}
I know the code isn't pretty and efficient. All in all it let's the user decide if he wants to use all detected hosts (response e) or only single ones (response r). But the mentioned problem occurs only in the second if-method. If I switch them it's again the latter one. So maybe the reason lies in the main program or in this BufferSize-Stuff? I don't know.
//EDIT 2: I think I found the problem: Somehow the integer BufferSize (shortly before the Console.Read()) is set to 0, so of course without any buffer it can't read the input. So the question remains: Why?
//EDIT 3: Okay, I'm done. It looks like I can't use the same name for the variables although they are in two different if-methods. I just named them sanInput2, alternativeNames2 etc. and now everything works.
try this, all of the variables are having values(you can use var also for all the variables):
var sanInput = Console.ReadLine();
string[] alternativeNames = sanInput.Split(',');
List<string> sanList = new List<string>(alternativeNames);
The problem you mention is, that debugging code in VS do assignements in two steps. First is to execute Console.ReadLine() (therefore you see Console.Readline returned message) and AFTER that is is assigned into sanInput. Same situation is after Split. Function is called, but not assigned yet.
My recommendation: use rather the step over instead of step inside. After time, you get used to this functionality and appreciate it.
I have been working successfully with the C# OpenXml SDK (Unofficial Microsoft Package 2.5 from NuGet) for some time now, but have recently noticed that the following line of code returns different results depending on what mood Microsoft Word appears to be in when the file gets saved:
var fields = document.Descendants<FieldCode>();
From what I can tell, when creating the document in the first place (using Word 2013 on Windows 8.1) if you use the Insert->QuickParts->Field and choose MergeField from the Field names left hand pane, and then provide a Field name in the field properties and click OK then the field code is correctly saved in the document as I would expect.
Then when using the aforementioned line of code I will receive a field code count of 1 field. If I subsequently edit this document (and even leave this field well alone) the subsequent saving could mean that this field code no longer is returned in my query.
Another case of the same curiousness is when I see the FieldCode nodes split across multiple items. So rather than seeing say:
" MERGEFIELD Author \\* MERGEFORMAT "
As the node name, I will see:
" MERGEFIELD Aut"
"hor \\* MERGEFORMAT"
Split as two FieldCode node values. I have no idea why this would be the case, but it certainly makes my ability to match nodes that much more exciting. Is this expected behaviour? A known bug? I don't really want to have to crack open the raw xml and edit this document to work until I understand what is going on. Many thanks all.
I came across this very problem myself, and found a solution that exists within OpenXML: a utility class called MarkupSimplifier which is part of the PowerTools for Open XML project. Using this class solved all the problems I was having that you describe.
The full article is located here.
Here are some pertinent exercepts :
Perhaps the most useful simplification that this performs is to merge adjacent runs with identical formatting.
It goes on to say:
Open XML applications, including Word, can arbitrarily split runs as necessary. If you, for instance, add a comment to a document, runs will be split at the location of the start and end of the comment. After MarkupSimplifier removes comments, it can merge runs, resulting in simpler markup.
An example of the utility class in use is:
SimplifyMarkupSettings settings = new SimplifyMarkupSettings
{
RemoveComments = true,
RemoveContentControls = true,
RemoveEndAndFootNotes = true,
RemoveFieldCodes = false,
RemoveLastRenderedPageBreak = true,
RemovePermissions = true,
RemoveProof = true,
RemoveRsidInfo = true,
RemoveSmartTags = true,
RemoveSoftHyphens = true,
ReplaceTabsWithSpaces = true,
};
MarkupSimplifier.SimplifyMarkup(wordDoc, settings);
I have used this many times with Word 2010 documents using VS2015 .Net Framework 4.5.2 and it has made my life much, much easier.
Update:
I have revisited this code and have found it clears upon runs on MERGEFIELDS but not IF FIELDS that reference mergefields e.g.
{if {MERGEFIELD When39} = "Y???" "Y" "N" }
I have no idea why this might be so, and examination of the underlying XML offers no hints.
Word will often split text runs with into multiple text runs for no reason I've ever understood. When searching, comparing, tidying etc. We preprocess the body with method which combines multiple runs into a single text run.
/// <summary>
/// Combines the identical runs.
/// </summary>
/// <param name="body">The body.</param>
public static void CombineIdenticalRuns(W.Body body)
{
List<W.Run> runsToRemove = new List<W.Run>();
foreach (W.Paragraph para in body.Descendants<W.Paragraph>())
{
List<W.Run> runs = para.Elements<W.Run>().ToList();
for (int i = runs.Count - 2; i >= 0; i--)
{
W.Text text1 = runs[i].GetFirstChild<W.Text>();
W.Text text2 = runs[i + 1].GetFirstChild<W.Text>();
if (text1 != null && text2 != null)
{
string rPr1 = "";
string rPr2 = "";
if (runs[i].RunProperties != null) rPr1 = runs[i].RunProperties.OuterXml;
if (runs[i + 1].RunProperties != null) rPr2 = runs[i + 1].RunProperties.OuterXml;
if (rPr1 == rPr2)
{
text1.Text += text2.Text;
runsToRemove.Add(runs[i + 1]);
}
}
}
}
foreach (W.Run run in runsToRemove)
{
run.Remove();
}
}
I tried to simplify the document with Powertools but the result was a corrupted word file. I make this routine for simplify only fieldcodes that has specifics names, works in all parts on the docs (maindocumentpart, headers and footers):
internal static void SimplifyFieldCodes(WordprocessingDocument document)
{
var masks = new string[] { Constants.VAR_MASK, Constants.INP_MASK, Constants.TBL_MASK, Constants.IMG_MASK, Constants.GRF_MASK };
SimplifyFieldCodesInElement(document.MainDocumentPart.RootElement, masks);
foreach (var headerPart in document.MainDocumentPart.HeaderParts)
{
SimplifyFieldCodesInElement(headerPart.Header, masks);
}
foreach (var footerPart in document.MainDocumentPart.FooterParts)
{
SimplifyFieldCodesInElement(footerPart.Footer, masks);
}
}
internal static void SimplifyFieldCodesInElement(OpenXmlElement element, string[] regexpMasks)
{
foreach (var run in element.Descendants<Run>()
.Select(item => (Run)item)
.ToList())
{
var fieldChar = run.Descendants<FieldChar>().FirstOrDefault();
if (fieldChar != null && fieldChar.FieldCharType == FieldCharValues.Begin)
{
string fieldContent = "";
List<Run> runsInFieldCode = new List<Run>();
var currentRun = run.NextSibling();
while ((currentRun is Run) && currentRun.Descendants<FieldCode>().FirstOrDefault() != null)
{
var currentRunFieldCode = currentRun.Descendants<FieldCode>().FirstOrDefault();
fieldContent += currentRunFieldCode.InnerText;
runsInFieldCode.Add((Run)currentRun);
currentRun = currentRun.NextSibling();
}
// If there is more than one Run for the FieldCode, and is one we must change, set the complete text in the first Run and remove the rest
if (runsInFieldCode.Count > 1)
{
// Check fielcode to know it's one that we must simplify (for not to change TOC, PAGEREF, etc.)
bool applyTransform = false;
foreach (string regexpMask in regexpMasks)
{
Regex regex = new Regex(regexpMask);
Match match = regex.Match(fieldContent);
if (match.Success)
{
applyTransform = true;
break;
}
}
if (applyTransform)
{
var currentRunFieldCode = runsInFieldCode[0].Descendants<FieldCode>().FirstOrDefault();
currentRunFieldCode.Text = fieldContent;
runsInFieldCode.RemoveAt(0);
foreach (Run runToRemove in runsInFieldCode)
{
runToRemove.Remove();
}
}
}
}
}
}
Hope this helps!!!
Please help me find the defect in my logic. I have two variables named "prev" and "next"...What i am basically doing is reading the data from my database every 5s and printing it out using Websync server if next and prev are NOT equal. I have two rows in my database . It looks like
ID
8
10
Here is the link to the code http://pastebin.com/Hb3eH2Qv
When i run my program, i get the result as
8 10 8 10
8 10
8 10 8 10
8 10
..... (so on)
But, the result should be just
8 10
I dont know how 8 10 8 10 appears. Data gets concatenated twice.
NOTE: You can just see the code in PublishLoop() function
private void PublishLoop()
{
String prev=String.Copy("");
String next=String.Copy("");
String ConnectionString = ConfigurationManager.ConnectionStrings["MyDbConn"].ToString();
SqlConnection connection = new SqlConnection(ConnectionString);
SqlCommand command = connection.CreateCommand();
command.CommandText = "select ID from Tab1";
command.Notification = null;
while (Running)
{
connection.Open();
using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
StreamWriter sw1 = new StreamWriter("C:\\Users\\Thothathri\\Desktop\\next.txt");
while ((reader.Read()))
{
//Response.Write(reader[0].ToString());
next = String.Concat(next,reader[0].ToString());
sw1.WriteLine(next);
}
sw1.Close();
if (!prev.Equals(next))
{
Publisher publisher = new Publisher(new PublisherArgs
{
DomainKey = "c80cb405-eb77-4574-9405-5ba51832f5e6",
DomainName="localhost"
});
Publication publication = publisher.Publish("/test", JSON.Serialize(next));
if (publication.Successful == true)
{
StreamWriter sw = new StreamWriter("C:\\Users\\Thothathri\\Desktop\\error123.txt");
sw.WriteLine("success");
sw.WriteLine(next);
sw.Close();
}
else
{
StreamWriter sw = new StreamWriter("C:\\Users\\Thothathri\\Desktop\\error123.txt");
sw.Write("failed");
sw.Close();
}
prev = String.Copy(next);
next = String.Copy("");
}
}
Thread.Sleep(5000);
}
}
Renuiz answered it in a comment, but it is because you're not clearing next.
So you build the string "8 10" in next, store it in prev. Next time you concat "8 10" with next, making "8 10 8 10". Which is different so you print it.
if (!prev.Equals(next))
{
....
prev = String.Copy(next);
next = String.Copy("");
}
This is the end of that loop. You should really be clearing next at the beginning of that loop.
Also you can just set the string
next = String.Empty;
I would declare next inside your while loop, as you don't need it in the greater scope, and I would call it current rather than next.
What is really wrong with your program logic - logic is not obvious. It is so obscure, that you can't understand where error is. So, my advise is following - if you can't find the error, try to simplify your code.
Currently your method has many responsibilities - it queries database, it dumps data to file, it publishes data somewhere and logs the results. And you stuck with all that stuff. If someone will need to change database query, or publishing logic - he will need to review all other stuff.
So, separate logic first:
private void PublishLoop()
{
string previousIDs = String.Empty;
int timeout = Int32.Parse(ConfigurationManager.AppSettings["publishTimeout"]);
while (Running)
{
string currentIDs = ConcatenateList(LoadIDs());
Dump(currentIDs);
if (!previousIDs.Equals(currentIDs))
{
try
{
Publish(currentIDs);
_log.Info("Published successfuly");
}
catch (PublicationException exception)
{
_log.Error("Publication failed");
}
previousIDs = currentIDs;
}
Thread.Sleep(timeout);
}
}
Well, I don't know much about your domain, so you probably can think about better names for variables and methods.
Here you have data access logic extracted to separate method (it's ok for first step of refactoring and for small applications). Keep in mind, that wrapping connection object into using block guarantee that connection will be closed in case of exception:
private IList<int> LoadIDs()
{
List<int> ids = new List<int>();
String connectionString = ConfigurationManager.ConnectionStrings["MyDbConn"].ConnectionString;
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = connection.CreateCommand();
command.CommandText = "select ID from Tab1";
command.Notification = null;
connection.Open();
using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
while ((reader.Read()))
ids.Add((int)reader["ID"]);
}
}
return ids;
}
Next - simple method for concatenating ids into one string:
private string ConcatenateList(IList<int> values)
{
return String.Join(" ", values.Select(value => value.ToString()).ToArray());
}
Dumping (mind, that file name moved to configuration file):
private void Dump(string ids)
{
using (StreamWriter writer = new StreamWriter(ConfigurationManager.AppSettings["dumpFilePath"]))
writer.WriteLine(ids);
}
And publishing logic:
private void Publish(string ids)
{
PublisherArgs args = new PublisherArgs
{
DomainKey = "c80cb405-eb77-4574-9405-5ba51832f5e6",
DomainName = "localhost"
};
Publisher publisher = new Publisher(args);
Publication publication = publisher.Publish("/test", JSON.Serialize(ids));
if (!publication.Successful)
throw new PublicationException();
}
I think that failures are exceptional and they not occur very often (so I decided to use exceptions for that case). But if it's something ordinary - you can simply use boolean method like TryPublish.
BTW you can use some logging library like log4net for logging successful and failure publishing. Or you can extract logging logic to separate method - this will make primary logic cleaner and easier to understand.
PS try to avoid comparison of boolean variables with true/false (publication.Successful == true) - you can occasionally assign value to your variable.