/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ * */#ifndef __XMLParser_h__#define __XMLParser_h__#include "StringParser.h"#include "OSQueue.h"#include "OSFileSource.h"#include "ResizeableStringFormatter.h"class DTDVerifier{public: virtual bool IsValidSubtag(char* tagName, char* subTagName) = 0; virtual bool IsValidAttributeName(char* tagName, char* attrName) = 0; virtual bool IsValidAttributeValue(char* tagName, char* attrName, char* attrValue) = 0; virtual char* GetRequiredAttribute(char* tagName, int index) = 0; virtual bool CanHaveValue(char* tagName) = 0;};class XMLTag{public: XMLTag(); XMLTag(char* tagName); ~XMLTag(); bool ParseTag(StringParser* parser, DTDVerifier* verifier, char* errorBuffer = NULL, int errorBufferSize = 0); //获取当前Tag下的某个属性值 char* GetAttributeValue(const char* attrName); //获取Tag值 char* GetValue() { return fValue; } //获取Tag名 char* GetTagName() { return fTag; } //获取嵌套Tag数量 UInt32 GetNumEmbeddedTags() { return fEmbeddedTags.GetLength(); } //根据索引值获取嵌套Tag XMLTag* GetEmbeddedTag(const UInt32 index = 0); //根据索引和tag名称获取嵌套Tag XMLTag* GetEmbeddedTagByName(const char* tagName, const UInt32 index = 0); //根据索引及属性获取嵌套Tag XMLTag* GetEmbeddedTagByAttr(const char* attrName, const char* attrValue, const UInt32 index = 0); //根据索引及tag名称及属性获取嵌套Tag XMLTag* GetEmbeddedTagByNameAndAttr(const char* tagName, const char* attrName, const char* attrValue, const UInt32 index = 0); //添加属性 void AddAttribute(char* attrName, char* attrValue); //删除属性 void RemoveAttribute(char* attrName); //添加嵌套Tag void AddEmbeddedTag(XMLTag* tag); //删除嵌套Tag void RemoveEmbeddedTag(XMLTag* tag); //设置当前TAG名称 void SetTagName( char* name); //设置当前Tag值 void SetValue( char* value); void FormatData(ResizeableStringFormatter* formatter, UInt32 indent);private: void ConsumeIfComment(StringParser* parser); char* fTag; //当前Tag名称 char* fValue; //当前Tag值 OSQueue fAttributes; //当前Tag的一组属性 OSQueue fEmbeddedTags; //当前Tag嵌套的一堆Tag OSQueueElem fElem; //作为Tag对列的一个元素,通过该元素就能访问整个XMLTag类 static UInt8 sNonNameMask[]; // stop when you hit a Word};class XMLAttribute{public: XMLAttribute(); ~XMLAttribute(); char* fAttrName; //属性名称 char* fAttrValue; //属性值 OSQueueElem fElem; //作为XMLAttribute对列的一个元素,通过该元素就能访问整个XMLAttribute类};class XMLParser{public: XMLParser( char* inPath, DTDVerifier* verifier = NULL); ~XMLParser(); // Check for existence, man. Bool16 DoesFileExist(); Bool16 DoesFileExistAsDirectory(); Bool16 CanWriteFile(); Bool16 ParseFile(char* errorBuffer = NULL, int errorBufferSize = 0); XMLTag* GetRootTag() { return fRootTag; } void SetRootTag(XMLTag* tag); void WriteToFile(char** fileHeader); private: XMLTag* fRootTag; OSFileSource fFile; char* fFilePath; DTDVerifier* fVerifier;};#endif/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ * */#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#ifndef __Win32__#include <unistd.h>#endif#include "XMLParser.h"#include "OSMemory.h"XMLParser::XMLParser( char* inPath, DTDVerifier* verifier) : fRootTag(NULL), fFilePath(NULL){ StrPtrLen thePath(inPath); fFilePath = thePath.GetAsCString(); fFile.Set(inPath); fVerifier = verifier;}XMLParser::~XMLParser(){ if (fRootTag) delete fRootTag; delete [] fFilePath;}Bool16 XMLParser::ParseFile(char* errorBuffer, int errorBufferSize){ if (fRootTag != NULL) { delete fRootTag; // flush old data fRootTag = NULL; } fFile.Set(fFilePath); if (errorBufferSize < 500) errorBuffer = NULL; // Just a hack to avoid checking everywhere if ((fFile.GetLength() == 0) || fFile.IsDir()) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Couldn't read xml file"); return false; // we don't have a valid file; } char* fileData = NEW char[ (SInt32) (fFile.GetLength() + 1)]; UInt32 theLengthRead = 0; fFile.Read(0, fileData, (UInt32) fFile.GetLength(), &theLengthRead); StrPtrLen theDataPtr(fileData, theLengthRead); StringParser theParser(&theDataPtr); fRootTag = NEW XMLTag(); Bool16 result = fRootTag->ParseTag(&theParser, fVerifier, errorBuffer, errorBufferSize); if (!result) { // got error parsing file delete fRootTag; fRootTag = NULL; } delete fileData; fFile.Close(); return result;}Bool16 XMLParser::DoesFileExist(){ Bool16 itExists = false; fFile.Set(fFilePath); if ((fFile.GetLength() > 0) && (!fFile.IsDir())) itExists = true; fFile.Close(); return itExists;}Bool16 XMLParser::DoesFileExistAsDirectory(){ Bool16 itExists = false; fFile.Set(fFilePath); if (fFile.IsDir()) itExists = true; fFile.Close(); return itExists;}Bool16 XMLParser::CanWriteFile(){ // // First check if it exists for reading FILE* theFile = ::fopen(fFilePath, "r"); if (theFile == NULL) return true; ::fclose(theFile); // // File exists for reading, check if we can write it theFile = ::fopen(fFilePath, "a"); if (theFile == NULL) return false; // // We can read and write ::fclose(theFile); return true;}void XMLParser::SetRootTag(XMLTag* tag){ if (fRootTag != NULL) delete fRootTag; fRootTag = tag;} void XMLParser::WriteToFile(char** fileHeader){ char theBuffer[8192]; ResizeableStringFormatter formatter(theBuffer, 8192); // // Write the file header for (UInt32 a = 0; fileHeader[a] != NULL; a++) { formatter.Put(fileHeader[a]); formatter.Put(kEOLString); } if (fRootTag) fRootTag->FormatData(&formatter, 0); // // New libC code. This seems to work better on Win32 formatter.PutTerminator(); FILE* theFile = ::fopen(fFilePath, "w"); if (theFile == NULL) return; qtss_fprintf(theFile, "%s", formatter.GetBufPtr()); ::fclose(theFile); #if __MacOSX__ (void) ::chown(fFilePath,76,80);//owner qtss, group admin#endif#ifndef __Win32__ ::chmod(fFilePath, S_IRUSR | S_IWUSR | S_IRGRP );#endif}UInt8 XMLTag::sNonNameMask[] ={ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //0-9 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //10-19 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //20-29 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //30-39 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, //40-49 '.' and '-' are name chars 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //50-59 ':' is a name char 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, //60-69 //stop on every character except a letter or number 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, //90-99 '_' is a name char 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, //120-129 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //130-139 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //140-149 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //150-159 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //160-169 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //170-179 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //180-189 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //190-199 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //200-209 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //210-219 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //220-229 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //230-239 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //240-249 1, 1, 1, 1, 1, 1 //250-255};XMLTag::XMLTag() : fTag(NULL), fValue(NULL), fElem(NULL){ //如此赋值后,就可以通过fElem访问整个类。 fElem = this;}XMLTag::XMLTag(char* tagName) : fTag(NULL), fValue(NULL), fElem(NULL){ fElem = this; StrPtrLen temp(tagName); fTag = temp.GetAsCString();}XMLTag::~XMLTag(){ if (fTag) delete fTag; if (fValue) delete fValue; OSQueueElem* elem; while ((elem = fAttributes.DeQueue()) != NULL) { XMLAttribute* attr = (XMLAttribute*)elem->GetEnclosingObject(); delete attr; } while ((elem = fEmbeddedTags.DeQueue()) != NULL) { XMLTag* tag = (XMLTag*)elem->GetEnclosingObject(); delete tag; } if (fElem.IsMemberOfAnyQueue()) fElem.InQueue()->Remove(&fElem); // remove from parent tag}void XMLTag::ConsumeIfComment(StringParser* parser){ if ((parser->GetDataRemaining() > 2) && ((*parser)[1] == '-') && ((*parser)[2] == '-')) { // this is a comment, so skip to end of comment parser->ConsumeLength(NULL, 2); // skip '--' // look for --> while((parser->GetDataRemaining() > 2) && ((parser->PeekFast() != '-') || ((*parser)[1] != '-') || ((*parser)[2] != '>'))) { if (parser->PeekFast() == '-') parser->ConsumeLength(NULL, 1); parser->ConsumeUntil(NULL, '-'); } if (parser->GetDataRemaining() > 2) parser->ConsumeLength(NULL, 3); // consume --> }}bool XMLTag::ParseTag(StringParser* parser, DTDVerifier* verifier, char* errorBuffer, int errorBufferSize){ while (true) { if (!parser->GetThru(NULL, '<')) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Couldn't find a valid tag"); return false; // couldn't find beginning of tag } char c = parser->PeekFast(); if (c == '/') { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "End tag with no begin tag on line %d", parser->GetCurrentLineNumber()); return false; // we shouldn't be seeing a close tag here } if ((c != '!') && (c != '?')) break; // this should be the beginning of a regular tag ConsumeIfComment(parser); // otherwise this is a processing instruction or a c-data, so look for the next tag } int tagStartLine = parser->GetCurrentLineNumber(); StrPtrLen temp; parser->ConsumeUntil(&temp, sNonNameMask); if (temp.Len == 0) { if (errorBuffer != NULL) { if (parser->GetDataRemaining() == 0) qtss_sprintf(errorBuffer, "Unexpected end of file on line %d", parser->GetCurrentLineNumber()); else qtss_sprintf(errorBuffer, "Unexpected character (%c) on line %d", parser->PeekFast(), parser->GetCurrentLineNumber()); } return false; // bad file } fTag = temp.GetAsCString(); parser->ConsumeWhitespace(); while ((parser->PeekFast() != '>') && (parser->PeekFast() != '/')) { // we must have an attribute value for this tag XMLAttribute* attr = new XMLAttribute; fAttributes.EnQueue(&attr->fElem); parser->ConsumeUntil(&temp, sNonNameMask); if (temp.Len == 0) { if (errorBuffer != NULL) { if (parser->GetDataRemaining() == 0) qtss_sprintf(errorBuffer, "Unexpected end of file on line %d", parser->GetCurrentLineNumber()); else qtss_sprintf(errorBuffer, "Unexpected character (%c) on line %d", parser->PeekFast(), parser->GetCurrentLineNumber()); } return false; // bad file } attr->fAttrName = temp.GetAsCString(); if (!parser->Expect('=')) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Missing '=' after attribute %s on line %d", attr->fAttrName, parser->GetCurrentLineNumber()); return false; // bad attribute specification } if (!parser->Expect('"')) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Attribute %s value not in quotes on line %d", attr->fAttrName, parser->GetCurrentLineNumber()); return false; // bad attribute specification } parser->ConsumeUntil(&temp, '"'); attr->fAttrValue = temp.GetAsCString(); if (!parser->Expect('"')) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Attribute %s value not in quotes on line %d", attr->fAttrName, parser->GetCurrentLineNumber()); return false; // bad attribute specification } if (verifier && !verifier->IsValidAttributeName(fTag, attr->fAttrName)) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Attribute %s not allowed in tag %s on line %d", attr->fAttrName, fTag, parser->GetCurrentLineNumber()); return false; // bad attribute specification } if (verifier && !verifier->IsValidAttributeValue(fTag, attr->fAttrName, attr->fAttrValue)) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Bad value for attribute %s on line %d", attr->fAttrName, parser->GetCurrentLineNumber()); return false; // bad attribute specification } parser->ConsumeWhitespace(); } if (parser->PeekFast() == '/') { // this is an empty element tag, i.e. no contents or end tag (e.g <TAG attr="value" /> parser->Expect('/'); if (!parser->Expect('>')) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "'>' must follow '/' on line %d", parser->GetCurrentLineNumber()); return false; // bad attribute specification } return true; // we're done with this tag } if (!parser->Expect('>')) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Bad format for tag <%s> on line %d", fTag, parser->GetCurrentLineNumber()); return false; // bad attribute specification } while(true) { parser->ConsumeUntil(&temp, '<'); // this is either value or whitespace if (parser->GetDataRemaining() < 4) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Reached end of file without end for tag <%s> declared on line %d", fTag, tagStartLine); return false; } if ((*parser)[1] == '/') { // we'll only assign a value if there were no embedded tags if (fEmbeddedTags.GetLength() == 0 && (!verifier || verifier->CanHaveValue(fTag))) fValue = temp.GetAsCString(); else { // otherwise this needs to have been just whitespace StringParser tempParser(&temp); tempParser.ConsumeWhitespace(); if (tempParser.GetDataRemaining() > 0) { if (errorBuffer) { if (fEmbeddedTags.GetLength() > 0) qtss_sprintf(errorBuffer, "Unexpected text outside of tag on line %d", tagStartLine); else qtss_sprintf(errorBuffer, "Tag <%s> on line %d not allowed to have data", fTag, tagStartLine); } } } break; // we're all done with this tag } if (((*parser)[1] != '!') && ((*parser)[1] != '?')) { // this must be the beginning of an embedded tag XMLTag* tag = NEW XMLTag(); fEmbeddedTags.EnQueue(&tag->fElem); if (!tag->ParseTag(parser, verifier, errorBuffer, errorBufferSize)) return false; if (verifier && !verifier->IsValidSubtag(fTag, tag->GetTagName())) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Tag %s not allowed in tag %s on line %d", tag->GetTagName(), fTag, parser->GetCurrentLineNumber()); return false; // bad attribute specification } } else { parser->ConsumeLength(NULL, 1); // skip '<' ConsumeIfComment(parser); } } parser->ConsumeLength(NULL, 2); // skip '</' parser->ConsumeUntil(&temp, sNonNameMask); if (!temp.Equal(fTag)) { char* newTag = temp.GetAsCString(); if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "End tag </%s> on line %d doesn't match tag <%s> declared on line %d", newTag, parser->GetCurrentLineNumber(),fTag, tagStartLine); delete newTag; return false; // bad attribute specification } if (!parser->GetThru(NULL, '>')) { if (errorBuffer != NULL) qtss_sprintf(errorBuffer, "Couldn't find end of tag <%s> declared on line %d", fTag, tagStartLine); return false; // bad attribute specification } return true;}char* XMLTag::GetAttributeValue(const char* attrName){ for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next()) { XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject(); if (!strcmp(attr->fAttrName, attrName)) return attr->fAttrValue; } return NULL;}XMLTag* XMLTag::GetEmbeddedTag(const UInt32 index){ if (fEmbeddedTags.GetLength() <= index) return NULL; OSQueueIter iter(&fEmbeddedTags); for (UInt32 i = 0; i < index; i++) { iter.Next(); } OSQueueElem* result = iter.GetCurrent(); return (XMLTag*)result->GetEnclosingObject();}XMLTag* XMLTag::GetEmbeddedTagByName(const char* tagName, const UInt32 index){ if (fEmbeddedTags.GetLength() <= index) return NULL; XMLTag* result = NULL; UInt32 curIndex = 0; for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next()) { XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject(); if (!strcmp(temp->GetTagName(), tagName)) { if (curIndex == index) { result = temp; break; } curIndex++; } } return result;}XMLTag* XMLTag::GetEmbeddedTagByAttr(const char* attrName, const char* attrValue, const UInt32 index){ if (fEmbeddedTags.GetLength() <= index) return NULL; XMLTag* result = NULL; UInt32 curIndex = 0; for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next()) { XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject(); if ((temp->GetAttributeValue(attrName) != NULL) && (!strcmp(temp->GetAttributeValue(attrName), attrValue))) { if (curIndex == index) { result = temp; break; } curIndex++; } } return result;}XMLTag* XMLTag::GetEmbeddedTagByNameAndAttr(const char* tagName, const char* attrName, const char* attrValue, const UInt32 index){ if (fEmbeddedTags.GetLength() <= index) return NULL; XMLTag* result = NULL; UInt32 curIndex = 0; for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next()) { XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject(); if (!strcmp(temp->GetTagName(), tagName) && (temp->GetAttributeValue(attrName) != NULL) && (!strcmp(temp->GetAttributeValue(attrName), attrValue))) { if (curIndex == index) { result = temp; break; } curIndex++; } } return result;}void XMLTag::AddAttribute( char* attrName, char* attrValue){ XMLAttribute* attr = NEW XMLAttribute; StrPtrLen temp(attrName); attr->fAttrName = temp.GetAsCString(); temp.Set(attrValue); attr->fAttrValue = temp.GetAsCString(); fAttributes.EnQueue(&attr->fElem);}void XMLTag::RemoveAttribute(char* attrName){ for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next()) { XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject(); if (!strcmp(attr->fAttrName, attrName)) { fAttributes.Remove(&attr->fElem); delete attr; return; } }}void XMLTag::AddEmbeddedTag(XMLTag* tag){ fEmbeddedTags.EnQueue(&tag->fElem);}void XMLTag::RemoveEmbeddedTag(XMLTag* tag){ fEmbeddedTags.Remove(&tag->fElem);}void XMLTag::SetTagName( char* name){ Assert (name != NULL); // can't have a tag without a name! if (fTag != NULL) delete fTag; StrPtrLen temp(name); fTag = temp.GetAsCString();} void XMLTag::SetValue( char* value){ if (fEmbeddedTags.GetLength() > 0) return; // can't have a value with embedded tags if (fValue != NULL) delete fValue; if (value == NULL) fValue = NULL; else { StrPtrLen temp(value); fValue = temp.GetAsCString(); }} void XMLTag::FormatData(ResizeableStringFormatter* formatter, UInt32 indent){ for (UInt32 i=0; i<indent; i++) formatter->PutChar('/t'); formatter->PutChar('<'); formatter->Put(fTag); if (fAttributes.GetLength() > 0) { formatter->PutChar(' '); for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next()) { XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject(); formatter->Put(attr->fAttrName); formatter->Put("=/""); formatter->Put(attr->fAttrValue); formatter->Put("/" "); } } formatter->PutChar('>'); if (fEmbeddedTags.GetLength() == 0) { if (fValue > 0) formatter->Put(fValue); } else { formatter->Put(kEOLString); for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next()) { XMLTag* current = (XMLTag*)iter.GetCurrent()->GetEnclosingObject(); current->FormatData(formatter, indent + 1); } for (UInt32 i=0; i<indent; i++) formatter->PutChar('/t'); } formatter->Put("</"); formatter->Put(fTag); formatter->PutChar('>'); formatter->Put(kEOLString);}XMLAttribute::XMLAttribute() : fAttrName(NULL), fAttrValue(NULL){ fElem = this;}XMLAttribute::~XMLAttribute(){ if (fAttrName) delete fAttrName; if (fAttrValue) delete fAttrValue; }
新闻热点
疑难解答