Reputation: 26908
For example, if I have CSS that included within document:
div {
color: red;
}
div.test {
border: 1px solid blue;
}
and html tag within document body:
<div id='demo'>
<div class='test'>123</div>
<div>456</div>
</div>
I want to convert everything within #demo
as a string containing tag with all styles it was using, such as:
var parent = $('#demo');
var result = convertExternalInternalStylesToInline(parent);
// the desired result:
result = '<div style="color: red; border: 1px solid blue;">123</div>' +
'<div style="color: red">456</div>'
What I need is the content of convertExternalInternalStylesToInline
function, that automatically fetch all descendant elements, apply the calculated/used styles then add css styles to those elements, then return it all as html string.
Can it be done using only client side javascript
? If yes, how?
(I need to know how to get all calculated/used styles for a tag)
minimal example:
function convertExternalInternalStylesToInline(parent) {
var parent = $(parent).clone(); // clone it before modify, is this deep clone?
parent.find('*').each(function(idx,el){ // fetch all children
var el = $(el); // convert to jquery object
// get all applied styles on this element
// el.? // --> don't know how
// apply css for each styles
el.css( css_prop, css_style );
});
// return as string, maybe:
return parent.html();
}
Upvotes: 2
Views: 6782
Reputation: 454
assuming you have the HTML in an array (lines: string[]):
I'm not sure why I spent so much time on this
let styleLines = this.getLinesInsideStyleTag(lines)
let htmlLinesSeparatedByTag = this.getLinesNotInsideStyleTag(lines)
let mapOfCSSrules = this.getMapOfCSSRuleNamesAndPropertiesFromCSSLines(styleLines)
let linesWithInlineRulesApplied = this.applyInlineStylesToLines(htmlLinesSeparatedByTag, mapOfCSSrules)
let finalString = ""
for(let v = 0; v < linesWithInlineRulesApplied.length; v++ ) {
finalString = finalString + linesWithInlineRulesApplied[v]
}
console.log(finalString)
getLinesInsideStyleTag(lines: any[]) {
let styleLines: any[] = []
let foundStylesStartTag = false
let foundStylesEndTag = false
for(let i = 0; i < lines.length; i++ ) {
if(lines[i].indexOf("<style ") != -1) {
foundStylesStartTag=true
} else if(lines[i].indexOf("</style>") != -1) {
foundStylesEndTag = true
}
if(foundStylesStartTag == true && foundStylesEndTag == false) {
if(lines[i].indexOf("<style") == -1 && lines[i].indexOf("</style") == -1) {
styleLines.push(lines[i])
}
}
}
return styleLines
}
getLinesNotInsideStyleTag(lines: any[]) {
let foundStylesStartTag = false
let foundStylesEndTag = false
let linesToKeep: any[] = []
for(let i = 0; i < lines.length; i++ ) {
if(lines[i].indexOf("<style ") != -1) {
foundStylesStartTag=true
} else if(lines[i].indexOf("</style>") != -1) {
foundStylesEndTag = true
}
if(foundStylesStartTag == false && foundStylesEndTag == false) {
linesToKeep.push(lines[i])
} else if(foundStylesStartTag == true && foundStylesEndTag == true) {
if(lines[i].indexOf("<style") == -1 && lines[i].indexOf("</style") == -1) {
linesToKeep.push(lines[i])
}
}
}
let actualLinesToKeep: any[] = []
for(let i = 0; i < linesToKeep.length; i++ ){
let thisLineSplitOnOpeningTag = linesToKeep[i].split("<")
let pushFullLine = false
let modifiedLine = ""
for(let y = 0; y < thisLineSplitOnOpeningTag.length; y++) {
if(thisLineSplitOnOpeningTag[0] !== "") {
pushFullLine = true
} else {
if(thisLineSplitOnOpeningTag.length > 2) {
//then the line contains nested tags (oof)
if(thisLineSplitOnOpeningTag[y].length > 0) {
if( y != thisLineSplitOnOpeningTag.length - 1) {
modifiedLine = modifiedLine + "<" + thisLineSplitOnOpeningTag[y]+"%*#"
} else {
modifiedLine = modifiedLine + "<" + thisLineSplitOnOpeningTag[y]
}
}
} else {
pushFullLine = true
}
}
}
if(pushFullLine == true) {
// console.log("3pushing full line because it doesn't have nested tags: "+linesToKeep[i])
actualLinesToKeep.push(linesToKeep[i])
} else {
actualLinesToKeep.push(modifiedLine)
}
}
// console.log("actualLinesToKeep: ")
// console.log(actualLinesToKeep)
return actualLinesToKeep
}
//e.g. you pass it
// myRule {
// color: blue;
// text-align: left;
// }
// you get back: a dictionary / map where "myRule" is the key, and "color: blue;" and "text-align: left;" are the values for that key.
getMapOfCSSRuleNamesAndPropertiesFromCSSLines(styleLines: any[]) {
// console.log("styleLines: ")
// console.log(styleLines)
//rule, properties
let CSSrules: Map<string,any[]> = new Map();
let rulesSplitOnClosingBracket = styleLines.toString().split("}")
for(let i = 0; i < rulesSplitOnClosingBracket.length; i++) {
let indexOfOpeningBracket = rulesSplitOnClosingBracket[i].indexOf("{")
let ruleName = rulesSplitOnClosingBracket[i].substring(0,indexOfOpeningBracket).trim()
ruleName = this.replaceAll(ruleName,",","").toLowerCase()
if(ruleName[0] === ".") {
ruleName = ruleName.substring(1)
}
//replace dots with a space
ruleName = ruleName.replace(/\./g,' ')
let propertiesOfThisRule = rulesSplitOnClosingBracket[i].substring(indexOfOpeningBracket+1).split(",")
let propertiesToKeep: any[] = []
for(let j = 0; j < propertiesOfThisRule.length; j++) {
propertiesOfThisRule[j] = propertiesOfThisRule[j].trim()
if(propertiesOfThisRule[j] !== undefined && propertiesOfThisRule[j].length > 0) {
propertiesToKeep.push(propertiesOfThisRule[j])
}
}
if(ruleName !== undefined && ruleName.length > 0 && propertiesToKeep !== undefined && propertiesToKeep.length > 0) {
CSSrules.set(ruleName, propertiesToKeep)
}
}
return CSSrules
}
applyInlineStylesToLines(htmlLinesSeparatedByTag: any[], mapOfCSSrules: Map<string,any[]>) {
let linesWithInlineRulesApplied: any[] = []
let ruleNames = Array.from(mapOfCSSrules.keys())
for(let r = 0; r < htmlLinesSeparatedByTag.length; r++) {
let lineSplitOnContinuationCharacter = htmlLinesSeparatedByTag[r].split("%*#")
let partsOfLineThatContainClosingTags: any[] = []
let partsOfLineThatDoNotContainClosingTags: any[] = []
for(let d = 0; d < lineSplitOnContinuationCharacter.length; d++) {
if(lineSplitOnContinuationCharacter[d].indexOf("</") != -1) {
partsOfLineThatContainClosingTags.push({orderNumber: d, line: lineSplitOnContinuationCharacter[d]})
} else if(lineSplitOnContinuationCharacter[d].indexOf("</") == -1) {
partsOfLineThatDoNotContainClosingTags.push({orderNumber: d, line: lineSplitOnContinuationCharacter[d]})
}
}
let orderNumbers1: any[number] = partsOfLineThatDoNotContainClosingTags.map(val => val.orderNumber)
let orderNumbers2: any[number] = partsOfLineThatContainClosingTags.map(val => val.orderNumber)
let maxOrderNumberFor1 = Math.max.apply(Math,orderNumbers1)
let maxOrderNumberFor2 = Math.max.apply(Math,orderNumbers2)
let maxOrderNumber: number;
if(maxOrderNumberFor1 > maxOrderNumberFor2) {
maxOrderNumber = maxOrderNumberFor1
} else {
maxOrderNumber = maxOrderNumberFor2
}
let thisActualLineWithStylesApplied = ""
for(let u = 0; u < maxOrderNumber+1; u++) {
let partOfLineWithoutClosingTag = partsOfLineThatDoNotContainClosingTags.filter(val => val.orderNumber == u)[0]?.line
let partOfLineWithClosingTag = partsOfLineThatContainClosingTags.filter(val => val.orderNumber == u)[0]?.line
if ( partOfLineWithoutClosingTag !== undefined ) {
let idxOfFirstSpace = partOfLineWithoutClosingTag.indexOf(" ")
for(let s = 0; s < ruleNames.length; s++) {
let applyThisRuleToThisLine = true
let textToCheckFor: any[] = ruleNames[s].split(" ")
for(let t = 0; t < textToCheckFor.length; t++) {
if(partOfLineWithoutClosingTag.indexOf(textToCheckFor[t]) == -1) {
applyThisRuleToThisLine = false
}
}
if(applyThisRuleToThisLine) {
let lineAfterApplyingStyle = partOfLineWithoutClosingTag.substring(0, idxOfFirstSpace) +" style=\""
for(let u = 0; u < mapOfCSSrules.get(ruleNames[s]).length; u++) {
let thisPropertyToApply = mapOfCSSrules.get(ruleNames[s])[u]
lineAfterApplyingStyle=lineAfterApplyingStyle+thisPropertyToApply
}
lineAfterApplyingStyle = lineAfterApplyingStyle +"\""
partOfLineWithoutClosingTag = lineAfterApplyingStyle + partOfLineWithoutClosingTag
let lastIndexOfLessThan = partOfLineWithoutClosingTag.lastIndexOf("<")
let lastIndexOfGreaterThan = partOfLineWithoutClosingTag.lastIndexOf(">")
partOfLineWithoutClosingTag = partOfLineWithoutClosingTag.substring(0,lastIndexOfLessThan) + partOfLineWithoutClosingTag.substring(lastIndexOfGreaterThan)
}
}
thisActualLineWithStylesApplied = thisActualLineWithStylesApplied + partOfLineWithoutClosingTag
}
if(partOfLineWithClosingTag !== undefined) {
thisActualLineWithStylesApplied = thisActualLineWithStylesApplied + partOfLineWithClosingTag
}
}
linesWithInlineRulesApplied.push(thisActualLineWithStylesApplied)
}
return linesWithInlineRulesApplied
}
Upvotes: 2
Reputation: 2188
Edit: Thanks to Kumar I realized my suggested method is nonstandard, and
getComputedStyle( element );
should be used instead, which is a bit more difficult to use and filter but has the advantage of giving you only the final rules that actually apply to the element after the CSS was evaluated (which also includes default, not explicitly declared styles, making it a bit trickier to use in this case).
getMatchedCSSRules( element );
just use this vanilla javascript function. It does exactly what you want. I'd explain it if there were anything to explain that isn't implied by the name of the function.
Upvotes: 1
Reputation: 19629
EDIT: Thanks to Winchestro, I looked up the window.getMatchedCSSRules
function, which is actually only in Webkit, and there is a discussion that it should be deprecated.
What you should be using is actually the window.getComputedStyle()
Read the docs at MDN.
Another very useful resource you may look into is the CSSUtilities set of libraries.
What you need are two separate things, a library to parse out your CSS Object Modal (CSSOM), and apply the relevant CSS to your DOM elements.
I know of a good nodejs library which does this called Juice.
There are is a library I found which would probably work on the front-end, called inlineresources, which has a browserified build
On second thoughts, I think you may be able to use Juice along with browserify for this... But you'll have to evaluate that possibility manually...
Upvotes: 4