| // Copyright (c) 2010 The Chromium Embedded Framework Authors. All rights |
| // reserved. Use of this source code is governed by a BSD-style license that |
| // can be found in the LICENSE file. |
| |
| #include "include/wrapper/cef_xml_object.h" |
| |
| #include <sstream> |
| |
| #include "include/base/cef_logging.h" |
| #include "include/base/cef_macros.h" |
| #include "include/cef_stream.h" |
| |
| namespace { |
| |
| class CefXmlObjectLoader { |
| public: |
| explicit CefXmlObjectLoader(CefRefPtr<CefXmlObject> root_object) |
| : root_object_(root_object) {} |
| |
| bool Load(CefRefPtr<CefStreamReader> stream, |
| CefXmlReader::EncodingType encodingType, |
| const CefString& URI) { |
| CefRefPtr<CefXmlReader> reader( |
| CefXmlReader::Create(stream, encodingType, URI)); |
| if (!reader.get()) |
| return false; |
| |
| bool ret = reader->MoveToNextNode(); |
| if (ret) { |
| CefRefPtr<CefXmlObject> cur_object(root_object_), new_object; |
| CefXmlObject::ObjectVector queue; |
| int cur_depth, value_depth = -1; |
| CefXmlReader::NodeType cur_type; |
| std::stringstream cur_value; |
| bool last_has_ns = false; |
| |
| queue.push_back(root_object_); |
| |
| do { |
| cur_depth = reader->GetDepth(); |
| if (value_depth >= 0 && cur_depth > value_depth) { |
| // The current node has already been parsed as part of a value. |
| continue; |
| } |
| |
| cur_type = reader->GetType(); |
| if (cur_type == XML_NODE_ELEMENT_START) { |
| if (cur_depth == value_depth) { |
| // Add to the current value. |
| cur_value << std::string(reader->GetOuterXml()); |
| continue; |
| } else if (last_has_ns && reader->GetPrefix().empty()) { |
| if (!cur_object->HasChildren()) { |
| // Start a new value because the last element has a namespace and |
| // this element does not. |
| value_depth = cur_depth; |
| cur_value << std::string(reader->GetOuterXml()); |
| } else { |
| // Value following a child element is not allowed. |
| std::stringstream ss; |
| ss << "Value following child element, line " |
| << reader->GetLineNumber(); |
| load_error_ = ss.str(); |
| ret = false; |
| break; |
| } |
| } else { |
| // Start a new element. |
| new_object = new CefXmlObject(reader->GetQualifiedName()); |
| cur_object->AddChild(new_object); |
| last_has_ns = !reader->GetPrefix().empty(); |
| |
| if (!reader->IsEmptyElement()) { |
| // The new element potentially has a value and/or children, so |
| // set the current object and add the object to the queue. |
| cur_object = new_object; |
| queue.push_back(cur_object); |
| } |
| |
| if (reader->HasAttributes() && reader->MoveToFirstAttribute()) { |
| // Read all object attributes. |
| do { |
| new_object->SetAttributeValue(reader->GetQualifiedName(), |
| reader->GetValue()); |
| } while (reader->MoveToNextAttribute()); |
| reader->MoveToCarryingElement(); |
| } |
| } |
| } else if (cur_type == XML_NODE_ELEMENT_END) { |
| if (cur_depth == value_depth) { |
| // Ending an element that is already in the value. |
| continue; |
| } else if (cur_depth < value_depth) { |
| // Done with parsing the value portion of the current element. |
| cur_object->SetValue(cur_value.str()); |
| cur_value.str(""); |
| value_depth = -1; |
| } |
| |
| // Pop the current element from the queue. |
| queue.pop_back(); |
| |
| if (queue.empty() || |
| cur_object->GetName() != reader->GetQualifiedName()) { |
| // Open tag without close tag or close tag without open tag should |
| // never occur (the parser catches this error). |
| NOTREACHED(); |
| std::stringstream ss; |
| ss << "Mismatched end tag for " |
| << std::string(cur_object->GetName()) << ", line " |
| << reader->GetLineNumber(); |
| load_error_ = ss.str(); |
| ret = false; |
| break; |
| } |
| |
| // Set the current object to the previous object in the queue. |
| cur_object = queue.back().get(); |
| } else if (cur_type == XML_NODE_TEXT || cur_type == XML_NODE_CDATA || |
| cur_type == XML_NODE_ENTITY_REFERENCE) { |
| if (cur_depth == value_depth) { |
| // Add to the current value. |
| cur_value << std::string(reader->GetValue()); |
| } else if (!cur_object->HasChildren()) { |
| // Start a new value. |
| value_depth = cur_depth; |
| cur_value << std::string(reader->GetValue()); |
| } else { |
| // Value following a child element is not allowed. |
| std::stringstream ss; |
| ss << "Value following child element, line " |
| << reader->GetLineNumber(); |
| load_error_ = ss.str(); |
| ret = false; |
| break; |
| } |
| } |
| } while (reader->MoveToNextNode()); |
| } |
| |
| if (reader->HasError()) { |
| load_error_ = reader->GetError(); |
| return false; |
| } |
| |
| return ret; |
| } |
| |
| CefString GetLoadError() { return load_error_; } |
| |
| private: |
| CefString load_error_; |
| CefRefPtr<CefXmlObject> root_object_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CefXmlObjectLoader); |
| }; |
| |
| } // namespace |
| |
| CefXmlObject::CefXmlObject(const CefString& name) |
| : name_(name), parent_(nullptr) {} |
| |
| CefXmlObject::~CefXmlObject() {} |
| |
| bool CefXmlObject::Load(CefRefPtr<CefStreamReader> stream, |
| CefXmlReader::EncodingType encodingType, |
| const CefString& URI, |
| CefString* loadError) { |
| Clear(); |
| |
| CefXmlObjectLoader loader(this); |
| if (!loader.Load(stream, encodingType, URI)) { |
| if (loadError) |
| *loadError = loader.GetLoadError(); |
| return false; |
| } |
| return true; |
| } |
| |
| void CefXmlObject::Set(CefRefPtr<CefXmlObject> object) { |
| DCHECK(object.get()); |
| |
| Clear(); |
| |
| name_ = object->GetName(); |
| Append(object, true); |
| } |
| |
| void CefXmlObject::Append(CefRefPtr<CefXmlObject> object, |
| bool overwriteAttributes) { |
| DCHECK(object.get()); |
| |
| if (object->HasChildren()) { |
| ObjectVector children; |
| object->GetChildren(children); |
| ObjectVector::const_iterator it = children.begin(); |
| for (; it != children.end(); ++it) |
| AddChild((*it)->Duplicate()); |
| } |
| |
| if (object->HasAttributes()) { |
| AttributeMap attributes; |
| object->GetAttributes(attributes); |
| AttributeMap::const_iterator it = attributes.begin(); |
| for (; it != attributes.end(); ++it) { |
| if (overwriteAttributes || !HasAttribute(it->first)) |
| SetAttributeValue(it->first, it->second); |
| } |
| } |
| } |
| |
| CefRefPtr<CefXmlObject> CefXmlObject::Duplicate() { |
| CefRefPtr<CefXmlObject> new_obj; |
| { |
| base::AutoLock lock_scope(lock_); |
| new_obj = new CefXmlObject(name_); |
| new_obj->Append(this, true); |
| } |
| return new_obj; |
| } |
| |
| void CefXmlObject::Clear() { |
| ClearChildren(); |
| ClearAttributes(); |
| } |
| |
| CefString CefXmlObject::GetName() { |
| CefString name; |
| { |
| base::AutoLock lock_scope(lock_); |
| name = name_; |
| } |
| return name; |
| } |
| |
| bool CefXmlObject::SetName(const CefString& name) { |
| DCHECK(!name.empty()); |
| if (name.empty()) |
| return false; |
| |
| base::AutoLock lock_scope(lock_); |
| name_ = name; |
| return true; |
| } |
| |
| bool CefXmlObject::HasParent() { |
| base::AutoLock lock_scope(lock_); |
| return (parent_ != nullptr); |
| } |
| |
| CefRefPtr<CefXmlObject> CefXmlObject::GetParent() { |
| CefRefPtr<CefXmlObject> parent; |
| { |
| base::AutoLock lock_scope(lock_); |
| parent = parent_; |
| } |
| return parent; |
| } |
| |
| bool CefXmlObject::HasValue() { |
| base::AutoLock lock_scope(lock_); |
| return !value_.empty(); |
| } |
| |
| CefString CefXmlObject::GetValue() { |
| CefString value; |
| { |
| base::AutoLock lock_scope(lock_); |
| value = value_; |
| } |
| return value; |
| } |
| |
| bool CefXmlObject::SetValue(const CefString& value) { |
| base::AutoLock lock_scope(lock_); |
| DCHECK(children_.empty()); |
| if (!children_.empty()) |
| return false; |
| value_ = value; |
| return true; |
| } |
| |
| bool CefXmlObject::HasAttributes() { |
| base::AutoLock lock_scope(lock_); |
| return !attributes_.empty(); |
| } |
| |
| size_t CefXmlObject::GetAttributeCount() { |
| base::AutoLock lock_scope(lock_); |
| return attributes_.size(); |
| } |
| |
| bool CefXmlObject::HasAttribute(const CefString& name) { |
| if (name.empty()) |
| return false; |
| |
| base::AutoLock lock_scope(lock_); |
| AttributeMap::const_iterator it = attributes_.find(name); |
| return (it != attributes_.end()); |
| } |
| |
| CefString CefXmlObject::GetAttributeValue(const CefString& name) { |
| DCHECK(!name.empty()); |
| CefString value; |
| if (!name.empty()) { |
| base::AutoLock lock_scope(lock_); |
| AttributeMap::const_iterator it = attributes_.find(name); |
| if (it != attributes_.end()) |
| value = it->second; |
| } |
| return value; |
| } |
| |
| bool CefXmlObject::SetAttributeValue(const CefString& name, |
| const CefString& value) { |
| DCHECK(!name.empty()); |
| if (name.empty()) |
| return false; |
| |
| base::AutoLock lock_scope(lock_); |
| AttributeMap::iterator it = attributes_.find(name); |
| if (it != attributes_.end()) { |
| it->second = value; |
| } else { |
| attributes_.insert(std::make_pair(name, value)); |
| } |
| return true; |
| } |
| |
| size_t CefXmlObject::GetAttributes(AttributeMap& attributes) { |
| base::AutoLock lock_scope(lock_); |
| attributes = attributes_; |
| return attributes_.size(); |
| } |
| |
| void CefXmlObject::ClearAttributes() { |
| base::AutoLock lock_scope(lock_); |
| attributes_.clear(); |
| } |
| |
| bool CefXmlObject::HasChildren() { |
| base::AutoLock lock_scope(lock_); |
| return !children_.empty(); |
| } |
| |
| size_t CefXmlObject::GetChildCount() { |
| base::AutoLock lock_scope(lock_); |
| return children_.size(); |
| } |
| |
| bool CefXmlObject::HasChild(CefRefPtr<CefXmlObject> child) { |
| DCHECK(child.get()); |
| |
| base::AutoLock lock_scope(lock_); |
| ObjectVector::const_iterator it = children_.begin(); |
| for (; it != children_.end(); ++it) { |
| if ((*it).get() == child.get()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool CefXmlObject::AddChild(CefRefPtr<CefXmlObject> child) { |
| DCHECK(child.get()); |
| if (!child.get()) |
| return false; |
| |
| CefRefPtr<CefXmlObject> parent = child->GetParent(); |
| DCHECK(!parent); |
| if (parent) |
| return false; |
| |
| base::AutoLock lock_scope(lock_); |
| |
| children_.push_back(child); |
| child->SetParent(this); |
| return true; |
| } |
| |
| bool CefXmlObject::RemoveChild(CefRefPtr<CefXmlObject> child) { |
| DCHECK(child.get()); |
| |
| base::AutoLock lock_scope(lock_); |
| ObjectVector::iterator it = children_.begin(); |
| for (; it != children_.end(); ++it) { |
| if ((*it).get() == child.get()) { |
| children_.erase(it); |
| child->SetParent(nullptr); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| size_t CefXmlObject::GetChildren(ObjectVector& children) { |
| base::AutoLock lock_scope(lock_); |
| children = children_; |
| return children_.size(); |
| } |
| |
| void CefXmlObject::ClearChildren() { |
| base::AutoLock lock_scope(lock_); |
| ObjectVector::iterator it = children_.begin(); |
| for (; it != children_.end(); ++it) |
| (*it)->SetParent(nullptr); |
| children_.clear(); |
| } |
| |
| CefRefPtr<CefXmlObject> CefXmlObject::FindChild(const CefString& name) { |
| DCHECK(!name.empty()); |
| if (name.empty()) |
| return nullptr; |
| |
| base::AutoLock lock_scope(lock_); |
| ObjectVector::const_iterator it = children_.begin(); |
| for (; it != children_.end(); ++it) { |
| if ((*it)->GetName() == name) |
| return (*it); |
| } |
| return nullptr; |
| } |
| |
| size_t CefXmlObject::FindChildren(const CefString& name, |
| ObjectVector& children) { |
| DCHECK(!name.empty()); |
| if (name.empty()) |
| return 0; |
| |
| size_t ct = 0; |
| |
| base::AutoLock lock_scope(lock_); |
| ObjectVector::const_iterator it = children_.begin(); |
| for (; it != children_.end(); ++it) { |
| if ((*it)->GetName() == name) { |
| children.push_back(*it); |
| ct++; |
| } |
| } |
| return ct; |
| } |
| |
| void CefXmlObject::SetParent(CefXmlObject* parent) { |
| base::AutoLock lock_scope(lock_); |
| if (parent) { |
| DCHECK(parent_ == nullptr); |
| parent_ = parent; |
| } else { |
| DCHECK(parent_ != nullptr); |
| parent_ = nullptr; |
| } |
| } |