Base64 Encoding for CSV with R Markdown - r

Base64 encoding of images is a really cool little feature in R Studio Markdown that creates all in one HTML pages that are easy to distribute or share. No need to worry about having the image as a seperate file. The browser knows what to do with it.
I'd like to extend this functionality to encoding CSV files as well. Looking at how they are doing it now, it looks like they are passing the information to .Call and using C/C++ to encode the file information.
From (line 177, and 192) : https://github.com/rstudio/markdown/blob/master/R/renderMarkdown.R
.b64EncodeFile <- function(inFile)
{
fileSize <- file.info(inFile)$size
if (fileSize > 0){
paste( "data:", .mimeType(inFile),";base64,",
.Call(rmd_b64encode_data,readBin(inFile,'raw',n=fileSize)),
sep='')
} else {
warning(inFile,'is empty!')
inFile
}
}
.b64EncodeImages <- function(html)
{
reg <- "<\\s*[Ii][Mm][Gg]\\s+[Ss][Rr][Cc]\\s*=\\s*[\"']([^\"']+)[\"']"
m <- gregexpr(reg,html,perl=TRUE)
if (m[[1]][1] != -1)
{
.b64EncodeImgSrc <- function(imgSrc)
{
inFile <- sub(reg,"\\1",imgSrc)
if (length(inFile) && file.exists(inFile))
imgSrc <- sub(inFile,.b64EncodeFile(inFile),imgSrc,fixed=TRUE)
imgSrc
}
regmatches(html,m) <- list(unlist(lapply(regmatches(html,m)[[1]],.b64EncodeImgSrc)))
}
html
}
Now, how do I accomplish the same thing, with a CSV file? Importantly, how do I get the browser to understand it.

If I read your intentions right, if you make an A element with the encoded data in the HREF attribute, then clicking on the link will get the file. Just tested this with an encoded image block I had lying around:
Click Me
So as long as you set the mime type (text/something?) all you need do is construct that element and stick it in your HTML file. User clicks link, file download starts, from its embedded base64 encoding. Sorted. Complete example with a missing mime-type which just lets the browser read it:
<html>
<head>
</head>
<body>
<h1>Test</h1>
click me
</body>
</html>
Where that data string has come from:
> markdown:::.b64EncodeFile("test.csv")
[1] "data:;base64,aWQsbmFtZSxhZ2UKMSwiRnJlZCBGb28iLDk5CjIsIkpvZSBCbG9nZ3MiLDIyCg=="

Related

sourceURL for CSS

Is debugging sourceURL possible for CSS in Firefox?
The solutions listed in the following do not appear to work:
//# sourceURL equivalent for CSS
Is there something like source maps for CSS?
MDN in Style Editor states a possibility but it seems to be for Sass.
Update on reply
Using sourceMappingURL & sourceURL has some incomplete result:
Inspector still shows file as blob ❎
blob:moz-extension://*****-****-******/*****-****-******
Style Editor lists file name ✅
Style Editor fails to display file (since there is no real URL) ❎
Error while fetching an original source: NetworkError when attempting to fetch resource.
Source URL: moz-extension://*****-****-******/****.css
I didn't find a solution with sourceURL. I used an empty inline source mapping as an alternative. "Empty" meaning that it does not contain any source changes, it just points at the source file.
The source mapping with /*# sourceMappingURL={sourcemapurl} */ in Firefox will behave almost like /*# sourceURL={url}*/ in Chrome.
It guess it depends on your use case if that is an option.
In my case the code looks like this (uses magic-string for generating the source mapping):
function addStyle (filename, css) {
const sourceMap = new MagicString(css).generateMap({
source: filename
})
css += '\n/*# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + ' */'
css += '\n/*# sourceURL=' + sourceMap.sources[0] + ' */'
document.head.appendChild(document.createElement('style')).innerHTML = css
}
addStyle('https://cvzi.github.io/stackoverflow/65274147/mystyle.css', `body{
font-family:sans-serif;
color:black;
background:green;
}
body h1{
color:white;
size:15;
}
`)
<script src="https://unpkg.com/magic-string#0.25.7/dist/magic-string.umd.js"></script>
<h1>This is h1</h1>

Rending HTML pages using Plumber in R

I understand that plumber is more suitable to build an API than a full fledged website, this said I am trying to display the dynamic data from the db (mongo) in HTML. All works fine but the way I use (heavily inspired from a titanic example) might not be the best one. Here is an example of my homepage:
#' Return result
#' #get /
#' #serializer html
function(ht) {
title <- "Title"
body_intro <- "Welcome to R.gift!"
body_model <- paste("This is just a test ...page from the db with name <b>", single[1], "</b>")
body_msg <- paste("the home page title is <b>", single[2] , "</b>",
"The home page content:<b>",
single[3], "</b>",
sep = "\n")
css <- ' <link href="https://cloud.typography.com/7/css/fonts.css" rel="stylesheet">'
about <- 'about page'
contact <- 'contact page'
result <- paste(
"<head>",
css,
"</head>",
"<html>",
'<div style="font-family: Giant Background A, Giant Background B;font-style: normal;font-weight: 400;font-size:20pt;">',
"<h1>", title, "</h1>",
"<body>",
body_intro, "</div>",
'<div style="font-family: Gotham A, Gotham B;font-style: normal;font-weight: 400;font-size:16pt;">',
"<p>", body_model, "</p>",
"<p>", body_msg, "</p>",
"<p>", about, "</p>",
"<p>", contact, "</p>",
"</div>",
"</body>",
"</html>",
collapse = "\n"
)
return(result)
}
so my question is if there is a more elegant way to achieve the same perhaps with a semi-templating system. The solution might be obvious (I am very new to R so bear with me). I know that plumber can server static files with
#* #assets ./files/static
list()
but I assume this wouldn't allow me to pass variables into index.html for example ?
The ideal scenario is having just tags like in any templating system.
Thanks in advance!
You can try something like this:
basicHTML <- "The home page title is <b> %s </b> The home page content: <b> %s </b>" # create a template in a file or in the script.
single <- c(title = "Hello World", bodyMsg = "lorem ipsum") # set up your parameters.
finalHTML <- sprintf(basicHTML, single[1], single[2])
Or maybe you feel more comfortable with this approach: https://shiny.rstudio.com/articles/templates.html
I hope this helps you.
My advice would be to do away with the server side rendering and do everything on the client side. You could use a frontend library like vue (if you understand HTML and basic javascript, you can use vue) and then do a GET request to the backend for the data you need. Then use that data to customize the UI. This approach let's R focus on data and the frontend focus on the presentation.

How to do different things in R in bookdown if output is html or latex

I am using bookdown to generate html and pdf at the same time. I would like to use dynamic tables (DT) in the html version, but a normal table in the pdf file. Is there a way to do this in markdown without having to have to separate files as input and using the yml file.
Something like
if (output = html) {
DT...
}
if (output = "pdf"){
xlatex ...
}
THanks
Renger
Edited thanks to Yihui's comment:
if( knitr:::is_latex_output() ) {
xlatex ...
} else {
DT...
}

Styling Google Translate widget for mobile websites

My website - www.forex-central.net - has the Google Translate drop-down widget on the top right of every page.
Only problem is it's a bit too wide for my website (5 cm), I would need a 4 cm version (which I've seen on other sites so I know this is possible)...but I have no idea how to tweak the code.
The code Google supplies for the widget I use is:
<script type="text/javascript">function googleTranslateElementInit() { new google.translate.TranslateElement({ pageLanguage: 'en', gaTrack: true, layout: google.translate.TranslateElement.InlineLayout.SIMPLE }, 'google_translate_element');}</script><script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
Any help would be greatly appreciated! I'm a bit of a novice and have searched for hours on this, not getting anywhere :-/
Something like this will get you started:
.goog-te-menu-frame {
max-width:100% !important; //or whatever width you want
}
However, you would also need to do something like:
.goog-te-menu2 { //the element that contains the table of options
max-width: 100% !important;
overflow: scroll !important;
box-sizing:border-box !important; //fixes a padding issue
height:auto !important; //gets rid of vertical scroll caused by box-sizing
}
But that second part can't actually be done because the translate interface is included in your page as an iframe. Fortunately, it doesn't have its own domain, so we can access it via Javascript like this:
$('.goog-te-menu-frame').contents().find('.goog-te-menu2').css(
{
'max-width':'100%',
'overflow':'scroll',
'box-sizing':'border-box',
'height':'auto'
}
)
But that won't work until the element actually exists (it's being loaded asynchronously) so we have to wrap that in something that I got here. Put it all together, you get this:
function changeGoogleStyles() {
if($('.goog-te-menu-frame').contents().find('.goog-te-menu2').length) {
$('.goog-te-menu-frame').contents().find('.goog-te-menu2').css(
{
'max-width':'100%',
'overflow':'scroll',
'box-sizing':'border-box',
'height':'auto'
}
)
} else {
setTimeout(changeGoogleStyles, 50);
}
}
changeGoogleStyles();
Whew.
You can use that same strategy to apply other styles to the translate box or perhaps alter the table styles to have it flow vertically instead of scroll horizontally offscreen, whatever. See this answer.
EDIT:
Even this doesn't work, because Google re-applies the styles every time you click the dropdown. In this case, we try and change height and box-sizing, but Google reapplies over those, while overflow and max-width stick. What we need is to put our styles somewhere they won't get overriden and add !importants [cringes]. Inline styles will do the trick (I also replaced our selector with a variable for succinctness and what is likely a negligible performance boost):
function changeGoogleStyles() {
if(($goog = $('.goog-te-menu-frame').contents().find('body')).length) {
var stylesHtml = '<style>'+
'.goog-te-menu2 {'+
'max-width:100% !important;'+
'overflow:scroll !important;'+
'box-sizing:border-box !important;'+
'height:auto !important;'+
'}'+
'</style>';
$goog.prepend(stylesHtml);
} else {
setTimeout(changeGoogleStyles, 50);
}
}
changeGoogleStyles();
The Google Translate widget creates an iframe with content from another domain (several files from Google servers). We would have to manipulate the content inside the iframe, but this so-called cross-site scripting did not work for me. I found another solution. I downloaded two of the many files which the widget uses, so I could edit them.
Bear in mind that Google can change its API anytime. The hack will have to be adapted then.
Prerequisite:
I assume that the widget is working on your website. You just want to fit it on smaller screens. My initial code looks like:
<div id="google_translate_element"></div>
<script type="text/javascript">
function googleTranslateElementInit()
{
new google.translate.TranslateElement({pageLanguage:'de', layout: google.translate.TranslateElement.InlineLayout.SIMPLE}, 'google_translate_element');
}
</script>
<script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
If your initial code looks different, you might have to adapt your solution accordingly.
Special tools used:
Chrome DevTools (adapt for other browsers)
Procedure:
In Google Chrome, right-click on your page containing the Google Translate widget.
Click Inspect. A window or side pane will apper with lots of HTML info.
In the top line, select the Sources tab.
Browse the sources tree to
/top/translate.google.com/translate_a/element.js?cb=googleTranslateElementInit
Click the file in the tree. The file content will be shown.
Under the code window of element.js, there is a little button with two curly brackets { }. Click this. It will sort the code for better readability. We will need this readability in the next steps.
Right-click inside the element.js code > Save as…. Save the file inside the files hierarchy of your website, in my case:
/framework/google-translate-widget/element.js
Point your <script> tag to the local element.js.
<!--<script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>-->
<script type="text/javascript" src="../framework/google-translate-widget/element.js?cb=googleTranslateElementInit"></script>
From now on, your website should load element.js from its local directory. Now is a good moment to check if your Google Translate widget still works. Also check in Chrome DevTools where the browser has taken the file from (Google server or local directory). It should sit in the sources tree under
/top/[your domain or IP]/framework/google-translate-widget/element.js?cb=googleTranslateElementInit
We need another file from Google servers. Browse the sources tree to
/top/translate.googleapis.com/translate_static/css/translateelement.css
Download this file after clicking the curly brackets { }. I saved it in my website files directory as
/framework/google-translate-widget/translateelement.css
In your website files directory, open element.js and change line 66:
//c._ps = b + '/translate_static/css/translateelement.css';
c._ps = '/framework/google-translate-widget/translateelement.css';
From now on, your website will also load translateelement.css from its local directory. Check this now.
Open your local translateeleent.css and append the following styles at the end:
/* Make all languages visible on small screens. */
.goog-te-menu2 {
width: 300px!important;
height: 300px!important;
overflow: auto!important;
}
.goog-te-menu2 table,
.goog-te-menu2 table tbody,
.goog-te-menu2 table tbody tr {
width: 100%!important;
height: 100%!important;
}
.goog-te-menu2 table tbody tr td {
width: 100%!important;
display: block!important;
}
.goog-te-menu2 table tbody tr td .goog-te-menu2-colpad {
visibility: none!important;
}
I borrowed the code from another answer: Google translate widget mobile overflow
The geometry might work now, but we broke another thing. The widget text showing “Select Language”, “Sélectionner une langue”, or whatever it says in you language, is locked to that language now. Since you want your other-language readers to understand the offer, the widget should adapt to their language as it used to work before our hack. Also, the listed languages’ names are affected. The reason for this bug can be found in the file element.js, which was silently tailored to our browser’s language setting. Look in element.js on lines 51 and 69
c._cl = 'fr';
_loadJs(b + '/translate_static/js/element/main_fr.js');
In my case, it was set to French (fr).
Correcting line 51 is as simple as
c._cl = 'auto'; //'fr';
Line 61 is trickier, because there is no 'auto' value available. There is a file main.js (without the _fr ending) available on Google servers, which provides English as a fallback, but we prefer the user’s language. Have a look in the file
/top/translate.googleapis.com/translate_a/l?client=…
It contains two objects. sl and tl meaning the source languages and target languages supported for translation. We have to check if the user’s browser is set to one of the target languages. There is a JavaScript constant navigator.language for this.
Edit element.js at line 69:
// determine browser language to display Google Translate widget in that language
var nl = navigator.language;
var tl = ["af","sq","am","ar","hy","az","eu","bn","my","bs","bg","ceb","ny",
"zh-TW","zh-CN","da","de","en","eo","et","tl","fi","fr","fy","gl",
"ka","el","gu","ht","ha","haw","iw","hi","hmn","ig","id","ga","is",
"it","ja","jw","yi","kn","kk","ca","km","rw","ky","ko","co","hr",
"ku","lo","la","lv","lt","lb","mg","ml","ms","mt","mi","mr","mk",
"mn","ne","nl","no","or","ps","fa","pl","pt","pa","ro","ru","sm",
"gd","sv","sr","st","sn","sd","si","sk","sl","so","es","sw","su",
"tg","ta","tt","te","th","cs","tr","tk","ug","uk","hu","ur","uz",
"vi","cy","be","xh","yo","zu"];
var gl = "";
if( tl.includes( nl )) gl = '_'+nl;
else
{
nl = nl.substring(0, 3);
if( tl.includes( nl)) gl = '_'+nl;
else
{
nl = nl.substring(0, 2);
if( tl.includes( nl)) gl = '_'+nl;
else gl = '';
}
}
_loadJs(b + '/translate_static/js/element/main'+gl+'.js');
//_loadJs(b + '/translate_static/js/element/main_fr.js');
… should do the trick.
Try using this in your CSS
.pac-container, .pac-item { width: 100px !important;}
where you can alter the with of the dropdown by altering 'the 100px' value.
This should work. Let me know if it doesn't and I'll have another look.

CSS:after encoding characters in content

I am using the following CSS, which seems to be working:
a.up:after{content: " ↓";}
a.down:after{content: " ↑";}
The characters however cannot seem to be encoded like this, as the output is literal and shows the actual string:
a.up:after{content: " ↓";}
a.down:after{content: " ↑";}
If I can't encode it, it feels like I should use something such as .append() in jQuery, as that supports the encoding. Any ideas?
To use encoded Unicode characters in content you need to provide either the characters themselves (as you do), or their UTF-8 escape sequences instead of HTML entities:
a.up:after { content: " \2193"; }
a.down:after { content: " \2191"; }
Why do you want to encode those characters anyway? Remember, you're writing CSS, not HTML. Your code:
a.up:after{content: " ↓";}
a.down:after{content: " ↑";}
is perfectly valid, as long as you save the file with UTF-8 encoding and send the appropriate header:
Content-Type: text/css; charset=utf-8
Encoding characters is only used in HTML so that there is no ambiguity between content and tags. Thus, you would encode< as < so that the browser doesn't think it's the beginning of a tag. Stuff like ↓ are just commodities for people who don't know how to use utf-8 (or can't, for whatever reason) :).
Just want to add that if you want to set dynamically the value of content via the attr() function, the above won't work. See
document.getElementById('wontwork').setAttribute('data-sym', ' \2714 ');
document.getElementById('willwork').setAttribute('data-sym', ' \u2714 ');
button::before {
content: attr(data-sym);
}
* {
font-size: 30px
}
<button id='wontwork' data-sym='to-be-replaced'>not rendered</button>
<button id='willwork' data-sym='to-be-replaced'>rendered !</button>

Resources