Hashtag character makes file incomplete when downloading

I have a text file and I’m doing some changes in it before the user downloads. All changes are made with Javascript/Typescript and don’t generate any errors. The problem I’m facing is that, when the user download the file, it always comes incomplete after a specific and unrelated word. If I console.log before the actual download, I can see the file perfectly fine. The source of the problem seems to be an added reference to the file, because if I remove this ‘Add references’ part, the file is download as expected. Sadly I cannot remove this part.

This function was made for when the user is navigating through the browser:

myDownloadFunction(file: Features[]) {
    ...
    // Feature is OpenLayer's Feature
    // https://openlayers.org/en/latest/apidoc/module-ol_Feature-Feature.html
	// Declare variables and minor changes
	let final_output:string = kml_format.writeFeatures(file);
	...

	// Add references
	for (let feature of this.featuresToExport) {
		let idToExport = feature.id_;
		let featureColor:string = feature.values_.color;
		let featureHexColor = this.getColorByName(featureColor);

		let colorElement = '<Style id="app_style_'+idToExport+'"><IconStyle><Icon><href>https://earth.google.com/earth/rpc/cc/icon?color='+featureHexColor+'&amp;id=2000&amp;scale=4</href></Icon></IconStyle></Style>';

		// Add style element
		let indexOfDocument = final_output.indexOf("Document");
		let indexOfClosingDocument = final_output.indexOf(">", indexOfDocument) + 1;
		let output = [
			final_output.slice(0, indexOfClosingDocument), 
			colorElement, 
			final_output.slice(indexOfClosingDocument)
		].join('');

		// Add reference to style element
		let indexOfPlacemark = output.indexOf('Placemark id="' + idToExport + '"');
		let indexOfClosingPlacemark = output.indexOf(">", indexOfPlacemark) + 1;
		output = [
			output.slice(0, indexOfClosingPlacemark), 
			'<styleUrl>#app_style_'+idToExport+'</styleUrl>', 
			output.slice(indexOfClosingPlacemark)
		].join('');

		final_output = output;
	}	
	
	this.mainDoc = "data:text/json;charset=utf-8," + final_output;
	console.log(this.mainDoc); // <-- Here I can see the whole document perfectly fine	

	let link = document.createElement("a");
	link.download = this.file_name + this.file_extension;
	link.href = this.mainDoc;
	document.body.appendChild(link);
	link.click();
	document.body.removeChild(link);
	link = null;
}

All variables are correctly obtained and the file end in a word the middle of the text, without relation to any variable.

Originally the method I used for editing the file was jQuery.parseXML() and the same error happened, so I tried to change the method to this one I posted above.

I imagine that the problem may be some asynchronous step that is still in progress when the download event is triggered, but analyzing the code that was passed in I can’t see any asynchronous part.


I tried to use FileSaver.js as an alternative method to download the file, but the same error happened.


I tried to encapsulate this part in a Promise to be sure nothing was being left behind, but this didn’t solve the issue either.

myDownloadFunction(file: Features[]) {
    ...
    // Feature is OpenLayer's Feature
    // https://openlayers.org/en/latest/apidoc/module-ol_Feature-Feature.html
	// Declare variables and minor changes
	let final_output:string = kml_format.writeFeatures(file);
	...

    // Add references
    this.addReference(final_output).then(fo2 => {
    	this.mainDoc = "data:text/json;charset=utf-8," + fo2;
	
    	let link = document.createElement("a");
    	link.download = this.file_name + this.file_extension;
    	link.href = fo2;
    	document.body.appendChild(link);
    	link.click();
    	document.body.removeChild(link);
    	link = null;
    });
}

addReference(final_output): Promise<string> {
	return new Promise((resolve, reject) => {
		this.featuresToExport.forEach((feature, index, arr) => {
			let idToExport = feature.id_;
			let featureColor:string = feature.values_.color;
			let featureHexColor = this.getColorByName(featureColor);			

			console.table({"idToExport": idToExport, "featureColor": featureColor, "featureHexColor": featureHexColor});

			let colorElement = '<Style id="sfmapp_style_'+idToExport+'"><IconStyle><Icon><href>https://earth.google.com/earth/rpc/cc/icon?color='+featureHexColor+'&amp;id=2000&amp;scale=4</href></Icon><hotSpot x="64" y="128" xunits="pixels" yunits="insetPixels"/></IconStyle></Style>';

			// Add style element
			let indexOfDocument = final_output.indexOf("Document");
			let indexOfClosingDocument = final_output.indexOf(">", indexOfDocument) + 1;
			let output = [
				final_output.slice(0, indexOfClosingDocument), 
				colorElement, 
				final_output.slice(indexOfClosingDocument)
			].join('');

			// Add reference to style element
			let indexOfPlacemark = output.indexOf('Placemark id="' + idToExport + '"');
			let indexOfClosingPlacemark = output.indexOf(">", indexOfPlacemark) + 1;
			output = [
				output.slice(0, indexOfClosingPlacemark), 
				'<styleUrl>#sfmapp_style_'+idToExport+'</styleUrl>', 
				output.slice(indexOfClosingPlacemark)
			].join('');

			final_output = output;

			if (index === arr.length - 1){ 
				resolve(final_output); 
			}
		});
	});
}

I think it’s important to point out that this method is working on the device, but it involves more steps, which is consistent with this theory of the asynchronicity problem.

Here you can see an example of how the file is suppose to be, and here how is being downloaded.


I have tried some other things and I think I have narrowed down to the source of the problem. When I remove the hashtag character (#) from the reference text, everything works as expected. If I leave the hashtag it breaks. Someone has a clue why this is happening? I tried to escape as we usually do (#) but that didn’t work.

let referenceElement = '<styleUrl>#app_style_'+idToExport+'</styleUrl>'; // It will break
let referenceElement = '<styleUrl>app_style_'+idToExport+'</styleUrl>'; // Working fine

This is likely an irrelevant, silly observation even if correct, but the first thing that jumps out at me is that you seem to be declaring text/json, yet sending something that does not look very JSON-y at all, but rather XML-y.

1 Like

Thank you for point this out. Based on this answer I tried to change the content type to:

  • text/html;
  • application/xhtml+xml;
  • text/xml;
  • application/xml

Sadly none of them worked.

I think your main problem is encoding, which is why the # character is biting you (they have special meanings in URLs). Probably the safest and most scalable thing to do is to base64 the whole thing, but you might be able to get away with just percent-encoding problematic characters. See MDN for more.

1 Like

Solved the problem by using the ASCII encoding reference for the hashtag character:

let referenceElement = '<styleUrl>%23app_style_'+idToExport+'</styleUrl>';