How to extract bullet information from word document? - interop

I want to extract information of bullets present in word document.
I want something like this :
Suppose the text below, is in word document :
Steps to Start car :
Open door
Sit inside
Close the door
Insert key
etc.
Then I want my text file like below :
Steps to Start car :
<BULET> Open door </BULET>
<BULET> Sit inside </BULET>
<BULET> Close the door </BULET>
<BULET> Insert key </BULET>
<BULET> etc.</BULET>
I am using C# language to do this.
I can extract paragraphs from word document and directly write them in text file with some formatting information like whether text is bold or is in italics, etc. but dont know how to extract this bullet information.
Can anyone please tell me how to do this?
Thanks in advance

You can do it by reading each sentence. doc.Sentences is an array of Range object. So you can get same Range object from Paragraph.
foreach (Paragraph para in oDoc.Paragraphs)
{
string paraNumber = para.Range.ListFormat.ListLevelNumber.ToString();
string bulletStr = para.Range.ListFormat.ListString;
MessageBox.Show(paraNumber + "\t" + bulletStr + "\t" + para.Range.Text);
}
Into paraNumber you can get paragraph level and into buttetStr you can get bullet as string.

I am using this OpenXMLPower tool by Eric White. Its free and available at NUGet package. you can install it from Visual studio package manager.
He has provided a ready to use code snippet. This tool has saved me many hours. Below is the way I have customized code snippet to use for my requirement.
Infact you can use these methods as it in your project.
private static WordprocessingDocument _wordDocument;
private StringBuilder textItemSB = new StringBuilder();
private List<string> textItemList = new List<string>();
/// Open word document using office SDK and reads all contents from body of document
/// </summary>
/// <param name="filepath">path of file to be processed</param>
/// <returns>List of paragraphs with their text contents</returns>
private void GetDocumentBodyContents()
{
string modifiedString = string.Empty;
List<string> allList = new List<string>();
List<string> allListText = new List<string>();
try
{
_wordDocument = WordprocessingDocument.Open(wordFileStream, false);
//RevisionAccepter.AcceptRevisions(_wordDocument);
XElement root = _wordDocument.MainDocumentPart.GetXDocument().Root;
XElement body = root.LogicalChildrenContent().First();
OutputBlockLevelContent(_wordDocument, body);
}
catch (Exception ex)
{
logger.Error("ERROR in GetDocumentBodyContents:" + ex.Message.ToString());
}
}
// This is recursive method. At each iteration it tries to fetch listitem and Text item. Once you have these items in hand
// You can manipulate and create your own collection.
private void OutputBlockLevelContent(WordprocessingDocument wordDoc, XElement blockLevelContentContainer)
{
try
{
string listItem = string.Empty, itemText = string.Empty, numberText = string.Empty;
foreach (XElement blockLevelContentElement in
blockLevelContentContainer.LogicalChildrenContent())
{
if (blockLevelContentElement.Name == W.p)
{
listItem = ListItemRetriever.RetrieveListItem(wordDoc, blockLevelContentElement);
itemText = blockLevelContentElement
.LogicalChildrenContent(W.r)
.LogicalChildrenContent(W.t)
.Select(t => (string)t)
.StringConcatenate();
if (itemText.Trim().Length > 0)
{
if (null == listItem)
{
// Add html break tag
textItemSB.Append( itemText + "<br/>");
}
else
{
//if listItem == "" bullet character, replace it with equivalent html encoded character
textItemSB.Append(" " + (listItem == "" ? "•" : listItem) + " " + itemText + "<br/>");
}
}
else if (null != listItem)
{
//If bullet character is found, replace it with equivalent html encoded character
textItemSB.Append(listItem == "" ? " •" : listItem);
}
else
textItemSB.Append("<blank>");
continue;
}
// If element is not a paragraph, it must be a table.
foreach (var row in blockLevelContentElement.LogicalChildrenContent())
{
foreach (var cell in row.LogicalChildrenContent())
{
// Cells are a block-level content container, so can call this method recursively.
OutputBlockLevelContent(wordDoc, cell);
}
}
}
if (textItemSB.Length > 0)
{
textItemList.Add(textItemSB.ToString());
textItemSB.Clear();
}
}
catch (Exception ex)
{
.....
}
}

I got the answer.....
First I was converting doc on paragraph basis. But instead of that if we process doc file sentence by sentence basis, it is possible to determine whether that sentence contains bullet or any kind of shape or if that sentence is part of table. So once we get this information, then we can convert that sentence appropriately. If someone needs source code, I can share it.

Related

LINQ way to change my inefficient program

Please consider the following records
I'm trying to flatten and group the data by Robot Name then by date + Left Factory time then group the address(es) for that date and time. Notice that some of the Left Factory times are identical.
I wrote the code below and it works. It gives me the output that I want. I was a Perl developer so what you see below is from that mentality. I'm sure there is a better way of doing it in LINQ. A little help please.
static void Main(string[] args)
{
if (args.Length < 0){
Console.WriteLine("Input file name is required");
return;
}
List<string> rawlst = File.ReadAllLines(args[0]).ToList<string>();
Dictionary<string, Dictionary<DateTime, List<string>>> dicDriver = new Dictionary<string, Dictionary<DateTime, List<string>>>();
foreach (string ln in rawlst)
{
try
{
List<string> parts = new List<string>();
parts = ln.Split(',').ToList<string>();
string[] dtparts = parts[1].Split('/');
string[] dttime = parts[15].Split(':');
DateTime dtrow = new DateTime(
int.Parse(dtparts[2]), int.Parse(dtparts[0]), int.Parse(dtparts[1]),
int.Parse(dttime[0]), int.Parse(dttime[1]), int.Parse(dttime[2]));
string rowAddress = parts[7] + " " + parts[9] + " " + parts[10] + " " + parts[11];
if (!dicDriver.Keys.Contains(parts[3]))
{
Dictionary<DateTime, List<string>> thisRec = new Dictionary<DateTime, List<string>>();
thisRec.Add(dtrow, new List<string>() { rowAddress });
dicDriver.Add(parts[3], thisRec);
}
else
{
Dictionary<DateTime, List<string>> thisDriver = new Dictionary<DateTime, List<string>>();
thisDriver = dicDriver[parts[3]];
if (!thisDriver.Keys.Contains(dtrow))
{
dicDriver[parts[3]].Add(dtrow, new List<string>() { rowAddress });
}
else
{
dicDriver[parts[3]][dtrow].Add(rowAddress);
}
}
}
catch (Exception e)
{
Console.WriteLine("ERROR:" + ln);
}
}
//output
string filename = DateTime.Now.Ticks.ToString() + ".out";
foreach (var name in dicDriver.Keys)
{
foreach (var dd in dicDriver[name])
{
Console.Write(name + "," + dd.Key + ",");
File.AppendAllText(filename, name + "," + dd.Key + Environment.NewLine);
foreach (var addr in dd.Value)
{
Console.Write("\t\t" + addr + Environment.NewLine);
File.AppendAllText(filename, "\t" + addr + Environment.NewLine);
}
}
Console.Write(Environment.NewLine);
File.AppendAllText(filename, Environment.NewLine);
}
Console.ReadLine();
}
You should separate your concerns: separate your input from the processing and from the output.
For example: suppose you would have to read your input from a database instead from a CSV file? Would that seriously change the way your process your fetched data? In your design, fetching the data is mixed with processing: although you know that the data that you want to process contains something like FactoryProcesses, you decide to present eache FactoryProcess as a string. A FactoryProcess is not a string. It describes how and when and who processed something in your factory. That is not a string, is it? However, it might be represented internally as a string, but the outside world should not know this. This way, if you change your FactoryProcess from being read by a CSV-file, to something provided by a database, the users of your FactoryProcess won't see any difference.
Separation of concerns makes your code easier to understand, easier to test, easier to change, and better to re-use.
So let's separate!
IEnumerable<FactoryProcess> ReadFactoryProcesses(string fileName)
{
// TODO: check fileName not null, file exists
using (var fileReader = new StreamReader(fileName))
{
// read the file Line by Line and split each line into one FactoryProcess object
string line = fileReader.ReadLine();
while (line != null)
{
// one line read, convert to FactoryProcess and yield return:
FactoryProcess factoryProcess = this.ToFactoryProcess(line);
yield return factoryProcess;
// read next line:
line = fileReader.ReadLine();
}
}
}
I'll leave the conversion of a read line into a FactoryProcess up to you. Tip: if the items in your lines are separated by a comma, or something similar, consider using Nuget Package CSVHelper. It makes if easier to convert a file into a sequence of FactoryProcesses.
I want to group the data by Robot Name then by date + Left Factory time then group the address(es) for that date and time.
First of all: make sure that the FactoryProcess class has the properties you actually need. Separate this representation from what it is in a file. Apparently you want to tread date + left factory as one item that represents the Date and Time that it left the factory. So Let's create a DateTime property for this.
class FactoryProcess
{
public int Id {get; set}
public int PartNo {get; set;}
public string RobotName {get; set;} // or if desired: use a unique RobotId
...
// DateTimes: ArrivalTime, OutOfFactoryTime, LeftFactoryTime
public DateTime ArrivalTime {get; set;}
public DateTime OutOfFactoryTime {get; set;}
public DateTime LeftFactoryTime {get; set;}
}
The reason that I put Date and Time into one DateTime, is because it will solve problems if an item arrives on 23:55 and leaves on 00:05 next day.
A procedure that converts a read CSV-line to a FactoryProcess should interpret your dates and times as strings and convert to FactoryProcess. You can create a constrcuctor for this, or a special Factory class
public FactoryProcess InterpretReadLine(string line)
{
// TODO: separate the parts, such that you've got the strings dateTxt, arrivalTimeTxt, ...
DateTime date = DateTime.Parse(dateTxt);
TimeSpan arrivalTime = TimeSpan.Parse(arrivalTimeTxt);
TimeSpan outOfFactoryTime = TimeSpan.Parse(outOfFactoryTimeTxt);
TimeSpan leftFactoryTime = TimeSpan.Parse(leftFactoryTimeTxt);
return new FactoryProces
{
Id = ...
PartNo = ..
RobotName = ...
// The DateTimes:
ArrivalTime = date + arrivalTime,
OutOfFactoryTime = date + outOfFactoryTime,
LeftFactoryTime = date + leftFactoryTime,
};
}
Now that you've created a proper method to convert your CSV-file into a sequence of FactoryProcesses, let's process them
I want to group the data by Robot Name then by date + Left Factory time then group the address(es) for that date and time.
var result = fetchedFactoryProcesses.GroupBy(
// parameter KeySelector: make groups of FactoryProcesses with same RobotName:
factoryProcess => factoryProcess.RobotName,
// parameter ResultSelector: from every group of FactoryProcesses with this RobotName
// make one new Object:
(robotName, processesWithThisRobotName) => new
{
RobotName = robotName,
// Group all processes with this RobotName into groups with same LeftFactoryTime:
LeftFactory = processesWithThisRobotName.GroupBy(
// KeySelector: make groups with same LeftFactoryTime
process => process.LeftFactoryTime,
// ResultSelector: from each group of factory processes with the same LeftFactoryTime
(leftFactoryTime, processesWithThisLeftFactoryTime) => new
{
LeftFactoryTime = leftFactoryTime,
FactoryProcesses = processesWithThisLeftFactoryTime,
// or even better: select only the properties you actually plan to use
FactoryProcesses = processesWithThisLeftFactoryTime.Select(process => new
{
Id = process.Id,
PartNo = process.PartNo,
...
// not needed: you know the value, because it is in this group
// RobotName = process.RobotName,
// LeftFactoryTime = process.LeftFactoryTime,
}),
})
});
For completeness: grouping your code together:
void ProcessData(string fileName)
{
var fetchedFactoryProcesses = ReadFactoryProcess(fileName); // fetch the data
var groups = fetchFactoryProcesses.ToGroups(); // put into groups
this.Display(groups); // output result;
}
Because I separated the input from the conversion of strings to FactoryProcesses, and separated this conversion from the grouping, it will be easy to test the classes separately:
your CSV-reader should return any file that is divided into lines, even if it does not contain FactoryProcesses
your conversion from read line to FactoryProcess should convert any string that is in the proper format, whether it is read from a file or gathered any other way
your grouping should group any sequence of FactoryProcesses, whether they come from a CSV-file or from a database or List<FactoryProcess>, which is very convenient, because in your tests it is way easier to create a test list, than a test CSV-file.
If in future you decide to change the source of your sequence of FactoryProcesses, for instance it comes from a database instead of a CSV-file, your grouping won't change. Or if you decide to support entering and leaving factories on different dates (multiple date values) only the conversion changes. If you decide to display the results in a tree-like fashion, or decide to write the groups in a database, your reading, conversion, grouping, etc won't change: what a high degree or re-usability!
Separating your concerns made it much easier to understand how to solve your grouping problem, without the hassle of splitting your read lines and converting Date + LeftFactory into one value.

How can I save and load textbox values, checkbox states, dropdown menu selections, etc., into a .txt file?

I am trying to implement a simple save-load function in my application, which would save the states of various GUI elements in my application (textboxes, checkboxes, dropdown menus, and so on) to a custom-named .txt file, and then load them back the next time user runs my application. I do not want to use My.Settings, because it is a portable application, and therefore the settings file has to be next to the executable. Also because my application is an editor, and the settings have to be bound by name to the current file the user is working with.
Write permissions is not an issue. I want to code this in a way so that I would only have to write down the names of the GUI elements to be mentioned once in my code, preferably in a list. Like this (pseudo-code):
'list
Dim ElementsToSave() as Object = {
Textbox1.text,
Checkbox1.Checked,
DropDownMenu1.SelectedItem,
.....
}
'save-load sub
Sub SaveLoad(Elements as Object, mode as string)
If mode = "save" then
For each Element in Elements
??? 'save each element state to .txt file
Next
If mode = "load" then
For each Element in Elements
??? 'load each element state from .txt file
Next
End if
End sub
'call
SaveLoad(ElementsToSave, "save")
'or
SaveLoad(ElementsToSave, "load")
I hope this conveys what I'm trying to achieve. Can anyone give any advice on how to make this work, please?
EDIT: I forgot to mention. It would be very nice if each value in the .txt file would be saved with a key that refers to a specific element, so that if I add more GUI elements in my application in the future, or re-arrange them, this save-load sub would always choose the correct value from the .txt file for a specific element.
using System.IO;
...
private enum ControlProperty
{
None = 0,
Text = 1,
Checked = 2,
SelectedValue = 3
}
private string GetSettingsFile()
{
FileInfo fi = new FileInfo(System.Reflection.Assembly.GetEntryAssembly().Location);
string path = Path.Combine(fi.Directory.FullName, "settings.txt");
return path;
}
private void test()
{
SaveSettings();
LoadSettings();
}
private void SaveSettings()
{
object[] vals = new object[] { this.Textbox1, ControlProperty.Text, this.Textbox1.Text, this.Checkbox1, ControlProperty.Checked, this.Checkbox1.Checked, this.Menu1, ControlProperty.SelectedValue, this.Menu1.SelectedValue };
string txt = "";
for (int i = 0; i < vals.Length; i += 3)
{
string controlID = (vals[i] as Control).ID;
ControlProperty property = (ControlProperty)vals[i + 1];
object state = vals[i + 2];
txt += controlID + ":" + property.ToString() + ":" + state.ToString() + "\n";
}
string file = GetSettingsFile();
File.WriteAllText(file, txt);
}
private void LoadSettings()
{
string file = GetSettingsFile();
string[] lines = File.ReadAllLines(file);
foreach (string s in lines)
{
string[] parts = s.Split(':');
if (parts.Length < 3) continue;
string id = parts[0];
var c = this.form1.FindControl(id);
ControlProperty prop = ControlProperty.None;
Enum.TryParse<ControlProperty>(parts[1], out prop);
string state = parts[2];
if (c is TextBox && prop == ControlProperty.Text)
{
TextBox t = c as TextBox;
t.Text = state;
}
else if (c is CheckBox && prop == ControlProperty.Checked)
{
CheckBox chk = c as CheckBox;
chk.Checked = state == "True";
}
else if (c is Menu && prop == ControlProperty.SelectedValue)
{
Menu m = c as Menu;
foreach (MenuItem menuItem in m.Items)
{
if (menuItem.Value == state)
{
menuItem.Selected = true;
}
}
}
}
}

Validation for textbox with two sets of phone numbers

I am trying to do a validation on a textbox that can allow the input of one or more phone number in a single textbox. What I am trying to do is to send an message to the phone numbers included in the textbox.
I have no problem when I enter just one set of number into the textbox and the message can be sent.
However, whenever I type two sets of digit into the same textbox, my validation error will appear.
I am using user controls and putting the user control in a listview.
Here are my codes:
private ObservableCollection<IFormControl> formFields;
internal ObservableCollection<IFormControl> FormFields
{
get
{
if (formFields == null)
{
formFields = new ObservableCollection<IFormControl>(new List<IFormControl>()
{
new TextFieldInputControlViewModel(){ColumnWidth = new GridLength(350) ,HeaderName = "Recipient's mobile number *" , IsMandatory = true, MatchingPattern = #"^[\+]?[1-9]{1,3}\s?[0-9]{6,11}$", Tag="phone", ContentHeight = 45, ErrorMessage = "Please enter recipient mobile number. "},
});
}
return formFields;
}
}
And here is the codes for the button click event:
private void OkButton_Click(object sender, RoutedEventArgs e)
{
MessageDialog clickMessage;
UICommand YesBtn;
int result = 0;
//Fetch Phone number
var phoneno = FormFields.FirstOrDefault(x => x.Tag?.ToLower() == "phone").ContentToStore;
string s = phoneno;
string[] numbers = s.Split(';');
foreach (string number in numbers)
{
int parsedValue;
if (int.TryParse(number, out parsedValue) && number.Length.Equals(8))
{
result++;
}
else
{ }
}
if (result.Equals(numbers.Count()))
{
try
{
for (int i = 0; i < numbers.Count(); i++)
{
Class.SMS sms = new Class.SMS();
sms.sendSMS(numbers[i], #"Hi, this is a message from Nanyang Polytechnic School of IT. The meeting venue is located at Block L." + Environment.NewLine + "Click below to view the map " + Environment.NewLine + location);
clickMessage = new MessageDialog("The SMS has been sent to the recipient.");
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timer_Tick;
timer.Start();
YesBtn = new UICommand("Ok", delegate (IUICommand command)
{
timer.Stop();
idleTimer.Stop();
var rootFrame = (Window.Current.Content as Frame);
rootFrame.Navigate(typeof(HomePage));
rootFrame.BackStack.Clear();
});
clickMessage.Commands.Add(YesBtn);
clickMessage.ShowAsync();
}
}
catch (Exception ex)
{ }
}
}
I am trying to separate the two numbers with ";" sign.... and I am wondering if that is the problem. Or maybe it is the matchingpattern that I have placed in.
The answer is quite simple, create a bool property in your TextFieldInputControlViewModel something like
public bool AcceptMultiple {get;set;}
and to keep things dynamic, create a char property as a separator like below:
public char Separator {get;set;}
Now, modify your new TextFieldInputControlViewModel() code statement by adding values to your new fields like below:
new TextFieldInputControlViewModel(){Separator = ';', AcceptMultiple = true, ColumnWidth = new GridLength(350) ,HeaderName = "Recipient's mobile number *" , IsMandatory = true, MatchingPattern = #"^[\+]?[1-9]{1,3}\s?[0-9]{6,11}$", Tag="phone", ContentHeight = 45, ErrorMessage = "Please enter recipient mobile number. "},
Once it's done, now in your checkValidation() function (or where you check the validation or pattern match) can be replaced with something like below:
if(AcceptMultiple)
{
if(Separator == null)
throw new ArgumentNullException("You have to provide a separator to accept multiple entries.");
string[] textItems = textField.Split(Separator);
if(textItems?.Length < 1)
{
ErrorMessage = "Please enter recipient mobile number." //assuming that this is your field for what message has to be shown.
IsError = true; //assuming this is your bool field that shows all the errors
return;
}
//do a quick check if the pattern matching is mandatory. if it's not, just return.
if(!IsMandatory)
return;
//your Matching Regex Pattern
Regex rgx = new Regex(MatchingPattern);
//loop through every item in the array to find the first entry that's invalid
foreach(var item in textItems)
{
//only check for an invalid input as the valid one's won't trigger any thing.
if(!rgx.IsMatch(item))
{
ErrorMessage = $"{item} is an invalid input";
IsError = true;
break; //this statement will prevent the loop from continuing.
}
}
}
And that'll do it.
I've taken a few variable names as an assumption as the information was missing in the question. I've mentioned it in the comments about them. Make sure you replace them.

HtmlAgility Pack get single node get null value

I am trying to get a single node with an XPath, but i am getting a null value on the node, don' t know why
WebClient wc = new WebClient();
string nodeValue;
string htmlCode = wc.DownloadString("http://www.freeproxylists.net/fr/?c=&pt=&pr=&a%5B%5D=0&a%5B%5D=1&a%5B%5D=2&u=50");
HtmlAgilityPack.HtmlDocument html = new HtmlAgilityPack.HtmlDocument();
html.LoadHtml(htmlCode);
HtmlNode node = html.DocumentNode.SelectSingleNode("//table[#class='DataGrid']/tbody/tr[#class='Odd']/td/a");
nodeValue = (node.InnerHtml);
I see at least 2 mistakes in your xpath compared to the html you're trying to get information from.
There are no <a> that has <tr class=Odd"> as an ancestor.
Even if your Xpath had worked then you would only have gotten one <td> since you have decided to SelectSingleNode instead of SelectNodes
It looks like the are doing some kind of lazy protection from what you're trying to do. Since the a-tag is just represented in hexadecimal enclosed in IPDecode. So really it is no problem to extract the link. But the least you could have done was to look at the html before posting. You clearly have not tried at all. Since the html you're getting from your current code is not the <body> of the link you gave us - meaning you have to get the htmlpage from the absolute url or just use Selenium.
But since I am such a swell guy I will make your entire solution for you using Xpath, Html Agility Pack and Selenium. The following solutions gets the html of the site. then reads only the <tr> that has class="Odd". After that it finds all the "encrypted" <a> and decodes them into a string and writes them into an array. After that there is a small example of how to get an attribute value from one anchor.
private void HtmlParser(string url)
{
HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
htmlDoc.OptionFixNestedTags=true;
GetHTML(url);
htmlDoc.Load("x.html", Encoding.ASCII, true);
HtmlNodeCollection nodes = htmlDoc.DocumentNode.SelectNodes("//table[#class='DataGrid']/descendant::*/tr[#class='Odd']/td/script");
List<string> urls = new List<string>();
foreach(HtmlNode x in nodes)
{
urls.Add(ConvertStringToUrl(x.InnerText));
}
Console.WriteLine(ReadingTheAnchor(urls[0]));
}
private string ConvertStringToUrl(string octUrl)
{
octUrl = octUrl.Replace("IPDecode(\"", "");
octUrl = octUrl.Remove(octUrl.Length -2);
octUrl = octUrl.Replace("%", "");
string ascii = string.Empty;
for (int i = 0; i < octUrl.Length; i += 2)
{
String hs = string.Empty;
hs = octUrl.Substring(i,2);
uint decval = System.Convert.ToUInt32(hs, 16);
char character = System.Convert.ToChar(decval);
ascii += character;
}
//Now you get the <a> containing the links. which all can be read as seperate html files containing just a <a>
Console.WriteLine(ascii);
return ascii;
}
private string ReadingTheAnchor(string anchor)
{
//returns url of anchor
HtmlDocument anchorHtml = new HtmlAgilityPack.HtmlDocument();
anchorHtml.LoadHtml(anchor);
HtmlNode h = anchorHtml.DocumentNode.SelectSingleNode("a");
return h.GetAttributeValue("href", "");
}
//using OpenQA.Selenium; using OpenQA.Selenium.Firefox;
private void GetHTML(string url)
{
using (var driver = new FirefoxDriver())
{
driver.Navigate().GoToUrl(url);
Console.Clear();
System.IO.File.WriteAllText("x.html", driver.PageSource);
}
}

How to search on multiple strings entered in single text box in mvc3

i have a single textbox named Keywords.
User can enter multiple strings for search.
How this is possible in mvc3?
I am using nhibernate as ORM.
Can i create criteria for this?
Edited Scenario
I have partial view to search job based on following values:
Keywords(multiple strings), Industry(cascading dropdown with functional area )//working well ,FunctionalArea//working well
Loaction(multiple locations), Experience//working well
In Controller i am retrieving these values from form collection.
What datatype should i use for keywords and location (string or string[] )?
public ActionResult SearchResult(FormCollection formCollection)
{
IList<Jobs> JobsSearchResultList = new List<Jobs>();
//string[] keywords = null;
string location = null;
int? industry = 0;
int? functionaArea = 0;
int? experience = 0;
string keywords = null;
if (formCollection["txtKeyword"] != "")
{
keywords = formCollection["txtKeyword"];
}
//if (formCollection["txtKeyword"] != "")
//{
// keywordAry = formCollection["txtKeyword"].Split(' ');
// foreach (string keyword in keywordAry)
// {
// string value = keyword;
// }
//}
......retrieving other values from formcollection
....
//Now passing these values to Service method where i have criteria for job search
JobsSearchResultList = oEasyJobsService.GetJobsOnSearchExists(keywords,industry,functionaArea,location,experience);
return View(JobsSearchResultList);
}
In Services i have done like:
public IList<EASYJobs> GetJobsOnSearchExists(string keywords, int? industryId, int? functionalAreaId, string location, int? experience)
{
IList<JobLocation> locationlist = new List<JobLocation>();
IList<Jobs> JobsList = null;
var disjunction = Expression.Disjunction();
ICriteria query = session.CreateCriteria(typeof(Jobs), "EJobs");
if (keywords != null)
{
foreach (string keyword in keywords)
{
string pattern = String.Format("%{0}%", keyword);
disjunction
.Add(Restrictions.InsensitiveLike("Jobs.keywords", pattern,MatchMode.Anywhere))
.Add(Restrictions.InsensitiveLike("YJobs.PostTitle",pattern,MatchMode.Anywhere));
}
query.Add(disjunction)
.Add(Expression.Eq("EASYJobs.Industry.IndustryId", industryId))
.Add(Expression.Eq("Jobs.FunctionalArea.FunctionalAreaId", functionalAreaId))
.Add(Expression.Eq("Jobs.RequiredExperience", experience)));
}
else
{..
}
JobsList = criteria.List<Jobs>();
}
Problems i am facing are:
In controller if i use string[],then Split(',') does not split the string with specified separator.It passes string as it is to Service.
2.In services i am trying to replace string with %{0}% ,strings with spaces are replaced/concat() here with given delimeter.
But the problem here is It always return the whole job list means not giving the required output.
Pleas help ...
As long as you have a delimiter you can break the input into pieces on you should be able to create an or expression with the parts. You can use a disjunction to combine an arbitrary number of criteria using OR's.
var criteria = session.CreateCriteria<TestObject>();
Junction disjunction = Restrictions.Disjunction();
var input = "key words";
foreach (var keyword in input.Split(" "))
{
ICriterion criterion = Restrictions.Eq("PropertyName", keyword);
disjunction.Add(criterion);
}
criteria.Add(disjunction);
Multiple keywords with special characters or extra spaces are replaced with single space with Regex expressions.
And then keywords are separated with Split("").
Its working as required....
if (!string.IsNullOrEmpty(keywords))
{
keywords = keywords.Trim();
keywords = System.Text.RegularExpressions.Regex.Replace(keywords, #"[^0-9a-zA-Z\._\s]", " ");
keywords = System.Text.RegularExpressions.Regex.Replace(keywords, #"[\s]+", " ");
if (keywords.IndexOf(" ") > 0)
{
string[] arr = keywords.Split(" ".ToCharArray());
for (int i = 0; i < arr.Length; i++)
{
if (!string.IsNullOrEmpty(arr[i]))
{
criteria.Add(Restrictions.Disjunction()
.Add(Expression.Like("EASYJobs.keywords", arr[i], MatchMode.Anywhere)));
}
}
}
else
{
criteria.Add(Restrictions.Disjunction()
.Add(Expression.Like("EASYJobs.keywords", keywords, MatchMode.Anywhere)));
}
}

Resources