Forcing the Horse

8

2

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.

Beta Decay

Posted 2014-09-08T19:45:49.567

Reputation: 21 478

@BetaDecay I added an answer. Am I too late to play? – rojo – 2014-12-27T01:45:28.760

Can we use LiveScript?

– nyuszika7h – 2014-12-27T14:29:02.740

@nyuszika7h only if you add the size of livescript.js to your byte count. :) – rojo – 2014-12-28T17:01:56.863

2Lol for xkcd XD – ThreeFx – 2014-09-08T21:06:19.637

9forcing doesn't contain the word force. – Dennis – 2014-09-08T22:53:31.460

2Either 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. – absinthe – 2014-09-08T22:59:44.063

2What about the equine equivalents of forcing, forcibly, forceps...? – Sean Latham – 2014-09-08T23:03:55.833

2"...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. – COTO – 2014-09-09T01:01:02.177

@COTO: Visibility as JavaScript understands it is easily achievable. Of course, that doesn't cover white text on white background, etc. – Dennis – 2014-09-09T01:15:30.643

@Dennis: I'm just saying that there are numerous non-visible tags, several CSS properties that can render elements invisible, numerous attributes whose values are never seen (and numerous other attributes whose values are seen), elements whose visibility is conditional, etc., etc. If you want to write code detecting the 101 different possibilities, more power to you. – COTO – 2014-09-09T01:53:16.187

I don't think "reinforce" is a conjugate of "force". – Ypnypn – 2014-09-09T19:25:58.803

@Ypnypn What would the right word be? – Beta Decay – 2014-09-09T19:42:49.367

Answers

5

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.

rojo

Posted 2014-09-08T19:45:49.567

Reputation: 166

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). – Dennis – 2014-12-27T03:58:25.063

@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. – rojo – 2014-12-27T04:11:28.680

@rojo Yep, you interpreted it correctly – Beta Decay – 2014-12-28T11:24:23.330

3

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.

Dennis

Posted 2014-09-08T19:45:49.567

Reputation: 196 637

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. – rojo – 2014-12-27T04:54:29.120

2

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("");
    })
  }
}

Optimizer

Posted 2014-09-08T19:45:49.567

Reputation: 25 836

1I request an ungolfed version=P – flawr – 2014-09-09T07:33:46.410

Wow... I thought these would be one-liners... Sort of replace('forc','hors') and that's it. Shows how much javascript I know... – Beta Decay – 2014-09-09T11:58:52.253

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.
  • < – Dennis – 2014-09-09T12:29:37.250

    As Dennis says above, this does not meet all of the requirements, therefore I have unaccepted this answer. – Beta Decay – 2014-09-25T17:04:20.663