8
\$\begingroup\$

This is a simple one: create the shortest browser script to change every appearance of force on a web page into horse. This script needs to be able to be pasted into the console and work on most modern browsers.

You must replace all occurences of the word force and conjugates of the word force (forcing, forced, reinforce). The cases must also be preserved.

You must not change any occurences which are not visible when the page is displayed in a browser. For example, meta tags or comments.

Example

This is a fOrcE log. It makes people who touch it very forceful. Don't try forcing it.

This is a hOrsE log. It makes people who touch it very horseful. Don't try horsing it.

Unfortunately this is a Javascript only challenge. The shortest code wins.

\$\endgroup\$
  • 2
    \$\begingroup\$ Lol for xkcd XD \$\endgroup\$ – ThreeFx Sep 8 '14 at 21:06
  • 9
    \$\begingroup\$ forcing doesn't contain the word force. \$\endgroup\$ – Dennis Sep 8 '14 at 22:53
  • 2
    \$\begingroup\$ Either the question allows replacement of forc into hors - or it is the replacement of all the different conjugations of force into their horse versions. In the former case, we get things like "Reinhorsing", etc. In the latter, only forcing would be an exception. OP should clarify. \$\endgroup\$ – absinthe Sep 8 '14 at 22:59
  • 2
    \$\begingroup\$ What about the equine equivalents of forcing, forcibly, forceps...? \$\endgroup\$ – Sean Latham Sep 8 '14 at 23:03
  • 2
    \$\begingroup\$ "...any occurrences which are not visible when the page is displayed in a browser." is far too general. In particular, there are dozens or even hundreds of different ways to insert non-visible content into web pages. Perhaps you should just limit the exclusion to one or two specific types of tag. This prevents .innerHTML replace-all solutions, which I'm guessing is why you want the exceptions in the first place. \$\endgroup\$ – COTO Sep 9 '14 at 1:01
5
\$\begingroup\$

175 bytes, ES5, XPATH

This tests successfully in the latest Firefox and Chrome. Criticism is welcome! This is my first golf swing, and I hope I'm doing it right.

d=document,f=d.evaluate('//text()',d,{},7,{});for(i=0;t=f.snapshotItem(i++);)t.data=t.data.replace(/(f)(or)(c)/ig,function(a,b,c,e){return(b=='f'?'h':'H')+c+(e=='c'?'s':'S')})

The document.evaluate() method is supported by all major browsers except IE, which I hope satisfies the requirement for "most modern browsers". And because the XPath selector selects only text nodes, this ought to leave attributes, comments, namespaces, and other data not intended for display untouched without needing to check for each node's offsetParent.

Ungolfed version:

var force = document.evaluate('//text()', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

for (var i = 0; textNode = force.snapshotItem(i++); /* nothing */) {
    textNode.data = textNode.data.replace(
        /(f)(or)(c)/ig,
        function (full, $1, $2, $3) {
            return ($1 == 'f' ? 'h' : 'H' )
                + $2
                + ($3 == 'c' ? 's' : 'S' );
        }
    );
}

Or if I replace function / return with ECMA6 fat arrow notation, I can get it down to 163 characters.

d=document,f=d.evaluate('//text()',d,{},7,{});for(i=0;t=f.snapshotItem(i++);)t.data=t.data.replace(/(f)(or)(c)/ig,r=(a,b,c,e)=>(b=='f'?'h':'H')+c+(e=='c'?'s':'S'))

This syntax currently only works in Firefox though, I think. Until more browsers adopt this syntax, the 175-byte solution above shall remain my official entry for the game.

\$\endgroup\$
  • \$\begingroup\$ Very interesting approach! There are two differences between our answers (which answer is behaving correctly is for the OP to judge): 1. Your regex will turn FORC into HORS. 2. Your code modifies all text nodes, while checking offsetParent filters out all tags with display:none style (which are not displayed in the browser). \$\endgroup\$ – Dennis Dec 27 '14 at 3:58
  • \$\begingroup\$ @Dennis Thanks! Yeah, I can add (?=e|ing) to my regexp if OP deems it necessary. Regarding display: none, I interpreted "For example, meta tags or comments." to mean stuff that isn't text nodes should be left alone, while text nodes, including invisible nodes, should be changed. I would be interested to see the OP confirm his intentions though. \$\endgroup\$ – rojo Dec 27 '14 at 4:11
  • \$\begingroup\$ @rojo Yep, you interpreted it correctly \$\endgroup\$ – Beta Decay Dec 28 '14 at 11:24
3
\$\begingroup\$

ECMAScript 5, 248 233 193 191 182 bytes

for(w=(d=document).createTreeWalker(d,4);t=w.nextNode();)
if(t.parentNode.offsetParent)t.data=t.data.replace(/(f)(or)(c)/gi,
function(a,b,c,d){return(b<'f'?'H':'h')+c+(d<'c'?'S':'s')})

Tested in Chrome and Firefox.

Ungolfed version

We should only modify visible TextNodes; URLs, attributes, etc. are not displayed in the browser. We can use a TreeWalker to find all of them. Visible TextNodes will be identified by checking if their ParentNode has a truthy offsetParent.1

// Create a TreeWalker that walks through all TextNodes. 

var walker = document.createTreeWalker(document, NodeFilter.SHOW_TEXT, null, false);

// While there are nodes left,

while(node = walker.nextNode())

// if the node is visible,

    if(node.parentNode.offsetParent)

// search for the string "forc" (ignoring case)

        node.data = node.data.replace(/(f)(or)(c)/gi,

// and replace an uppercase/lowercase F/C with an uppercase/lowercase H/S.

            function(match, F, OR, C)
            {
                return (F != 'f' ? 'H' : 'h') + OR + (C != 'c' ? 'S' : 's')
            }
)

1 This is buggy in IE 9.

\$\endgroup\$
  • \$\begingroup\$ You know, you can drop this down to 193 bytes if you get rid of n=[],c=0, populating then looping back through n. Just do if(t.parentNode.offsetParent)t.data=t.data.replace etc. \$\endgroup\$ – rojo Dec 27 '14 at 4:54
2
\$\begingroup\$

200 bytes, ES6

Here is an ES6 version. Run it in latest Firefox's web console.

for(a=document.all,i=0;n=a[i++];)(p=>p&&p.offsetParent?n.innerHTML=n.innerHTML.replace(/f[o0]rc(e|ing)/gi,m=>(v=>{k=[...m];k[0]=k[0]<'F'?'H':'h';k[3]=k[3]<'C'?'S':'s'})()||k.join("")):0)(n.parentNode)

I'll add the ungolfed version if requested :)

Here is the ungolfed version

var all = document.all;
for (var i = 0; i < all.length; i++) {
  if (all[i].parentNode && all[i].parentNode.offsetParent) {
    all[i].innerHTML = all[i].innerHTML.replace(/f[o0]rc(e|ing)/gi, function(matched) {
      var k = matched.split(""); // convert to array
      k[0] = k[0] == "F"? "H" : "h";
      k[3] = k[3] == "C"? "S" : "s";
      return k.join("");
    })
  }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I request an ungolfed version=P \$\endgroup\$ – flawr Sep 9 '14 at 7:33
  • \$\begingroup\$ Wow... I thought these would be one-liners... Sort of replace('forc','hors') and that's it. Shows how much javascript I know... \$\endgroup\$ – Beta Decay Sep 9 '14 at 11:58
  • 1
    \$\begingroup\$ 1. This approach doesn't work. innerHTML will change all occurrences inside visible elements, so if there's a comment inside, it will get touched as well. 2. One could argue that ES6 doesn't really work on most modern browsers. \$\endgroup\$ – Dennis Sep 9 '14 at 12:29
  • \$\begingroup\$ As Dennis says above, this does not meet all of the requirements, therefore I have unaccepted this answer. \$\endgroup\$ – Beta Decay Sep 25 '14 at 17:04

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.