HtmlCxx用户手册

中科院计算所网络数据科学与工程研究中心

信息抽取小组

gengyun@sohu.com

1.1 简介

HtmlCxx是一款简洁的,非验证式的,用C++编写的css1和html解析器。和其他的几款Html解析器相比,它具有以下的几个特点:

使用由KasperPeeters编写的强大的tree.h库文件,可以实现类似STL的DOM树遍历和导航。

可以通过解析后生成的树,逐字节地重新生成原始文档。

打包好的Css解析器。

额外的属性解析功能

看似很像C++代码的C++代码(其实已不再是C++了)

原始文档中的tags/elements的偏移值都存储在DOM树的节点当中。

Htmlcxx的解析策略其实是尝试模仿mozilla firefox(http://www.mozilla.org)的模式。因此你应当尝试去解析那些由firefox所生成的文档。然而不同于firefox浏览器,htmlcxx并不会将一些原本不存在的东西加入到所生成的文档当中去。因此,在将生成树进行序列化的时候,能够完全地还原和原始Byte大小一样的HTML文档。

1.2 快速上手

下面我们通过一个简单的小例子来让大家对如何使用HtmlCxx进行开发有一个快速,直观地了解。

1.       安装环境

操作系统: Ubuntu 10.10 (32-bit Linux)

编译环境: GCC 4.4.4

HtmlCxx版本: 0.85

2.       源代码下载

各个版本的HtmlCxx可以从著名的开源代码网站SourceForge上下载。由于HtmlCxx已经于2005年停止更新,我们采用的是其最后更新的版本0.85。

除此之外,使用Ubuntu的用户还可以直接在命令行下使用以下命令很方便地进行安装:

 

sudo apt-get install htmlcxx

 

安装好后,可以到usr/include/htmlcxx目录下进行查看,其中包含了一些可以使用的功能的.h文件。

3.       编写小例子

在随意一个文件夹下新建一个文件,如Temp.cc,之后打开该文件,并输入如下代码:

 

#include <string>

#include <iostream>

#include <sstream>

#include <htmlcxx/html/ParserDom.h>

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

using namespace std;

using namespace htmlcxx;

int main()

{

  //解析一段Html代码

string html ="<html><body>hey</body></html>";

HTML::ParserDom parser;

tree<HTML::Node> dom = parser.parseTree(html);

  //输出整棵DOM树

  cout<< dom << endl;

  //输出树中所有的超链接节点

tree<HTML::Node>::iterator it = dom.begin();

tree<HTML::Node>::iterator end = dom.end();

  for(; it != end; ++it)

  {

    if (strcasecmp(it->tagName().c_str(), "A") == 0)

    {

      it->parseAttributes();

       cout <<it->attribute("href").second << endl;

    }

  }

  //输出所有的文本节点

  it= dom.begin();

  end= dom.end();

  for(; it != end; ++it)

  {

   if ((!it->isTag()) && (!it->isComment()))

    {

     cout << it->text();

    }

  }

cout << endl;

}

 

 

将代码编写好保存后,打开控制台进入Temp.cc的目录下,输入以下的命令使用gcc将其编译: 

gcc –o Temp Temp.cc –Iusr/include/htmlcxx-lhtmlcxx

 

请注意上面这段代码中的第一个-I是大写的i,第二个-l是小写的L。

编译生成可执行文件Temp,直接输入./Temp运行,程序如果正常运行则应当出现如下的结果:

 

至此我们的htmlcxx已经可以成功地使用和运行了!

 

二.源代码分析

2.1 简介

         代码分为html和css两个部分,我们主要分析html部分。比较复杂的是tree.h文件,里面不但包含了对树的结构定义,而且包含了许多遍历算法。该文件的说明可以在http://tree.phi-sci.com/documentation.html上找到。在学习HtmlCxx的源代码的时候,要注意到的一点就是不仅应学习它的原理,更因为它引用了开源的树结构代码tree.h,优秀的编码风格贯穿始终,代码十分工整和简洁,广泛适用C++的template机制使其可扩展性非常强,为我们学习C++ 语言编程提供了良好的示范教材。

         由于其注释代码内部较少,下面这段源代码解析主要为方便大家理解。

2.2 ParserDom.h(.cc)

         我们首先来分析ParserDom.h这个文件。上面的例子中我们就用到了这个头文件来对Html文档进行解析。这里我们需要说明,一般的XML格式文档(包括Html文档)解析有两种方式,即DOM方式和SAX方式,下面简要介绍一下这两种方式的异同。

        

DOM方式:

         DOM(DocumentObject Model 文档对象模型)方式,从名字上来看就是将XML格式的文档读至内存,并为每个节点建立一个对象,之后我们可以通过操作对象的方式来对节点进行操作,即是将XML的节点映射到了内存中的一系列对象之上。

         该方法的优点是在建立好模型后的查询和修改速度较快。

该方法的缺点是在处理之前需要将整个XML文档全部读入内存进行解析,并根据XML树生成一个对象模型,当文档很大时,DOM方式就会突现出其肿大的特性,一个300KB的XML文档可以导致RAM或者虚拟内存中3000000KB的DOM树模型。

 

SAX方式:

SAX(Simple APIfor XML)是一种基于事件(Event)的XML处理模式,该模式和DOM相反,不需要将整个XML树读入内存后再进行处理,而是将XML文档作为一个输入“流”来处理,在处理该流的时候会触发很多事件,如SAX解析器在处理文档第一个字符的时候,发现是文档的开始,就会触发Start doucment事件,用户可以重载这个事件函数来对该事件进行相应处理。又如当遇到新的节点时,则会触发Start element事件,此时我们可以对该节点名进行一些判断,并作出相应的处理等。

该方法的优点是不用等待所有数据都被处理后再开始分析,且不需要将整个文档都装入内存中,这对大文档来说是个巨大的优点。一般来说SAX要比DOM方式快很多。

该方法的缺点是由于在处理过程中没有储存任何数据,因此在处理过程中不能够对数据流进行后移,或者回溯等操作。

 

在熟悉了这两种方式之后,我们可以看到在ParserDom.h文件中include了ParserSax.h文件,这是由于在HtmlCxx中,DOM方式是基于SAX方式之上的,关于这点我们之后再说。

首先我们来看一下ParserDom.h中ParserDom类的几个方法和数据成员的声明。

class ParserDom : public ParserSax

{

         public:

                   ParserDom(){}                            //构造方法

                   ~ParserDom(){}                 //析构方法

 

                   consttree<Node> &parseTree(const std::string &html);       //通过String字符串来解析树

                   consttree<Node> &getTree()                   //返回(解析好的)数据成员mHtmlTree

                   {return mHtmlTree; }

 

         protected:

                   //声明了一系列虚函数,用于之后的重载

                   virtualvoid beginParsing();      //开始解析

 

                   virtualvoid foundTag(Node node, bool isEnd);         //寻找指定标签

                   virtualvoid foundText(Node node); //寻找文本

                   virtualvoid foundComment(Node node);         //寻找注释文本

 

                   virtualvoid endParsing();         //结束解析

                                    

                   tree<Node>mHtmlTree;                   //数据成员,用于存放解析好的树

                   tree<Node>::iteratormCurrentState;  //用于遍历树的迭代器,具体声明可见tree.h文件

};

在ParseDom类之后我们还可以见到有如下代码:

 

std::ostream&operator<<(std::ostream &stream, const tree<HTML::Node>&tr);

该段代码重写了<<操作符,使得可以该操作符可以直接输出tree<HTML::Node>类型的变量。

 

parseTree(const std::string &html)

该方法主要是调用ParserSax.h中的parse()方法(并最终经过一系列调用,调用到ParserSax.tcc中的parse()方法)对输入的一段string字符串形式的html文档进行解析。

 

const tree<HTML::Node>&ParserDom::parseTree(const std::string &html)

{

         this->parse(html);                      //调用ParserSax.h中的parse方法对html字符串进行解析,并且将结果存入数据成员mHtmlTree

         returnthis->getTree();    //返回受保护的数据成员mHtmlTree

}

beginParsing()

该方法主要是为解析之前做一些初始化的工作,主要是在树的根节点之前插入一个新的节点作为新根节点。

 

void ParserDom::beginParsing()

{

         mHtmlTree.clear();           //首先清空mHtmlTree

         tree<HTML::Node>::iteratortop = mHtmlTree.begin();         //之后申请一个top游标,指向mHtmlTree树的开始节点

         HTML::Nodelambda_node;    //申请一个新节点

         lambda_node.offset(0);            //初始化moffset值为0,具体请查阅node.h和node.cc文件

         lambda_node.length(0);

         lambda_node.isTag(true);

         lambda_node.isComment(false);

         mCurrentState= mHtmlTree.insert(top,lambda_node);        //将这个节点插入到树中去成为根节点,注意是插入到top游标节点的之前,即previous sibling,具体请查阅tree.h中的insert方法。

}

 

endParsing()

该方法主要作用是停止当前的解析,并记录下已经解析的长度。

 

void ParserDom::endParsing()

{

         tree<HTML::Node>::iteratortop = mHtmlTree.begin();         //获取根节点

         top->length(mCurrentOffset);//将根节点的length数据成员设置为当年已解析的距离

}

 

foundComment()

该方法的主要作用是在当前正在解析的节点(即mCurrentState游标所指向的节点)下将节点作为注释内容插入进去。实际这个方法上和后面的foundText()方法完全一样,因为无论是Comment或Text都是作为Node添加进去的,对于程序来讲没有本质上的区别。

 

void ParserDom::foundComment(Node node)

{

         //Addchild content node, but do not update current state

         //在当前节点下添加一个新的节点node,但是不更新当前解析的进度,即不修改mCurrentState游标的位置

         mHtmlTree.append_child(mCurrentState,node);

}

 

foundText()

该方法的主要作用和前一个方法雷同,从函数名的字面解释来讲应当是添加文本。

 

void ParserDom::foundComment(Node node)

{

         //Addchild content node, but do not update current state

         //在当前节点下添加一个新的节点node,但是不更新当前解析的进度,即不修改mCurrentState游标的位置

         mHtmlTree.append_child(mCurrentState,node);

}

 

foundTag(Nodenode, bool isEnd)

该方法的主要作用是添加一个新的标签节点,并且需要根据isEnd变量进行判断是起始标签如<div>或<a>等,还是结束标签如</div>或</a>等。

 

void ParserDom::foundTag(Node node, boolisEnd)

{

         if(!isEnd)

         {

                   //appendto current tree node

                   tree<HTML::Node>::iteratornext_state;

                   next_state= mHtmlTree.append_child(mCurrentState, node);

                   mCurrentState= next_state;

         }

         else

         {

                   //Lookif there is a pending open tag with that same name upwards

                   //IfmCurrentState tag isn't matching tag, maybe a some of its parents

                   //matches

                   vector<tree<HTML::Node>::iterator > path;

                   tree<HTML::Node>::iteratori = mCurrentState;

                   boolfound_open = false;

                   while(i != mHtmlTree.begin())

                   {

#ifdef DEBUG

                            cerr<< "comparing " << node.tagName() << " with" << i->tagName()<<endl<<":";

                            if(!i->tagName().length()) cerr << "Tag with no name at"<<i->offset()<<";"<<i->offset()+i->length();

#endif

                            assert(i->isTag());

                            assert(i->tagName().length());

                            boolequal;

                            constchar *open = i->tagName().c_str();

                            constchar *close = node.tagName().c_str();

                            equal= !(strcasecmp(open,close));

                            if(equal)

                            {

                                     DEBUGP("Foundmatching tag %s\n", i->tagName().c_str());

                                     //Closingtag closes this tag

                                     //Setlength to full range between the opening tag and

                                     //closingtag

                                     i->length(node.offset()+ node.length() - i->offset());

                                     i->closingText(node.text());

 

                                     mCurrentState= mHtmlTree.parent(i);

                                     found_open= true;

                                     break;

                            }

                            else

                            {

                                     path.push_back(i);

                            }

 

                            i= mHtmlTree.parent(i);

                   }

 

                   if(found_open)

                   {

                            //Ifmatch was upper in the tree, so we need to invalidate child

                            //nodesthat were waiting for a close

                            for(unsigned int j = 0; j < path.size(); ++j)

                            {

//                                  path[j]->length(node.offset()- path[j]->offset());

                                     mHtmlTree.flatten(path[j]);

                            }

                   }

                   else

                   {

                            DEBUGP("Unmatchedtag %s\n", node.text().c_str());

 

                            //Treat as comment

                            node.isTag(false);

                            node.isComment(true);

                            mHtmlTree.append_child(mCurrentState,node);

                   }

         }

}

 

 

这段算法的思路很简单,首先根据isEnd判断要添加的节点是否是结束tag节点,如果不是则直接将该节点插入到当前节点下。否则的话则需要向前寻找其对应的起始tag节点,一直回溯找到树的根节点,若没有找到则报错。此时还需要考虑如下可能,即该起始tag节点和所插入的结束tag节点之间还可能有其他的tag节点,如插入的是</div>,而可能会有<div><a>…</a><div>…</div></div>,此时第一个<div>才是其所对应的起始tag节点。为了解决这个问题,本函数里采用了一个将树路径以vetor存储的方式进行查找匹配。

1.       将所插入节点与当前节点的tagName进行比较。如果相同或者当前节点就是根节点,则前往第三步。

2.       将当前节点重置为当前节点的父节点,并将当前节点按顺序添加至path。回到第1步。

3.       检查是否找到tagName相同的节点,如果找到,我们需要调用flatten()方法调整树的结构,从而消除掉原本正在等待结束tag的起始tag节点。

4.       否则即为未找到,报错。

为了方便大家理解,我们稍微介绍一下这里使用到的flatten()方法,这是一个很有意思的树操作方法,其效果如下图所示:

关于flatten()具体可以参看tree.h中的具体算法。在这个算法中,由于建树时的算法特性,需要在找到匹配的起始标签后,对path路径上的所有节点执行flatten算法,这样就可以将原本需要等待结束tag标签的起始标签放到合适的位置。具体原因可以去查看ParseSax.tcc中构建树节点的算法来理解。

本方法主要是提供给ParseSax.tcc中的解析方法所调用。

 

operator<<(ostream &stream, consttree<HTML::Node> &tr)

本方法主要重写了<<操作符,使其可以直接输出tree<HTML::Node>类型的对象。

 

ostream &HTML::operator<<(ostream&stream, const tree<HTML::Node> &tr)

{

         tree<HTML::Node>::pre_order_iteratorit = tr.begin();

         tree<HTML::Node>::pre_order_iteratorend = tr.end();

         introotdepth = tr.depth(it);

         stream<< "-----" << endl;

         unsignedint n = 0;

         while( it != end )

         {

                   intcur_depth = tr.depth(it);

                   for(inti=0; i < cur_depth - rootdepth; ++i) stream << "  ";

                   stream<< n << "@";

                   stream<< "[" << it->offset() << ";";

                   stream<< it->offset() + it->length() << ") ";

                   stream<< (string)(*it) << endl;

                   ++it,++n;

         }

         stream<< "-----" << endl;

         returnstream;

}

 

代码很简单,就是利用了前序的游标pre_order_iterator循环遍历整棵树并且进行一些格式化的输出。关于pre_order_iterator的定义可以参考tree.h部分代码。

 

2.3 ParserSax.h(.cc)

         前面我们介绍过了Sax方式和DOM方式的区别,实际上在HtmlCxx中,这两种方式是结合使用的,即首先用SAX方式对流进行解析,解析出的结果采用DOM方式保存到一个树型的tree<Node>数据结构中。

        

         首先我们还是来看一下ParserSax.h头文件中对于数据成员和函数的声明:

 

                   classParserSax

                   {

                            public:

                                     ParserSax(): mpLiteral(0), mCdata(false) {}    //构造方法,初始化两个成员变量的值

                                     virtual~ParserSax() {}      //析构方法,空

 

                                     /**Parse the html code */

                                     voidparse(const std::string &html);       //解析一个string格式的html文档

 

                                     template<typename _Iterator>

                                     voidparse(_Iterator begin, _Iterator end);    //使用两个类型相同的形参格式的解析器模板

 

                            protected:

                                     //Redefine this if you want to do some initialization before

                                     //the parsing

                                     virtualvoid beginParsing() {}    //空,可进行扩展

                                     //以下几个虚函数,都在ParseDom.cc中进行了实现

                                     virtualvoid foundTag(Node node, bool isEnd) {}     

                                     virtualvoid foundText(Node node) {}

                                     virtualvoid foundComment(Node node) {}

                                     virtualvoid endParsing() {}

                                     //以下是几个模板,大部分都在ParseSax.tcc文件中实现

                                     template<typename _Iterator>

                                     voidparse(_Iterator &begin, _Iterator &end,

                                                        std::forward_iterator_tag);

 

                                     template<typename _Iterator>

                                     voidparseHtmlTag(_Iterator b, _Iterator c);

 

                                     template<typename _Iterator>

                                     voidparseContent(_Iterator b, _Iterator c);

 

                                     template<typename _Iterator>

                                     voidparseComment(_Iterator b, _Iterator c);

 

                                     template<typename _Iterator>

                                     _IteratorskipHtmlTag(_Iterator ptr, _Iterator end);

                                    

                                     template<typename _Iterator>

                                     _IteratorskipHtmlComment(_Iterator ptr, _Iterator end);

                                     //几个数据成员

                                     unsignedlong mCurrentOffset;      //当前正在解析的偏移位置

                                     constchar *mpLiteral;    //具体作用需要到ParseSax.tcc中才显现出来

                                     boolmCdata;  //同上

                   };

 

打开ParserSax.cc可以看到,里面空荡荡地只有一个Parse(const std::string &html)方法

 

void htmlcxx::HTML::ParserSax::parse(conststd::string &html)

{

//      std::cerr<< "Parsing string" << std::endl;

         parse(html.c_str(),html.c_str() + html.length());

}

 

该方法实际上直接调用了另一个template<typename _Iterator> void parse(_Iterator begin, _Iterator end)方法,而这个方法和一些其他主要方法都是在ParseSax.tcc中进行实现。

2.4 ParseSax.tcc

         注意.tcc文件和一般的.cc文件有所不同,应当是基于turboc++的,因为turbo c++是很多年没用过的东西了,具体我也不是很懂有什么区别。

         ParseSax.tcc这个文件中主要实现了对html文档的文本流的解析,并将其转化为一个个的节点,并存储到Html树中去。其实现了一整套的Html解析流程,值得我们学习。该流程主要针对的是FireFox生成的Html格式来进行的解析。并且使用parseContent(),parseTag(),parseComment()对一段内容作为内容,标签,注释等进行不同的处理。

         如果读者曾经写过类似的Html解析器,那么在看这段代码的时候会非常清晰,即使没有写过也没关系,因为这段代码的逻辑很清晰,可以借鉴其作为对Html解析器的标准来进行学习。

         其基本思路就是,按序遍历整个字符串,遇到’<’则认为其是tag标签的起始处,在tag中遇到’/’则认为该标签是结束型tag标签,遇到’>’则认为是tag标签的结束处,遇到’!’则认为其是注释字段的起始或结束处。之后运行相应的parseContent(),parseTag(),parseComment()进行解析即可。

 

parse(_Iterator &begin, _Iterator&end, std::forward_iterator_tag)

由于这段代码较长,我们将其分解后进行讲解。

 

voidhtmlcxx::HTML::ParserSax::parse(_Iterator &begin, _Iterator &end,std::forward_iterator_tag)

{

         typedef_Iterator iterator;

//      std::cerr<< "Parsing forward_iterator" << std::endl;

         mCdata= false;

         mpLiteral= 0;

         mCurrentOffset= 0;

         this->beginParsing();        //调用beginParsing()进行初始化

 

//      DEBUGP("Parsedtext\n");

上面这段代码进行了一些成员变量的初始化,并且调用了beginParsing()进行相应的初始化。

 

         while(begin != end)

         {

                   *begin;// This is for the multi_pass to release the buffer

 

这个while循环是最外层的循环,其循环的依据是begin和end两个游标,这两个游标始终指向了待解析的文本串的起始端和结束端。

 

                   while(c != end)

                   {

                            //For some tags, the text inside it is considered literal and is

                            //only closed for its </TAG> counterpart

上面这个while循环是次外层的循环,该循环每次结束都即为解析出一个节点。

 

                            while(mpLiteral)

                            {

//                                  DEBUGP("Treatingliteral %s\n", mpLiteral);

在这个循环的判断条件中,mpLiteral默认初始值为0,在运行后面的ParseHtmlTag()方法时候会更改它的值,如果不为零,则说明当前的tagName应当为以下这个数组的str属性其中之一。

 

static

struct literal_tag {

         intlen;

         constchar* str;

         intis_cdata;

}  

literal_mode_elem[] =

{  

         {6,"script", 1},

         {5,"style", 1},

         {3,"xmp", 1},

         {9,"plaintext", 1},

         {8,"textarea", 0},

         {0,0, 0}

};

其意义就是说,当遇到这些特殊节点的时候,需要进入该循环进行一些特别的处理,即应当着手向后寻找该tagName对应的</tagName>标签,其间遇到的所有内容都应当作为普通文本进行处理。

我们继续分析代码:

 

//                                  DEBUGP("Treatingliteral %s\n", mpLiteral);

                                     while(c != end && *c != '<') ++c;  //向后不断查找,直到找到了文件末尾或’<’才停止,此刻说明解析完毕或者解析到了某个tag标签

                                     if(c == end) {             //如果成立说明解析完毕

                                               if(c != begin) this->parseContent(begin, c);   //此时最后一个解析的标签末尾和文档末尾之间可能还有一段内容需要解析

                                               gotoDONE;      //转到结束阶段

                                     }

 

                                     iteratorend_text(c);   //申请一个新游标end_text,初始值赋入c

                                     ++c; //将c游标前进一步

 

此处申请的end_text,主要是为了在后面的代码中记录下当前’<’符号的位置。

 

                                     if(*c == '/')                 //判断’<’后面紧跟的是否为’/’符号

                                     {

                                               ++c;           //前进一步,指向tagName的起始位置

                                               constchar *l = mpLiteral;

                                               while(*l && ::tolower(*c) == *l)     //逐字节将mpLiteral和tagName进行比较

                                               {

                                                        ++c;

                                                        ++l;

                                               }

跳出最后一个while循环时,c和l应当指向的是mpLiteral和tagName从起始位置开始的最小公共子串的结束位置,如果匹配成功则l应当指向字符串的最后一个位置+1,即为空。此处需要注意的是,Mozilla浏览器一旦遇到/plaintext就会停止解析,因此这里需要对此情况作出判断。不过作者此处貌似没有写得很完善,还期待将来做出修改。

 

                                               //FIXME: Mozilla stops when it sees a /plaintext. Check

                                               //other browsers and decide what to do

                                               if(!*l && strcmp(mpLiteral, "plaintext"))

                                               {

                                                        //matched all and is not tag plaintext

                                                        //说明不是plaintext标签,且此时l指针为空

                                                        while(isspace(*c)) ++c; //跳过空格字符

 

                                                        if(*c == '>')                //直到找到标签结束位置

                                                        {

                                                                 ++c;

                                                                 if(begin != end_text)

                                                                           this->parseContent(begin,end_text); //处理这段标签之前的那段“文本”

                                                                 mpLiteral= 0;  //更改循环进入条件

                                                                 c= end_text;  //修改游标值

                                                                 begin= c;          //修改游标值

                                                                 break;                //跳出循环,此时说明已经找到了对应tagName的</tagName>标签

                                                        }

                                               }

 

 

除此之外,后面的一段代码还考虑到了在寻找过程中遇到注释的情况,也需要做出相应处理。

 

 

                                     elseif (*c == '!')

                                     {

                                               // we may find a comment andwe should support it

                                               iteratore(c);

                                               ++e;

                                               if(e != end && *e == '-' && ++e != end && *e == '-')

                                               {

//                                                     DEBUGP("Parsingcomment\n");

                                                        ++e;

                                                        c= this->skipHtmlComment(e, end);

                                               }

                                     }

}

上面这段代码很简单,就是对遇到注释后的进行处理,直接调用skipHtmlComment()方法就可以很方便地进行处理了,关于skipHtmlComment()方法的详细介绍我们会在后面给出。

         到这里为止while(mpLiteral)循环的代码就结束了,下面的代码则是解析一般tag标签的代码。

 

 

                            if(*c == '<')                         //判断是否是一个标签的开始

                            {

                                     iteratord(c);             //申请一个新游标d,初始为这个’<’的位置

                                     ++d;                             //将其后移一位

                                     if(d != end)                //如果不是文档的结尾

                                     {

 

此时d所指向的应当是’<’之后的那个字符,现在要通过对这个字符进行判断来进行不同的处理,一共分四种情况进行处理:普通字符,’/’符号,’!’符号,’?’或’%’符号。分别对应tagName,结束tag标签,注释字段,以及一些特殊字段如<?xml 或 <%VBSCRIPT。

         首先是解析普通起始型标签的:

 

 

                                               if(isalpha(*d))                    //如果d指向的是普通字符

                                               {

                                                        //beginning of tag 说明是tag标签的起始

                                                        if(begin != c)    //如果标签之前还有一段文本

                                                                 this->parseContent(begin,c);          //则对其进行处理

 

//                                                     DEBUGP("Parsingbeginning of tag\n");

                                                        d= this->skipHtmlTag(d, end);         //找到Tag标签的结束位置

                                                        this->parseHtmlTag(c,d);        //对Tag标签的起始位置和结束位置之间的这段标签内容适用parseHtmltag(X,X)进行处理。

 

                                                        //continue from the end of the tag

                                                        c= d;         //将游标移动到标签之后的位置

                                                        begin= c;

                                                        break;       //成功解析出一个节点,跳出循环

                                               }

 

 

其次是解析普通结束型标签的:

 

 

                                               if(*d == '/')

                                               {

                                                        if(begin != c)    //如果标签之前还有一段文本

                                                                 this->parseContent(begin,c); //则对其进行处理

                                                        iteratore(d);    //申请一个新标签e,初始化为d

                                                        ++e;          //将其后移一位

                                                        if(e != end && isalpha(*e))

                                                        {

                                                                 //说明此时e指向的是结尾型标签的起始处

                                                                 d= this->skipHtmlTag(d, end); //寻找标签结束位置

                                                                 this->parseHtmlTag(c,d); //对这段位置之间的标签进行处理

                                                        }

                                                        else

                                                        {

                                                                 //不是一个标准的结束型标签,和Mozilla采用一样的处理方式

                                                                 d= this->skipHtmlTag(d, end);

                                                                 this->parseComment(c,d);

                                                        }

                                                        //将游标移动到tag标签之后继续

                                                        c= d;

                                                        begin= c;

                                                        break;       //解析出一个节点,跳出循环

                                               }

之后是处理注释型标签的,即“<!--”型标签:

 

 

                                               if(*d == '!')

                                               {

                                                        //说明是注释标签

                                                        if(begin != c)  //如果此标签之前还有一段文本

                                                                 this->parseContent(begin,c); //则对其进行处理

 

                                                        iteratore(d);    //申请一个游标e,初始化为d

                                                        ++e;//将e后移一位

 

                                                        if(e != end && *e == '-' && ++e != end && *e == '-')

                                                        {

//                                                              DEBUGP("Parsingcomment\n");

                                                                 ++e;

                                                                 d= this->skipHtmlComment(e, end); //查找到标签结尾

                                                        }

                                                        else

                                                        {

                                                                 d= this->skipHtmlTag(d, end); //也可能注释直到文档结束

                                                        }

 

                                                        this->parseComment(c,d); //将这段文本作为注释进行处理

 

                                                        //移动游标至标签结尾处

                                                        c= d;

                                                        begin= c;

                                                        break;       //成功解析出一个标签,跳出循环

                                               }

 

最后这段代码是处理一些诸如<?xml 或 <%VBSCRIPT之类的特殊标签的:

 

                                               if(*d == '?' || *d == '%')

                                               {

                                               //这段代码很简单

                                                        if(begin != c)    //如果标签之前还有一段文本

                                                                 this->parseContent(begin,c); //则对其进行处理

 

                                                        d= this->skipHtmlTag(d, end);  //找到标签末尾

 

                                                        this->parseComment(c,d);     //对这段标签进行处理

 

                                                        //移动游标至末尾

                                                        c= d;

                                                        begin= c;

                                                        break;//成功解析出标签,跳出循环

                                               }

 

之后还有一点结束代码,以及将游标c移位的代码。

 

                                               }

                                     }

                            }

                            c++;                    //每次while循环后都需要后移游标c

                   }

                   //在所有标签都处理完后,可能在文档末尾还有一些文本要进行处理

                   if(begin != c)

                   {

                            this->parseContent(begin,c);          //处理这段文本

                            begin= c;

                   }

         }

 

DONE:               //作为GOTO代码的位置,即为解析结束后执行的收尾工作

         this->endParsing();

         return;

}

 

至此,Sax方式的html流解析主体代码基本就分析完毕了。下面我们来分析一下上面代码中分别用到的对注释,标签,内容进行解析的几个方法parseComment,parseHtmlTag,parseContent等,这些方法的原理和实现方法都很简单,读者可一目了然。

 

parseComment(_Iteratorb, _Iterator c)

这段代码的形参为两个游标,他们分别指示了要处理的文本的起始位置和结束位置,并将这段文本作为注释来进行处理。

 

template <typename _Iterator>

voidhtmlcxx::HTML::ParserSax::parseComment(_Iterator b, _Iterator c)

{

//      DEBUGP("Creatingcomment node %s\n", std::string(b, c).c_str());

//申请一个新的Node节点

         htmlcxx::HTML::Nodecom_node;

         //FIXME:set_tagname shouldn't be needed, but first I must check

         //legacycode

         //申请一个string类型,用游标b,c之间的这段文本为其初始化赋值

         std::stringcomment(b, c);

         //以下是利用各种变量为代表该Node的一些属性的数据成员进行复制

         //具体方法的细节我们在后面分析Node.h(.cc)的时候再进行解释

         com_node.tagName(comment);     //tag名

         com_node.text(comment);     //文本

         com_node.offset(mCurrentOffset);         //在文档中的偏移值

         com_node.length((unsignedint)comment.length());      //节点长度

         com_node.isTag(false);   //是否是标签

         com_node.isComment(true); //是否是注释

        

         mCurrentOffset+= com_node.length();          //将当前解析位置后移,移动到该节点的结束位置

 

         //Call callback method

         //回调方法,将该节点插入已有的Html树中

         this->foundComment(com_node);

}

 

parseContent(_Iteratorb, _Iterator c)

这段代码的形参为两个游标,他们分别指示了要处理的文本的起始位置和结束位置,并将这段文本作为文本内容来进行处理。

 

template <typename _Iterator>

voidhtmlcxx::HTML::ParserSax::parseContent(_Iterator b, _Iterator c)

{

//      DEBUGP("Creatingtext node %s\n", (std::string(b, c)).c_str());

//申请一个新的Node节点

         htmlcxx::HTML::Nodetxt_node;

         //FIXME:set_tagname shouldn't be needed, but first I must check

         //legacycode

//申请一个string类型,用游标b,c之间的这段文本为其初始化赋值

         std::stringtext(b, c);

         //以下是利用各种变量为代表该Node的一些属性的数据成员进行复制

         //具体方法的细节我们在后面分析Node.h(.cc)的时候再进行解释

         txt_node.tagName(text);        //tag名

         txt_node.text(text);                  //文本

         txt_node.offset(mCurrentOffset);  //在文档中的偏移值

         txt_node.length((unsignedint)text.length());                   //节点长度

         txt_node.isTag(false);      //是否是标签

         txt_node.isComment(false);   //是否是注释

 

         mCurrentOffset+= txt_node.length();    //将当前解析位置后移,移动到该节点的结束位置

 

         //Call callback method

//回调方法,将该节点插入已有的Html树中

         this->foundText(txt_node);

}

parseHtmlTag(_Iteratorb, _Iterator c)

这段代码的形参为两个游标,他们分别指示了要处理的文本的起始位置和结束位置,并将这段文本作为Html标签来进行处理。

我们分三段来看这段代码,首先看第一段:

 

template <typename _Iterator>

voidhtmlcxx::HTML::ParserSax::parseHtmlTag(_Iterator b, _Iterator c)

{

         _Iteratorname_begin(b);        //申请一个新的游标name_begin游标,初始为b

         ++name_begin;        //将name_begin后移一位

         boolis_end_tag = (*name_begin == '/');          //判断name_begin是否是’/’,如果是则说明是结束型tag标签

         if(is_end_tag) ++name_begin;        //如果是结束型的tag标签,则再将name_begin前进一位,跳过’/’符号

 

         _Iteratorname_end(name_begin);         //申请一个新的name_end游标,记录一下name_begin标签。

         while(name_end != c && isalnum(*name_end))  //将name_end后移,直到遇到了文档末尾或者普通字符为止。

         {

                   ++name_end;

         }

 

         std::stringname(name_begin, name_end); //取name_begin和name_end之间的这段文本认为是tagName并赋值给一个string型的变量name

 

之后,我们要对这个Name做一些判断,判断其是否是特殊类型的tag标签,即是否是前面literal_mode_elem[]数组中的某个标签。

 

         if(!is_end_tag)       //当然首先得判断这不是一个结束型的标签

         {

                   std::string::size_typetag_len = name.length();

                   for(int i = 0; literal_mode_elem[i].len; ++i)     //循环进行查找

                   {

                            if(tag_len == literal_mode_elem[i].len)  //进行比较

                            {

                                #ifdefined(WIN32) && !defined(__MINGW32__) //这段就是根据系统采用不同的string比较函数

                                     if(!_stricmp(name.c_str(), literal_mode_elem[i].str))

                                     #else

                                     if(!strcasecmp(name.c_str(), literal_mode_elem[i].str))

                                     #endif

                                     {

                                               mpLiteral= literal_mode_elem[i].str;     //如果找到,则将其赋值为mpLiteral

                                               break;       //并退出循环

                                     }

                            }

                   }

         }

之后,我们就可以将其作为Node节点来做一些存储的工作了。

 

         htmlcxx::HTML::Nodetag_node;

         //bynow, length is just the size of the tag

         //注意到,目前这还只是起始节点,因此长度只是<tag>本身的长度

         std::stringtext(b, c);       

         tag_node.length(static_cast<unsignedint>(text.length()));  //节点长度

         tag_node.tagName(name);    //tag名

         tag_node.text(text);        //节点文本

         tag_node.offset(mCurrentOffset);          //在文档中的偏移位置

         tag_node.isTag(true);      //是否是tag标签

         tag_node.isComment(false);  //是否是注释

 

         mCurrentOffset+= tag_node.length();   //将当前的解析位置后移

 

         this->foundTag(tag_node,is_end_tag);         //调用方法将其加入到已有的Html树中

}

 

 

这个文件中的最后还有几个小方法,作用和思路都很简单,主要都是为上面几个方法进行服务的,我们简单地作一下介绍。

 

find_next_quote(_Iteratorc, _Iterator end, char quote)

本方法主要是从游标c处开始,直到游标end之间,寻找到第一个quote字符的位置并返回其游标,未找到则返回end的位置。

 

 template <typename _Iterator>

static inline

_Iterator find_next_quote(_Iterator c,_Iterator end, char quote)

{

//      std::cerr<< "generic find" << std::endl;

         while(c != end && *c != quote) ++c;

         returnc;

}

*find_next_quote(constchar *c, const char *end, char quote)

本方法主要是从字符指针*c开始,直到字符指针*end之间,寻找到第一个quote字符的位置并返回指向其的字符指针,未找到则返回*end指针。

 

 template <>

inline

const char *find_next_quote(const char *c,const char *end, char quote)

{

//      std::cerr<< "fast find" << std::endl;

         constchar *d = reinterpret_cast<const char*>(memchr(c, quote, end - c));

 

         if(d) return d;

         elsereturn end;

}

skipHtmlTag(_Iteratorc, _Iterator end)

从游标c的位置开始,直到游标end的位置之间,寻找到该标签所对应的结束’>’符号的位置,并以游标形式返回。其中要注意对tag标签内的属性做了另外的处理,即能够识别并跳过属性中的’>’符号,防止出错。

 

 

template <typename _Iterator>

_Iteratorhtmlcxx::HTML::ParserSax::skipHtmlTag(_Iterator c, _Iterator end)

{

         while(c != end && *c != '>')

         {

                   if(*c != '=')

                   {

                            ++c;

                   }

                   else

                   {// found an attribute

                            ++c;

                            while(c != end && isspace(*c)) ++c;

                            if(c == end) break;

                            if(*c == '\"' || *c == '\'')

                            {

                                     _Iteratorsave(c);

                                     charquote = *c++;

                                     c= find_next_quote(c, end, quote);

//                                  while(c != end && *c != quote) ++c;

//                                  c= static_cast<char*>(memchr(c, quote, end - c));

                                     if(c != end)

                                     {

                                               ++c;

                                     }

                                     else

                                     {

                                               c= save;

                                               ++c;

                                     }

//                                  DEBUGP("Quotes:%s\n", std::string(save, c).c_str());

                            }

                   }

         }

         if(c != end) ++c;

         returnc;

}

2.5 Node.h(.cc)

Node.h里定义了树的每个节点的数据类型,重载了一些运算符使其方便进行一些逻辑运算,并提供了根据节点内文本分析和存储节点属性的parseAttributes()方法。我们在使用HtmlCxx进行html解析的时候,经常会对Node数据类型进行操作以及用Node进行输出,因此理解其的数据结构十分重要。

 

首先我们来看Node.h头文件的定义,其数据成员全部声明为protected类型:

 

                             protected:

                                     std::stringmText;    //节点的内部文本

                                     std::stringmClosingText;        

                                     unsignedint mOffset;      //节点在原文档中的偏移值

                                     unsignedint mLength;     //节点的字符长度的

                                     std::stringmTagName;   //节点的Tag名

                                     std::map<std::string,std::string> mAttributes; //存放属性的二维数组

                                     boolmIsHtmlTag;    //节点是否是HtmlTag标签

                                     boolmComment;     //节点是否是注释

再看它的成员函数,全部为public。

 

                            public:

                                     Node(){}          //构造函数

                                     //Node(constNode &rhs); //uses default

                                     ~Node(){}         //析构函数

 

                                     inlinevoid text(const std::string& text) { this->mText = text; }

                                     inlineconst std::string& text() const { return this->mText; }

 

                                     inlinevoid closingText(const std::string &text) { this->mClosingText = text; }

                                     inlineconst std::string& closingText() const { return mClosingText; }

 

                                     inlinevoid offset(unsigned int offset) { this->mOffset = offset; }

                                     inlineunsigned int offset() const { return this->mOffset; }

 

                                     inlinevoid length(unsigned int length) { this->mLength = length; }

                                     inlineunsigned int length() const { return this->mLength; }

 

                                     inlinevoid tagName(const std::string& tagname) { this->mTagName = tagname; }

                                     inlineconst std::string& tagName() const { return this->mTagName; }

 

                                     boolisTag() const { return this->mIsHtmlTag; }

                                     voidisTag(bool is_html_tag){ this->mIsHtmlTag = is_html_tag; }

 

                                     boolisComment() const { return this->mComment; }

                                     voidisComment(bool comment){ this->mComment = comment; }

以上所有函数都是对各个数据成员的简单的赋值取值函数,十分简单就不加以解释了。

主要了解一下下面这个函数

 

                                     std::pair<bool,std::string> attribute(const std::string &attr) const

                                     {

                                               std::map<std::string,std::string>::const_iterator i = this->mAttributes.find(attr);

                                               if(i != this->mAttributes.end()) {

                                                        returnmake_pair(true, i->second);

                                               }else {

                                                        returnmake_pair(false, std::string());

                                               }

                                     }

 

 

该函数主要功能是根据参数string &attr,查找到mAttributes[]中的某个项,并且返回该项的第二个成员值,即根据属性的名称查找该属性的值。返回的形式为一个make_pair数据类型,如果不为最后一个属性,则为make_pair(true,属性值),而如果是最后一个属性则返回一个make_pair(false,空字符串)。

之后还有一些关于操作符重载的声明:

 

                                     operatorstd::string() const;

                                     std::ostream&operator<<(std::ostream &stream) const;

 

                                     conststd::map<std::string, std::string>& attributes() const { returnthis->mAttributes; }

                                     voidparseAttributes();

 

                                     booloperator==(const Node &rhs) const;

 

下面我们来看Node.cc文件中的内容。

 

parseAttributes()

由于这段代码较长,因此我们也分段对其进行解析。这段代码默认在mText中已经存放了节点的文本数据。并且通过对该文本数据进行解析,将所有诸如<…..ID = “abc”…>之类的属性信息识别并储存起来。

 

          if(!(this->isTag())) return;        //判断是否是Tag标签节点,不是则返回

          constchar *end;

         constchar *ptr = mText.c_str();      //*ptr作为mText的*char类型指针

         if((ptr = strchr(ptr, '<')) == 0) return;       //将ptr指向字符串中首先出现的<位置,并判断该位置是否合法,如果不合法则返回。

         ++ptr;       //将ptr后移一个字节

 

          //Skip initial blankspace

         while(isspace(*ptr)) ++ptr;     //跳过接下来所有遇到的空格字符

        

         //Skip tagname

         if(!isalpha(*ptr)) return;          //判断是否是普通字符,不是则返回

         while(!isspace(*ptr)) ++ptr;   //不断后移ptr指针,直到遇到空格字符

 

         //Skip blankspace after tagname

         while(isspace(*ptr)) ++ptr;     //跳过接下来所有遇到的空格字符

 

下面进入一个循环,该循环将一直不断向后解析该tag标签,直到遇到了’>’字符或者指针为空才退出。

 

           while(*ptr && *ptr != '>')

         {

                   stringkey, val;

 

                   //跳过所有无法识别的字节

                   while(*ptr && !isalnum(*ptr) && !isspace(*ptr)) ++ptr;

 

                   //跳过所有的空格字符

                   while(isspace(*ptr)) ++ptr;

                  

                   end= ptr;

                   //将end指向ptr之后第一个不为普通字符或’-’字符的位置

                   while(isalnum(*end) || *end == '-') ++end;

                   //将ptr至end之间的这段文本变为小写字符赋值给key

                   key.assign(end- ptr, '\0');        

                   transform(ptr,end, key.begin(), ::tolower);

                   ptr= end;

                   //此时key中应当存放的是属性名

                   //跳过所有的空格字符

                   while(isspace(*ptr)) ++ptr;

 

下面是解析并获取属性的值

          while(*ptr && *ptr != '>')

         {

                   stringkey, val;

 

                   //跳过所有无法识别的字节

                   while(*ptr && !isalnum(*ptr) && !isspace(*ptr)) ++ptr;

 

                   //跳过所有的空格字符

                   while(isspace(*ptr)) ++ptr;

                  

                   end= ptr;

                   //将end指向ptr之后第一个不为普通字符或’-’字符的位置

                   while(isalnum(*end) || *end == '-') ++end;

                   //将ptr至end之间的这段文本变为小写字符赋值给key

                   key.assign(end- ptr, '\0');  //注意要加上’\0’

                   transform(ptr,end, key.begin(), ::tolower);

                   ptr= end;

                   //此时key中应当存放的是属性名

                   //跳过所有的空格字符

                   while(isspace(*ptr)) ++ptr;

                   if(*ptr == '=')           //如果后面跟的是’=’符号,说明后面就是属性值

                   {

                            ++ptr;                //将ptr后移一位

                            while(isspace(*ptr)) ++ptr;     //跳过所有的空格字符

                            if(*ptr == '"' || *ptr == '\'')     //判断属性值是否被引号括起

                            {

                                     charquote = *ptr;

//                                  fprintf(stderr,"Trying to find quote: %c\n", quote);

                                     constchar *end = strchr(ptr + 1, quote);

                                     if(end == 0)

                                     {

                                               //b= mText.find_first_of(" >", a+1);

                                               constchar *end1, *end2;

                                               end1= strchr(ptr + 1, ' ');

                                               end2= strchr(ptr + 1, '>');

                                               if(end1 && end1 < end2) end = end1;

                                               elseend = end2;

                                               if(end == 0) return;

                                     }

                                     constchar *begin = ptr + 1;

                                     while(isspace(*begin) && begin < end) ++begin;

                                     constchar *trimmed_end = end - 1;

                                     while(isspace(*trimmed_end) && trimmed_end >= begin) --trimmed_end;

                                     val.assign(begin,trimmed_end + 1);

                                     ptr= end + 1;

                            }

                            else                  //否则直接获取其值

                            {

                                     end= ptr;

                                     while(*end && !isspace(*end) && *end != '>') end++;

                                     val.assign(ptr,end);

                                     ptr= end;

                            }

//                         fprintf(stderr,"%s = %s\n", key.c_str(), val.c_str());

                            mAttributes.insert(make_pair(key,val));

                   }

                   else//否则说明格式有错误,并将该key对应的值按空字符串插入结果集

                   {

//                         fprintf(stderr,"D: %s\n", key.c_str());

                            mAttributes.insert(make_pair(key,string()));

                   }

         }

}

 

之后,还有几个关于操作符重载的代码,很好理解,这里仅仅将其列出来就不详细说明了。

bool Node::operator==(const Node &n)const //判断两个Node是否都为tag标签,且tag名是否相等

{

         if(!isTag() || !n.isTag()) return false;

         return!(strcasecmp(tagName().c_str(), n.tagName().c_str()));

}

 

Node::operator string() const {                 //根据是否是tag标签,返回tagName或者text

         if(isTag()) return this->tagName();

         returnthis->text();

}

 

ostream &Node::operator<<(ostream&stream) const {       //按对象输出Node

         stream<< (string)(*this);

         returnstream;

}

 

三.总结

 对HtmlCxx进行学习,可以很好地掌握标准Html解析的流程,以及解析器的内部结构,并掌握C++编程时的一些好习惯和系统组织方式。遗憾的是HtmlCxx在2005年之后就不再更新,且仅仅支持Mozilla的Html标准。它对Html的良构性要求比较高,对于一些含有错误的Html文档,在解析时可能会出现一些意想不到的错误。

在编程过程中,我们可以针对具体的需求,很方便地对其的内部数据结构进行调整,如为节点新加一些属性等。

目前的文档中没有包含tree.h的代码说明,因为它属于第三方代码,希望了解的可以去http://tree.phi-sci.com/documentation.html了解具体的情况。后续有机会还会专门写一篇关于这个tree.h的文档。

如果对此文档中的代码解析有什么问题,或者发现了什么问题,请您发送邮件到我的邮箱gengyun@sohu.com。

 

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 关于导入项目找不到javax.annotation.Nullable的问题

    这几天在项目中导入源码时,发现 import javax.annotation.Nullable处报错。仔细观察,发现在JDK中javax.annotation文件夹下并没有Nullable这个类。经过苦苦搜索,终于发现问题,底层库中并没有使用jdk中的annotation,很多博客上说导入android库里的annotation,这样的确可以…...

    2024/4/17 19:15:47
  2. ajaxPro 在vs2010 MVC下使用

    在global中加入下句代码 routes.IgnoreRoute("ajaxpro/{*pathInfo}"); 原帖: Ajax.NET is the first Ajax library for the .NET framework. Up until the day ASP .NET 3.5 was released, there was nothing quite like it. In ASP .NET 2.0, the UpdatePanel was …...

    2024/5/5 17:09:10
  3. Fragment onViewCreated 的作用

    onViewCreated 这个也是自己经常使用的到的,今天礼拜天在家复习知识点 这里记录一下Fragment 是 3.0版本 API 11 的时候引入的,刚开始的时候并没有onViewCreated ,它是在API 13的时候引入的 估计这个就是为啥Fragment 生命周期里面没有onViewCreated吧但是onViewCreated 在F…...

    2024/5/2 13:43:53
  4. 利用c语言栈实现括号匹配

    栈是一种只允许在栈顶增加和删除元素的数据结构。 利用栈实现括号匹配会非常简单,只需要设置一个栈,指向栈顶的top;利用top++或top–实现入栈和出栈。 实现匹配还需要条件判断,当出现右括号时候,就与栈顶元素比较。#include <stdio.h> #include <stdlib.h> #i…...

    2024/5/5 18:34:16
  5. 小甲鱼Python3学习笔记之第十九讲(仅记录学习)

    第十九讲:函数,我的地盘听我的一、知识点:0.函数与过程:过程(procedure)是简单的,特殊且没有返回值。函数有返回值。Python严格来说只有函数没有过程。1.局部变量:在局部生效的变量,如在函数中定义的变量。2.全局变量:在函数外定义的变量,作用于整个模块。函数内若试…...

    2024/5/5 21:20:00
  6. AjaxPro版自动完成(Autocomplete)功能实现

    07年的时候写过一篇有关自动完成(Atuocomplete)的文章 asp.net Ajax ---AutoComplete控件使用 ,那篇文章中使用的是Asp.net Ajax ControlTollKit中的一个控件,虽然那时对里面几十个控件都研究过,不过遗憾的是在实际开发中确从未用到过,鉴于现在Ajaxpro的易用性和普遍性,本…...

    2024/4/10 8:29:57
  7. 分享:Web开发必备手册大集合,不断更新中...

    分享:Web开发必备手册大集合,不断更新中... 您在网上找到的一些手册可能不完整、不清晰、或者不是易用的版本。 这里整理了较完善、易用的官方手册,以及中文版本。经过老技术们使用OK的,并作了说明。希望对您日常工作或学习有帮助。 代码大全(第2版)中文版PHP5中文参考手册…...

    2024/4/12 6:12:09
  8. 小甲鱼python零基础017函数:Python的乐高积木

    一、测试题0. 你有听说过DRY吗?1. 都是重复一段代码,为什么我要使用函数(而不使用简单的拷贝黏贴)呢?2. 函数可以有多个参数吗?3. 创建函数使用什么关键字,要注意什么?4. 请问这个函数有多少个参数?1. def MyFun((x, y), (a, b)): 2. return x * y - a * b5. 请问…...

    2024/4/13 5:14:45
  9. leetcode 20. 简单括号匹配

    leetcode 20 简单括号匹配今天开始,记录自己做的leetcode的题。 使用c# 第一次发博,本人大四实习生,水平有限。有错望海涵。思路括号有三种:[] 、{} 、() 空字符串为有效字符串 括号之内可以包含括号 从左到右开始匹配括号,第一个匹配完成的括号一定是最里面的那组括号…...

    2024/4/19 8:51:51
  10. 源码解析Android架构组件ViewModel

    ViewModel是google官方的MVVM架构组件,目前已经集成到了最新的支持库中了,是MVVM架构的核心组件之一。不懂MVVM的请看之前的文章:(一)Android官方MVVM框架实现组件化之整体结构 网上看到的ViewModel的博文千篇一律,实在忍不了,自己写看了源码写了一篇,欢迎拍砖!ViewMode…...

    2024/4/16 21:08:59
  11. 栈的应用一之括号匹配问题

    括号匹配问题:给一个类似这样的字符串:char a[]="(())abc{[(])}";检测三种括号的左右括号是否匹配?分析:先取出一个字符,并判断是不是括号(任意括号)?1. 不是括号,取下一个字符。2. 是括号。(1) 是左括号。压栈(2) 是右括号。和栈顶元素比较① 栈…...

    2024/4/18 11:07:01
  12. androidx中的Fragment懒加载方案

    在进入正文之前要强调一下,本文的分析基于androidx 1.1.0版本,文中提到的setMaxLifecycle()方法是1.1.0-alpha07版本才引入的。 最近把Android Studio更新到了3.5版本,新建项目时发现竟然已经强制使用androidx包了。于是想着把以前项目中的一些公共类,像BaseActivity、Base…...

    2024/4/12 19:29:12
  13. 《ABAQUS 6.14超级学习手册》——1.5 ABAQUS帮助文档

    本节书摘来自异步社区《ABAQUS 6.14超级学习手册》一书中的第1章,第1.5节,作者: 齐威 更多章节内容可以访问云栖社区“异步社区”公众号查看。 1.5 ABAQUS帮助文档 ABAQUS为用户提供了便捷和详实的帮助文档,帮助各个层次的用户完成自己的分析。下面将对ABAQUS的帮助文档进行…...

    2024/4/18 0:13:56
  14. [Python]小甲鱼Python视频第018课(函数:灵活即强大)课后题及参考解答

    # -*- coding: utf-8 -*- """ Created on Wed Mar 6 19:28:00 2019@author: Administrator """"""测试题:0. 请问以下哪个是形参哪个是实参? def MyFun(x):return x ** 3y = 3 print(MyFun(y))y是实参 x是形参1. 函数文档和直…...

    2024/4/20 17:41:08
  15. RxUtil 一个RxJava实用工具类的集合

    RxUtil一个实用的RxJava1工具类库如果你使用的是RxJava2,请移步RxUtil2关于我内容RxBus 支持多事件定义,支持数据携带,支持全局和局部的事件订阅和注销 订阅池管理 线程调度辅助工具 RxBinding 使用工具类 RxJava常用方法工具类1、演示(请star支持) 1.1、RxBusDemo下载2、…...

    2024/4/19 12:02:58
  16. 小甲鱼python视频006Pyhon之常用操作符--笔记 2018.1.1

    知识点1:算术操作符,先乘除后加减 参考《笨办法学python》习题3 // 不管整数还是浮点数的除法,它都会是一个地板除法 >>> 10//8 1 **幂运算 >>> 3**2 #3为底数,2为指数 9 幂运算优先级:比其左侧的一元操作符优先级高,比其右侧的一元操作符优先级低 &g…...

    2024/4/12 6:12:09
  17. PHP - Manual手册 - 下载

    PHP - Manual手册 - 下载[PHP: Download documentation:] http://www.php.net/download-docs.php [PHP - 官方网站] http://www.php.net/[PHP - 关键词] php[PHP - 相关论坛] http://php.board.newsmth.net/http://bbs.pku.edu.cn/, homepage看版http://forum.csdn.net/SLis…...

    2024/4/15 14:50:12
  18. 项目模版(C#),已配置好 Log4net 、AjaxPro 和 AjaxToolKit

    一个简单的项目模版,已配置好 Log4net 、AjaxPro 和 AjaxToolKit,并有简单的例子!使用步骤: 1)将下载的 TongYong.zip 放到vs的安装目录 :Microsoft Visual Studio 8/Common7/IDE/ProjectTemplates/CSharp/Web/10332)模版的使用1. 打开vs 20052. 新建项目3. 选择编程语言…...

    2024/4/17 10:37:14
  19. 【Python】最长括号匹配问题:给定字符串,仅包含左括号‘(’和右括号‘)’,它可能不是括号匹配的,设计算法,找出最长匹配的括号子串

    最长括号匹配 示例:给定字符串,仅包含左括号‘(’和右括号‘)’,它可能不是括号匹配的,设计算法,找出最长匹配的括号子串。算法分析只有在右括号和左括号发生匹配时,才有可能更新最终解。 计算s[0…i]中左括号数目与右括号数目的差x,若x为0时,考察最终解是否可以更新,…...

    2024/4/18 2:23:04
  20. Canvas.drawBitmap()方法绘图空白

    问题出现的场景:将一个ImageView上原有的Bitmap进行放缩操作后,重新设置到该ImageView上。放缩部分代码如下:private void bitmapScale(float x,float y){Bitmap newBitmap = Bitmap.createBitmap((int) (mBitmap.getWidth() * x),(int) (mBitmap.getHeight() * y), mBitmap…...

    2024/4/12 12:44:08

最新文章

  1. 今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 5月5日,星期日

    每天一分钟&#xff0c;知晓天下事&#xff01; 2024年5月5日 星期日 农历三月廿七 立夏 1、 近日国际金价大幅震荡&#xff0c;跌至近一个月新低。 2、 2024亚洲少年攀岩锦标赛&#xff1a;中国选手包揽U14和U12速度赛男女组前三名。 3、 马来西亚将进一步优化中国游客入境程…...

    2024/5/5 21:55:41
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 五一假期来临,各地景区云旅游、慢直播方案设计与平台搭建

    一、行业背景 经文化和旅游部数据中心测算&#xff0c;今年清明节假期3天全国国内旅游出游1.19亿人次&#xff0c;按可比口径较2019年同期增长11.5%&#xff1b;国内游客出游花费539.5亿元&#xff0c;较2019年同期增长12.7%。踏青赏花和户外徒步成为假期的热门出游主题。随着…...

    2024/4/30 7:54:16
  4. 计组第三版书例题

    基础知识过一下 存储器与CPU的连接主要通过数据总线、地址总线和控制总线实现。CPU首先向存储器发送地址信号&#xff0c;然后发出读写控制信号&#xff0c;最后在数据总线上进行数据的读写操作 。这种连接方式确保了CPU能够正确地访问和控制存储器中的数据。 https://blog.cs…...

    2024/5/5 20:23:31
  5. 星际门计划:微软与OpenAI联手打造未来AI超级计算机

    每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

    2024/5/5 9:17:00
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/4 23:54:56
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/4 23:54:56
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/4 23:55:17
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/4 23:55:16
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/4 18:20:48
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/4 23:55:06
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/4 23:55:01
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57