6
\$\begingroup\$

The goal is to take CSS rules from a regular CSS file and apply them to their respective targets in an HTML file, but by placing the rules in the style="" attribute (using the <style> tag would be too simple) and removing the class="" and id="" attributes.

Rules:

  • Only ids #id and classes .class selectors must be supported, without more complex rules (parent selector #a #b or multiple classes .a.b, etc.)
  • Reducing rules is not required, for example the rules #a{color:red} .b{color:blue} applied to <div id="a" class="b"></div> can output <div style="color:blue;color:red;"></div> and the browser will only use the last rule (hereby red)
  • The paths to input files can be transmitted by command line arguments or hard-coded in the script/program
  • You can't call an external library, you can only include classes or functions built in the language (or from another language if needed)
  • Standard “loopholes” are forbidden
  • You program must produce the expected result but also works with other input files (to avoid hard-coded solution)
  • The output does not need to be saved to any file
  • Shortest code wins

Inputs:

CSS file

#h1 {color:Red;}
#bigger {font-size:1.1em;}
.code {font-family:Monospace;}
.golf {background-color:LightGreen;}

Only ids and classes are used in order to keep it simple.

HTML file

<!DOCTYPE html>
<html>
<head><title>Hello World!</title></head>
<body>
<h1 id="h1">Hello World!</h1>
<ol>
    <li class="code">Code</li>
    <li id="bigger" class="golf">Golf</li>
    <li class="code golf">Code golf</li>
</ol>
</body>
</html>

Expected output:

<!DOCTYPE html>
<html>
<head><title>Hello World!</title></head>
<body>
<h1 style="color:Red">Hello World!</h1>
<ol>
    <li style="font-family:Monospace;">Code</li>
    <li style="font-size:1.1em;background-color:LightGreen;">Golf</li>
    <li style="font-family:Monospace;background-color:LightGreen;">Code golf</li>
</ol>
</body>
</html>

Example on jsfiddle.

\$\endgroup\$
2
+100
\$\begingroup\$

bash + sed 133 185 179

sed -re 's/ass="([^"]*) ([^"]*)"/ass="\1" class="\2"/g;'"$(
    sed -re 's/^#/id§/;s/^\./class§/;
        s/(.*)§(\S*) \{(.*)\}/s@\1="\2"@style="\3"@g/' $CSS
      )"';s/yle="(.*)" style="/yle="\1/g' $HTML

sed -re 's/class="([^"]*) ([^"]*)"/class="\1" class="\2"/g;
    '"$(sed -re 's/^#/id /;s/^\./class /;
        s/(\S*) (\S*) \{(.*)\}/s@\1="\2"@style="\3"@g/' $CSS
    )"';s/style="(.*)" style="/style="\1/g' $HTML

In this (specific) case, this will render:

<!DOCTYPE html>
<html>
<head><title>Hello World!</title></head>
<body>
<h1 style="color:Red;">Hello World!</h1>
<ol>
    <li style="font-family:Monospace;">Code</li>
    <li style="font-size:1.1em;background-color:LightGreen;">Golf</li>
    <li style="font-family:Monospace;background-color:LightGreen;">Code golf</li>
</ol>
</body>
</html>
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ The classes are ignored? \$\endgroup\$ – A.L May 21 '14 at 11:37
  • \$\begingroup\$ Oops, multi-class support add 52 bytes... \$\endgroup\$ – F. Hauri May 21 '14 at 11:54
3
\$\begingroup\$

JavaScript - 589 (573)

It's 573 when processed by a minifier.

Not smaller then the Python version, but processes CSS using DOM objects instead of text strings. This should be able to process more complex CSS selectors also. But then the id and class attributes are not handled at the moment.

x=document
x.write('<link rel="stylesheet" href="'+c+'"/>')
window.onload=function(){
var s,p,n,t,i,j,r=x.styleSheets[0].cssRules,d=x.getElementById('i').contentWindow.document
for(i=0;i<r.length;i++){s=r[i].selectorText
p=r[i].cssText.match(/\{\s*(.*?)\s*\}/)
n=d.querySelectorAll(s)
for(j=0;j<n.length;j++){t=n[j].getAttribute('style')==null?'':n[j].getAttribute('style')
n[j].setAttribute('style',(t+p[1]).replace(': ',':'))
n[j].removeAttribute(s.charAt(0)=='#'?'id':'class')
}}alert(new XMLSerializer().serializeToString(d))}
x.write('<iframe src="'+h+'" id="i"></iframe>')

Runs in Firefox if you put the script, the files to process and this web page in the same folder:

<html><body>
<script type="text/javascript">
var c='css2inline.css',
    h='css2inline-input.html'
</script>
<script type="text/javascript" src="a.js"></script>
</body></html>

Replace c and h with CSS and HTML files, open this page and an alert shows the HTML.

Note The resulting HTML is a serialization of the DOM tree. That causes the result to be not exactly formatted as requested, but it represents the same HTML. Your call to say if you find it correct or not.

Expanded:

// Add CSS to current document.
x = document
x.write('<link rel="stylesheet" href="'+c+'"/>')

window.onload = function(){
    var s,p,n,t,i,j,
        r = x.styleSheets[0].cssRules,

        // Get the HTML to process.
        d = x.getElementById('i').contentWindow.document

    // Loop through CSS rules.    
    for(i = 0; i < r.length; i++){

        // Get CSS selector from rule.
        s = r[i].selectorText

        // Get text with CSS properties.
        p = r[i].cssText.match(/\{\s*(.*?)\s*\}/)

        // Get DOM nodes matching the CSS selector.
        n = d.querySelectorAll(s)

        // Add CSS properties to style of selected nodes.
        for(j = 0; j < n.length; j++){

            // Preserve current value.
            t = n[j].getAttribute('style') == null ? '' : n[j].getAttribute('style')

            // Add rule properties.         
            n[j].setAttribute('style',(t + p[1]).replace(': ',':'))

            // Remove id/class attribute from element.
            n[j].removeAttribute(s.charAt(0) == '#' ? 'id' : 'class')
        }
    }
    // Output serialized document.
    alert(new XMLSerializer().serializeToString(d))
}

// Create document with input HTML.
x.write('<iframe src="'+h+'" id="i"></iframe>')
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I started thinking about a jQuery solution yesterday but I didn't find an easy way to modify the HTML page inplace. I like the idea of the </iframe>. I don't see any problem with the serialization if it's only differences in tabulation. \$\endgroup\$ – A.L May 15 '14 at 13:56
  • \$\begingroup\$ With jQuery it might be possible to reduce code size. But that can't work without the jQuery library. And I liked investigating the DOM manipulation for CSS. \$\endgroup\$ – Kwebble May 15 '14 at 14:00
  • 1
    \$\begingroup\$ Oops, in fact the rules don't allow jQuery because it's not a part of the Javascript language. I started to think about a pure JS solution. \$\endgroup\$ – A.L May 15 '14 at 14:02
  • \$\begingroup\$ Buggy. If a node has an id and a class, it should get the rules of both, where they don't conflict. Where they do conflict, #id should have priority over .class regardless of the order of the rules in the CSS. \$\endgroup\$ – Peter Taylor May 15 '14 at 14:22
  • \$\begingroup\$ @Peter_Taylor: true, the script doesn't do that. But that's not how I read the requirements. And from my limited Python knowledge I don't see it handled in the Python version either. But I may very well be wrong about that. \$\endgroup\$ – Kwebble May 15 '14 at 20:42
3
\$\begingroup\$

Python - 508, 501, 483

The idea is to read all the rules then apply them by using xpath functions, too bad it can't select nodes with multiple classes (I didn't find own to target class="code golf").

Golfed

import sys,xml.etree.ElementTree as E
r=E.parse(sys.argv[2])
def a(e,f):e.set('style',(e.attrib.get('style')or'')+f)
for l in open(sys.argv[1]):
 s=l.replace("\n",'').split(' ');b=s[0];c=s[1][1:-1]
 if(b[:1])=='#':
  for e in r.findall('.//*[@id=\''+b[1:]+'\']'):a(e,c);e.attrib.pop('id')
 else:
  for e in r.findall('.//*[@class]'):
   d=e.attrib.get('class').split(' ')
   if b[1:]in d:
    a(e,c);e.set('class',' '.join(d[1:]))
    if len(d) == 1: e.attrib.pop('class')
E.dump(r)

Usage

python script.py input.css input.html

Ungolfed code

import sys, xml.etree.ElementTree as ET
root = ET.parse(sys.argv[2])

# append rules to style="" attribute
def a(e,f):
    e.set('style',(e.attrib.get('style')or'') + f)

# CSS file
for l in open(sys.argv[1]):
    split = l.replace("\n", '').split(' ')
    selector = split[0]
    rules = split[1][1:-1]

    # #id
    if (b[0:1]) == '#':
        for e in root.findall('.//*[@id=\''+selector[1:]+'\']'):
            a(e,rules)
            # remove id
            e.attrib.pop('id')
    # .class
    else:
        for e in root.findall('.//*[@class]'):
            d=e.attrib.get('class').split(' ')
            if selector[1:] in d:
                a(e,rules)
                e.set('class', ' '.join(d[1:]))
                # remove class
                if len(d) == 1:
                    e.attrib.pop('class')

ET.dump(root)
| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ You can get it down to 475 by indenting with only one space and also in your first for loop use ; to remove line breaks and also the indenting, you also had a space in a(e,c);e.set('class',' '.join(d[1:])). \$\endgroup\$ – Harry Beadle May 15 '14 at 10:23
  • 1
    \$\begingroup\$ You can also replace b[0:1] with b[:1] \$\endgroup\$ – Harry Beadle May 15 '14 at 10:32
  • \$\begingroup\$ @BritishColour : thanks, I will update my answer in the next days. \$\endgroup\$ – A.L May 17 '14 at 1:23
  • \$\begingroup\$ @BritishColour : I replaced the tabs with spaces, but I'm not sure it saves space to use one space or one tab since both are characters. How should I use ; to remove line breaks? Thanks. \$\endgroup\$ – A.L May 19 '14 at 19:33
  • \$\begingroup\$ In the first for loop instead of having each line with a \n and four spaces change it so that it is line1;line2;line3 ect \$\endgroup\$ – Harry Beadle May 19 '14 at 20:20
1
\$\begingroup\$

Lua - 340 335

f='(%w+)'a={}b={}for l in io.lines(arg[2])do i=l:sub(1,1)t=i=='#'and a or b
k,v=l:match(f..' {(.-)}')t[k]=v end
for l in io.lines(arg[1])do
c=''g='class="(.-)"'h='style="'l=l:gsub('id='..f,function(v)l:gsub(g,function(e)c=e:gsub(f,b)end)return h..a[v]..c..'"'end)l=l:gsub(g,function(e)return #c<1 and h..e:gsub(f,b)..'"'end)print(l)end


Usage: execute from the command line lua script.lua input.html input.css

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.