views:

3426

answers:

6

We've noticed that IE7 has an odd behavor with code blocks posted on Stack Overflow. For example, this little code block:

public PageSizer(string href, int index)
{
    HRef = href;
    PageIndex = index;
}

Copy and pasted from IE7, ends up like this:

public PageSizer(string href, int index){    HRef = href;    PageIndex = index;    }

Not exactly what we had in mind.. the underlying HTML source actually looks fine; if you View Source, you'll see this:

<pre><code>public PageSizer(string href, int index)
{
    HRef = href;
    PageIndex = index;
}
</code></pre>

So what are we doing wrong? Why can't IE7 copy and paste this HTML in a rational way?

Update: this specifically has to do with <pre> <code> blocks that are being modified at runtime via JavaScript. The native HTML does render and copy correctly; it's the JavaScript modified version of that HTML which doesn't behave as expected. Note that copying and pasting into WordPad or Word works because IE is putting different content in the rich text clipboard compared to the plain text clipboard that Notepad gets its data from.

A: 

Remove the inner <code>. IE's copy/paste behavior could see that as an inline tag and forget about the visible whitespace.

Armin Ronacher
+12  A: 

Here's the issue:

Your code colorization script replaces line breaks with <br /> tags. When copying/pasting, IE7 apparently doesn't translate the <br /> tag into a linebreak like it does for the screen.

In other words, your code becomes this:

public PageSizer(string href, int index)<br />{<br />    HRef = href;<br />    PageIndex = index;<br />    }

But you want it to become this:


public PageSizer(string href, int index)<br />
{<br />
    HRef = href;<br />
    PageIndex = index;<br />
}<br />

In the latest version of prettify.js on Google Code, the line responsible is line 1001 (part of recombineTagsAndDecorations):


html.push(htmlChunk.replace(newlineRe, '<br />'));

Edited, based on the comments:
For IE7, this is what the line should probably be changed to:


html.push(htmlChunk.replace(newlineRe, '\n'));

(Assuming newlineRe is a placeholder).

This fix also holds up in Chrome, and FFX3... I'm not sure which (if any) browsers need the <br /> tags.

Update: More information in my second response:
http://stackoverflow.com/questions/136443/why-doesnt-ie7-copy-precode-blocks-to-the-clipboard-correctly#156069

AaronSieb
So why does it look fine if you paste into MS Word?
Andrew Johnson
Probably either because Word pastes as formatted HTML, or because of the CR + LF issues you mentioned earlier.
AaronSieb
Did some testing -- Paste Special -> Unformatted text still pastes everything on one line in Word. Rich text editors seem to be fed <br />s as line breaks, plain text is fed whatever whitespace existed in the source with <br />s ignored. My solution should still work around this bug.
AaronSieb
I don't this this solution will work: because the prettify code absorbs the CODE tag, but leaves the PRE tag, it'll render with double spacing.
Andrew Johnson
Hm, you're right about the double spacing. In IE7, Chrome, and FFX3 the linebreaks show up even without the <br /> tags. I wonder which browsers need them in?If nothing else, IE7 can be detected and fed the linebreaks while the other browsers get the <br/> tags.
AaronSieb
IE will give double spacing to rich-text editors as it will translate the newlines properly and add the Line Feeds for BRs. Looking again at the generated HTML it has both pre and code and either cause the IE bug. The only solution is to fix jquery to not replace the whitespace.
Andrew Johnson
Does it have to be done at the jQuery level? I'm pretty sure that changing a line in prettify.js will do it (see above; I've edited the final portion of my post).
AaronSieb
I was thinking about this over the weekend: I remembered about the separate copy buffers for Windows, although I didn't realise about the third one for HTML! It does have to be done at a jQuery level, but it has to be more complicated what you are thinking of. I'll expand redo my answer to explain.
Andrew Johnson
To clarify, when I said "jQuery level" I meant actually modifying the jQuery library itself, which doesn't appear to be the case. It does appear likely that prettify.js needs to be changed, though (which I think is what you were referring to).
AaronSieb
Yeah, I notice the script is just pasted into SO's jquery.js script.
Andrew Johnson
+1  A: 

This looks like a bug in IE, BR tags inside the PRE or CODE are not being converted into newlines in the plain text copy buffer. The rich text copy buffer is fine, so the paste works as expected for applications like wordpad.

The prettify script, that colours the code, removes all the whitespace and replaces it with HTML tags for spaces and new lines. The generated code looks something like this:

<pre><code>code<br/>&nbsp;&nbsp;code<br/>&nbsp;&nbsp;code<br/>code</code></pre>

The PRE and CODE tags are rendered by defaults with the CSS style of {whitespace: pre}. In this case, IE is failing to turn the BR tags into newlines. It would work on your original HTML because IE will successfully turn actual newlines into newlines.

In order to fix it you have 3 options. (I am presuming you want nice HTML and the ability to work well with and without javascript enabled on the client):

  1. You could place the code inside a normal div and use CSS to render it using {whitespace: pre}. This is a simple solution, although might not please an HTML markup purist.

  2. You could have two copies of the code, one using proper PRE / CODE tags and another in a normal div. In your CSS you hide the normal div. Using javascript you prettify the normal div and hide the pre/code version.

  3. Modify the prettify script to recognise that it is acting on a PRE or CODE element and to not replace the whitespace in that event.


Notes:

  • What is important is not the HTML in your source, but the HTML that is generated after the prettify script has ran on it.

  • This bug is still present even if the white-space mode of the PRE is changed to normal using CSS.

Andrew Johnson
It isn't a CR + LF issue. I pasted the text into a hex editor, and there were no CR/LF characters present.The discrepancies between Word, WordPad, and Notepad are caused because IE7 saves three copies of everything you copy to the clipboard: Plain text, HTML, and RTF.
AaronSieb
HTML format is correct, because it includes the actual HTML in use (which isn't wrong). RTF is probably derived from the HTML version using the same algorithms that are used to display information on-screen.
AaronSieb
A div with white-space:pre still has the same problem as a PRE/CODE block (<br /> tags are ignored in the plain text clipboard), and would still require prettify.js to be modified.
AaronSieb
Are you sure? I can't test it until later as I don't have a windows install at work, however, on Friday I did try setting the pre and code to white-space:normal and I still got the bug.
Andrew Johnson
I have a test document for this issue that I can use to play around with the HTML.<div style="white-space:pre">...</div>Behaves identically to<pre><code>...</code></pre>At least in terms of copy/paste. Of course, in the actual document the ... consists of multiple lines plus a few <br>s.
AaronSieb
A: 

bad news : none of the proposed fixes work. Modifying prettify.js around line 1000

html.push(htmlChunk.replace(newlineRe, '\n'));

This causes double-spacing in other browsers, and still doesn't solve the IE7 copy to notepad problem! So even if I selectively detected IE7, this "fix" doesn't fix anything.

I guess maybe it is simply a bug in IE7 having to do with JavaScript rebuilding a <pre> element -- no matter how many \n newlines I put in there, nothing changes w/r/t to the paste to notepad behavior.

Jeff Atwood
This would probably work if you used a '\r' instead of a '\n'. I've provided a more complex answer that appears to work.
Andrew Johnson
Are you sure your cached copy of the preffify file is replaced?
Joel Coehoorn
A: 

@Jeff Atwood It's the right idea, but the implementation still needs work. I guess my air code just didn't cut it :)

I suspect that the fix I mentioned earlier doesn't work because prettify is doing some additional processing on the text after line ~1000 is called.

Trying to track the content backwards from when it's added to the page, I came across this comment around line 1227:


// Replace <br>s with line-feeds so that copying and pasting works
// on IE 6.
// Doing this on other browsers breaks lots of stuff since \r\n is
// treated as two newlines on Firefox, and doing this also slows
// down rendering.

When I took the isIE6 condition off of the code, it mostly worked in IE7 (there was an extra line break at the top and bottom), and Firefox 3... But I'd assume that it causes issues with older versions of FFX.

At the very least, it appears that IE7 will require \r\n, instead of just \n. Figuring out exactly what will work with which browsers will take a more extensive test setup than I have handy at the moment.

Anyway, inserting the \r\n for IE7 appears to be basically what needs to happen. I'll keep poking around prettify to see if I can narrow it down further.

UPDATE: IE7 appears to strip newline characters (\r or \n) from strings that are assigned to an innerHTML property. It looks like they need to be added back in, around line 1227.

A correct solution would probably mean inserting a placeholder tag around line 1000, and then replacing it around line 1227.

AaronSieb
+25  A: 

It seems that this is a known bug for IE6 and prettify.js has a workaround for it. Specifically it replaces the BR tags with '\r\n'.

By modifying the check to allow for IE6 or 7 then the cut-and-paste will work correctly from IE7, but it will render with a newline followed by a space. By checking for IE7 and providing just a '\r' instead of a '\r\n' it will continue to cut-and-paste and render correctly.

Add this code to prettify.js:

function _pr_isIE7() {
  var isIE7 = navigator && navigator.userAgent &&
       /\bMSIE 7\./.test(navigator.userAgent);
  _pr_isIE7 = function () { return isIE7; };
  return isIE7;
}

and then modify the prettyPrint function as follows:

   function prettyPrint(opt_whenDone) {
     var isIE6 = _pr_isIE6();
+    var isIE7 = _pr_isIE7();

...

-        if (isIE6 && cs.tagName === 'PRE') {
+        if ((isIE6 || isIE7) && cs.tagName === 'PRE') {
          var lineBreaks = cs.getElementsByTagName('br');
+         var newline;
+         if (isIE6) {
+           newline = '\r\n';
+         } else {
+           newline = '\r';
+         }
          for (var j = lineBreaks.length; --j >= 0;) {
            var lineBreak = lineBreaks[j];
            lineBreak.parentNode.replaceChild(
-               document.createTextNode('\r\n'), lineBreak);
+               document.createTextNode(newline), lineBreak);
          }

You can see a working example here.

Note: I haven't tested the original workaround in IE6, so I'm guessing it renders without the space caused by the '\n' that is seen in IE7, otherwise the fix is simpler.

Andrew Johnson
hey, cool -- thank you! I will try this!
Jeff Atwood
I notice you haven't put a fix into production yet. Let me know how the testing went when you get around it it.
Andrew Johnson
Thanks much for the fix. That's implemented at revision 89 of prettify. Please report further problems at http://code.google.com/p/google-code-prettify/issues/detail?id=71
Mike Samuel