2

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();
}
4
  • Do you want to make client side which means that you can consider jQuery, or you want to make it pure javascript? Commented Jan 15, 2015 at 6:20
  • yes, jquery is ok, since it is also can be run on client-side (web browser). Commented Jan 15, 2015 at 6:22
  • I have edited my question, hope its more clear now, what I want is automated one, not setting the inline styles one by one programatically. Commented Jan 15, 2015 at 6:40
  • 1
    from the history, viewing the first version also, it's pretty clear that he wanted to inline a pre-existing css. Commented Jan 15, 2015 at 8:44

3 Answers 3

4

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...

Sign up to request clarification or add additional context in comments.

2 Comments

I have test their examples, inlineresources doesn't work as expected __
fair enough. I'm usually using getComputedStyle myself (to get around webkits stricter security regulations concerning external not CORS-enabled stylesheets), but I thought getMatchedCSSRules might be easier to use for this case. :) Good catch!
2

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
    }





Comments

1

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.

2 Comments

ah, thank you, thanks to that function, I got this: stackoverflow.com/questions/2952667
careful: this is used only in webkit, and could be deprecated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.