How to using XPath in WebBrowser Control? - javascript

In C# WinForms sample application, I have used WebBrowser control. I want to use JavaScript XPath to select single node. To do this, I use XPathJS
But with the following code, the returned value of vResult is always NULL.
bool completed = false;
WebBrowser wb = new WebBrowser();
wb.ScriptErrorsSuppressed = true;
wb.DocumentCompleted += delegate { completed = true; };
wb.Navigate("http://stackoverflow.com/");
while (!completed)
{
Application.DoEvents();
Thread.Sleep(100);
}
if (wb.Document != null)
{
HtmlElement head = wb.Document.GetElementsByTagName("head")[0];
HtmlElement scriptEl = wb.Document.CreateElement("script");
mshtml.IHTMLScriptElement element = (mshtml.IHTMLScriptElement)scriptEl.DomElement;
element.src = "https://raw.github.com/andrejpavlovic/xpathjs/master/build/xpathjs.min.js";
head.AppendChild(scriptEl);
// Initialize XPathJS
wb.Document.InvokeScript("XPathJS.bindDomLevel3XPath");
string xPathQuery = #"count(//script)";
string code = string.Format("document.evaluate('{0}', document, null, XPathResult.ANY_TYPE, null);", xPathQuery);
var vResult = wb.Document.InvokeScript("eval", new object[] { code });
}
Is there a way to do JavaScript XPath with WebBrowser control ?
Rem : I'd like to avoid using HTML Agility Pack, I wanted to directly manipulate WebBrowser control's DOM's content mshtml.IHTMLElement

I have found solution, here is the code:
bool completed = false;
WebBrowser wb = new WebBrowser();
wb.ScriptErrorsSuppressed = true;
wb.DocumentCompleted += delegate { completed = true; };
wb.Navigate("http://stackoverflow.com/");
while (!completed)
{
Application.DoEvents();
Thread.Sleep(100);
}
if (wb.Document != null)
{
HtmlElement head = wb.Document.GetElementsByTagName("head")[0];
HtmlElement scriptEl = wb.Document.CreateElement("script");
mshtml.IHTMLScriptElement element = (mshtml.IHTMLScriptElement)scriptEl.DomElement;
element.text = System.IO.File.ReadAllText(#"wgxpath.install.js");
head.AppendChild(scriptEl);
// Call wgxpath.install() from JavaScript code, which will ensure document.evaluate
wb.Document.InvokeScript("eval", new object[] { "wgxpath.install()" });
string xPathQuery = #"count(//script)";
string code = string.Format("document.evaluate('{0}', document, null, XPathResult.NUMBER_TYPE, null).numberValue;", xPathQuery);
int iResult = (int) wb.Document.InvokeScript("eval", new object[] { code });
}
I use "A pure JavaScript XPath library": wicked-good-xpath and download the wgxpath.install.js

Related

Downloading files in Blazor Webassembly when using navigation manager and needing to check user claims

I have a Blazor WASM solution in which I am trying to build functionality for downloading data from a (Radzen) datagrid. I start off by calling an export service from the code of my component like so:
await _exportService.Export("expense-claims-admin", type, new Query()
{
OrderBy = grid.Query.OrderBy,
Filter = grid.Query.Filter,
Select = "CreateDate,User.FirstName,User.LastName,Status,ExpenseRecords.Count AS NumberOfExpenseRecords,MileageRecords.Count AS NumberOfMileageRecords,TotalAmount"
});
My export service then runs the following code:
public async Task Export(string table, string type, Query query = null)
{
_navigationManager.NavigateTo(query != null ? query.ToUrl($"/export/{table}/{type}") : $"/export/{table}/{type}", true);
}
This calls a controller in my server project, which runs a different action depending on the request URL. For example:
[HttpGet("/export/expense-claims-admin/excel")]
public IActionResult ExportExpenseClaimsToExcelAdmin()
{
var claimsPrincipal = User;
var companyId = claimsPrincipal.FindFirst(c => c.Type == "companyId");
if (companyId != null)
{
return ToExcel(ApplyQuery(context.expense_claims.Where(x => x.DeleteDate == null && x.CompanyId == int.Parse(companyId.Value)), Request.Query));
}
//TODO - Return something other than null
return null!;
}
This then calls one of two methods depending on whether I'm trying to export to Excel or CSV. Below is the ToExcel method that gets called above:
public FileStreamResult ToExcel(IQueryable query, string fileName = null)
{
var columns = GetProperties(query.ElementType);
var stream = new MemoryStream();
using (var document = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook))
{
var workbookPart = document.AddWorkbookPart();
workbookPart.Workbook = new Workbook();
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet();
var workbookStylesPart = workbookPart.AddNewPart<WorkbookStylesPart>();
GenerateWorkbookStylesPartContent(workbookStylesPart);
var sheets = workbookPart.Workbook.AppendChild(new Sheets());
var sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart), SheetId = 1, Name = "Sheet1" };
sheets.Append(sheet);
workbookPart.Workbook.Save();
var sheetData = worksheetPart.Worksheet.AppendChild(new SheetData());
var headerRow = new Row();
foreach (var column in columns)
{
headerRow.Append(new Cell()
{
CellValue = new CellValue(column.Key),
DataType = new EnumValue<CellValues>(CellValues.String)
});
}
sheetData.AppendChild(headerRow);
foreach (var item in query)
{
var row = new Row();
foreach (var column in columns)
{
var value = GetValue(item, column.Key);
var stringValue = $"{value}".Trim();
var cell = new Cell();
var underlyingType = column.Value.IsGenericType &&
column.Value.GetGenericTypeDefinition() == typeof(Nullable<>) ?
Nullable.GetUnderlyingType(column.Value) : column.Value;
var typeCode = Type.GetTypeCode(underlyingType);
if (typeCode == TypeCode.DateTime)
{
if (!string.IsNullOrWhiteSpace(stringValue))
{
cell.CellValue = new CellValue() { Text = ((DateTime)value).ToOADate().ToString(System.Globalization.CultureInfo.InvariantCulture) };
cell.DataType = new EnumValue<CellValues>(CellValues.Number);
cell.StyleIndex = (UInt32Value)1U;
}
}
else if (typeCode == TypeCode.Boolean)
{
cell.CellValue = new CellValue(stringValue.ToLower());
cell.DataType = new EnumValue<CellValues>(CellValues.Boolean);
}
else if (IsNumeric(typeCode))
{
if (value != null)
{
stringValue = Convert.ToString(value, CultureInfo.InvariantCulture);
}
cell.CellValue = new CellValue(stringValue);
cell.DataType = new EnumValue<CellValues>(CellValues.Number);
}
else
{
cell.CellValue = new CellValue(stringValue);
cell.DataType = new EnumValue<CellValues>(CellValues.String);
}
row.Append(cell);
}
sheetData.AppendChild(row);
}
workbookPart.Workbook.Save();
}
if (stream?.Length > 0)
{
stream.Seek(0, SeekOrigin.Begin);
}
var result = new FileStreamResult(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
result.FileDownloadName = (!string.IsNullOrEmpty(fileName) ? fileName : "Export") + ".xlsx";
return result;
}
The issue I'm hitting is that the claims against the User object are never populated with the information that I need to do the checks in my export controller. I know this is because I'm calling the controller with navigation manager rather than going through a httpclient request so no authorisation token is passed, but if I use httpclient instead, the file is not downloaded by the browser.
I have tried using a combination of httpclient and javascript to get the file to download (code below) but this is returning a text file rather than an xlsx or csv file and the content is just gibberish (to me anyway).
Here's the code in my service if I use httpclient instead of navigation manager:
public async Task Export(string table, string type, Query query = null)
{
var response = await _client.GetAsync(query != null ? query.ToUrl($"/export/{table}/{type}") : $"/export/{table}/{type}");
var fileStream = await response.Content.ReadAsStreamAsync();
using var streamRef = new DotNetStreamReference(stream: fileStream);
await _js.InvokeVoidAsync("downloadFileFromStream", "Export", streamRef);
}
and here's the javascript code I'm using to download the file:
async function downloadFileFromStream(fileName, contentStreamReference) {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
triggerFileDownload(fileName, url);
URL.revokeObjectURL(url);
}
function triggerFileDownload(fileName, url) {
const anchorElement = document.createElement("a");
anchorElement.href = url;
if (fileName) {
anchorElement.download = fileName;
}
anchorElement.click();
anchorElement.remove();
}
I've read that using jsinterop to do file downloads is slow and that you're also limited by file size, so it seems like the best way to get this working would be to call the controller with navigation manager, but I just can't work out how to get those user claims if I do that and I really need to check those to make sure I'm returning the right data.

inDesign scripting: Include remote .js-file in script-file (and use it's variables)

I'm trying to access a remote .jsfile within an inDesign script to use it's variables. I found functions for including js-files locally but haven't found a good way to include.
http://remote-site.com/test.js:
var testVar = "it works!";
myscript.js, including locally (working):
app.doScript(new File("/Users/Popmouth/test.js"));
alert(testVar);
myscript.js, including locally including remotely (not working):
app.doScript(new File("http://remote-site.com/test.js"));
alert(testVar);
I also found this snippet, this alert works (alerts the content of the file, i.e. "var testVar = "it works!;") but I don't know how to use the vars in my alert function below:
var HTTPFile = function (url,port) {
if (arguments.length == 1) {
url = arguments[0];
port = 80;
};
this.url = url;
this.port = port;
this.httpPrefix = this.url.match(/http:\/\//);
this.domain = this.httpPrefix == null ? this.url.split("/")[0]+":"+this.port :this.url.split("/")[2]+":"+this.port;
this.call = "GET "+ (this.httpPrefix == null ? "http://"+this.url : this.url)+" HTTP/1.0\r\nHost:" +(this.httpPrefix == null ? this.url.split("/")[0] :this.url.split("/")[2])+"\r\nConnection: close\r\n\r\n";
this.reply = new String();
this.conn = new Socket();
this.conn.encoding = "binary";
HTTPFile.prototype.getFile = function(f) {
var typeMatch = this.url.match(/(\.)(\w{3,4}\b)/g);
if (this.conn.open(this.domain,"binary")) {
this.conn.write(this.call);
this.reply = this.conn.read(9999999999);
this.conn.close();
} else {
this.reply = "";
}
return this.reply.substr(this.reply.indexOf("\r\n\r\n")+4);;
};
}
var remoteFile = new HTTPFile("http://remote-site.com/test.js");
alert(.getFile());
This function
Ok, so I went to adobe-forums and got the following string which replaces app.doScript(new File("/Users/Popmouth/test.js"));:
var remoteCode = 'http://remote-site.com/test.js'; //location of your remote file
var script = app.doScript("do shell script \"curl 'remoteCode'\"".replace("remoteCode", remoteCode), ScriptLanguage.APPLESCRIPT_LANGUAGE);
// Set script args
app.scriptArgs.setValue("scriptArg1", "Hello");
// Set environmental vars
$.setenv("envVar1", "World");
// Run the "remote" script
app.doScript(script, ScriptLanguage.JAVASCRIPT);

How to render a WebUserControl client-side

I created a web user control which has some ASP.NET and third-party controls.
I've already tested my control in the markup of a page and it works well.
<APP:MyControl ID="myControl1" InitializeWithValue="Employee" />
<APP:MyControl ID="myControl2" InitializeWithValue="Supplier" />
Now, I want to render this control in the same page but not when the page renders at the first time. In fact, I will click a button which will call a callback and in that moment I want to render the control.
I was trying to do something like this.
void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
MyControl c1 = new MyControl();
c1.InitializeWithValue = Person.Enum.Employee; //<--CRASH
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter(sb);
HtmlTextWriter hw = new HtmlTextWriter(sw);
c1.RenderControl(hw);
return sb.ToString();
}
Then on my client-side receiver
Test
<div id="divControls"></div>
<script>
function receiver(arguments, context) {
document.getElementById('divControls').innerHTML = arguments;
}
</script>
But it crash at the moment of the initialization because all the inner controls of the WebUserControl are null. They have not been initialized.
This is the property of the control
[Browsable(true)]
public Person.Enum InitializeWithValue
{
get { return this.enumPersonValue; }
set
{
this.enumPersonValue = value;
//Literal control
this.litPersonType.Text = "Employee"; //<-- CRASH because litPersonType is null.
}
}
Some can help me to correct this or suggest me another way render a web control but client-side?
UserControls usually are tighted to the Page Life Cycle, so you would need to recreate a whole page and render it instead.
Second, you should load your usercontrol using its .ascx file and not only its class, this is required because only your class is not enough.
You can still try to do like you was doing only creating but using Page.LoadControl:
void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
MyControl c1 = (MyControl)page.LoadControl("/Controls/myControl.ascx");
c1.InitializeWithValue = Person.Enum.Employee; //<--CRASH
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter(sb);
HtmlTextWriter hw = new HtmlTextWriter(sw);
c1.RenderControl(hw);
return sb.ToString();
}
But if your control has Events related to the Page Life Cycle you might need to create a whole context to it:
You can do it all like this:
using (var stringWriter = new StringWriter())
{
using (var page = new Page())
{
MyControl myControl = (MyControl)page.LoadControl("/Controls/myControl.ascx");
var head = new HtmlHead();
page.Controls.Add(head);
var form = new HtmlForm();
page.Controls.Add(form);
var div = new HtmlGenericControl("div");
div.ID = "myControlContent";
form.Controls.Add(div);
div.Controls.Add(myControl);
HttpContext.Current.Server.Execute(page, stringWriter, false);
}
return stringWriter.ToString()
}
Then in your JS you can parse the whole html and get only the innerHTML of div#myControlContent, for example:
function receiver(arguments, context) {
var parser = new DOMParser();
var doc = parser.parseFromString(arguments, "text/html");
document.getElementById('divControls').innerHTML = doc.getElementById('myControlContent').innerHTML;
}
note that DOMParser, is available only in mordern browsers, but you can find other approachs for older browsers...

How to evaluate xml with namespaces in office add ins?

I'm trying to search for particular nodes in office xml. I'm using wgxpath for it. Here is my code. But every time empty result is returned. Probably wgxpath doesn't work with namespaces (although it accepts nsResolver as parameter)
Word.run(function (ctx) {
var xml = ctx.document.body.getOoxml();
ctx.sync().then(function () {
try {
var parser = new DOMParser();
var doc = parser.parseFromString(xml, "text/xml");
var win = { 'document': doc };
wgxpath.install(win);
var nsResolver = doc.createNSResolver(doc.ownerDocument == null ? doc.documentElement : doc.ownerDocument.documentElement);
var result = doc.evaluate("/pkg:package/pkg:part/pkg:xmlData/w:styles/w:docDefaults/w:pPrDefault/w:pPr/w:spacing", doc, nsResolver, win.XPathResult.ANY_TYPE, null);
var items = [];
try {
var item = result.iterateNext();
while (item) {
items.push(item);
item = result.iterateNext();
}
}
catch (e) {
reject(e);
}
resolve(items);
});
});
I've also tried another example with simple xml:
var parser = new DOMParser();
var test = "<pkg:count xmlns:pkg='http://schemas.microsoft.com/office/2006/xmlPackage'><pkg:a>1</pkg:a></pkg:count>";
var doc = parser.parseFromString(test, "text/xml");
var win = { 'document': doc };
wgxpath.install(win);
var nsResolver = function(prefix) {
var ns = {
'pkg': 'http://schemas.microsoft.com/office/2006/xmlPackage'
};
return ns[prefix] || null;
}
var result = doc.evaluate('/pkg:count/pkg:a', doc, nsResolver, win.XPathResult.ANY_TYPE, null);
Result is the same: with prefixes empty result is returned, but if I remove prefixes, evaluation works.
How can I evaluate xml with namespaces in office add ins? Any other solution? I've tried to use ActiveXObject("Microsoft.XMLDOM"), but get "Can't create object" exception.
The built in XML parsing methods do support namespaces, but not xpath.
E.g. Document.getElementsByTagNameNS

Firefox addon: return HTML from custom protocol handler?

I'm trying to return document content from a custom protocol handler.
I can redirect to a URL's fine, but I can't return the document body myself.
newChannel: function(aURI)
{
var ioservice = Cc["#mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
var resource = aURI.spec.split(":")[1];
var wheretogo = WhereToGo(resource);
var uri = ioservice.newURI(wheretogo, null, null);
if (uri) {
var channel = ioservice.newChannelFromURI(uri, null).QueryInterface(Ci.nsIHttpChannel);
return channel;
} else {
return false;
}
},
classDescription: "Protocol Handler",
contractID: "#mozilla.org/network/protocol;1?name=myproto",
classID: Components.ID('{xxxx}'),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
I've tried accessing the window from within the wheretogo method:
var ww = Components.classes["#mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
var win = ww.activeWindow.document.innerHTML = 'test';
But this doesn't do anything (no error either).
I also tried:
return "data:text/html,test";
But this too doesn't do anything.
Any ideas?
Using a data: URI should be the simplest approach but you cannot simply return a string - the method newChannel has to return a channel. The following should work:
newChannel: function(aURI)
{
var htmlData = "test";
var ioservice = Cc["#mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
var uri = ioservice.newURI("data:text/html," + encodeURIComponent(htmlData),
null, null);
var channel = ioservice.newChannelFromURI(uri);
return channel;
},
Some notes:
nsIIOService.newChannelFromURI takes only one parameter (your code gave it two).
newChannel cannot return a boolean, it has to return null or (better) throw an exception, e.g. throw Components.results.NS_ERROR_UNEXPECTED.
You don't need QueryInterface, newChannel can return any channel - not necessarily an HTTP channel.

Categories

Resources