I'm writing a client and server who obviously has to work with each other. In both the client and the server, I have a class to construct messages. These messages are sent over through websockets.
Now the problem lies in the method I use to construct messages. I make use of the Buffer class and both the read...BE functions and the write...BE functions. Somehow, the data written on the client doesn't correspondent with the data read on the server.
One example of a message sent to the server is the LOGIN message:
import ClientMessage from "../../protocol/ClientMessage";
import { LOGIN } from "../../protocol/OpCodes/ClientOpCodes";
export default class RequestLogin extends ClientMessage {
constructor(username: string, look: string) {
super(LOGIN);
this.appendString(username);
this.appendString(look);
}
}
The LOGIN from the opcodes is equal to 1, and the message ID should be 1.
Next, once a login button is pressed, this code is executed:
this.game.communicationManager.sendMessage(new RequestLogin(username, look));
This is the sendMessage function:
sendMessage(message: ClientMessage) {
if (this.client.connected) {
//console.log('Sent [' + message.id + ']: ' + message.constructor.name);
this.client.send(message.getPacket());
}
}
Last, this is the ClientMessage class:
export default class ClientMessage {
body: Buffer;
pos: number;
constructor(id: number) {
this.body = Buffer.alloc(512);
this.pos = 0;
this.appendShort(id);
}
appendShort(i: number) {
this.body.writeInt16BE(i, this.pos);
this.pos += 2;
}
appendInt(i: number) {
this.body.writeInt32BE(i, this.pos);
this.pos += 4;
}
appendString(str: string) {
this.appendShort(str.length);
this.body.write(str, 'utf8');
this.pos += str.length + 2;
}
getPacket(): string {
return this.body.toString();
}
}
(I'm aware 512 is a bit overkill, but I assume it doesn't matter really in terms of reading).
Now, going over to the server, the ClientMessage on the server side looks like this:
export default class ClientMessage {
packet: Buffer;
id: number;
pos:number;
constructor(packet:string) {
this.packet = Buffer.from(packet);
this.pos = 0;
this.id = this.getShort();
}
getShort(): number {
const value = this.packet.readInt16BE(this.pos);
this.pos += 2;
return value;
}
getInt(): number {
const value = this.packet.readInt32BE(this.pos);
this.pos += 4;
return value;
}
getString(): string {
const strlen = this.getShort();
const value = this.packet.toString('utf8', this.pos, this.pos + strlen);
this.pos += strlen;
return value;
}
}
This is the callback for this.ws.on('message'
onDataArrival(message:string): any {
const clientMessage:ClientMessage = new ClientMessage(message);
console.log(clientMessage.id);
console.log(clientMessage.getString());
}
Oddly enough, the id is already wrong. Where it should be 1 (cause the LOGIN equals to 1), clientMessage.id has 26738 as value which is WAY higher. I'm not sure what I'm doing wrong or what I'm missing. (of course, the string is incorrect as well)
Found it out already. The problem was my appendString on the clientside.
appendString(str: string) {
this.appendShort(str.length);
this.body.write(str, this.pos, 'utf8');
this.pos += str.length;
}
I had to write the string at the correctb position of course.
Related
I started coding a validation of my Excel Sheet. I implemented quite a bit so I try to keep the code short.
My source code:
function main(workbook: ExcelScript.Workbook) {
console.log("starting...");
let cs = createVariantCategorySheet(workbook.getWorksheet("variant categories"));
cs.validateSheet();
for (let m of cs.logMessages) {
console.log(m);
}
console.log("finished...");
}
function createVariantCategorySheet(worksheet: ExcelScript.Worksheet): ImportSheet {
let sheet = new ImportSheetBuilder()
.name("variant categories")
.index(1)
.worksheet(worksheet)
.addColumnInfo(
new ColumnInfoBuilder()
.index(4)
.name("Sort Order")
// .addChecker(new NotEmptyChecker())
.addChecker(new UniqueSortOrderChecker(worksheet, worksheet.getRange("B:B")))
.build()
)
.build();
return sheet;
}
class ImportSheet {
public name: string;
public index: number;
public worksheet?: ExcelScript.Worksheet
columns: ColumnInfo[];
public logMessages: string[];
constructor() {
this.index = -1;
this.name = "";
this.columns = [];
this.logMessages = [];
}
// ... Some usefull private methods ...
validateSheet() {
let rowCount = this.getRowCount();
for (let rowIndex = 1; rowIndex < rowCount; rowIndex++) {
if (this.isRowEmpty(rowIndex)) {
this.logMessages.push("no more lines... ")
return;
}
this.validatingRow(rowIndex);
}
}
private validatingRow(rowIndex: number) {
this.logMessages.push("iterating over line [" + rowIndex + "]")
for (let columnItem of this.columns) {
this.validatingColumn(columnItem, this.rangeToValidate(rowIndex, columnItem.index));
}
}
private validatingColumn(columnItem: ColumnInfo, rangeToValidate: ExcelScript.Range){
for (let validator of columnItem.validator) {
let stringToValidate = rangeToValidate.getValue().toString();
this.logMessages.push("###### " + validator.isValid)
if (!validator.isValid(stringToValidate, rangeToValidate.getRowIndex())) {
this.logMessages.push("## ## ERROR: Failed Validation " + validator.getName() +
" in '" + rangeToValidate.getAddress() + "' for column '" +
columnItem.name + "'.");
} else {
this.logMessages.push("## ## INFO: Success Validation " + validator.getName() +
" in '" + rangeToValidate.getAddress() + "' for column '" +
columnItem.name + "'.");
}
}
}
}
class ColumnInfo {
name: string;
index: number;
validator: Checker[];
}
interface Checker {
isValid(value: string, rowIndex: number): boolean
getName(): string
}
class NotEmptyChecker implements Checker {
isValid(value: string): boolean {
if (value)
return true;
else
return false;
}
getName(): string {
return NotEmptyChecker.name;
}
}
class UniqueSortOrderChecker implements Checker {
sortOrderMap: Map<string, Set<string>>;
range: ExcelScript.Range
worksheet: ExcelScript.Worksheet
constructor(worksheet: ExcelScript.Worksheet, range: ExcelScript.Range) {
this.range = range;
this.worksheet = worksheet;
this.sortOrderMap = new Map<string, Set<string>>();
}
isValid(value: string, rowIndex: number): boolean {
return true;
}
getName(): string {
return UniqueSortOrderChecker.name;
}
}
I left out the Builders as they just create the objects.
The above code works and validates the file like I wish.
The generated code for the Method UniqueSortOrderChecker.isValid looks like:
function (value, rowIndex) {
ExcelScript.engine.traceLine(undefined);
return true;
}
So the interesting part is, once I change the UniqueSortOrderChecker into:
isValid(value: string, rowIndex: number): boolean {
console.log("test");
return true;
}
The generated code looks now totally different and returns a promise:
function (value, rowIndex) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
ExcelScript.engine.traceLine(264);
return [4 /*yield*/, console.log("test")];
case 1:
(_a.sent());
ExcelScript.engine.traceLine(undefined);
return [2 /*return*/, true];
}
});
});
}
Same happens when I try to read cell value like:
isValid(value: string, rowIndex: number): boolean {
let cellValue = this.range.getRow(rowIndex).getValue().toString();
return true;
}
turns it into a promise return type like:
function (value, rowIndex) {
return __awaiter(this, void 0, void 0, function () {
var cellValue;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
ExcelScript.engine.traceLine(264);
return [4 /*yield*/, this.range.getRow(rowIndex).getValue()];
case 1:
cellValue = (_a.sent()).toString();
ExcelScript.engine.traceLine(undefined);
return [2 /*return*/, true];
}
});
});
}
Can anyone help me out here? As once it is turned into a promise the return value is always true and my validation does not work.
Hope the question is not to long, but I wanted to give context.
Office Script relies on some complex logic underneath to give inherently asynchronous code the appearance of synchronous execution.
As of 2021-07-01 underneath the covers every Office Script method including console.log returns a promise. As you have discovered!
The complex logic applies awaits to these pieces of code to make them match the IntelliSense and appear synchronous.
The majority of common cases as of 2021-07-01 are covered by this logic, however there are some gaps in the complex transformation logic that may miss some scenarios.
Use of Office Script methods and console.log are not yet supported in class methods.
If you must use Office Script methods or console.log in classes today, you can mark these class methods as async and await them.
If this feature is important for your scenario I recommend sending feedback in the Office Script Editor click the ... and then click Send Feedback.
I'm building an office-js add-in for Excel. I need to upload the current workbook to a back end server. I've implemented an example from the Micrsoft Documentation, which seems to work fine the first time I call it, but on subsequent calls, it causes Excel to crash. I'm using Excel 365 version 1812 (build 11126.20132)
Here is the link to the example in the MS docs:
https://learn.microsoft.com/en-us/javascript/api/office/office.document
There are many examples on this page, to find the one I'm working from search for "The following example gets the document in Office Open XML" I've included the example below for ease of reference.
The code just get's the current file and dumps the characters to the console's log. It works fine the first but crashes Excel the second time--after it has shown the length of FileContent.
export function getDocumentAsCompressed() {
Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: 65536 /*64 KB*/ },
function (result) {
if (result.status == "succeeded") {
// If the getFileAsync call succeeded, then
// result.value will return a valid File Object.
var myFile = result.value;
var sliceCount = myFile.sliceCount;
var slicesReceived = 0, gotAllSlices = true, docdataSlices = [];
console.log("File size:" + myFile.size + " #Slices: " + sliceCount);
// Get the file slices.
getSliceAsync(myFile, 0, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}else {
console.log("Error:", result.error.message);
}
});
}
function getSliceAsync(file, nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived) {
file.getSliceAsync(nextSlice, function (sliceResult) {
if (sliceResult.status == "succeeded") {
if (!gotAllSlices) { // Failed to get all slices, no need to continue.
return;
}
// Got one slice, store it in a temporary array.
// (Or you can do something else, such as
// send it to a third-party server.)
// console.log("file part",sliceResult.value.data)
docdataSlices[sliceResult.value.index] = sliceResult.value.data;
if (++slicesReceived == sliceCount) {
// All slices have been received.
file.closeAsync();
onGotAllSlices(docdataSlices);
}
else {
getSliceAsync(file, ++nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}
}
else {
gotAllSlices = false;
file.closeAsync();
console.log("getSliceAsync Error:", sliceResult.error.message);
}
});
}
function onGotAllSlices(docdataSlices) {
var docdata = [];
for (var i = 0; i < docdataSlices.length; i++) {
docdata = docdata.concat(docdataSlices[i]);
}
var fileContent = new String();
for (var j = 0; j < docdata.length; j++) {
fileContent += String.fromCharCode(docdata[j]);
}
console.log("fileContent.length",fileContent.length)
// Now all the file content is stored in 'fileContent' variable,
// you can do something with it, such as print, fax...
}
Here is the result
File size:21489 #Slices: 1
fileContent.length 21489
Original example from Microsoft documentation (https://learn.microsoft.com/en-us/javascript/api/office/office.document)
// The following example gets the document in Office Open XML ("compressed") format in 65536 bytes (64 KB) slices.
// Note: The implementation of app.showNotification in this example is from the Visual Studio template for Office Add-ins.
function getDocumentAsCompressed() {
Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: 65536 /*64 KB*/ },
function (result) {
if (result.status == "succeeded") {
// If the getFileAsync call succeeded, then
// result.value will return a valid File Object.
var myFile = result.value;
var sliceCount = myFile.sliceCount;
var slicesReceived = 0, gotAllSlices = true, docdataSlices = [];
app.showNotification("File size:" + myFile.size + " #Slices: " + sliceCount);
// Get the file slices.
getSliceAsync(myFile, 0, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}
else {
app.showNotification("Error:", result.error.message);
}
});
}
function getSliceAsync(file, nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived) {
file.getSliceAsync(nextSlice, function (sliceResult) {
if (sliceResult.status == "succeeded") {
if (!gotAllSlices) { // Failed to get all slices, no need to continue.
return;
}
// Got one slice, store it in a temporary array.
// (Or you can do something else, such as
// send it to a third-party server.)
docdataSlices[sliceResult.value.index] = sliceResult.value.data;
if (++slicesReceived == sliceCount) {
// All slices have been received.
file.closeAsync();
onGotAllSlices(docdataSlices);
}
else {
getSliceAsync(file, ++nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}
}
else {
gotAllSlices = false;
file.closeAsync();
app.showNotification("getSliceAsync Error:", sliceResult.error.message);
}
});
}
function onGotAllSlices(docdataSlices) {
var docdata = [];
for (var i = 0; i < docdataSlices.length; i++) {
docdata = docdata.concat(docdataSlices[i]);
}
var fileContent = new String();
for (var j = 0; j < docdata.length; j++) {
fileContent += String.fromCharCode(docdata[j]);
}
// Now all the file content is stored in 'fileContent' variable,
// you can do something with it, such as print, fax...
}
// The following example gets the document in PDF format.
Office.context.document.getFileAsync(Office.FileType.Pdf,
function(result) {
if (result.status == "succeeded") {
var myFile = result.value;
var sliceCount = myFile.sliceCount;
app.showNotification("File size:" + myFile.size + " #Slices: " + sliceCount);
// Now, you can call getSliceAsync to download the files,
// as described in the previous code segment (compressed format).
myFile.closeAsync();
}
else {
app.showNotification("Error:", result.error.message);
}
}
);
Since you're using Excel, have you tried the CreateWorkbork API? Might be a good workaround if the Document API has a bug, like Xuanzhou indicated earlier.
Here's a CreateDocument snippet that you can load into Script Lab. It shows how to create a Workbook copy based on an existing file.
Hope all that is helpful.
We already have a fix for it now. But the fix still need some time to go to production. Please try it several days later and let me know if the issue still exists. Thanks.
I am currently trying to export some data from my application in either Excel or CSV. What is the best way to accomplish this? Should I export from the backend, or export once I have the data client side using a library within Angular 2? My Web API 2 controller currently produces a list and then sends it as JSON to the front end.
That all works, I am just struggling with exporting the list.
Here is a sample of what I am doing
[HttpGet]
[Route("/api/preview/{item}")]
public IActionResult Preview(string item)
{
if (item!= null)
{
var preview = _context.dbPreview.FromSql("Exec sampleStoredProcedure {0}, 1", item).ToList();
return Ok(preview);
}
}
That is how I am generating my data that is sent to Angular 2.
I can provide any Angular 2 code if it is necessary but it is just a normal service. Was not sure if there was some library that worked well with Angular 2 to do an export. I've seen some things for javascript but alaSQL but it does not seem like it would work with Angular 2.
Any ideas?
I've looked at the source code from PrimeNG DataTable and I think you can use the exportCSV code for exporting a csv of your data.
The "trick" is to generate a string starting with data:text/csv;charset=utf-8 and make this downloadable by the user.
Something like the following code should work for you (maybe you need to modify it a bit so it fits to your data).
Most of the code is copied from PrimeNG except the download method. That method is copied from a SO answer.
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app works!';
csvSeparator = ';';
value = [
{ name: 'A3', year: 2013, brand: 'Audi' },
{ name: 'Z3', year: 2015, brand: 'BMW' }
];
columns = [
{ field: 'name', header: 'Name' },
{ field: 'year', header: 'Production data' },
{ field: 'brand', header: 'Brand' },
];
constructor() {
console.log(this.value);
this.exportCSV('cars.csv'); // just for show casing --> later triggered by a click on a button
}
download(text, filename) {
let element = document.createElement('a');
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
exportCSV(filename) {
let data = this.value, csv = '';
// csv = "data:text/csv;charset=utf-8,";
//headers
for (let i = 0; i < this.columns.length; i++) {
if (this.columns[i].field) {
csv += this.columns[i].field;
if (i < (this.columns.length - 1)) {
csv += this.csvSeparator;
}
}
}
//body
this.value.forEach((record, j) => {
csv += '\n';
for (let i = 0; i < this.columns.length; i++) {
if (this.columns[i].field) {
console.log(record[this.columns[i].field]);
// resolveFieldData seems to check if field is nested e.g. data.something --> probably not needed
csv += record[this.columns[i].field]; //this.resolveFieldData(record, this.columns[i].field);
if (i < (this.columns.length - 1)) {
csv += this.csvSeparator;
}
}
}
});
// console.log(csv);
// window.open(encodeURI(csv)); // doesn't display a filename!
this.download(csv, filename);
}
// resolveFieldData(data: any, field: string): any {
// if(data && field) {
// if(field.indexOf('.') == -1) {
// return data[field];
// }
// else {
// let fields: string[] = field.split('.');
// let value = data;
// for(var i = 0, len = fields.length; i < len; ++i) {
// value = value[fields[i]];
// }
// return value;
// }
// }
// else {
// return null;
// }
// }
}
AWolfs answer got me on the right track but I did some tweaking to get it working with Internet Explorer.
This function converts my array to my string for my csv file. (I had to create a new object that was my column headers). I then just pass the data that is generated by my service to the function and it does the parsing for me. For more complex data I believe you would need to do some additional logic but I have basic text so it all worked out for me.
exportCSV(filename, CsvData) {
let data = CsvData, csv = '';
console.log(data);
//headers
for (let i = 0; i < this.columns.length; i++) {
if (this.columns[i].field) {
csv += this.columns[i].field;
if (i < (this.columns.length - 1)) {
csv += this.csvSeparator;
}
}
}
//body
CsvData.forEach((record, j) => {
csv += '\n';
for (let i = 0; i < this.columns.length; i++) {
if (this.columns[i].field) {
console.log(record[this.columns[i].field]);
csv += record[this.columns[i].field];
if (i < (this.columns.length - 1)) {
csv += this.csvSeparator;
}
}
}
});
this.DownloadFile(csv, filename);
}
That was pretty much the same as AWolfs answer but I had to make some modifications to the DownloadFile function to get it to work with additional browsers. This function just accepts the huge string that makes up your .CSV file and the filename.
DownloadFile(text, filename) {
console.log(text);
var blob = new Blob([text], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob, filename);
}
else //create a link and click it
{
var link = document.createElement("a");
if (link.download !== undefined) // feature detection
{
// Browsers that support HTML5 download attribute
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
This code needs cleaned up but I wanted to update my question with an answer for anyone who was struggling with the same thing. This should at least get you started. This works in both Chrome and IE.
Thanks.
I need to be able to have several possible values under the same key in a mapping. Today, Solidity's mappings are mono-valued: writing a value overwrite the previous one (which is still in the blockchain, but not retrievable by a contract). I wrote this code to have multi-valued mappings:
contract MVM {
struct Bucket {
bool exists;
uint num; // Never decreases: we can only add records, not remove them.
mapping(uint => Record) records;
}
struct Record {
bool exists;
string info;
}
// Do not make it public: the compiler crashes when generating
// accessors https://github.com/ethereum/solidity/issues/633
mapping(string => Bucket) data;
function set(string key, string value) {
if (data[key].exists) {
data[key].records[data[key].num] = Record(true, value);
}
else {
data[key].exists = true;
data[key].records[0] = Record(true, value);
}
data[key].num++;
}
function num_of(string key) returns (uint) {
return data[key].num; // Guaranteed to be initialized as zero?
}
function get(string key, uint index) returns (string) {
if (!data[key].exists || !data[key].records[index].exists) {
throw;
}
return data[key].records[index].info;
}
}
An example of its use from the geth console:
> mvs.set.sendTransaction("foo", "bar", {from:eth.accounts[0], gas: 1000000})
"0x79c52c437a94f3301775acec5639404eff563fce1a99ad097f5db28f109d7ab5"
> mvm.set.sendTransaction("foo", "thing", {from:eth.accounts[0], gas: 1000000})
"0xb26b8d34691b0da5cb48af68933e81b514199f4ed8bd2b557767c8b55da85f50"
> mvm.get.call("foo")
"bar"
> mvm.get.call("foo", 1)
"thing"
> mvm.num_of.call("foo")
2
Is there a flaw in my approach? Or a better solution?
contract MVM {
struct Record {
bool exists;
string info;
}
mapping(string => Record[]) data;
// If you want to iterate the whole thing, you can use this:
string[] keysNames;
function set(string key, string value) {
// Remove this if, if you don't need iteration
if (data[key].length == 0) {
keysNames.push(key);
}
data[key].push(Record(true, value));
}
function num_of(string key) constant returns (uint) {
return data[key].length;
}
function get(string key, uint index) constant returns (string) {
if (data[key][index].exists == false) {
throw;
}
return data[key][index].info;
}
function exampleIterate() constant returns (string last) {
uint keysLen = keysNames.length;
for(uint i = 0; i < keysLen; i++) {
uint recordsLen = data[keysNames[i]].length;
for(uint j = 0; j < recordsLen; j++) {
last = data[keysNames[i]][j].info;
}
}
}
}
I want to use functions as keys in a Map like this:
var timers : Map<Void->Void, snow.api.Timer>;
But Haxe won't compile:
Abstract Map has no #:to function that accepts IMap<Void -> Void, snow.api.Timer>
Is there a way to do this ?
It's easy to write a custom implementation:
import haxe.Constraints;
class FunctionMap<K:Function,V> implements IMap<K,V> {
private var _keys : Array<K>;
private var _values : Array<V>;
public function new () {
_keys = [];
_values = [];
}
public function get(k:K):Null<V> {
var keyIndex = index(k);
if (keyIndex < 0) {
return null;
} else {
return _values[keyIndex];
}
}
public function set(k:K, v:V):Void {
var keyIndex = index(k);
if (keyIndex < 0) {
_keys.push(k);
_values.push(v);
} else {
_values[keyIndex] = v;
}
}
public function exists(k:K):Bool {
return index(k) >= 0;
}
public function remove(k:K):Bool {
var keyIndex = index(k);
if (keyIndex < 0) {
return false;
} else {
_keys.splice(keyIndex, 1);
_values.splice(keyIndex, 1);
return true;
}
}
public function keys():Iterator<K> {
return _keys.iterator();
}
public function iterator():Iterator<V> {
return _values
.iterator();
}
public function toString():String {
var s = new StringBuf();
s.add("{");
for( i in 0..._keys.length ) {
s.add('<function>');
s.add(" => ");
s.add(Std.string(_values[i]));
if( i < _keys.length - 1 )
s.add(", ");
}
s.add("}");
return s.toString();
}
private function index(key:K) : Int {
for (i in 0..._keys.length) {
if (Reflect.compareMethods(key, _keys[i])) {
return i;
}
}
return -1;
}}
http://try.haxe.org/#DdF31
I just tried this in try.haxe.org, and the compiler doesn't seem to like it, so I'm guessing the answer is "no."
You could get around this with some cleverness:
class Test {
static function main() {
var map:Map<VoidVoid,String>;
map = new Map<VoidVoid,String>();
var thing = {func:foo};
map.set(thing,"bar");
trace(map.get({func:foo})); //fails
trace(map.get(thing)); //succeeds;
}
static function foo():Void
{
}
}
typedef VoidVoid = {
var func:Void->Void;
}
But that's not an ideal solution because wrapping it in a typedef like that will make it fail if it's not the exact same instance, even if the value inside is the same.
I also tried making a Map<Dynamic,String> since you can stuff function references in those, but that didn't work either.
At this point I should ask, what problem are you trying to solve this way? Perhaps it could be better solved some other way.