Jack
Jack

Reputation: 5768

How to modify CDATA with greasemonkey?

There is a webpage that has all the HTML structure made with Javascript. Example:

<script language="JavaScript" type="text/javascript">
//<![CDATA[
function abcd()
{
document.writeln('<div id="abc">');
document.writeln('<div class="Wrapper">');
document.writeln('Test');
...
}
</script>

What I want to do is replace all instances of the word Test with <b>Test</b> but I'm not sure how I'm even supposed to get the elements/HTML with it made this way.

Upvotes: 0

Views: 501

Answers (1)

Brock Adams
Brock Adams

Reputation: 93433

You want to wrap Test, and the offending HTML is added by javascript. If that is so, then there are three basic approaches:

  1. You can just wrap the target words as they appear. This is the most robust approach. See below for more detail.

  2. You can often rewrite the offending javascript function later. For example:

    // ==UserScript==
    // @name     YOUR_SCRIPT_NAME
    // @include  http://YOUR_SERVER.COM/YOUR_PATH/*
    // ==/UserScript==
    
    function abcd () {
        document.writeln('<div id="abc">');
        document.writeln('<div class="Wrapper">');
        document.writeln('<b>Test</b>');
        ...
    }
    
    var D                   = document;
    var scriptNode          = D.createElement ('script');
    scriptNode.type         = "text/javascript";
    scriptNode.textContent  = abcd;
    
    var targ    = D.getElementsByTagName('head')[0] || D.body || D.documentElement;
    targ.appendChild (scriptNode);
    


    will replace the evil version of abcd (), hopefully before it is run.

  3. You could try to intercept that <script> node after it loads and before it executes (once the script has run, changing the source would have no effect).

    This is possible with Firefox, see this answer for Greasemonkey code that does that. I don't recommend that approach in this case, though.

CDATA is not a factor for any of these approaches.



Wrapping target text as it appears:

  1. Use the waitForKeyElements() utility to catch the nodes of interest as they appear. There is no need to analyze the page's javascript or worry about changes to it that might break something.

  2. Use a common-ish DOM approach to wrapping target text, without breaking HTML or trashing event listeners. In this case, it's the wrapTextWithElement object.

  3. jQuery makes it all easier, clearer.

Here's a complete script. You can also test it against this demo page.

// ==UserScript==
// @name     YOUR_SCRIPT_NAME
// @include  http://fiddle.jshell.net/gH4nV/*
// @include  http://YOUR_SERVER.COM/YOUR_PATH/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant    GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change introduced
    in GM 1.0.   It restores the sandbox.
*/

waitForKeyElements ("div.Wrapper", emboldenTheWordTest);

function emboldenTheWordTest (jNode) {
    var testWrapper = new wrapTextWithElement ('Test', '<b>');
    testWrapper.wrap (jNode);
}

function wrapTextWithElement (targText, elemToWrapWith, bCaseSensitive) {
    var self            = this;
    var bCaseSensitive  = bCaseSensitive || false;
    self.targRegEx      = new RegExp ("(" + targText + ")", bCaseSensitive ? "" : "i");
    self.elemToWrapWith = elemToWrapWith;

    self.wrap = function (node) {
        $(node).contents ().each ( function () {
            if (this.nodeType === Node.ELEMENT_NODE) {
                self.wrap (this);
            }
            else if (this.nodeType === Node.TEXT_NODE) {
                var ndText  = this.nodeValue;

                if (self.targRegEx.test (ndText) ) {
                    var replaceNodes = $.map (
                        ndText.split (self.targRegEx),
                        function (phrase) {
                            if (self.targRegEx.test (phrase) ) {
                                var wrapped = $(self.elemToWrapWith, {text: phrase} );

                                return wrapped.get ();
                            }
                            else {
                                if (phrase == "")
                                    return null;
                                else
                                    return document.createTextNode (phrase)
                            }
                        }
                    );
                    $(this).replaceWith (replaceNodes);
                }
            }
        } );
    };
}

Upvotes: 1

Related Questions