I'm trying to make a web page "static", with all styles as inline.
For this I first save all computed styles, then remove all applied style (class and css files), check the difference with saved styles and add inline for missing styles.
This is my function:
var allstyles=null;
var allelms=null;
function FixHTML()
{
if (!allstyles)
{
console.log("remove scripts");
var elms=document.getElementsByTagName('SCRIPT');
for(var i=elms.length-1;i>-1;i--)
{
var elm=elms[i];
if (elm.tagName=="SCRIPT" && !elm.innerHTML.match(/FixHTML/))
{
elm.parentElement.removeChild(elm);
}
}
//sauvegarde des styles
console.log("save styles");
allstyles=[];
allelms=document.getElementsByTagName('*');
for(i=0;i<allelms.length;i++)
{
elm=allelms[i];
if (!elm.id)
elm.id="tmpid"+i;
var id=elm.id;
allstyles[id]=[];
var style=getStyleObject(elm);
for(var key in style)
{
allstyles[id][key]=style[key];
}
if (elm.className)
elm.className="";
}
console.log("delete stylesheet links");
elms=document.getElementsByTagName('LINK');
for(i=elms.length-1;i>-1;i--)
{
elm=elms[i];
console.log(elm.href);
if (elm.rel="stylesheet")
elm.href="nowhere";
}
}
setTimeout(RestoreClassStyles,2000);
}
function RestoreClassStyles()
{
console.log("restore class styles",allstyles);
allelms=document.getElementsByTagName('*');
for(var i=0;i<allelms.length;i++)
{
var elm=allelms[i];
var id=elm.id;
var style=getStyleObject(elm);
for(var key in allstyles[id])
{
if (allstyles[id][key]!=style[key])
{
console.log(key);
elm.style[key]=allstyles[id][key];
}
}
}
}
function getStyleObject(dom){
var style;
var returns = {};
if(window.getComputedStyle){
var camelize = function(a,b){
return b.toUpperCase();
};
style = window.getComputedStyle(dom, null);
for(var i = 0, l = style.length; i < l; i++){
var prop = style[i];
var camel = prop.replace(/\-([a-z])/g, camelize);
var val = style.getPropertyValue(prop);
returns[camel] = val;
};
return returns;
};
console.log("not found",elm);
return "";
}
It "works a bit" but some styles are incorrect so I need help to find what I'm missing in that code.
Working example: http://jsfiddle.net/owecfdr2/
Instead of using inline styles, I would convert the link elements to style elements. You can do that like this (I've stuck to ES5 because it looked like you were doing so in your code):
function linkToStyle(link) {
var css = [];
var sheet = link.sheet;
var rules = sheet.cssRules || sheet.rules;
for (var i = 0; i < rules.length; ++i) {
var rule = rules[i];
css.push(rule.cssText);
}
var style = document.createElement("style");
style.type = "text/css";
style.appendChild(
document.createTextNode(css.join("\r\n"))
);
return style;
}
document.querySelectorAll("link[rel=stylesheet]").forEach(function(link) {
var style = linkToStyle(link);
var parent = link.parentNode;
parent.insertBefore(style, link);
parent.removeChild(link);
});
That finds all the link elements with rel="stylesheet" and replaces them with style elements with the same CSS text, by getting the CSS stylesheet object from the link element, looping through its CSS rules, and using the cssText of each rule.
As misorude points out in a comment, any URLs in the linked CSS stylesheets are relative to the stylesheet, not the page linking to the style sheet, so if your stylesheets and page are not in the same place from a URL perspective (e.g., same directory), it gets a bit more complicated: you'll have to adjust the URLs to account for that. A simple version would just handle rules of type CSSRule.STYLE_RULE (which are CSSStyleRules), looping through the properties to find and adjust URLs. (If there are imports, it gets more complicated.)
That code relies on forEach on the NodeList returned by querySelectorAll. Most browsers have that now; this answer shows how to detect if the browser doesn't and polyfill it.
Related
I'm using http://caja.appspot.com/html-css-sanitizer-minified.js to sanitize user html, however in some instances I want to restrict the tags used to just a white list.
I've found https://code.google.com/p/google-caja/wiki/CajaWhitelists which describes how to define a white list, but I can't work out how to pass it to the html_sanitize method provided by html-css-sanitizer-minified.js
I've tried calling html.sanitizeWithPolicy(the_html, white_list); but I get an error:
TypeError: a is not a function
Which is hard to debug due to the minification, but it seems likely that html-css-sanitizer-minified.js does not contain everything in the html-sanitizer.js file.
I've tried using html-sanitizer.js combined with cssparser.js instead of the minified version, but I get errors before calling it, presumably because I am missing other dependencies.
How can I make this work?
Edit: sanitizeWithPolicy does exist in the minified file, but something is missing further down the process. This suggests that this file can't be used with a custom white list. I'm now investigating if it is possible to work out which uniminified files I need to include to make my own version.
Edit2: I was missing two files https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/plugin/html4-defs.js?spec=svn1950&r=1950 and https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/plugin/uri.js?r=5170
However I am now getting an error because sanitizeWithPolicy expects a function not a whitelist object. Also the html4-defs.js file is very old and according to this I would have to build the caja project in order get a more recent one.
I solved this by downloading the unminified files
https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/plugin/html-sanitizer.js
https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/plugin/uri.js
https://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/plugin/html4-defs.js?spec=svn1950&r=1950
(This last one is from an old revision. This file is built from the Java files, would be great if a more up to date one was available.)
I then added a new function to html-sanitizer.js
/**
* Trims down the element white list to just those passed in whilst still not allowing unsafe elements.
* #param {array} custom_elements An array of elements to include.
*/
function useCustomElements(custom_elements) {
var length = custom_elements.length;
var new_elements = {};
for (var i = 0; i < length; i++) {
var key = custom_elements[i].toLowerCase();
if (typeof elements.ELEMENTS[key] !== 'undefined') {
new_elements[key] = elements.ELEMENTS[key];
}
}
elements.ELEMENTS = new_elements;
};
I then made this function public with this near the end of the file withthe other public function statements.
html.useCustomElements = html['useCustomElements'] = useCustomElements;
Now I can call it like so:
var raw = '<p>This element is kept</p><div>this element is not</div>';
var white_list ['p', 'b'];
html.useCustomElements(white_list)
var sanitized = html.sanitize(raw);
I then manually added some html5 elements to the html4-defs.js file (The ones that just define block elements like and ).
The attributes sanitization was still broken. This is due to the html4-defs.js file being out of date with the html-sanitizer.js. I changed this in html-sanitizer.js :
if ((attribKey = tagName + '::' + attribName,
elements.ATTRIBS.hasOwnProperty(attribKey)) ||
(attribKey = '*::' + attribName,
elements.ATTRIBS.hasOwnProperty(attribKey))) {
atype = elements.ATTRIBS[attribKey];
}
to
if (elements.ATTRIBS.hasOwnProperty(attribName)) {
atype = elements.ATTRIBS[attribName];
}
This is far from ideal but without compiling Caja and generating an up to date html-defs.js file I can't see a way around this.
This still leaves css sanitization. I would like this as well, but I am missing the css def files and can't find any that work via search so I have turned it off for now.
EDIT: I've managed to extract the html-defs from html-css-sanitizer-minified.js.
I've uploaded a copy to here. It includes elements like 'nav' so it has been updated for html5.
I've tried to do the same for the css parsing, I managed to extract the defs, but they depend on a bit count, and I can't find anyway to calculate what bits were used for which defaults.
I've decided on another approach. I've left the other answer in case I manage to find the bit values for the css definitions as it would be preferable to this one if I could get it to work.
This time I've taken the html-css-sanitizer-minified file and injected a bit of code into it so that the element and attributes can be modified.
Search for :
ka=/^(?:https?|mailto)$/i,m={};
And after it insert the following:
var unmodified_elements = {};
for(var property_name in $.ELEMENTS) {
unmodified_elements[property_name] = $.ELEMENTS[property_name];
};
var unmodified_attributes = {};
for(var property_name in $.ATTRIBS) {
unmodified_attributes[property_name] = $.ATTRIBS[property_name];
};
var resetElements = function () {
$.ELEMENTS = {};
for(var property_name in unmodified_elements) {
$.ELEMENTS[property_name] = unmodified_elements[property_name];
}
$.f = $.ELEMENTS;
};
var resetAttributes = function () {
$.ATTRIBS = {};
for(var property_name in unmodified_attributes) {
$.ATTRIBS[property_name] = unmodified_attributes[property_name];
}
$.m = $.ATTRIBS;
};
var resetWhiteLists = function () {
resetElements();
resetAttributes();
};
/**
* Trims down the element white list to just those passed in whilst still not allowing unsafe elements.
* #param {array} custom_elements An array of elements to include.
*/
var applyElementsWhiteList = function(custom_elements) {
resetElements();
var length = custom_elements.length;
var new_elements = {};
for (var i = 0; i < length; i++) {
var key = custom_elements[i].toLowerCase();
if (typeof $.ELEMENTS[key] !== 'undefined') {
new_elements[key] = $.ELEMENTS[key];
}
}
$.f = new_elements;
$.ELEMENTS = new_elements;
};
/**
* Trims down the attribute white list to just those passed in whilst still not allowing unsafe elements.
* #param {array} custom_attributes An array of attributes to include.
*/
var applyAttributesWhiteList = function(custom_attributes) {
resetAttributes();
var length = custom_attributes.length;
var new_attributes = {};
for (var i = 0; i < length; i++) {
var key = custom_attributes[i].toLowerCase();
if (typeof $.ATTRIBS[key] !== 'undefined') {
new_attributes[key] = $.ATTRIBS[key];
}
}
$.m = new_attributes;
$.ATTRIBS = new_attributes;
};
m.applyElementsWhiteList = applyElementsWhiteList;
m.applyAttributesWhiteList = applyAttributesWhiteList;
m.resetWhiteLists = resetWhiteLists;
You can now apply a white list with :
var raw = "<a>element tags removed</a><p class='class-removed' style='color:black'>the p tag is kept</p>";
var tag_white_list = [
'p'
];
var attribute_white_list = [
'*::style'
];
html.applyElementsWhiteList(tag_white_list);
html.applyAttributesWhiteList(attribute_white_list);
var san = html.sanitize(raw);
This approach also sanatizes the styles, which I needed. Another white list could be injected for those, but I don't need that so I havn't written one.
I'd like to make my textarea font family, font size, color different based on query string with javascript. I am not a javascript expert. I hope anyone able to help me to.
EXAMPLE URL: http://www.domain.com/?color=#000&size=32px&fontfamily=serif
So it must generate css like:
textarea {font-family:serif;font-size:32px;color:#000}
Is that seems imposibble done using javascript or jquery?
Although this is a seemingly simple question - accessing query string values in javascript is not directly straightforward. See this question for many answers of how to access them: How can I get query string values in JavaScript?
For the next part, I'm going to work based on the assumption that you're using PHP and have the following added somewhere to give easy access to the query strings. You can change this according to whatever method you prefer to gain access to query paramater values.
<script>window.$_GET = <?php echo json_encode($_GET); ?>;</script>
Next part of this question is how to create the css rules from those query paramaters. There's another Stack Overflow question answering this as well: How to dynamically create CSS class in JavaScript and apply?
Using the method from the accepted answer there here's your code - untested so there may be some minor typos.
var css = 'textarea {';
if ('font' in $_GET) css += 'font-family:'+$_GET.font+';';
if ('size' in $_GET) css += 'font-size:'+$_GET.size+';';
if ('color' in $_GET) css += 'color:'+$_GET.color+';';
css += '}';
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
document.getElementsByTagName('head')[0].appendChild(style);
Also, note your URL is malformed. It should be
http://www.domain.com/?color=#000&size=32px&fontfamily=serif
not
http://www.domain.com/?color=#000?size=32px?fontfamily=serif
As you're not using valid CSS properties in your querystring, and it's not even a valid querystring, you have to first change the querystring as key/value pairs are seperated with & not ?, and then use a map to get proper CSS properties.
Once that is done, it's just a matter of seperating the key/value pairs, and iterating over elements to apply the styles
var cssMap = {
color: 'color',
size : 'fontSize',
fontfamily : 'fontFamily'
}
function applyCSS(selector, qs) {
var props = qs.split('&'),
elems = document.querySelectorAll(selector),
styles = {};
for (var i=props.length; i--;) {
var parts = props[i].split('=');
styles[cssMap[parts[0]]] = parts[1];
}
for (var j=elems.length; j--;) {
for (var style in styles) {
elems[j].style[style] = styles[style];
}
}
return styles;
}
applyCSS('textarea', window.location.search);
FIDDLE
Hope this helps.
function getQueryString() {
var query_string = {};
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (typeof query_string[pair[0]] === "undefined") {
query_string[pair[0]] = pair[1];
} else if (typeof query_string[pair[0]] === "string") {
var arr = [ query_string[pair[0]], pair[1] ];
query_string[pair[0]] = arr;
} else {
query_string[pair[0]].push(pair[1]);
}
}
return query_string;
}
var queryString = getQueryString();
var sColor = queryString.color;
var sSize = queryString.size;
var sFont = queryString.fontfamily;
var textAreaList = document.getElementsByTagName("textarea");
for(var i=0; i < textAreaList.length; i++){
var el = textAreaList[i];
el.style.color = sColor;
el.style.fontSize = sSize;
el.style.fontFamily = sFont;
}
Hi i have build my own text-wrap class. and i've run into a small problem it is kind off slow, because my script that checks the size of the font puts the string into a div with the classes but thats on a big scale intens for the DOM. so is there another way?
as you can see i tryed to build a cache controller but that makes it slower
var textMetrics = function (appendTo) {
var span;
var cache = [];
this.init = function () {
span = document.createElement("span");
appendTo.appendChild(span);
span.style.position = 'absolute';
span.style.left = -9999 + 'px';
};
this.checkCache = function (word, style) {
for (var i = 0; i < cache.length; i++) {
if (cache[i].word == word) {
return cache[i].value;
}
}
return false;
};
this.addCache = function (word, style, value) {
cache.push({
"word": word,
"style": style,
"value": value
});
};
this.getSize = function (word, style) {
word = word.replaceAll(" ", " ");
//var inCache = this.checkCache(word, style);
var inCache = false;
if (inCache === false) {
span.innerHTML = word;
for (var i in style) {
span.style[i] = style[i];
}
var coords = {
"width": span.offsetWidth,
"height": span.offsetHeight
};
for (var i in style) {
span.style[i] = "";
}
span.innerHTML = "";
this.addCache(word, style, coords);
return coords;
}
else {
return inCache;
}
};
this.init();
};
You could consider making your cache a dictionary (JS object) instead of a list:
var cache = {};
this.addCache = function (word, style, value) {
cache[word] = value;
};
this.checkCache = function (word, style) {
var value = cache[word];
if (typeof value != "undefined")
return value;
return false;
};
I didn't really get what your style variable is about — maybe you should add it to the cache key as well.
Since you are basically using a dictionary the best format for your cache is a simple javascript object that behaves as a hashmap.
var cache = {};
You can then assign words to it as follows:
this.addCache = function (word, style, value) {
cache[word] = {style: style, value: value};
};
And check them as follows:
this.checkCache = function (word) {
return cache[word];
};
So then you can:
var cachedItem = this.checkCache(word);
if (cachedItem ) {
alert(cachedItem.value);
}
This should speed up your cache searches considerably as you dont have to loop through an array that keeps getting larger and larger.
You could try to approximate the text width using the widths of individual characters. This will introduce problems when there's special kerning for combinations like "ff", of course.
I wrote a function that caches the widths of pairs of characters to accommodate for that. Thus, only a constant number of DOM manipulations is needed. It's on http://jsfiddle.net/wbL9Q/6/ and https://gist.github.com/1562233 (too much code for here).
However, while this worked for me in Safari (Mac), it did not give the correct results in Firefox. Apparently, some even more complex kerning is applied there. Maybe extending the algorithm to triples of characters (or even more) could help.
(Sorry for posting my 2nd answer, but I thought it makes sense because it's a completely different aspect.)
I guess this will be voted down, as it doesn't contain enough jQuery, but here it goes :)
What is the most effective way to get the element(s) returned by the jQuery selector below using plain old javascript?
$('a[title="some title text here"]', top.document)
If you're using a modern browser, you could use this:
window.top.document.querySelectorAll('a[title="some title text here"]')
Not sure if it’s the most effective, but at least it works.
var links = top.document.getElementsByTagName('a');
var result = [];
var linkcount = links.length;
for ( var i = 0; i < linkcount; i++) {
if (links[i].getAttribute('title') === 'some title text here') {
result.push(links[i]);
}
}
Here is an example
var getElements = function(tagName, attribute, value, callback) {
var tags = window.document.getElementsByTagName(tagName);
for (var i=0; i < tags.length; i++) {
var tag = tags[i];
if (tag.getAttribute(attribute) == value) {
callback(tag);
}
};
};
getElements("a", "title", "PHP power player at Hettema & Bergsten. Click to learn more.", function(tag) {
console.log(tag);
});
I'm trying to find a good way to collect the names of classes defined in the stylesheets included with a given document. I know about document.StyleSheetList but it doesn't seem like it'd be easy to parse. What I'm looking for is something like, for a stylesheet document such as:
.my_class {
background: #fff000;
}
.second_class {
color: #000000;
}
I could extract an array like ["my_class", "second_class"]. This obviously assumes the favorable scenario of a fully loaded dom and stylesheets.
I've been looking everywhere for a good way to do something like this and so far, have made little progress. Does anyone have any idea about how to pull this off? Thanks!
This will show all rules defined in the stylesheets.
var allRules = [];
var sSheetList = document.styleSheets;
for (var sSheet = 0; sSheet < sSheetList.length; sSheet++)
{
var ruleList = document.styleSheets[sSheet].cssRules;
for (var rule = 0; rule < ruleList.length; rule ++)
{
allRules.push( ruleList[rule].selectorText );
}
}
The thing, though, is that it includes all rules regardless of being class or tag or id or whatever..
You will need to explain in more detail what you want to happen for non class rules (or combined rules)
You were on track with document.styleSheets (https://developer.mozilla.org/en/DOM/document.styleSheets)
https://developer.mozilla.org/en/DOM/stylesheet.cssRules
Here's a quick and dirty method to output all class selectorTexts to the console in Firefox + Firebug.
var currentSheet = null;
var i = 0;
var j = 0;
var ruleKey = null;
//loop through styleSheet(s)
for(i = 0; i<document.styleSheets.length; i++){
currentSheet = document.styleSheets[i];
///loop through css Rules
for(j = 0; j< currentSheet.cssRules.length; j++){
//log selectorText to the console (what you're looking for)
console.log(currentSheet.cssRules[j].selectorText);
//uncomment to output all of the cssRule contents
/*for(var ruleKey in currentSheet.cssRules[j] ){
console.log(ruleKey +': ' + currentSheet.cssRules[j][ruleKey ]);
}*/
}
}
This is probably not something you really want to be doing except as part of a refactoring process, but here is a function that should do what you want:
function getClasses() {
var classes = {};
// Extract the stylesheets
return Array.prototype.concat.apply([], Array.prototype.slice.call(document.styleSheets)
.map(function (sheet) {
if(null == sheet || null == sheet.cssRules) return;
// Extract the rules
return Array.prototype.concat.apply([], Array.prototype.slice.call(sheet.cssRules)
.map(function(rule) {
// Grab a list of classNames from each selector
return rule.selectorText.match(/\.[\w\-]+/g) || [];
})
);
})
).filter(function(name) {
// Reduce the list of classNames to a unique list
return !classes[name] && (classes[name] = true);
});
}
What about
.something .other_something?
Do you want a pool of classNames that exist? Or a pool of selectors?
Anyway, have you tried iterating through document.styleSheets[i].cssRules? It gives you the selector text. Parsing that with some regexp kungfu should be easier...
Do you need it to be crossbrowser?
You can accompish this with jQuery. Example would be
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script>
$(document).ready(function(){
var allobjects = $("*")
});
</script>
Check out the jQuery website: http://api.jquery.com/all-selector/