I have the following HTML code and need to determine the index of "Number of Strings" using the <span> id. I'm using Nokogiri to parse the HTML and get the row.
doc = Nokogiri::parse(myfile.html)
table = doc.xpath("//span[#id='NumStrs']/../../..")
row = table.xpath["tr[1]"]
Here is the HTML:
<tr>
<th id ="langframe">
<span id="cabinet">
Cabinet</span>
</th>
<th id ="langbb1">
<span id="bb1">
BB1</span>
</th>
<th id ="langbb2">
<span id="bb2">
BB2</span>
</th>
<th id ="langtemp">
<span id="Temp">
Temperature</span>
</th>
<th id="langstrs">
<span id="StringsPresent">
Strings Present</span>
</th>
<th id="langmstrQty">
<span id="NumStrs">
Number of Strings</span>
</th>
</tr>
I'd do it using Ruby's with_index combined with a select:
require 'nokogiri' # => true
doc = Nokogiri::HTML(<<EOT)
<tr>
<th id ="langframe">
<span id="cabinet">
Cabinet</span>
</th>
<th id ="langbb1">
<span id="bb1">
BB1</span>
</th>
<th id ="langbb2">
<span id="bb2">
BB2</span>
</th>
<th id ="langtemp">
<span id="Temp">
Temperature</span>
</th>
<th id="langstrs">
<span id="StringsPresent">
Strings Present</span>
</th>
<th id="langmstrQty">
<span id="NumStrs">
Number of Strings</span>
</th>
</tr>
EOT
th_idx = doc.search('th').to_enum.with_index.select { |th, idx| th.text['Number of Strings'] }.first
That returns:
th_idx
# => [#(Element:0x3fe72d83cd3c {
# name = "th",
# attributes = [
# #(Attr:0x3fe72d4440f4 { name = "id", value = "langmstrQty" })],
# children = [
# #(Text "\n"),
# #(Element:0x3fe72d43c3e0 {
# name = "span",
# attributes = [
# #(Attr:0x3fe72d439b04 { name = "id", value = "NumStrs" })],
# children = [ #(Text "\nNumber of Strings")]
# }),
# #(Text "\n")]
# }),
# 5]
The index is:
th_idx.last # => 5
Once you have th_idx, you can easily access parent or child nodes to find out about its surroundings:
th_node = th_idx.first
th_node['id'] # => "langmstrQty"
th_node.at('span')
# => #(Element:0x3fd5110286d8 {
# name = "span",
# attributes = [
# #(Attr:0x3fd511021b6c { name = "id", value = "NumStrs" })],
# children = [ #(Text "\nNumber of Strings")]
# })
th_node.at('span')['id'] # => "NumStrs"
with_index adds a 0-based index to each element passed to it. to_enum is required because search returns a NodeSet, which isn't an enumerator so to_enum returns that.
If you want a 1-based index use with_index(1).
Got it working, not sure if this is the efficient way to do it.. but it works
header = table.xpath("tr[1]")
value = header.xpath("//span[#id='#{id}']").text
index = header.search('th//text()').collect {|text| text.to_s.strip}.reject(&:empty?).index(value)+1
Related
I actually have projects and modules table. When i click button,
i want to get the data from model of that specific project.
Here is my code :
this is my table where i want to show the data of modules that represent specific project.
<table class="report">
<tr>
<th class="report-th"> Module ID </th>
<th class="report-th"> Module Name </th>
<th class="report-th"> Module Status </th>
</tr>
#if($modules->where('project_id','=',$project->id))
#foreach($modules as $module)
<tr>
<td>{{$module->id}}</td>
<td>{{$module->title}}</td>
<td>{{$module->status}}</td>
</tr>
#endforeach
#endif
</table>
below is the code in controller:
public function show()
{
$sortBy = 'id';
$sortDirection = 'ASC';
if (request('sortby') || request('sortdir')) {
$sortBy = request('sortby');
$sortDirection = request('sortdir');
}
$projects = projects::orderBy($sortBy, $sortDirection)->paginate(6);
$modules= modules::all();
return view('tms.projects', compact('projects','modules'));
}
Here's the table structure:
<table class="tb-stock tb-option">
<tr>
<th class="bgc2">col1</th>
<th class="bgc2">col2</th>
<th class="bgc2">col3</th>
</tr>
<tr class="alt-row">
<th class="">2018/1/29</th>
<td class="">0.11</td>
<td class=" b-b">0.50</td>
</tr>
<tr class="alt-row">
<th class="">2018/1/30</th>
<td class="">0.22</td>
<td class=" b-b">0.55</td>
</tr>
</table>
I want to get all the elements below "tr" (including "th" and "td")
How can I use linq to achieve this ?
Problems locate at "..tr.Elements("td|th").."
code:
HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.Load(ms, Encoding.UTF8);
List<List<string>> table =
doc.DocumentNode.SelectSingleNode("//table[#class='tb-stock tb-option']")
.Descendants("tr")
.Skip(1)
.Where(tr => tr.Elements("th").Count() >= 1)
.Select(tr => tr.Elements("td|th").Select(td => td.InnerText).ToList())
.ToList();
You can use the following code for extracting inner texts of td or th elements I test it in my local the output is :
2018/1/29
0.11
0.50
2018/1/30
0.22
0.55
You can filter the elements in line :
// both td and th
.Where(node => "td|th".Contains(node.Name))
// only td
.Where(node => "td".Contains(node.Name))
The working code is :
HtmlDocument doc = new HtmlDocument();
doc.Load("test.html", Encoding.UTF8);
List<string> table =
doc.DocumentNode.SelectSingleNode("//table[#class='tb-stock tb-option']")
.Descendants("tr")
.Skip(1)
.Where(tr => tr.Elements("th").Count() >= 1)
.SelectMany(tr => tr.ChildNodes)
.Where(node => "td|th".Contains(node.Name))
.Select(node => node.InnerText)
.ToList();
foreach (var str in table)
{
Console.WriteLine(str);
}
I am trying to test my Angular 2 Template but doing something incorrect as I don't get any filteredFirms to repeat through.
Here is my directive controller code:
(my actual firm service is just getting a dummy json file and return an array of firm objects, but I'm not testing my service here so I'm mocking this call as you can see in my spec file below.)
export class FirmListComponent implements OnInit {
constructor(public firmService: FirmService) { }
public ngOnInit() {
this.firmService.stateObservable.subscribe((state) => {
this.firms = state.firms;
this.filteredFirms = this.firms;
});
this.getFirms();
}
public getFirms(value?: string) {
this.loading = true;
this.firmService.getFirms(value).subscribe((response: any) => {
this.loading = false;
});
}
}
}
My directive template:
<thead>
<tr>
<th class="checkbox-col">
<md-checkbox [(ngModel)]="selectAll" (click)="selectAllChanged()" aria-label="Select All"></md-checkbox>
</th>
<th>
Firm Name
</th>
<th>
Country
</th>
<th>
Industry
</th>
<th>
EDF
</th>
<th>
LGD
</th>
<th>
Modified
</th>
<th>
Modified By
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let firm of filteredFirms; let i = index" class="animate-repeat" [ngClass]="{'active': firm.selected}">
<td class="checkbox-col">
<md-checkbox [(ngModel)]="firm.selected" aria-label="firm.name" (change)="selectFirm(i)"></md-checkbox>
</td>
<td>{{firm.name}}</td>
<td>{{firm.country}}</td>
<td>{{firm.industry}}</td>
<td>
<span class="label bg-purple600">US 4.0</span>
<span class="label bg-green600">US 4.0</span>
</td>
<td>
<span class="label bg-pink800">US 4.0</span>
<span class="label bg-orange300">US 4.0</span>
</td>
<td>{{firm.modifiedOn}}</td>
<td>{{firm.modifiedBy}}</td>
</tr>
</tbody>
My tests for the table head portion pass just fine, but I don't get any rows when I try to test the table body.
My spec file testing the template:
describe('Firm List Component', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MaterialModule, FormsModule, AppModule],
declarations: [FirmListComponent],
providers: [FirmService]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(FirmListComponent);
component = fixture.componentInstance;
debugEl = fixture.debugElement;
element = fixture.nativeElement;
firmService = fixture.debugElement.injector.get(FirmService);
// mockFirms is just an array of objects with firm data
getObservableSpy = spyOn(firmService, 'stateObservable')
.and.returnValue(mockFirms);
getFirmsSpy = spyOn(firmService, 'getFirms')
.and.returnValue(Observable.of(mockFirms));
});
}));
it('should show firms after getFirms observable', () => {
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
// this test passes
var rowHeaderLength = element.querySelectorAll('th').length;
expect(rowHeaderLength).toBe(8);
// this test does not, rowDataLength is 0
// selecting the rows by class so I don't get the tr in the header here
var rowDataLength = element.querySelectorAll('.animate-repeat').length;
expect(rowDataLength).toBe(10);
});
});
}
Any help is appreciated. Thanks
i have the following search form that contains; text box and two drop down lists:-
#using (Ajax.BeginForm("Search", "Question",
new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "re",
LoadingElementId = "progress"
}))
{ <table >
<tr>
<th>
Description:-
</th>
<th>
<input type="text" name="q" data-autocomplete-source="#Url.Action("QuickSearch", "Question")" />
</th>
</tr>
<tr>
<th>
Difficulty Level:-
</th>
<th>
#Html.DropDownList("q2")
</th>
</tr>
<tr>
<th>
Create By:-
</th>
<th>
#Html.DropDownList("q3")
</th>
</tr>
</table>
<input type="submit" value="Search Questions" />
}
so how i can define "Any" in my two drop down lists so i can write something as the follow in my Search action method:-
public IQueryable<Question> searchquestions3(string q, int? q2 , string)
{
return from u in entities1.Questions
where (u.Description.Contains(q) && (u.DifficultyID == q2 || q2 == "Any" ) && ( u.CreatedBy == q3 || q3 == "Any"))
select u;}
You should do your drop down as a DropDownListFor and populate and bind it in a view model.
Then you can do something like
#Html.DropDownListFor(m => m.DifficultyID, Model.Difficulties, "Any")
i used something as
var list = elearningrepository.FindAllLevels().ToList()
list.Insert(0, new TypeOfYourObject() {ValueProperty = "Any"});
I want to add an id to the "tr" elements of the mvccontrib grid I build:
<tr id="0"/>
<tr id="1"/>
so if the table contains 10 rows, the ids are 0 through to 9.
One way is to add an additional item to my entity to store this value and then create this as a hidden column with the id as the value of this item - not very elegant.
Is there a more elegant way to do this?
Thanks
I've got this far but now it complains at the RenderUsing line, any ideas?
#model IEnumerable<Tens.Models.UserPreviousNamesView>
<div class="demo_jui">
#{
var userId = 0;
foreach (var item in Model)
{
userId = item.Id;
break;
}
#(Html.Grid(Model.Select((item,index) => new { Item = item, Index = index}))
.Columns(col =>
{
col.For(p => p.Item.Title);
col.For(p => p.Item.Name);
col.Custom(#<text>
#Ajax.ActionLink("Delete", "DeleteUserPreviousName", "Summary", null, null, new { id = item.Item.Id, #class = "deleteUserPreviousName" })
</text>).Encode(false);
})
.RowAttributes(p => new Hash(Id => "id"+p.Item.Index.ToString()))
.Attributes(Id => "userPreviousNamesTable")
.Empty("You currently have no Previous Names.")
.RenderUsing(new Tens.GridRenderers.UserPreviousNamesGridRenderer<Tens.Models.UserPreviousNamesView>()));
}
You could transform the model to add it a row index and then use the RowAttributes method:
#model IEnumerable<MyViewModel>
#(Html
.Grid(Model.Select((item, index) => new { Item = item, Index = index }))
.Columns(column =>
{
column.For(x => x.Item.Foo);
column.For(x => x.Item.Bar);
})
.RowAttributes(x => new Hash(id => string.Format("id{0}", x.Item.Index)))
)
Also I have pre-pended the id with the id keyword as ids in HTML cannot statr with a number as shown in your example.
Sample output:
<table class="grid">
<thead>
<tr>
<th>Foo</th>
<th>Bar</th>
</tr>
</thead>
<tbody>
<tr id="id0" class="gridrow">
<td>foo 1</td>
<td>bar 1</td>
</tr>
<tr id="id1" class="gridrow_alternate">
<td>foo 2</td>
<td>bar 2</td>
</tr>
<tr id="id2" class="gridrow">
<td>foo 3</td>
<td>bar 3</td>
</tr>
</tbody>
</table>
You can always show hide columns without adding id to particular row or columns like below
$(".mvcGridDollarHeader th:nth-child(16)").hide();
$(".mvcGrid td:nth-child(16)").hide();
Where mvcGrid is tableStyle and mvcGridDollarHeader is header style.
#grid1.GetHtml(
tableStyle: "mvcGrid",
displayHeader: true,
emptyRowCellValue: "",
headerStyle: "mvcGridDollarHeader",