i have the following code:
HtmlAgilityPack.HtmlNodeCollection nodeCollection = bodyNode.SelectNodes("//ul[#class='myClass']//li");
which grabs about 250 LI's
the UL format is a bit weird and it goes like this:
<ul>
<li>
<h5>Parent</h5>
Some more tags here...
</li>
<li>
<h4>Child of the prev li</h4>
</li>
<li>
<h4>Child of the prev li</h4>
</li>
<!-- and so on -->
<!-- Then again -->
<li>
<h5>Parent</h5>
Some more tags here...
</li>
<li>
<h4>Child of the prev li</h4>
</li>
<li>
<h4>Child of the prev li</h4>
</li>
<!-- child li's are not constant, this is only for demo -->
</ul>
i need to separate the LI's into groups where each group contains the parent LI and all the children LI's
anyone can help with this?
If I understood correctly this is want you want
HtmlNodeCollection liList = doc.DocumentNode.SelectNodes("//ul//li");
List<List<HtmlNode>> liGroups = new List<List<HtmlNode>>();
List<HtmlNode> liGroup = null;
foreach (HtmlNode li in liList)
{
if (li.InnerText.Contains("Parent"))
{
if (liGroup != null)
liGroups.Add(liGroup);
liGroup = new List<HtmlNode>();
liGroup.Add(li);
}
else
{
liGroup.Add(li);
}
}
liGroups.Add(liGroup);
What you will have at the end is a list liGroups that will have other list liGroup. For your above html it will show that liGroups have 2 liGroup because in your above html you have 2 parents and both two liGroup will have 3 li (1 parent + 2 childs) because both parents have same amount of children.
After that you do with them whatever you want for example:
MessageBox.Show(liGroups[0][2].InnerText); //Show from the first group the 3rd's li InnerText
var tree = new Dictionary<HtmlNode, List<HtmlNode>>();
foreach (var node in nodeCollection)
if (node.SelectSingleNode("h5[text()='Parent']") != null)
tree.Add(node, new List<HtmlNode>());
else
tree.Last().Value.Add(node);
or
var groups = nodeCollection.Group();
static class Extensions
{
public static ILookup<HtmlNode, HtmlNode> Group(this HtmlNodeCollection collection)
{
return collection.Where(n => !n.IsParent()).ToLookup(n => n.GetParent());
}
public static bool IsParent(this HtmlNode node, string header = "Parent")
{
var h = node.Element("h5");
return h != null && h.InnerText == header;
}
public static HtmlNode GetParent(this HtmlNode node)
{
while (!node.IsParent())
node = node.PreviousSibling;
return node;
}
}
Related
I am working on MVC 5. Using WEB API, fetch the data now it is time to apply the HTML PAGE design and CSS.
#foreach (var item in Model)
{
if (Model.First() == item)
{
///APPLY FIRST RECORD CSS:- Works FINE
}
else {
<div class="row">
<div class="col-sm-4">
</div>
</div>
}
}
In the else portion, every time it generates the new ROW for a SINGLE record. But I am interested to display record 2 3 4 in SECOND ROW. 5 6 7 Record in the THIRD ROW and so on.
If it is the first item, open a div and then put the items in it. Close the div when the number of columns is 3 or close the div if the item is at the end of the list.
The following algorithm does this for you
#{
int i = 0;
int columnCounter = 1;
bool newRow = false;
}
#foreach (var item in Model)
{
//add and open div row
if (i == 0 || newRow)
{
newRow = false;
#:<div class="row" style="border:2px solid red;">
}
<div class="col-md-4" style="padding:0;">
<div style="height:40px;background:#f6f6f6;width:100%;text-align:center;">
<span>Column #i</span>
</div>
</div>
//close div row if column count == 3 or reach of end list
if (columnCounter == 3 || i == Model.Count - 1)
{
newRow = true;
columnCounter = 1;
#:</div>
}
else
{
columnCounter = columnCounter + 1;
}
}
result:
I have Thymeleaf pagination at departments page, which work good, but i have one problem.
When i try to update department name, this renamed department gone from page and shown at last page as last row.
How can i fix this? I want to update departments name and have it on the same place, not at the end.
This is my code.
Service:
public Page<Department> findPaginatedDepartments(final Pageable pageable) {
List<Department> departments = departmentRepository.findAll();
int pageSize = pageable.getPageSize();
int currentPage = pageable.getPageNumber();
int startItem = currentPage * pageSize;
final List<Department> list;
if (departments.size() < startItem) {
list = Collections.emptyList();
} else {
int toIndex = Math.min(startItem + pageSize, departments.size());
list = departments.subList(startItem, toIndex);
}
return new PageImpl<>(list, PageRequest.of(currentPage, pageSize), departments.size());
}
Controller:
#GetMapping()
public String getAllDepartments(
Model model,
#RequestParam("page") Optional<Integer> page,
#RequestParam("size") Optional<Integer> size) {
int currentPage = page.orElse(1);
int pageSize = size.orElse(10);
Page<Department> departmentPage = departmentService.findPaginatedDepartments(PageRequest.of(currentPage - 1, pageSize));
model.addAttribute("departmentPage", departmentPage);
int totalPages = departmentPage.getTotalPages();
if (totalPages > 0) {
List<Integer> pageNumbers = IntStream.rangeClosed(1, totalPages)
.boxed()
.collect(Collectors.toList());
model.addAttribute("pageNumbers", pageNumbers);
}
return "/department/departments";
}
View:
<div class="departments_wrapper main_menu_wrapper">
<div id="dep-grid" class="departments_table highlight">
<div class="head_departments">
<span class="head-left-grid" th:onclick="'javascript:sortDepartments()'">Name<i id="arrow-sort" class="tiny material-icons arrow-sort-button">expand_more</i></span>
<span class="head-right-grid">Edit</span>
</div>
<ul>
<div class="dep-body" th:each="department : ${departmentPage.content}">
<li id="dep-li" class="left-column" th:text="${department.name}"></li>
<li class="right-column">
<div class="dep_edit">
<a id="dep-modal-pic" class="edit_dep modal-trigger" href="#modal3"
th:onclick="'javascript:showFunctionModal(\'' + ${department.id} +'\' , \'' + ${department.name} +'\');'"><i
class="material-icons">more_horiz</i></a>
</div>
</li>
</div>
</ul>
</div>
<div class="pagination pagination-dep">
<ul>
<li class="disabled"><i class="material-icons">chevron_left</i></li>
<li><a th:if="${departmentPage.totalPages > 0}" th:each="pageNumber : ${pageNumbers}"
th:href="#{/departments(size=${departmentPage.size}, page=${pageNumber})}" th:text="${pageNumber}"
th:class="${pageNumber==departmentPage.number + 1} ? active"></a></li>
<li class="disabled"><i class="material-icons">chevron_right</i></li>
</ul>
</div>
</div>
Update Department name
#Transactional
public void updateDepartment(final Long id, final Department department) {
final Department departmentFound = departmentRepository.getOne(id);
departmentFound.setName(department.getName());
departmentRepository.saveAndFlush(departmentFound);
}
Solved!
After many time of search i find, that it is the postgres "feature".
So, in my DepartmentService i just wrote comparator, which will sort Departments by id.
And this solved my problem.
final Comparator<Department> cmp = Comparator.comparing(Department::getId);
How do we remove the inline height attribute from html?
<tr style="height:2px;">
</tr>
<tr style="height:2px;">
</tr>
I want only height attributes to be removed from all tr tags.
Thanks a lot in advance,
You can:
If your trs have no other styles other than height, you can simply remove strip them from their style attribute (the line I commented out)
Otherwise, you can write something like the snippet below to filter which style keys you want to remove
string html = #"<tr style='height:2px;'>
</tr>
<tr style='height:2px;'>
</tr>";
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
var trs = doc.DocumentNode.SelectNodes("tr");
foreach (var tr in trs)
{
Console.WriteLine(tr.OuterHtml);
//tr.Attributes.Remove("style");
var filteredStyles = GetStyles(tr.GetAttributeValue("style"), "height");
tr.SetAttributeValue("style", string.Join(":", filteredStyles));
Console.WriteLine(tr.OuterHtml);
}
Helper function:
private static List<string> GetStyles(string style, params string[] keysToRemove)
{
List<string> styles = new List<string>();
var stylesKeyPairs = style.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
if (keysToRemove != null)
{
foreach (var styleKeyPair in stylesKeyPairs)
{
var styleKeys = styleKeyPair.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (!keysToRemove.Contains(styleKeys.FirstOrDefault()))
styles.Add(styleKeyPair);
}
}
else
styles.AddRange(stylesKeyPairs);
return styles;
}
Output (for both solutions, in this case):
I am building such kind of ul using class TagBuilder
var ulTag = new TagBuilder("ul");
...
foreach(..items...)
{
var liTag = new TagBuilder("li");
....
ulTag.InnerHtml += liTag.ToString();
}
return new MvcHtmlString(ulTag.ToString());
But the output html getting produced in one line like that
<ul id="menu"><li>Home</li><li>About</li>.....</ul>
But i want to have it like that:
<ul id="menu">
<li>Home</li>
<li>About</li>
.....
</ul>
Is there any way to add \r\t or kind of Environment.NewLine after each tag?
Not really a clean way but it might work:
var ulTag = new TagBuilder("ul");
ulTag.InnerHTML += Environment.NewLine;
foreach(..items...)
{
var liTag = new TagBuilder("li");
ulTag.InnerHTML += string.Format(" {0}{1}", liTag.ToString(), Environment.NewLine);
...
}
i'm call the "down" function but am getting an invalid argument using 1.6.1_rc2
here's the html snippet:
<TR id=000000214A class="activeRow searchResultsDisplayOver" conceptID="0000001KIU">
<TD>
<DIV class=gridRowWrapper>
<SPAN class=SynDesc>Asymmetric breasts</SPAN>
<DIV class=buttonWrapper>
<SPAN class=btnAddFav title="Add to Favorites"> </SPAN>
</DIV>
</DIV>
</TD>
</TR>
here's the code:
var description = row.down('span.SynDesc').innerHTML;
row is a dom reference to the element.
prototype is appending a # then the id of the element:
findElements: function(root) {
root = root || document;
var e = this.expression, results;
switch (this.mode) {
case 'selectorsAPI':
if (root !== document) {
var oldId = root.id, id = $(root).identify();
id = id.replace(/[\.:]/g, "\\$0");
e = "#" + id + " " + e;
}
results = $A(root.querySelectorAll(e)).map(Element.extend); <-- e = "#000000214A span.SynDesc"
root.id = oldId;
return results;
case 'xpath':
return document._getElementsByXPath(this.xpath, root);
default:
return this.matcher(root);
}
i get an "invalid argument" error?
if i put a breakpoint before the offending line and change e to be equal to "span.SynDesc" it works fine.
help. :)
I ran into this. Changing the TR's ID to start with a letter should fix the problem. It turns out that legal HTML IDs match /^[A-Za-z][A-Za-z0-9_:.-]*$/.