65.9K
CodeProject 正在变化。 阅读更多。
Home

HTML 被挂钩

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (6投票s)

2002年9月4日

6分钟阅读

viewsIcon

178282

downloadIcon

3010

本文展示了如何在浏览网页时控制内容



交互式地高亮显示任意网页中的区块

本文介绍如何在浏览网页时控制所浏览的内容。文章中描述并提供了几个应用程序在演示应用程序中。尽管演示应用程序是用C#编写的,但目标受众更广泛,因为涉及的代码可以直接转换为C++、VB或其他语言。此外,HTML钩子主要通过JavaScript实现。

HTML 钩子?

在Internet Explorer发布了6个主要版本之后,用户仍然只能使用一个相当基础的浏览器。只要您的需求只是浏览,一切都还好。Internet Explorer可以方便地让您进行浏览,但如果您想重用网页中有趣的部分内容、订阅内容更新、自动化处理等等怎么办?事实上,Internet Explorer 6.0 中并没有解决这些问题,那些试图控制HTML的人面临着巨大的鸿沟。

  • HTML 是一种具有显示语义的语言。HTML无法描述组件、目录等。这就是为什么行业在努力引入新语言。然而,要根除HTML需要多年的时间,更不用说高科技投资的低迷了。
  • HTML是由有状态的Web服务器提供的,这些服务器利用cookie和会话ID来提供内容,从而使网页难以处理。
  • HTML是Web开发人员使用的高级服务器端语言的处理结果。因此,HTML是一种扁平化的格式,除了显示之外,没有其他目的或能力。
  • 如今的网站没有简单的HTML,因为每个Web开发人员都使用各种JavaScript、动态HTML、多层事件驱动的HTML。要在您的应用程序中重用HTML,您需要这两种运行时。
从技术上讲,HTML钩子是一种开发人员订阅特定浏览器事件的方式,目的是为最终用户提供浏览器++软件,这些软件旨在以尽可能智能的方式浏览,并使Web成为一个更好、更可靠的工作场所。

在某种程度上,HTML已经完全可以被钩取,因为几乎每个HTML标签都可以附加与点击事件相关的行为。但这并不能真正产生应用程序,因为事件在同一个高度受保护的网页空间中与HTML一起工作,为开发人员提供的钩取能力非常少,甚至没有,从而为最终用户提供的附加功能也非常少。

Internet Explorer API允许我们托管一个Web浏览器实例,并订阅特定事件,例如当页面已加载并处于交互模式时发出信号。通过利用此事件、一些其他调整以及Internet Explorer API提供文档对象模型(DOM)的事实,我们将能够更改Web页面刚刚加载和Web页面准备好显示之间HTML代码,使我们能够控制实际看到的内容及其行为。让我们从第一个例子开始。

高亮显示任意网页中的区块

从一个标准的基于窗体的C#应用程序开始,我们将Web浏览器控件拖放到上面,并订阅当Web页面准备就绪时触发的事件,即OnNavigateComplete


订阅页面就绪事件

当页面就绪时,如果我们想更改HTML代码或应用事件,我们可以利用IHTMLWindow级别上可用的名为execScript的方法,并为其提供JavaScript代码。
// event called when the web browser updates its view and finishes 
// parsing a new web page
private void OnNavigateComplete(object sender, 
                   AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
{
    String code = <...some javascript code...>

    // exec Javascript
    //
    IHTMLDocument2 doc = (IHTMLDocument2) this.axWebBrowser1.Document;
    if (doc != null)
    {
        IHTMLWindow2 parentWindow = doc.parentWindow;
        if (parentWindow != null)
            parentWindow.execScript(code, "javascript");
    }
}

这就是为什么我们现在需要JavaScript魔法。我们如何高亮显示区块?这引出了两个问题:当我们只有一个HTML标签的层次结构(臭名昭著的DOM)时,什么是区块?如何进行高亮显示?

从经验丰富的Web设计师的角度来看,第一个问题的答案很明显。不用说,90%的网页使用<table>标签来定位网页中的内容。我们很幸运,我们可以假设表格区块实际上是Web组件,例如导航栏、主内容、版权栏等等。当然,这并非总是如此,但确实非常普遍。不妨试试,这正是实例演示!

HTML逆向工程将在另一篇文章中讨论。

第二个答案紧随第一个。我们将检查鼠标光标下的HTML元素。处理过程需要足够快,以避免不必要地减慢浏览体验。我们只需利用DOM功能从当前元素遍历其父元素,然后寻找一个<table>标签。一旦找到它,我们只需实时更改其边框和背景颜色,使其高亮显示。我们当然是幸运的,因为我们做的每一次更改都会自动反映在网页中,而无需完全刷新,这是动态HTML的一个好处。以下是JavaScript代码(boxify.js):

  document.onmouseover = dohighlight;
  document.onmouseout = dohighlightoff;

  var BGCOLOR = "#444444";
  var BORDERCOLOR = "#FF0000";

  function dohighlight()
  {
    var elem = window.event.srcElement;

    while (elem!=null && elem.tagName!="TABLE")
        elem = elem.parentElement;

    if (elem==null) return;

    if (elem.border==0)
    {
        elem.border = 1;

        // store current values in custom tag attributes
        //
        elem.oldcolor = elem.style.backgroundColor; // store backgroundcolor
        elem.style.backgroundColor = BGCOLOR; // new background color

        elem.oldbordercolor = elem.style.borderColor; // same with bordercolor
        elem.style.borderColor = BORDERCOLOR;

        var rng = document.body.createTextRange();
        rng.moveToElementText(elem);

// following code is in comment but ready to use if required
// -> it can select the highlighted box content
// -> or put automatically the content in the clipboard to ease copy/paste
/*      var bCopyToClipboardMode = 1;
        if (!bCopyToClipboardMode)
            rng.select();
        else
            rng.execCommand("Copy"); */
    }
  }

  function dohighlightoff()
  {
    var elem = window.event.srcElement;

    while (elem!=null && elem.tagName!="TABLE")
        elem = elem.parentElement;

    if (elem==null) return;

    if (elem.border==1)
    {
        elem.border = 0;

        // recover values from custom tag attribute values
        elem.style.backgroundColor = elem.oldcolor;
        elem.style.borderColor = elem.oldbordercolor;
    }
  }
为了实现交互式高亮显示,我们在应用程序的右侧角落有一个组合框。组合框的开发方式如下:我们将此组件从*工具箱窗口*拖放到窗体上,然后在*属性窗口*的“项集合”中插入可选项目,并将组合框样式选择为“DropDownList”以禁用编辑。我们无法在*属性窗口*中进行的一件事是选择初始索引,为此我们必须手动添加代码:this.comboBox1.SelectedIndex = 0;。结果就是我的这个组合框。


在Web浏览器应用程序中添加一个不寻常的组合框

正如组合框所固有意味着的那样,在本文中,我们有其他一些钩子可以玩。首先,让我介绍一下状态切换是如何管理的。

protected enum NavState
{
    None,
    NoPopup,
    Boxify
};


// event called when selection changes in the combobox
private void OnNavigationModeChanged(object sender, 
                                     System.EventArgs e)
{
    if ( comboBox1.Text=="NoPopup" )
    {
        NavigationState = NavState.NoPopup;
    }
    else if ( comboBox1.Text=="Boxify" )
    {
        NavigationState = NavState.Boxify;
    }
    else
    {
        NavigationState = NavState.None;
    }

    // synchronize UI
    SyncUI("");
}


// event called when the web browser updates its view and finishes 
// parsing a new web page
private void OnNavigateComplete(object sender, 
               AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
{
    String sURL = (string) e.uRL;
    if (sURL=="about:blank")
        return;

    SyncUI( sURL );

}



// applogic
//

protected void SyncUI(String sURL)
{
    if (sURL.Length>0)
        textBox1.Text = sURL; // update UI

    String code;

    if ( NavigationState == NavState.NoPopup )
    {
        // squeeze down onload events (when web page loads)
        String code1 =	"document.onload=null;" +
                        "window.onload=null;" +
                        "for (i=0; i<window.frames.length; i++) { " +
                        " window.frames[i].document.onload=null;" + 
                        "window.frames[i].onload=null; };";

        // squeeze down onunload events (when web page is closed)
        String code2 =	"document.onunload=null;" +
                        "window.onunload=null;" +
                        "for (i=0; i<window.frames.length; i++) { " +
                        " window.frames[i].document.onunload=null;" + 
                        "window.frames[i].onunload=null; };";

        code = code1 + code2;

     }
     else if ( NavigationState == NavState.Boxify )
     {
         // read boxify.js
         FileStream fin = new FileStream("boxify.js", FileMode.Open, 
                                    FileAccess.Read, FileShare.ReadWrite) ;
         StreamReader tr = new StreamReader(fin) ;
         code = tr.ReadToEnd();
         tr.Close();
         fin.Close();

         if (code.Length==0) Console.WriteLine("Cannot find boxify.js file");
     }
     else
     {
         // stop boxify.js
         //
         code = "document.onmouseover = null; document.onmouseout = null;"  ;
     }

     // exec Javascript
     //
     IHTMLDocument2 doc = (IHTMLDocument2) this.axWebBrowser1.Document;
     if (doc != null)
     {
          IHTMLWindow2 parentWindow = doc.parentWindow;
          if (parentWindow != null)
               parentWindow.execScript(code, "javascript");
     }

}

禁止弹出窗口

另一个不错的HTML钩子技术是防止弹出窗口打开。Web设计人员习惯于在退出当前网页时执行JavaScript的技巧,许多人为此目的打开弹出窗口(特别是色情网站)。我们所做的是,一旦页面准备好,就覆盖这些“回调”,并将它们强制设为null

因为DOM是一个更丰富的对象模型,所以在文档级别(代表网页的对象)强制设为null是不够的,我们需要在窗口级别以及任何子窗口级别(称为框架)都这样做。

参见上面的代码。

保存HTML以供重用

即使保存HTML以供重用本身就值得写一篇文章,但这里还是介绍一下实现这一目标所需的几行代码。事实上,如果我们使用C++,我们会将IHTMLDocument接口强制转换为IPersistFile并对其应用Save()方法,但在C#中,IPersistFile的替代品是UCOMIPersistFile,位于System.Runtime.InteropServices命名空间下。以下是使用C#将HTML代码存储在硬盘上所需的内容。
IHTMLDocument2 doc = (IHTMLDocument2) this.axWebBrowser1.Document;
UCOMIPersistFile pf = (UCOMIPersistFile) doc;
pf.Save(@"c:\myhtmlpage.html",true);
就是这么简单。
© . All rights reserved.