Set value of var in foreach - Razor View Engine - asp.net-mvc

I hope/suspect this is easy, so I will ask here and make a fool out of my self if it is.
I have a foreach loop in my view, mind you this is a Razor view. I dont know if the ASP.NET View engine does the same... but it might. I want to flip a bool on each loop, but it does not see to let me. The view engine chokes to death. Why? How can I fix it? I did a for loop and I did mod 2 for now, but I really need to understand this.
This is what I tried:
#{
var IsOdd = false;
}
#foreach(var foo in bar)
{
#{ IsOdd = !IsOdd; }
<div class="#(IsOdd ? "odd" : "even")">#foo</div>
}

Try this:
#{
var IsOdd = false;
}
#foreach(var foo in bar)
{
IsOdd = !IsOdd;
<div class="#(IsOdd ? "odd" : "even")">#foo</div>
}
(Worked for me with MVC 3 RC.)

Related

Extract visual text from Google Classic Site page using Apps Script in Google Sheets

I have about 5,000 Classic Google Sites pages that I need to have a Google Apps script under Google Sheets examine one by one, extract the data, and enter that data into the Google Sheet row by row.
I wrote an apps script to use one of the sheets called "Pages" that contains the exactly URL of each page row by row, to run down while doing the extraction.
That in return would get the HTML contents and I would then use regex to extract the data I want which is the values to the right of each of the following...
Job name
Domain owner
Urgency/Impact
ISOC instructions
Which would then write that date under the proper columns in the Google Sheet.
This worked except for one big problem. The HTML is not consistent. Also, ID's and tags were not used so really it makes trying to do this through SitesApp.getPageByUrl not possible.
Here is the code I came up with for that attempt.
function startCollection () {
var masterList = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pages");
var startRow = 1;
var lastRow = masterList.getLastRow();
for(var i = startRow; i <= lastRow; i++) {
var target = masterList.getRange("A"+i).getValue();
sniff(target)
};
}
function sniff (target) {
var pageURL = target;
var pageContent = SitesApp.getPageByUrl(pageURL).getHtmlContent();
Logger.log("Scraping: ", target);
// Extract the job name
var JobNameRegExp = new RegExp(/(Job name:<\/b><\/td><td style='text-align:left;width:738px'>)(.*?)(\<\/td>)/m);
var JobNameValue = JobNameRegExp.exec(pageContent);
var JobMatch = JobNameValue[2];
if (JobMatch == null){
JobMatch = "NOTE FOUND: " + pageURL;
}
// Extract domain owner
var DomainRegExp = new RegExp(/(Domain owner:<\/b><\/td><td style='text-align:left;width:738px'><span style='font-family:arial,sans,sans-serif;font-size:13px'>)(.*?)(<\/span>)/m);
var DomainValue = DomainRegExp.exec(pageContent);
Logger.log("DUMP1:",SitesApp.getPageByUrl(pageURL).getHtmlContent());
var DomainMatch = DomainValue[2];
if (JobMatch == null){
DomainMatch = "N/A";
}
// Extract Urgency & Impact
var UrgRegExp = new RegExp(/(Urgency\/Impact:<\/b><\/td><td style='text-align:left;width:738px'>)(.*?)(<\/td>)/m);
var UrgValue = UrgRegExp.exec(pageContent);
var UrgMatch = UrgValue[2];
if (JobMatch == null){
UrgMatch = "N/A";
}
// Extract ISOC Instructions
var ISOCRegExp = new RegExp(/(ISOC instructions:<\/b><\/td><td style='text-align:left;width:738px'>)(.*?)(<\/td>)/m);
var ISOCValue = ISOCRegExp.exec(pageContent);
var ISOCMatch = ISOCValue[2];
if (JobMatch == null){
ISOCMatch = "N/A";
}
// Add record to sheet
var row_data = {
Job_Name:JobMatch,
Domain_Owner:DomainMatch,
Urgency_Impact:UrgMatch,
ISOC_Instructions:ISOCMatch,
};
insertRowInTracker(row_data)
}
function insertRowInTracker(rowData) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Jobs");
var rowValues = [];
var columnHeaders = sheet.getDataRange().offset(0, 0, 1).getValues()[0];
Logger.log("Writing to the sheet: ", sheet.getName());
Logger.log("Writing Row Data: ", rowData);
columnHeaders.forEach((header) => {
rowValues.push(rowData[header]);
});
sheet.appendRow(rowValues);
}
So for my next idea, I have thought about using UrlFetchApp.fetch. The one problem I have though is that these pages on that Classics Google Site sit behind a non-shared with the public domain. While using SitesApp.getPageByUrl has the script ask for authorization and works, SitesApp.getPageByUrl does not meaning when it tries to call the direct page, it just gets the Google login page.
I might be able to work around this and turn them public, but I am still working on that.
I am running out of ideas fast on this one and hoping there is another way I have not thought of or seen. What I would really like to do is not even mess with the HTML content. I would like to use apps script under the Google Sheet to just look at the actual data presented on the page and then match a text and capture the value to the right of it.
For example have it go down the list of URLS on sheet called "Pages" and do the following for each page:
Find the following values:
Find the text "Job name:", capture the text to the right of it.
Find the text "Domain owner:", capture the text to the right of it.
Find the text "Urgency/Impact:", capture the text to the right of it.
Find the text "ISOC instructions:", capture the text to the right of it.
Write those values to a new row in sheet called "Jobs" as seen below.
Then move on the the next URL in the sheet called "Pages" and repeat until all rows in the sheet "Pages" have been completed.
Example of the data I want to capture
I have created an exact copy of one of the pages for testing and is public.
https://sites.google.com/site/2020dump/test
An inspect example
The raw HTML of the table which contains all the data I am after.
<tr>
<td style="width:190px"><b>Domain owner:</b></td>
<td style="text-align:left;width:738px">IT.FinanceHRCore </td>
</tr>
<tr>
<td style="width:190px"> <b>Urgency/Impact:</b></td>
<td style="text-align:left;width:738px">Medium (3 - Urgency, 3 - Impact) </td>
</tr>
<tr>
<td style="width:190px"><b>ISOC instructions:</b></td>
<td style="text-align:left;width:738px">None </td>
</tr>
<tr>
<td style="width:190px"></td>
<td style="text-align:left;width:738px"> </td>
</tr>
</tbody>
</table>
Any examples of how I can accomplish this? I am not sure how from an apps script perspective to go about not looking at HTML and only looking at the actual data displayed on the page. For example looking for the text "Job name:" and then grabbing the text to the right of it.
The goal at the end of the day is to transfer the data from each page into one big Google Sheet so we can kill off the Google Classic Site.
I have been scraping data with apps script using regular expressions for a while, but I will say that the formatting of this page does make it difficult.
A lot of the pages that I scrape have tables in them so I made a helper script that will go through and clean them up and turn them into arrays. Copy and paste the script below into a new google script:
function scrapetables(html,startingtable,extractlinksTF) {
var totaltables = /<table.*?>/g
var total = html.match(totaltables)
var tableregex = /<table[\s\S]*?<\/table>/g;
var tables = html.match(tableregex);
var arrays = []
var i = startingtable || 0;
while (tables[i]) {
var thistable = []
var rows = tables[i].match(/<tr[\s\S]*?<\/tr>/g);
if(rows) {
var j = 0;
while (rows[j]) {
var thisrow = tablerow(rows[j])
if(thisrow.length > 2) {
thistable.push(tablerow(rows[j]))
} else {thistable.push(thisrow)}
j++
}
arrays.push(thistable);
}
i++
}
return arrays;
}
function removespaces(string) {
var newstring = string.trim().replace(/[\r\n\t]/g,'').replace(/ /g,' ');
return newstring
}
function tablerow(row,extractlinksTF) {
var cells = row.match(/<t[dh][\s\S]*?<\/t[dh]>/g);
var i = 0;
var thisrow = [];
while (cells[i]) {
thisrow.push(removehtmlmarkup(cells[i],extractlinksTF))
i++
}
return thisrow
}
function removehtmlmarkup(string,extractlinksTF) {
var string2 = removespaces(string.replace(/<\/?[A-Za-z].*?>/g,''))
var obj = {string: string2}
//check for link
if(/<a href=.*?<\/a>/.test(string)) {
obj['link'] = /<a href="(.*?)"/.exec(string)[1]
}
if(extractlinksTF) {
return obj;
} else {return string2}
}
Running this got close, but at the moment, this doesn't handle nested tables well so I cleaned up the input by sending only the table that we want by isolating it with a regular expression:
var tablehtml = /(<table[\s\S]{200,1000}Job Name[\s\S]*?<\/table>)/im.exec(html)[1]
Your parent function will then look like this:
function sniff(pageURL) {
var html= SitesApp.getPageByUrl(pageURL).getHtmlContent();
var tablehtml = /(<table[\s\S]{200,1000}Job Name[\s\S]*?<\/table>)/im.exec(html)[1]
var table = scrapetables(tablehtml);
var row_data =
{
Job_Name: na(table[0][3][1]), //indicates the 1st table in the html, row 4, cell 2
Domain_Owner: na(table[0][4][1]), // indicates 1st table in the html, row 5, cell 2 etc...
Urgency_Impact: na(table[0][5][1]),
ISOC_Instructions: na(table[0][6][1])
}
insertRowInTracker(row_data)
}
function na(string) {
if(string) {
return string
} else { return 'N/A'}
}

Query ViewData, then change value based on result

I have this code snippet in my controller
outputmodel4.Add(new SP_data.student()
{
student_id = (decimal)SPOutput4["student_id"],
student_no = (string)SPOutput4["student_no"],
course_id = (decimal)SPOutput4["course_id"],
floor_no = (int)SPOutput4["floor_no"],
tutor_id = (decimal)SPOutput4["tutor_id"],
capacity_id = (decimal)SPOutput4["capacity_id"],
});
}
ViewData["Output"] = outputmodel4;
what I am trying to do it query Capacity_id, and if the number is (say) 2, then display "All Most Full" on my view.
I think the say to accomplish this is
#foreach (var item in ViewData["Output"] as IEnumerable<app.Models.SP_data.student>)
{
<TD>
#if (item.capacity_id = 2)
{
All Most Full
}
else if (item.capacity_id = 5)
{
Full
}
else if etc..
</TD>
}
.. but I getting errors in the view
Cannot implicitly convert type decimal to bool
can someone please advise, and help ?
thanks

Warning on Razor when <img width="#someVar">

Having the following .cshtml :
#{ int someVar = 300; }
<img width="#someVar">
Visual Studio (2012) gives me a warning :
Warning 1 '#width' is not a valid value of attribute 'width'.
It does work ok, but I'm wondering if there's any way to pass a variable to the width attribute without having this warning.
Thanks!
Just wrap the value in parentheses, like so:
#{ int someVar = 300; }
<img width="#(someVar)">
The warning should then disappear.

how i can replace text of bookmark when bookmark is in the table?

I use this code to replace text of bookmark in word :
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open("doc3.docx", true))
{
var bookmarkStarts = wordDoc.MainDocumentPart.Document.Body.Descendants<BookmarkStart>();
foreach (var start in bookmarkStarts)
{
OpenXmlElement elem = item.NextSibling();
while (elem != null && !(elem is BookmarkEnd))
{
OpenXmlElement nextElem = elem.NextSibling();
elem.Remove();
elem = nextElem;
}
item.Parent.InsertBefore<Run>(new Run(new Text("Hello")), item);
}
wordDoc.Close();
}
But this not work where the bookmark is in the table.
Have you checked that you don't delete any bookmarks with your approach?
I've run your test code after editing a little (you don't have a var name items in your example code), and I've succesfully inserted Hello in 2 bookmarks out of a table, and 2 in a table, without any issues.
Which leads me to believe your problem lies elsewhere.
Have you looked at the open-xml in your document after you've run your program?
Is there any errors?
I've experienced bookmarks being placed the oddest places in a word-document when you leave the placing to word, and not you.
You can also end up with bookmarks overlapping each other like this
<bookmark1 start><xml xml><bookmark2 start><bookmark1 end><xml xml><bookmark2 end>
If you run into that case, your code will delete the bookmarkstart 2 before it reaches bookmarkend 1, and that will cause your bookmark to not be replaced.
You'll easily run into that problem with larger complex documents.
The way I solved it was to "sort" the bookmarks before doing any editing.
So the example above would become
<bookmark1 start><xml xml><bookmark1 end><bookmark2 start><xml xml><bookmark2 end>
after the sort
The code I use to do this look like this:
var bookmarks = mainPart.Document.Body.Descendants<BookmarkStart>();
for (int i = 0; i < bookmarks.Count(); i++)
{
var bks = bookmarks.ElementAt(i);
var next = bks.NextSibling();
if (next is BookmarkEnd)
{
var bme = (BookmarkEnd)next;
if (int.Parse(bks.Id) - int.Parse(bme.Id) == 1)
{
var copy = (BookmarkEnd)next.Clone();
bks.Parent.RemoveChild<BookmarkEnd>(bme);
bks.Parent.InsertBefore<BookmarkEnd>(copy, bks);
}
}
}
Which i'll admit isn't totally fool-proof but have worked well for me.
Another check you can add, to avoid deleting bookmarks is in your replace method
This will make sure you don't delete bookmarkstarts as you remove elements when inserting text
while (elem != null && !(elem is BookmarkEnd)) //fjern elementer
{
OpenXmlElement nextElem = elem.NextSibling();
if (elem.LocalName != "bookmarkStart")
elem.Remove();
elem = nextElem;
}
Good luck :)

asp.net mvc razor multiply two item and convert to string

When I write #(line.Quantity * line.Product.Price).ToString("c") the result is
39,00.ToString("c")
and #line.Quantity * line.Product.Price.ToString("c") result is
2 * line.Product.Price.ToString("c")
How can i multiply two values and convert it to string in a razor view?
try
#((line.Quantity * line.Product.Price).ToString("c"))
The problem is that razor do not know when the output string ends since # is used to display code in HTML. Spaces switches razor back to HTML mode.
Wrapping everything into parenthesis makes razor evaluate the entire code block.
Although the most proper way would be to introduce a new property in your model:
public class MyModel
{
public double Total { get { return Quantity * Product.Price; }}
//all other code here
}
and simply use:
#line.Total.ToString("c")
this is an old question but I have just had the same issue and here is the resolution for it.
If you need to perform a calculation on a razor view, you can do it the following way:
if you are outside of c# block (such as #foreach or #if ):
you can wrap your calculation into #{ } and they won't be rendered.
<p>Some text</p>
#{ var x = Model.Y * Model.Z; }
<p>X equals #x.ToString()</p>
if you are inside of a c# block:
you can simply put your calculations in { }.
<p>Some text</p>
#foreach (var x in Model.Y)
{
{ var z = x * 2; }
<p>Z equals #z.ToString()</p>
}

Resources