diff options
26 files changed, 3153 insertions, 0 deletions
diff --git a/build/common.gypi b/build/common.gypi index 8376ef8a..6d7d5332 100644 --- a/build/common.gypi +++ b/build/common.gypi @@ -49,6 +49,7 @@ 'gen_core_neon_offsets_gyp%': '<(gen_core_neon_offsets_gyp)', 'webrtc_vp8_dir%': '<(webrtc_vp8_dir)', 'include_opus%': '<(include_opus)', + 'rtc_relative_path%': 1, 'rbe_components_path%': '<(rbe_components_path)', 'external_libraries%': '0', 'json_root%': '<(DEPTH)/third_party/jsoncpp/source/include/', @@ -178,6 +179,9 @@ '<!@(pkg-config --cflags dbus-glib-1)', ], }], + ['rtc_relative_path==1', { + 'defines': ['EXPAT_RELATIVE_PATH',], + }], ['enable_video==1', { 'defines': ['WEBRTC_MODULE_UTILITY_VIDEO',], }], diff --git a/build/merge_libs.gyp b/build/merge_libs.gyp index e8c5c99f..d257c991 100644 --- a/build/merge_libs.gyp +++ b/build/merge_libs.gyp @@ -12,6 +12,7 @@ 'merge_libs_dependencies': [ '../webrtc.gyp:webrtc', '../sound/sound.gyp:rtc_sound', + '../libjingle/xmllite/xmllite.gyp:rtc_xmllite', ], }, 'targets': [ diff --git a/libjingle/xmllite/OWNERS b/libjingle/xmllite/OWNERS new file mode 100644 index 00000000..47ccde74 --- /dev/null +++ b/libjingle/xmllite/OWNERS @@ -0,0 +1,22 @@ +/* + * Copyright The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +henrika@webrtc.org +henrike@webrtc.org +henrikg@webrtc.org +hta@webrtc.org +jiayl@webrtc.org +juberti@webrtc.org +mflodman@webrtc.org +perkj@webrtc.org +pthatcher@webrtc.org +sergeyu@chromium.org +tommi@webrtc.org + +per-file BUILD.gn=kjellander@webrtc.org diff --git a/libjingle/xmllite/qname.cc b/libjingle/xmllite/qname.cc new file mode 100644 index 00000000..6abde321 --- /dev/null +++ b/libjingle/xmllite/qname.cc @@ -0,0 +1,78 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/qname.h" + +namespace buzz { + +QName::QName() { +} + +QName::QName(const QName& qname) + : namespace_(qname.namespace_), + local_part_(qname.local_part_) { +} + +QName::QName(const StaticQName& const_value) + : namespace_(const_value.ns), + local_part_(const_value.local) { +} + +QName::QName(const std::string& ns, const std::string& local) + : namespace_(ns), + local_part_(local) { +} + +QName::QName(const std::string& merged_or_local) { + size_t i = merged_or_local.rfind(':'); + if (i == std::string::npos) { + local_part_ = merged_or_local; + } else { + namespace_ = merged_or_local.substr(0, i); + local_part_ = merged_or_local.substr(i + 1); + } +} + +QName::~QName() { +} + +std::string QName::Merged() const { + if (namespace_[0] == '\0') + return local_part_; + + std::string result; + result.reserve(namespace_.length() + 1 + local_part_.length()); + result += namespace_; + result += ':'; + result += local_part_; + return result; +} + +bool QName::IsEmpty() const { + return namespace_.empty() && local_part_.empty(); +} + +int QName::Compare(const StaticQName& other) const { + int result = local_part_.compare(other.local); + if (result != 0) + return result; + + return namespace_.compare(other.ns); +} + +int QName::Compare(const QName& other) const { + int result = local_part_.compare(other.local_part_); + if (result != 0) + return result; + + return namespace_.compare(other.namespace_); +} + +} // namespace buzz diff --git a/libjingle/xmllite/qname.h b/libjingle/xmllite/qname.h new file mode 100644 index 00000000..1e772d3d --- /dev/null +++ b/libjingle/xmllite/qname.h @@ -0,0 +1,83 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_QNAME_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_QNAME_H_ + +#include <string> + +namespace buzz { + +class QName; + +// StaticQName is used to represend constant quailified names. They +// can be initialized statically and don't need intializers code, e.g. +// const StaticQName QN_FOO = { "foo_namespace", "foo" }; +// +// Beside this use case, QName should be used everywhere +// else. StaticQName instances are implicitly converted to QName +// objects. +struct StaticQName { + const char* const ns; + const char* const local; + + bool operator==(const QName& other) const; + bool operator!=(const QName& other) const; +}; + +class QName { + public: + QName(); + QName(const QName& qname); + QName(const StaticQName& const_value); + QName(const std::string& ns, const std::string& local); + explicit QName(const std::string& merged_or_local); + ~QName(); + + const std::string& Namespace() const { return namespace_; } + const std::string& LocalPart() const { return local_part_; } + std::string Merged() const; + bool IsEmpty() const; + + int Compare(const StaticQName& other) const; + int Compare(const QName& other) const; + + bool operator==(const StaticQName& other) const { + return Compare(other) == 0; + } + bool operator==(const QName& other) const { + return Compare(other) == 0; + } + bool operator!=(const StaticQName& other) const { + return Compare(other) != 0; + } + bool operator!=(const QName& other) const { + return Compare(other) != 0; + } + bool operator<(const QName& other) const { + return Compare(other) < 0; + } + + private: + std::string namespace_; + std::string local_part_; +}; + +inline bool StaticQName::operator==(const QName& other) const { + return other.Compare(*this) == 0; +} + +inline bool StaticQName::operator!=(const QName& other) const { + return other.Compare(*this) != 0; +} + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_QNAME_H_ diff --git a/libjingle/xmllite/qname_unittest.cc b/libjingle/xmllite/qname_unittest.cc new file mode 100644 index 00000000..2a129b88 --- /dev/null +++ b/libjingle/xmllite/qname_unittest.cc @@ -0,0 +1,114 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <string> +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/base/gunit.h" + +using buzz::StaticQName; +using buzz::QName; + +TEST(QNameTest, TestTrivial) { + QName name("test"); + EXPECT_EQ(name.LocalPart(), "test"); + EXPECT_EQ(name.Namespace(), ""); +} + +TEST(QNameTest, TestSplit) { + QName name("a:test"); + EXPECT_EQ(name.LocalPart(), "test"); + EXPECT_EQ(name.Namespace(), "a"); + QName name2("a-very:long:namespace:test-this"); + EXPECT_EQ(name2.LocalPart(), "test-this"); + EXPECT_EQ(name2.Namespace(), "a-very:long:namespace"); +} + +TEST(QNameTest, TestMerge) { + QName name("a", "test"); + EXPECT_EQ(name.LocalPart(), "test"); + EXPECT_EQ(name.Namespace(), "a"); + EXPECT_EQ(name.Merged(), "a:test"); + QName name2("a-very:long:namespace", "test-this"); + EXPECT_EQ(name2.LocalPart(), "test-this"); + EXPECT_EQ(name2.Namespace(), "a-very:long:namespace"); + EXPECT_EQ(name2.Merged(), "a-very:long:namespace:test-this"); +} + +TEST(QNameTest, TestAssignment) { + QName name("a", "test"); + // copy constructor + QName namecopy(name); + EXPECT_EQ(namecopy.LocalPart(), "test"); + EXPECT_EQ(namecopy.Namespace(), "a"); + QName nameassigned(""); + nameassigned = name; + EXPECT_EQ(nameassigned.LocalPart(), "test"); + EXPECT_EQ(nameassigned.Namespace(), "a"); +} + +TEST(QNameTest, TestConstAssignment) { + StaticQName name = { "a", "test" }; + QName namecopy(name); + EXPECT_EQ(namecopy.LocalPart(), "test"); + EXPECT_EQ(namecopy.Namespace(), "a"); + QName nameassigned(""); + nameassigned = name; + EXPECT_EQ(nameassigned.LocalPart(), "test"); + EXPECT_EQ(nameassigned.Namespace(), "a"); +} + +TEST(QNameTest, TestEquality) { + QName name("a-very:long:namespace:test-this"); + QName name2("a-very:long:namespace", "test-this"); + QName name3("a-very:long:namespaxe", "test-this"); + EXPECT_TRUE(name == name2); + EXPECT_FALSE(name == name3); +} + +TEST(QNameTest, TestCompare) { + QName name("a"); + QName name2("nsa", "a"); + QName name3("nsa", "b"); + QName name4("nsb", "b"); + + EXPECT_TRUE(name < name2); + EXPECT_FALSE(name2 < name); + + EXPECT_FALSE(name2 < name2); + + EXPECT_TRUE(name2 < name3); + EXPECT_FALSE(name3 < name2); + + EXPECT_TRUE(name3 < name4); + EXPECT_FALSE(name4 < name3); +} + +TEST(QNameTest, TestStaticQName) { + const StaticQName const_name1 = { "namespace", "local-name1" }; + const StaticQName const_name2 = { "namespace", "local-name2" }; + const QName name("namespace", "local-name1"); + const QName name1 = const_name1; + const QName name2 = const_name2; + + EXPECT_TRUE(name == const_name1); + EXPECT_TRUE(const_name1 == name); + EXPECT_FALSE(name != const_name1); + EXPECT_FALSE(const_name1 != name); + + EXPECT_TRUE(name == name1); + EXPECT_TRUE(name1 == name); + EXPECT_FALSE(name != name1); + EXPECT_FALSE(name1 != name); + + EXPECT_FALSE(name == name2); + EXPECT_FALSE(name2 == name); + EXPECT_TRUE(name != name2); + EXPECT_TRUE(name2 != name); +} diff --git a/libjingle/xmllite/xmlbuilder.cc b/libjingle/xmllite/xmlbuilder.cc new file mode 100644 index 00000000..9d982c33 --- /dev/null +++ b/libjingle/xmllite/xmlbuilder.cc @@ -0,0 +1,130 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlbuilder.h" + +#include <set> +#include <vector> +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/base/common.h" + +namespace buzz { + +XmlBuilder::XmlBuilder() : + pelCurrent_(NULL), + pelRoot_(), + pvParents_(new std::vector<XmlElement *>()) { +} + +void +XmlBuilder::Reset() { + pelRoot_.reset(); + pelCurrent_ = NULL; + pvParents_->clear(); +} + +XmlElement * +XmlBuilder::BuildElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + QName tagName(pctx->ResolveQName(name, false)); + if (tagName.IsEmpty()) + return NULL; + + XmlElement * pelNew = new XmlElement(tagName); + + if (!*atts) + return pelNew; + + std::set<QName> seenNonlocalAtts; + + while (*atts) { + QName attName(pctx->ResolveQName(*atts, true)); + if (attName.IsEmpty()) { + delete pelNew; + return NULL; + } + + // verify that namespaced names are unique + if (!attName.Namespace().empty()) { + if (seenNonlocalAtts.count(attName)) { + delete pelNew; + return NULL; + } + seenNonlocalAtts.insert(attName); + } + + pelNew->AddAttr(attName, std::string(*(atts + 1))); + atts += 2; + } + + return pelNew; +} + +void +XmlBuilder::StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + XmlElement * pelNew = BuildElement(pctx, name, atts); + if (pelNew == NULL) { + pctx->RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (!pelCurrent_) { + pelCurrent_ = pelNew; + pelRoot_.reset(pelNew); + pvParents_->push_back(NULL); + } else { + pelCurrent_->AddElement(pelNew); + pvParents_->push_back(pelCurrent_); + pelCurrent_ = pelNew; + } +} + +void +XmlBuilder::EndElement(XmlParseContext * pctx, const char * name) { + RTC_UNUSED(pctx); + RTC_UNUSED(name); + pelCurrent_ = pvParents_->back(); + pvParents_->pop_back(); +} + +void +XmlBuilder::CharacterData(XmlParseContext * pctx, + const char * text, int len) { + RTC_UNUSED(pctx); + if (pelCurrent_) { + pelCurrent_->AddParsedText(text, len); + } +} + +void +XmlBuilder::Error(XmlParseContext * pctx, XML_Error err) { + RTC_UNUSED(pctx); + RTC_UNUSED(err); + pelRoot_.reset(NULL); + pelCurrent_ = NULL; + pvParents_->clear(); +} + +XmlElement * +XmlBuilder::CreateElement() { + return pelRoot_.release(); +} + +XmlElement * +XmlBuilder::BuiltElement() { + return pelRoot_.get(); +} + +XmlBuilder::~XmlBuilder() { +} + +} // namespace buzz diff --git a/libjingle/xmllite/xmlbuilder.h b/libjingle/xmllite/xmlbuilder.h new file mode 100644 index 00000000..354bf0b7 --- /dev/null +++ b/libjingle/xmllite/xmlbuilder.h @@ -0,0 +1,61 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef _xmlbuilder_h_ +#define _xmlbuilder_h_ + +#include <string> +#include <vector> +#include "webrtc/libjingle/xmllite/xmlparser.h" +#include "webrtc/base/scoped_ptr.h" + +#ifdef EXPAT_RELATIVE_PATH +#include "expat.h" +#else +#include "third_party/expat/v2_0_1/Source/lib/expat.h" +#endif // EXPAT_RELATIVE_PATH + +namespace buzz { + +class XmlElement; +class XmlParseContext; + + +class XmlBuilder : public XmlParseHandler { +public: + XmlBuilder(); + + static XmlElement * BuildElement(XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts); + virtual void EndElement(XmlParseContext * pctx, const char * name); + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len); + virtual void Error(XmlParseContext * pctx, XML_Error); + virtual ~XmlBuilder(); + + void Reset(); + + // Take ownership of the built element; second call returns NULL + XmlElement * CreateElement(); + + // Peek at the built element without taking ownership + XmlElement * BuiltElement(); + +private: + XmlElement * pelCurrent_; + rtc::scoped_ptr<XmlElement> pelRoot_; + rtc::scoped_ptr<std::vector<XmlElement*> > pvParents_; +}; + +} + +#endif diff --git a/libjingle/xmllite/xmlbuilder_unittest.cc b/libjingle/xmllite/xmlbuilder_unittest.cc new file mode 100644 index 00000000..8a5ce08d --- /dev/null +++ b/libjingle/xmllite/xmlbuilder_unittest.cc @@ -0,0 +1,177 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <iostream> +#include <sstream> +#include <string> +#include "webrtc/libjingle/xmllite/xmlbuilder.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::XmlBuilder; +using buzz::XmlElement; +using buzz::XmlParser; + +TEST(XmlBuilderTest, TestTrivial) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing/>"); + EXPECT_EQ("<testing/>", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestAttributes1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing a='b'/>"); + EXPECT_EQ("<testing a=\"b\"/>", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestAttributes2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing e='' long='some text'/>"); + EXPECT_EQ("<testing e=\"\" long=\"some text\"/>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNesting1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "<top><first/><second><third></third></second></top>"); + EXPECT_EQ("<top><first/><second><third/></second></top>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNesting2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "<top><fifth><deeper><and><deeper/></and><sibling><leaf/>" + "</sibling></deeper></fifth><first/><second><third></third>" + "</second></top>"); + EXPECT_EQ("<top><fifth><deeper><and><deeper/></and><sibling><leaf/>" + "</sibling></deeper></fifth><first/><second><third/>" + "</second></top>", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing a='>'/>"); + EXPECT_EQ("<testing a=\">\"/>", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing a='<>&"'/>"); + EXPECT_EQ("<testing a=\"<>&"\"/>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting3) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing a='so "important"'/>"); + EXPECT_EQ("<testing a=\"so "important"\"/>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting4) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing a='"important", yes'/>"); + EXPECT_EQ("<testing a=\""important", yes\"/>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestQuoting5) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "<testing a='<what is "important">'/>"); + EXPECT_EQ("<testing a=\"<what is "important">\"/>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing>></testing>"); + EXPECT_EQ("<testing>></testing>", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing><>&"</testing>"); + EXPECT_EQ("<testing><>&\"</testing>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText3) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing>so <important></testing>"); + EXPECT_EQ("<testing>so <important></testing>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText4) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing><important>, yes</testing>"); + EXPECT_EQ("<testing><important>, yes</testing>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestText5) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "<testing>importance &<important>&</testing>"); + EXPECT_EQ("<testing>importance &<important>&</testing>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNamespace1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing xmlns='foo'/>"); + EXPECT_EQ("<testing xmlns=\"foo\"/>", builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNamespace2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing xmlns:a='foo' a:b='c'/>"); + EXPECT_EQ("<testing xmlns:a=\"foo\" a:b=\"c\"/>", + builder.BuiltElement()->Str()); +} + +TEST(XmlBuilderTest, TestNamespace3) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing xmlns:a=''/>"); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + +TEST(XmlBuilderTest, TestNamespace4) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing a:b='c'/>"); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + +TEST(XmlBuilderTest, TestAttrCollision1) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, "<testing a='first' a='second'/>"); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + +TEST(XmlBuilderTest, TestAttrCollision2) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "<testing xmlns:a='foo' xmlns:b='foo' a:x='c' b:x='d'/>"); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + +TEST(XmlBuilderTest, TestAttrCollision3) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, + "<testing xmlns:a='foo'><nested xmlns:b='foo' a:x='c' b:x='d'/>" + "</testing>"); + EXPECT_TRUE(NULL == builder.BuiltElement()); +} + diff --git a/libjingle/xmllite/xmlconstants.cc b/libjingle/xmllite/xmlconstants.cc new file mode 100644 index 00000000..0b9a9ff5 --- /dev/null +++ b/libjingle/xmllite/xmlconstants.cc @@ -0,0 +1,25 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlconstants.h" + +namespace buzz { + +const char STR_EMPTY[] = ""; +const char NS_XML[] = "http://www.w3.org/XML/1998/namespace"; +const char NS_XMLNS[] = "http://www.w3.org/2000/xmlns/"; +const char STR_XMLNS[] = "xmlns"; +const char STR_XML[] = "xml"; +const char STR_VERSION[] = "version"; +const char STR_ENCODING[] = "encoding"; + +const StaticQName QN_XMLNS = { STR_EMPTY, STR_XMLNS }; + +} // namespace buzz diff --git a/libjingle/xmllite/xmlconstants.h b/libjingle/xmllite/xmlconstants.h new file mode 100644 index 00000000..0064ee9d --- /dev/null +++ b/libjingle/xmllite/xmlconstants.h @@ -0,0 +1,30 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLCONSTANTS_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLCONSTANTS_H_ + +#include "webrtc/libjingle/xmllite/qname.h" + +namespace buzz { + +extern const char STR_EMPTY[]; +extern const char NS_XML[]; +extern const char NS_XMLNS[]; +extern const char STR_XMLNS[]; +extern const char STR_XML[]; +extern const char STR_VERSION[]; +extern const char STR_ENCODING[]; + +extern const StaticQName QN_XMLNS; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLCONSTANTS_H_ diff --git a/libjingle/xmllite/xmlelement.cc b/libjingle/xmllite/xmlelement.cc new file mode 100644 index 00000000..2d90836e --- /dev/null +++ b/libjingle/xmllite/xmlelement.cc @@ -0,0 +1,496 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlelement.h" + +#include <ostream> +#include <sstream> +#include <string> +#include <vector> + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlbuilder.h" +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" +#include "webrtc/libjingle/xmllite/xmlprinter.h" +#include "webrtc/base/common.h" + +namespace buzz { + +XmlChild::~XmlChild() { +} + +bool XmlText::IsTextImpl() const { + return true; +} + +XmlElement* XmlText::AsElementImpl() const { + return NULL; +} + +XmlText* XmlText::AsTextImpl() const { + return const_cast<XmlText *>(this); +} + +void XmlText::SetText(const std::string& text) { + text_ = text; +} + +void XmlText::AddParsedText(const char* buf, int len) { + text_.append(buf, len); +} + +void XmlText::AddText(const std::string& text) { + text_ += text; +} + +XmlText::~XmlText() { +} + +XmlElement::XmlElement(const QName& name) : + name_(name), + first_attr_(NULL), + last_attr_(NULL), + first_child_(NULL), + last_child_(NULL), + cdata_(false) { +} + +XmlElement::XmlElement(const XmlElement& elt) : + XmlChild(), + name_(elt.name_), + first_attr_(NULL), + last_attr_(NULL), + first_child_(NULL), + last_child_(NULL), + cdata_(false) { + + // copy attributes + XmlAttr* attr; + XmlAttr ** plast_attr = &first_attr_; + XmlAttr* newAttr = NULL; + for (attr = elt.first_attr_; attr; attr = attr->NextAttr()) { + newAttr = new XmlAttr(*attr); + *plast_attr = newAttr; + plast_attr = &(newAttr->next_attr_); + } + last_attr_ = newAttr; + + // copy children + XmlChild* pChild; + XmlChild ** ppLast = &first_child_; + XmlChild* newChild = NULL; + + for (pChild = elt.first_child_; pChild; pChild = pChild->NextChild()) { + if (pChild->IsText()) { + newChild = new XmlText(*(pChild->AsText())); + } else { + newChild = new XmlElement(*(pChild->AsElement())); + } + *ppLast = newChild; + ppLast = &(newChild->next_child_); + } + last_child_ = newChild; + + cdata_ = elt.cdata_; +} + +XmlElement::XmlElement(const QName& name, bool useDefaultNs) : + name_(name), + first_attr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL), + last_attr_(first_attr_), + first_child_(NULL), + last_child_(NULL), + cdata_(false) { +} + +bool XmlElement::IsTextImpl() const { + return false; +} + +XmlElement* XmlElement::AsElementImpl() const { + return const_cast<XmlElement *>(this); +} + +XmlText* XmlElement::AsTextImpl() const { + return NULL; +} + +const std::string XmlElement::BodyText() const { + if (first_child_ && first_child_->IsText() && last_child_ == first_child_) { + return first_child_->AsText()->Text(); + } + + return std::string(); +} + +void XmlElement::SetBodyText(const std::string& text) { + if (text.empty()) { + ClearChildren(); + } else if (first_child_ == NULL) { + AddText(text); + } else if (first_child_->IsText() && last_child_ == first_child_) { + first_child_->AsText()->SetText(text); + } else { + ClearChildren(); + AddText(text); + } +} + +const QName XmlElement::FirstElementName() const { + const XmlElement* element = FirstElement(); + if (element == NULL) + return QName(); + return element->Name(); +} + +XmlAttr* XmlElement::FirstAttr() { + return first_attr_; +} + +const std::string XmlElement::Attr(const StaticQName& name) const { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + return attr->value_; + } + return std::string(); +} + +const std::string XmlElement::Attr(const QName& name) const { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + return attr->value_; + } + return std::string(); +} + +bool XmlElement::HasAttr(const StaticQName& name) const { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + return true; + } + return false; +} + +bool XmlElement::HasAttr(const QName& name) const { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + return true; + } + return false; +} + +void XmlElement::SetAttr(const QName& name, const std::string& value) { + XmlAttr* attr; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + break; + } + if (!attr) { + attr = new XmlAttr(name, value); + if (last_attr_) + last_attr_->next_attr_ = attr; + else + first_attr_ = attr; + last_attr_ = attr; + return; + } + attr->value_ = value; +} + +void XmlElement::ClearAttr(const QName& name) { + XmlAttr* attr; + XmlAttr* last_attr = NULL; + for (attr = first_attr_; attr; attr = attr->next_attr_) { + if (attr->name_ == name) + break; + last_attr = attr; + } + if (!attr) + return; + if (!last_attr) + first_attr_ = attr->next_attr_; + else + last_attr->next_attr_ = attr->next_attr_; + if (last_attr_ == attr) + last_attr_ = last_attr; + delete attr; +} + +XmlChild* XmlElement::FirstChild() { + return first_child_; +} + +XmlElement* XmlElement::FirstElement() { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText()) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement* XmlElement::NextElement() { + XmlChild* pChild; + for (pChild = next_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText()) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement* XmlElement::FirstWithNamespace(const std::string& ns) { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextWithNamespace(const std::string& ns) { + XmlChild* pChild; + for (pChild = next_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::FirstNamed(const QName& name) { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::FirstNamed(const StaticQName& name) { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextNamed(const QName& name) { + XmlChild* pChild; + for (pChild = next_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement * +XmlElement::NextNamed(const StaticQName& name) { + XmlChild* pChild; + for (pChild = next_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement(); + } + return NULL; +} + +XmlElement* XmlElement::FindOrAddNamedChild(const QName& name) { + XmlElement* child = FirstNamed(name); + if (!child) { + child = new XmlElement(name); + AddElement(child); + } + + return child; +} + +const std::string XmlElement::TextNamed(const QName& name) const { + XmlChild* pChild; + for (pChild = first_child_; pChild; pChild = pChild->next_child_) { + if (!pChild->IsText() && pChild->AsElement()->Name() == name) + return pChild->AsElement()->BodyText(); + } + return std::string(); +} + +void XmlElement::InsertChildAfter(XmlChild* predecessor, XmlChild* next) { + if (predecessor == NULL) { + next->next_child_ = first_child_; + first_child_ = next; + } + else { + next->next_child_ = predecessor->next_child_; + predecessor->next_child_ = next; + } +} + +void XmlElement::RemoveChildAfter(XmlChild* predecessor) { + XmlChild* next; + + if (predecessor == NULL) { + next = first_child_; + first_child_ = next->next_child_; + } + else { + next = predecessor->next_child_; + predecessor->next_child_ = next->next_child_; + } + + if (last_child_ == next) + last_child_ = predecessor; + + delete next; +} + +void XmlElement::AddAttr(const QName& name, const std::string& value) { + ASSERT(!HasAttr(name)); + + XmlAttr ** pprev = last_attr_ ? &(last_attr_->next_attr_) : &first_attr_; + last_attr_ = (*pprev = new XmlAttr(name, value)); +} + +void XmlElement::AddAttr(const QName& name, const std::string& value, + int depth) { + XmlElement* element = this; + while (depth--) { + element = element->last_child_->AsElement(); + } + element->AddAttr(name, value); +} + +void XmlElement::AddParsedText(const char* cstr, int len) { + if (len == 0) + return; + + if (last_child_ && last_child_->IsText()) { + last_child_->AsText()->AddParsedText(cstr, len); + return; + } + XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_; + last_child_ = *pprev = new XmlText(cstr, len); +} + +void XmlElement::AddCDATAText(const char* buf, int len) { + cdata_ = true; + AddParsedText(buf, len); +} + +void XmlElement::AddText(const std::string& text) { + if (text == STR_EMPTY) + return; + + if (last_child_ && last_child_->IsText()) { + last_child_->AsText()->AddText(text); + return; + } + XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_; + last_child_ = *pprev = new XmlText(text); +} + +void XmlElement::AddText(const std::string& text, int depth) { + // note: the first syntax is ambigious for msvc 6 + // XmlElement* pel(this); + XmlElement* element = this; + while (depth--) { + element = element->last_child_->AsElement(); + } + element->AddText(text); +} + +void XmlElement::AddElement(XmlElement *child) { + if (child == NULL) + return; + + XmlChild ** pprev = last_child_ ? &(last_child_->next_child_) : &first_child_; + *pprev = child; + last_child_ = child; + child->next_child_ = NULL; +} + +void XmlElement::AddElement(XmlElement *child, int depth) { + XmlElement* element = this; + while (depth--) { + element = element->last_child_->AsElement(); + } + element->AddElement(child); +} + +void XmlElement::ClearNamedChildren(const QName& name) { + XmlChild* prev_child = NULL; + XmlChild* next_child; + XmlChild* child; + for (child = FirstChild(); child; child = next_child) { + next_child = child->NextChild(); + if (!child->IsText() && child->AsElement()->Name() == name) + { + RemoveChildAfter(prev_child); + continue; + } + prev_child = child; + } +} + +void XmlElement::ClearAttributes() { + XmlAttr* attr; + for (attr = first_attr_; attr; ) { + XmlAttr* to_delete = attr; + attr = attr->next_attr_; + delete to_delete; + } + first_attr_ = last_attr_ = NULL; +} + +void XmlElement::ClearChildren() { + XmlChild* pchild; + for (pchild = first_child_; pchild; ) { + XmlChild* to_delete = pchild; + pchild = pchild->next_child_; + delete to_delete; + } + first_child_ = last_child_ = NULL; +} + +std::string XmlElement::Str() const { + std::stringstream ss; + XmlPrinter::PrintXml(&ss, this); + return ss.str(); +} + +XmlElement* XmlElement::ForStr(const std::string& str) { + XmlBuilder builder; + XmlParser::ParseXml(&builder, str); + return builder.CreateElement(); +} + +XmlElement::~XmlElement() { + XmlAttr* attr; + for (attr = first_attr_; attr; ) { + XmlAttr* to_delete = attr; + attr = attr->next_attr_; + delete to_delete; + } + + XmlChild* pchild; + for (pchild = first_child_; pchild; ) { + XmlChild* to_delete = pchild; + pchild = pchild->next_child_; + delete to_delete; + } +} + +} // namespace buzz diff --git a/libjingle/xmllite/xmlelement.h b/libjingle/xmllite/xmlelement.h new file mode 100644 index 00000000..70c6f799 --- /dev/null +++ b/libjingle/xmllite/xmlelement.h @@ -0,0 +1,234 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLELEMENT_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLELEMENT_H_ + +#include <iosfwd> +#include <string> + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/base/scoped_ptr.h" + +namespace buzz { + +class XmlChild; +class XmlText; +class XmlElement; +class XmlAttr; + +class XmlChild { + public: + XmlChild* NextChild() { return next_child_; } + const XmlChild* NextChild() const { return next_child_; } + + bool IsText() const { return IsTextImpl(); } + + XmlElement* AsElement() { return AsElementImpl(); } + const XmlElement* AsElement() const { return AsElementImpl(); } + + XmlText* AsText() { return AsTextImpl(); } + const XmlText* AsText() const { return AsTextImpl(); } + + + protected: + XmlChild() : + next_child_(NULL) { + } + + virtual bool IsTextImpl() const = 0; + virtual XmlElement* AsElementImpl() const = 0; + virtual XmlText* AsTextImpl() const = 0; + + + virtual ~XmlChild(); + + private: + friend class XmlElement; + + XmlChild(const XmlChild& noimpl); + + XmlChild* next_child_; +}; + +class XmlText : public XmlChild { + public: + explicit XmlText(const std::string& text) : + XmlChild(), + text_(text) { + } + explicit XmlText(const XmlText& t) : + XmlChild(), + text_(t.text_) { + } + explicit XmlText(const char* cstr, size_t len) : + XmlChild(), + text_(cstr, len) { + } + virtual ~XmlText(); + + const std::string& Text() const { return text_; } + void SetText(const std::string& text); + void AddParsedText(const char* buf, int len); + void AddText(const std::string& text); + + protected: + virtual bool IsTextImpl() const; + virtual XmlElement* AsElementImpl() const; + virtual XmlText* AsTextImpl() const; + + private: + std::string text_; +}; + +class XmlAttr { + public: + XmlAttr* NextAttr() const { return next_attr_; } + const QName& Name() const { return name_; } + const std::string& Value() const { return value_; } + + private: + friend class XmlElement; + + explicit XmlAttr(const QName& name, const std::string& value) : + next_attr_(NULL), + name_(name), + value_(value) { + } + explicit XmlAttr(const XmlAttr& att) : + next_attr_(NULL), + name_(att.name_), + value_(att.value_) { + } + + XmlAttr* next_attr_; + QName name_; + std::string value_; +}; + +class XmlElement : public XmlChild { + public: + explicit XmlElement(const QName& name); + explicit XmlElement(const QName& name, bool useDefaultNs); + explicit XmlElement(const XmlElement& elt); + + virtual ~XmlElement(); + + const QName& Name() const { return name_; } + void SetName(const QName& name) { name_ = name; } + + const std::string BodyText() const; + void SetBodyText(const std::string& text); + + const QName FirstElementName() const; + + XmlAttr* FirstAttr(); + const XmlAttr* FirstAttr() const + { return const_cast<XmlElement *>(this)->FirstAttr(); } + + // Attr will return an empty string if the attribute isn't there: + // use HasAttr to test presence of an attribute. + const std::string Attr(const StaticQName& name) const; + const std::string Attr(const QName& name) const; + bool HasAttr(const StaticQName& name) const; + bool HasAttr(const QName& name) const; + void SetAttr(const QName& name, const std::string& value); + void ClearAttr(const QName& name); + + XmlChild* FirstChild(); + const XmlChild* FirstChild() const { + return const_cast<XmlElement *>(this)->FirstChild(); + } + + XmlElement* FirstElement(); + const XmlElement* FirstElement() const { + return const_cast<XmlElement *>(this)->FirstElement(); + } + + XmlElement* NextElement(); + const XmlElement* NextElement() const { + return const_cast<XmlElement *>(this)->NextElement(); + } + + XmlElement* FirstWithNamespace(const std::string& ns); + const XmlElement* FirstWithNamespace(const std::string& ns) const { + return const_cast<XmlElement *>(this)->FirstWithNamespace(ns); + } + + XmlElement* NextWithNamespace(const std::string& ns); + const XmlElement* NextWithNamespace(const std::string& ns) const { + return const_cast<XmlElement *>(this)->NextWithNamespace(ns); + } + + XmlElement* FirstNamed(const StaticQName& name); + const XmlElement* FirstNamed(const StaticQName& name) const { + return const_cast<XmlElement *>(this)->FirstNamed(name); + } + + XmlElement* FirstNamed(const QName& name); + const XmlElement* FirstNamed(const QName& name) const { + return const_cast<XmlElement *>(this)->FirstNamed(name); + } + + XmlElement* NextNamed(const StaticQName& name); + const XmlElement* NextNamed(const StaticQName& name) const { + return const_cast<XmlElement *>(this)->NextNamed(name); + } + + XmlElement* NextNamed(const QName& name); + const XmlElement* NextNamed(const QName& name) const { + return const_cast<XmlElement *>(this)->NextNamed(name); + } + + // Finds the first element named 'name'. If that element can't be found then + // adds one and returns it. + XmlElement* FindOrAddNamedChild(const QName& name); + + const std::string TextNamed(const QName& name) const; + + void InsertChildAfter(XmlChild* predecessor, XmlChild* new_child); + void RemoveChildAfter(XmlChild* predecessor); + + void AddParsedText(const char* buf, int len); + // Note: CDATA is not supported by XMPP, therefore using this function will + // generate non-XMPP compatible XML. + void AddCDATAText(const char* buf, int len); + void AddText(const std::string& text); + void AddText(const std::string& text, int depth); + void AddElement(XmlElement* child); + void AddElement(XmlElement* child, int depth); + void AddAttr(const QName& name, const std::string& value); + void AddAttr(const QName& name, const std::string& value, int depth); + void ClearNamedChildren(const QName& name); + void ClearAttributes(); + void ClearChildren(); + + static XmlElement* ForStr(const std::string& str); + std::string Str() const; + + bool IsCDATA() const { return cdata_; } + + protected: + virtual bool IsTextImpl() const; + virtual XmlElement* AsElementImpl() const; + virtual XmlText* AsTextImpl() const; + + private: + QName name_; + XmlAttr* first_attr_; + XmlAttr* last_attr_; + XmlChild* first_child_; + XmlChild* last_child_; + bool cdata_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLELEMENT_H_ diff --git a/libjingle/xmllite/xmlelement_unittest.cc b/libjingle/xmllite/xmlelement_unittest.cc new file mode 100644 index 00000000..df8faedb --- /dev/null +++ b/libjingle/xmllite/xmlelement_unittest.cc @@ -0,0 +1,258 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <iostream> +#include <sstream> +#include <string> +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" + +using buzz::QName; +using buzz::XmlAttr; +using buzz::XmlChild; +using buzz::XmlElement; + +std::ostream& operator<<(std::ostream& os, const QName& name) { + os << name.Namespace() << ":" << name.LocalPart(); + return os; +} + +TEST(XmlElementTest, TestConstructors) { + XmlElement elt(QName("google:test", "first")); + EXPECT_EQ("<test:first xmlns:test=\"google:test\"/>", elt.Str()); + + XmlElement elt2(QName("google:test", "first"), true); + EXPECT_EQ("<first xmlns=\"google:test\"/>", elt2.Str()); +} + +TEST(XmlElementTest, TestAdd) { + XmlElement elt(QName("google:test", "root"), true); + elt.AddElement(new XmlElement(QName("google:test", "first"))); + elt.AddElement(new XmlElement(QName("google:test", "nested")), 1); + elt.AddText("nested-value", 2); + elt.AddText("between-", 1); + elt.AddText("value", 1); + elt.AddElement(new XmlElement(QName("google:test", "nested2")), 1); + elt.AddElement(new XmlElement(QName("google:test", "second"))); + elt.AddText("init-value", 1); + elt.AddElement(new XmlElement(QName("google:test", "nested3")), 1); + elt.AddText("trailing-value", 1); + + // make sure it looks ok overall + EXPECT_EQ("<root xmlns=\"google:test\">" + "<first><nested>nested-value</nested>between-value<nested2/></first>" + "<second>init-value<nested3/>trailing-value</second></root>", + elt.Str()); + + // make sure text was concatenated + XmlChild * pchild = + elt.FirstChild()->AsElement()->FirstChild()->NextChild(); + EXPECT_TRUE(pchild->IsText()); + EXPECT_EQ("between-value", pchild->AsText()->Text()); +} + +TEST(XmlElementTest, TestAttrs) { + XmlElement elt(QName("", "root")); + elt.SetAttr(QName("", "a"), "avalue"); + EXPECT_EQ("<root a=\"avalue\"/>", elt.Str()); + + elt.SetAttr(QName("", "b"), "bvalue"); + EXPECT_EQ("<root a=\"avalue\" b=\"bvalue\"/>", elt.Str()); + + elt.SetAttr(QName("", "a"), "avalue2"); + EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue\"/>", elt.Str()); + + elt.SetAttr(QName("", "b"), "bvalue2"); + EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\"/>", elt.Str()); + + elt.SetAttr(QName("", "c"), "cvalue"); + EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\"/>", elt.Str()); + + XmlAttr * patt = elt.FirstAttr(); + EXPECT_EQ(QName("", "a"), patt->Name()); + EXPECT_EQ("avalue2", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_EQ(QName("", "b"), patt->Name()); + EXPECT_EQ("bvalue2", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_EQ(QName("", "c"), patt->Name()); + EXPECT_EQ("cvalue", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_TRUE(NULL == patt); + + EXPECT_TRUE(elt.HasAttr(QName("", "a"))); + EXPECT_TRUE(elt.HasAttr(QName("", "b"))); + EXPECT_TRUE(elt.HasAttr(QName("", "c"))); + EXPECT_FALSE(elt.HasAttr(QName("", "d"))); + + elt.SetAttr(QName("", "d"), "dvalue"); + EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\" d=\"dvalue\"/>", + elt.Str()); + EXPECT_TRUE(elt.HasAttr(QName("", "d"))); + + elt.ClearAttr(QName("", "z")); // not found, no effect + EXPECT_EQ("<root a=\"avalue2\" b=\"bvalue2\" c=\"cvalue\" d=\"dvalue\"/>", + elt.Str()); + + elt.ClearAttr(QName("", "b")); + EXPECT_EQ("<root a=\"avalue2\" c=\"cvalue\" d=\"dvalue\"/>", elt.Str()); + + elt.ClearAttr(QName("", "a")); + EXPECT_EQ("<root c=\"cvalue\" d=\"dvalue\"/>", elt.Str()); + + elt.ClearAttr(QName("", "d")); + EXPECT_EQ("<root c=\"cvalue\"/>", elt.Str()); + + elt.ClearAttr(QName("", "c")); + EXPECT_EQ("<root/>", elt.Str()); +} + +TEST(XmlElementTest, TestBodyText) { + XmlElement elt(QName("", "root")); + EXPECT_EQ("", elt.BodyText()); + + elt.AddText("body value text"); + + EXPECT_EQ("body value text", elt.BodyText()); + + elt.ClearChildren(); + elt.AddText("more value "); + elt.AddText("text"); + + EXPECT_EQ("more value text", elt.BodyText()); + + elt.ClearChildren(); + elt.AddText("decoy"); + elt.AddElement(new XmlElement(QName("", "dummy"))); + EXPECT_EQ("", elt.BodyText()); + + elt.SetBodyText("replacement"); + EXPECT_EQ("replacement", elt.BodyText()); + + elt.SetBodyText(""); + EXPECT_TRUE(NULL == elt.FirstChild()); + + elt.SetBodyText("goodbye"); + EXPECT_EQ("goodbye", elt.FirstChild()->AsText()->Text()); + EXPECT_EQ("goodbye", elt.BodyText()); +} + +TEST(XmlElementTest, TestCopyConstructor) { + XmlElement * element = XmlElement::ForStr( + "<root xmlns='test-foo'>This is a <em a='avalue' b='bvalue'>" + "little <b>little</b></em> test</root>"); + + XmlElement * pelCopy = new XmlElement(*element); + EXPECT_EQ("<root xmlns=\"test-foo\">This is a <em a=\"avalue\" b=\"bvalue\">" + "little <b>little</b></em> test</root>", pelCopy->Str()); + delete pelCopy; + + pelCopy = new XmlElement(*(element->FirstChild()->NextChild()->AsElement())); + EXPECT_EQ("<foo:em a=\"avalue\" b=\"bvalue\" xmlns:foo=\"test-foo\">" + "little <foo:b>little</foo:b></foo:em>", pelCopy->Str()); + + XmlAttr * patt = pelCopy->FirstAttr(); + EXPECT_EQ(QName("", "a"), patt->Name()); + EXPECT_EQ("avalue", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_EQ(QName("", "b"), patt->Name()); + EXPECT_EQ("bvalue", patt->Value()); + + patt = patt->NextAttr(); + EXPECT_TRUE(NULL == patt); + delete pelCopy; + delete element; +} + +TEST(XmlElementTest, TestNameSearch) { + XmlElement * element = XmlElement::ForStr( + "<root xmlns='test-foo'>" + "<firstname>George</firstname>" + "<middlename>X.</middlename>" + "some text" + "<lastname>Harrison</lastname>" + "<firstname>John</firstname>" + "<middlename>Y.</middlename>" + "<lastname>Lennon</lastname>" + "</root>"); + EXPECT_TRUE(NULL == + element->FirstNamed(QName("", "firstname"))); + EXPECT_EQ(element->FirstChild(), + element->FirstNamed(QName("test-foo", "firstname"))); + EXPECT_EQ(element->FirstChild()->NextChild(), + element->FirstNamed(QName("test-foo", "middlename"))); + EXPECT_EQ(element->FirstElement()->NextElement(), + element->FirstNamed(QName("test-foo", "middlename"))); + EXPECT_EQ("Harrison", + element->TextNamed(QName("test-foo", "lastname"))); + EXPECT_EQ(element->FirstElement()->NextElement()->NextElement(), + element->FirstNamed(QName("test-foo", "lastname"))); + EXPECT_EQ("John", element->FirstNamed(QName("test-foo", "firstname"))-> + NextNamed(QName("test-foo", "firstname"))->BodyText()); + EXPECT_EQ("Y.", element->FirstNamed(QName("test-foo", "middlename"))-> + NextNamed(QName("test-foo", "middlename"))->BodyText()); + EXPECT_EQ("Lennon", element->FirstNamed(QName("test-foo", "lastname"))-> + NextNamed(QName("test-foo", "lastname"))->BodyText()); + EXPECT_TRUE(NULL == element->FirstNamed(QName("test-foo", "firstname"))-> + NextNamed(QName("test-foo", "firstname"))-> + NextNamed(QName("test-foo", "firstname"))); + + delete element; +} + +class XmlElementCreatorThread : public rtc::Thread { + public: + XmlElementCreatorThread(int count, buzz::QName qname) : + count_(count), qname_(qname) {} + + virtual ~XmlElementCreatorThread() { + Stop(); + } + + virtual void Run() { + std::vector<buzz::XmlElement*> elems; + for (int i = 0; i < count_; i++) { + elems.push_back(new XmlElement(qname_)); + } + for (int i = 0; i < count_; i++) { + delete elems[i]; + } + } + + private: + int count_; + buzz::QName qname_; +}; + +// If XmlElement creation and destruction isn't thread safe, +// this test should crash. +TEST(XmlElementTest, TestMultithread) { + int thread_count = 2; // Was 100, but that's too slow. + int elem_count = 100; // Was 100000, but that's too slow. + buzz::QName qname("foo", "bar"); + + std::vector<rtc::Thread*> threads; + for (int i = 0; i < thread_count; i++) { + threads.push_back( + new XmlElementCreatorThread(elem_count, qname)); + threads[i]->Start(); + } + + for (int i = 0; i < thread_count; i++) { + threads[i]->Stop(); + delete threads[i]; + } +} diff --git a/libjingle/xmllite/xmllite.gyp b/libjingle/xmllite/xmllite.gyp new file mode 100644 index 00000000..c4483232 --- /dev/null +++ b/libjingle/xmllite/xmllite.gyp @@ -0,0 +1,40 @@ +# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +{ + 'includes': [ '../../build/common.gypi', ], + 'targets': [ + { + 'target_name': 'rtc_xmllite', + 'type': 'static_library', + 'dependencies': [ + '<(webrtc_root)/base/base.gyp:webrtc_base', + '<(DEPTH)/third_party/expat/expat.gyp:expat', + ], + 'export_dependent_settings': [ + '<(DEPTH)/third_party/expat/expat.gyp:expat', + ], + 'sources': [ + 'qname.cc', + 'qname.h', + 'xmlbuilder.cc', + 'xmlbuilder.h', + 'xmlconstants.cc', + 'xmlconstants.h', + 'xmlelement.cc', + 'xmlelement.h', + 'xmlnsstack.cc', + 'xmlnsstack.h', + 'xmlparser.cc', + 'xmlparser.h', + 'xmlprinter.cc', + 'xmlprinter.h', + ], + }, + ], +} diff --git a/libjingle/xmllite/xmllite_tests.gyp b/libjingle/xmllite/xmllite_tests.gyp new file mode 100644 index 00000000..a9173bc9 --- /dev/null +++ b/libjingle/xmllite/xmllite_tests.gyp @@ -0,0 +1,34 @@ +# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +{ + 'includes': [ '../../build/common.gypi', ], + 'targets': [ + { + 'target_name': 'rtc_xmllite_unittest', + 'type': 'executable', + 'dependencies': [ + '<(DEPTH)/testing/gtest.gyp:gtest', + '<(webrtc_root)/base/base_tests.gyp:webrtc_base_tests_utils', + 'xmllite.gyp:rtc_xmllite', + ], + 'cflags_cc!': [ + '-Wnon-virtual-dtor', + ], + 'sources': [ + 'qname_unittest.cc', + 'xmlbuilder_unittest.cc', + 'xmlelement_unittest.cc', + 'xmlnsstack_unittest.cc', + 'xmlparser_unittest.cc', + 'xmlprinter_unittest.cc', + ], + }, + ], +} + diff --git a/libjingle/xmllite/xmlnsstack.cc b/libjingle/xmllite/xmlnsstack.cc new file mode 100644 index 00000000..bac2fc69 --- /dev/null +++ b/libjingle/xmllite/xmlnsstack.cc @@ -0,0 +1,178 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlnsstack.h" + +#include <sstream> +#include <string> +#include <vector> + +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" + +namespace buzz { + +XmlnsStack::XmlnsStack() : + pxmlnsStack_(new std::vector<std::string>), + pxmlnsDepthStack_(new std::vector<size_t>) { +} + +XmlnsStack::~XmlnsStack() {} + +void XmlnsStack::PushFrame() { + pxmlnsDepthStack_->push_back(pxmlnsStack_->size()); +} + +void XmlnsStack::PopFrame() { + size_t prev_size = pxmlnsDepthStack_->back(); + pxmlnsDepthStack_->pop_back(); + if (prev_size < pxmlnsStack_->size()) { + pxmlnsStack_->erase(pxmlnsStack_->begin() + prev_size, + pxmlnsStack_->end()); + } +} + +std::pair<std::string, bool> XmlnsStack::NsForPrefix( + const std::string& prefix) { + if (prefix.length() >= 3 && + (prefix[0] == 'x' || prefix[0] == 'X') && + (prefix[1] == 'm' || prefix[1] == 'M') && + (prefix[2] == 'l' || prefix[2] == 'L')) { + if (prefix == "xml") + return std::make_pair(NS_XML, true); + if (prefix == "xmlns") + return std::make_pair(NS_XMLNS, true); + // Other names with xml prefix are illegal. + return std::make_pair(STR_EMPTY, false); + } + + std::vector<std::string>::iterator pos; + for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) { + pos -= 2; + if (*pos == prefix) + return std::make_pair(*(pos + 1), true); + } + + if (prefix == STR_EMPTY) + return std::make_pair(STR_EMPTY, true); // default namespace + + return std::make_pair(STR_EMPTY, false); // none found +} + +bool XmlnsStack::PrefixMatchesNs(const std::string& prefix, + const std::string& ns) { + const std::pair<std::string, bool> match = NsForPrefix(prefix); + return match.second && (match.first == ns); +} + +std::pair<std::string, bool> XmlnsStack::PrefixForNs(const std::string& ns, + bool isattr) { + if (ns == NS_XML) + return std::make_pair(std::string("xml"), true); + if (ns == NS_XMLNS) + return std::make_pair(std::string("xmlns"), true); + if (isattr ? ns == STR_EMPTY : PrefixMatchesNs(STR_EMPTY, ns)) + return std::make_pair(STR_EMPTY, true); + + std::vector<std::string>::iterator pos; + for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) { + pos -= 2; + if (*(pos + 1) == ns && + (!isattr || !pos->empty()) && PrefixMatchesNs(*pos, ns)) + return std::make_pair(*pos, true); + } + + return std::make_pair(STR_EMPTY, false); // none found +} + +std::string XmlnsStack::FormatQName(const QName& name, bool isAttr) { + std::string prefix(PrefixForNs(name.Namespace(), isAttr).first); + if (prefix == STR_EMPTY) + return name.LocalPart(); + else + return prefix + ':' + name.LocalPart(); +} + +void XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) { + pxmlnsStack_->push_back(prefix); + pxmlnsStack_->push_back(ns); +} + +void XmlnsStack::RemoveXmlns() { + pxmlnsStack_->pop_back(); + pxmlnsStack_->pop_back(); +} + +static bool IsAsciiLetter(char ch) { + return ((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z')); +} + +static std::string AsciiLower(const std::string & s) { + std::string result(s); + size_t i; + for (i = 0; i < result.length(); i++) { + if (result[i] >= 'A' && result[i] <= 'Z') + result[i] += 'a' - 'A'; + } + return result; +} + +static std::string SuggestPrefix(const std::string & ns) { + size_t len = ns.length(); + size_t i = ns.find_last_of('.'); + if (i != std::string::npos && len - i <= 4 + 1) + len = i; // chop off ".html" or ".xsd" or ".?{0,4}" + size_t last = len; + while (last > 0) { + last -= 1; + if (IsAsciiLetter(ns[last])) { + size_t first = last; + last += 1; + while (first > 0) { + if (!IsAsciiLetter(ns[first - 1])) + break; + first -= 1; + } + if (last - first > 4) + last = first + 3; + std::string candidate(AsciiLower(ns.substr(first, last - first))); + if (candidate.find("xml") != 0) + return candidate; + break; + } + } + return "ns"; +} + +std::pair<std::string, bool> XmlnsStack::AddNewPrefix(const std::string& ns, + bool isAttr) { + if (PrefixForNs(ns, isAttr).second) + return std::make_pair(STR_EMPTY, false); + + std::string base(SuggestPrefix(ns)); + std::string result(base); + int i = 2; + while (NsForPrefix(result).second) { + std::stringstream ss; + ss << base; + ss << (i++); + ss >> result; + } + AddXmlns(result, ns); + return std::make_pair(result, true); +} + +void XmlnsStack::Reset() { + pxmlnsStack_->clear(); + pxmlnsDepthStack_->clear(); +} + +} diff --git a/libjingle/xmllite/xmlnsstack.h b/libjingle/xmllite/xmlnsstack.h new file mode 100644 index 00000000..174f8aef --- /dev/null +++ b/libjingle/xmllite/xmlnsstack.h @@ -0,0 +1,45 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLNSSTACK_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLNSSTACK_H_ + +#include <string> +#include <vector> +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/base/scoped_ptr.h" + +namespace buzz { + +class XmlnsStack { +public: + XmlnsStack(); + ~XmlnsStack(); + + void AddXmlns(const std::string& prefix, const std::string& ns); + void RemoveXmlns(); + void PushFrame(); + void PopFrame(); + void Reset(); + + std::pair<std::string, bool> NsForPrefix(const std::string& prefix); + bool PrefixMatchesNs(const std::string & prefix, const std::string & ns); + std::pair<std::string, bool> PrefixForNs(const std::string& ns, bool isAttr); + std::pair<std::string, bool> AddNewPrefix(const std::string& ns, bool isAttr); + std::string FormatQName(const QName & name, bool isAttr); + +private: + + rtc::scoped_ptr<std::vector<std::string> > pxmlnsStack_; + rtc::scoped_ptr<std::vector<size_t> > pxmlnsDepthStack_; +}; +} + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLNSSTACK_H_ diff --git a/libjingle/xmllite/xmlnsstack_unittest.cc b/libjingle/xmllite/xmlnsstack_unittest.cc new file mode 100644 index 00000000..a57a4b07 --- /dev/null +++ b/libjingle/xmllite/xmlnsstack_unittest.cc @@ -0,0 +1,241 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlnsstack.h" + +#include <iostream> +#include <sstream> +#include <string> + +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::NS_XML; +using buzz::NS_XMLNS; +using buzz::QName; +using buzz::XmlnsStack; + +TEST(XmlnsStackTest, TestBuiltin) { + XmlnsStack stack; + + EXPECT_EQ(std::string(NS_XML), stack.NsForPrefix("xml").first); + EXPECT_EQ(std::string(NS_XMLNS), stack.NsForPrefix("xmlns").first); + EXPECT_EQ("", stack.NsForPrefix("").first); + + EXPECT_EQ("xml", stack.PrefixForNs(NS_XML, false).first); + EXPECT_EQ("xmlns", stack.PrefixForNs(NS_XMLNS, false).first); + EXPECT_EQ("", stack.PrefixForNs("", false).first); + EXPECT_EQ("", stack.PrefixForNs("", true).first); +} + +TEST(XmlnsStackTest, TestNsForPrefix) { + XmlnsStack stack; + stack.AddXmlns("pre1", "ns1"); + stack.AddXmlns("pre2", "ns2"); + stack.AddXmlns("pre1", "ns3"); + stack.AddXmlns("", "ns4"); + + EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first); + EXPECT_TRUE(stack.NsForPrefix("pre1").second); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("ns4", stack.NsForPrefix("").first); + EXPECT_EQ("", stack.NsForPrefix("pre3").first); + EXPECT_FALSE(stack.NsForPrefix("pre3").second); +} + +TEST(XmlnsStackTest, TestPrefixForNs) { + XmlnsStack stack; + stack.AddXmlns("pre1", "ns1"); + stack.AddXmlns("pre2", "ns2"); + stack.AddXmlns("pre1", "ns3"); + stack.AddXmlns("pre3", "ns2"); + stack.AddXmlns("pre4", "ns4"); + stack.AddXmlns("", "ns4"); + + EXPECT_EQ("", stack.PrefixForNs("ns1", false).first); + EXPECT_FALSE(stack.PrefixForNs("ns1", false).second); + EXPECT_EQ("", stack.PrefixForNs("ns1", true).first); + EXPECT_FALSE(stack.PrefixForNs("ns1", true).second); + EXPECT_EQ("pre3", stack.PrefixForNs("ns2", false).first); + EXPECT_TRUE(stack.PrefixForNs("ns2", false).second); + EXPECT_EQ("pre3", stack.PrefixForNs("ns2", true).first); + EXPECT_TRUE(stack.PrefixForNs("ns2", true).second); + EXPECT_EQ("pre1", stack.PrefixForNs("ns3", false).first); + EXPECT_EQ("pre1", stack.PrefixForNs("ns3", true).first); + EXPECT_EQ("", stack.PrefixForNs("ns4", false).first); + EXPECT_TRUE(stack.PrefixForNs("ns4", false).second); + EXPECT_EQ("pre4", stack.PrefixForNs("ns4", true).first); + EXPECT_EQ("", stack.PrefixForNs("ns5", false).first); + EXPECT_FALSE(stack.PrefixForNs("ns5", false).second); + EXPECT_EQ("", stack.PrefixForNs("ns5", true).first); + EXPECT_EQ("", stack.PrefixForNs("", false).first); + EXPECT_EQ("", stack.PrefixForNs("", true).first); + + stack.AddXmlns("", "ns6"); + EXPECT_EQ("", stack.PrefixForNs("ns6", false).first); + EXPECT_TRUE(stack.PrefixForNs("ns6", false).second); + EXPECT_EQ("", stack.PrefixForNs("ns6", true).first); + EXPECT_FALSE(stack.PrefixForNs("ns6", true).second); +} + +TEST(XmlnsStackTest, TestFrames) { + XmlnsStack stack; + stack.PushFrame(); + stack.AddXmlns("pre1", "ns1"); + stack.AddXmlns("pre2", "ns2"); + + stack.PushFrame(); + stack.AddXmlns("pre1", "ns3"); + stack.AddXmlns("pre3", "ns2"); + stack.AddXmlns("pre4", "ns4"); + + stack.PushFrame(); + stack.PushFrame(); + stack.AddXmlns("", "ns4"); + + // basic test + EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first); + EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_EQ("ns4", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + + // pop the default xmlns definition + stack.PopFrame(); + EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first); + EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_EQ("", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + + // pop empty frame (nop) + stack.PopFrame(); + EXPECT_EQ("ns3", stack.NsForPrefix("pre1").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre3").first); + EXPECT_EQ("ns4", stack.NsForPrefix("pre4").first); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_EQ("", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + + // pop frame with three defs + stack.PopFrame(); + EXPECT_EQ("ns1", stack.NsForPrefix("pre1").first); + EXPECT_EQ("ns2", stack.NsForPrefix("pre2").first); + EXPECT_EQ("", stack.NsForPrefix("pre3").first); + EXPECT_FALSE(stack.NsForPrefix("pre3").second); + EXPECT_EQ("", stack.NsForPrefix("pre4").first); + EXPECT_FALSE(stack.NsForPrefix("pre4").second); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_EQ("", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + + // pop frame with last two defs + stack.PopFrame(); + EXPECT_FALSE(stack.NsForPrefix("pre1").second); + EXPECT_FALSE(stack.NsForPrefix("pre2").second); + EXPECT_FALSE(stack.NsForPrefix("pre3").second); + EXPECT_FALSE(stack.NsForPrefix("pre4").second); + EXPECT_FALSE(stack.NsForPrefix("pre5").second); + EXPECT_TRUE(stack.NsForPrefix("").second); + EXPECT_EQ("", stack.NsForPrefix("pre1").first); + EXPECT_EQ("", stack.NsForPrefix("pre2").first); + EXPECT_EQ("", stack.NsForPrefix("pre3").first); + EXPECT_EQ("", stack.NsForPrefix("pre4").first); + EXPECT_EQ("", stack.NsForPrefix("pre5").first); + EXPECT_EQ("", stack.NsForPrefix("").first); +} + +TEST(XmlnsStackTest, TestAddNewPrefix) { + XmlnsStack stack; + + // builtin namespaces cannot be added + EXPECT_FALSE(stack.AddNewPrefix("", true).second); + EXPECT_FALSE(stack.AddNewPrefix("", false).second); + EXPECT_FALSE(stack.AddNewPrefix(NS_XML, true).second); + EXPECT_FALSE(stack.AddNewPrefix(NS_XML, false).second); + EXPECT_FALSE(stack.AddNewPrefix(NS_XMLNS, true).second); + EXPECT_FALSE(stack.AddNewPrefix(NS_XMLNS, false).second); + + // namespaces already added cannot be added again. + EXPECT_EQ("foo", stack.AddNewPrefix("http://a.b.com/foo.htm", true).first); + EXPECT_EQ("bare", stack.AddNewPrefix("http://a.b.com/bare", false).first); + EXPECT_EQ("z", stack.AddNewPrefix("z", false).first); + EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/foo.htm", true).second); + EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/bare", true).second); + EXPECT_FALSE(stack.AddNewPrefix("z", true).second); + EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/foo.htm", false).second); + EXPECT_FALSE(stack.AddNewPrefix("http://a.b.com/bare", false).second); + EXPECT_FALSE(stack.AddNewPrefix("z", false).second); + + // default namespace usable by non-attributes only + stack.AddXmlns("", "http://my/default"); + EXPECT_FALSE(stack.AddNewPrefix("http://my/default", false).second); + EXPECT_EQ("def", stack.AddNewPrefix("http://my/default", true).first); + + // namespace cannot start with 'xml' + EXPECT_EQ("ns", stack.AddNewPrefix("http://a.b.com/xmltest", true).first); + EXPECT_EQ("ns2", stack.AddNewPrefix("xmlagain", false).first); + + // verify added namespaces are still defined + EXPECT_EQ("http://a.b.com/foo.htm", stack.NsForPrefix("foo").first); + EXPECT_TRUE(stack.NsForPrefix("foo").second); + EXPECT_EQ("http://a.b.com/bare", stack.NsForPrefix("bare").first); + EXPECT_TRUE(stack.NsForPrefix("bare").second); + EXPECT_EQ("z", stack.NsForPrefix("z").first); + EXPECT_TRUE(stack.NsForPrefix("z").second); + EXPECT_EQ("http://my/default", stack.NsForPrefix("").first); + EXPECT_TRUE(stack.NsForPrefix("").second); + EXPECT_EQ("http://my/default", stack.NsForPrefix("def").first); + EXPECT_TRUE(stack.NsForPrefix("def").second); + EXPECT_EQ("http://a.b.com/xmltest", stack.NsForPrefix("ns").first); + EXPECT_TRUE(stack.NsForPrefix("ns").second); + EXPECT_EQ("xmlagain", stack.NsForPrefix("ns2").first); + EXPECT_TRUE(stack.NsForPrefix("ns2").second); +} + +TEST(XmlnsStackTest, TestFormatQName) { + XmlnsStack stack; + stack.AddXmlns("pre1", "ns1"); + stack.AddXmlns("pre2", "ns2"); + stack.AddXmlns("pre1", "ns3"); + stack.AddXmlns("", "ns4"); + + EXPECT_EQ("zip", + stack.FormatQName(QName("ns1", "zip"), false)); // no match + EXPECT_EQ("pre2:abracadabra", + stack.FormatQName(QName("ns2", "abracadabra"), false)); + EXPECT_EQ("pre1:a", + stack.FormatQName(QName("ns3", "a"), false)); + EXPECT_EQ("simple", + stack.FormatQName(QName("ns4", "simple"), false)); + EXPECT_EQ("root", + stack.FormatQName(QName("", "root"), false)); // no match + + EXPECT_EQ("zip", + stack.FormatQName(QName("ns1", "zip"), true)); // no match + EXPECT_EQ("pre2:abracadabra", + stack.FormatQName(QName("ns2", "abracadabra"), true)); + EXPECT_EQ("pre1:a", + stack.FormatQName(QName("ns3", "a"), true)); + EXPECT_EQ("simple", + stack.FormatQName(QName("ns4", "simple"), true)); // no match + EXPECT_EQ("root", + stack.FormatQName(QName("", "root"), true)); +} diff --git a/libjingle/xmllite/xmlparser.cc b/libjingle/xmllite/xmlparser.cc new file mode 100644 index 00000000..bdb8be7d --- /dev/null +++ b/libjingle/xmllite/xmlparser.cc @@ -0,0 +1,261 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlparser.h" + +#include <string> +#include <vector> + +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlnsstack.h" +#include "webrtc/libjingle/xmllite/xmlnsstack.h" +#include "webrtc/base/common.h" + +namespace buzz { + + +static void +StartElementCallback(void * userData, const char *name, const char **atts) { + (static_cast<XmlParser *>(userData))->ExpatStartElement(name, atts); +} + +static void +EndElementCallback(void * userData, const char *name) { + (static_cast<XmlParser *>(userData))->ExpatEndElement(name); +} + +static void +CharacterDataCallback(void * userData, const char *text, int len) { + (static_cast<XmlParser *>(userData))->ExpatCharacterData(text, len); +} + +static void +XmlDeclCallback(void * userData, const char * ver, const char * enc, int st) { + (static_cast<XmlParser *>(userData))->ExpatXmlDecl(ver, enc, st); +} + +XmlParser::XmlParser(XmlParseHandler *pxph) : + pxph_(pxph), sentError_(false) { + expat_ = XML_ParserCreate(NULL); + XML_SetUserData(expat_, this); + XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback); + XML_SetCharacterDataHandler(expat_, CharacterDataCallback); + XML_SetXmlDeclHandler(expat_, XmlDeclCallback); +} + +void +XmlParser::Reset() { + if (!XML_ParserReset(expat_, NULL)) { + XML_ParserFree(expat_); + expat_ = XML_ParserCreate(NULL); + } + XML_SetUserData(expat_, this); + XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback); + XML_SetCharacterDataHandler(expat_, CharacterDataCallback); + XML_SetXmlDeclHandler(expat_, XmlDeclCallback); + context_.Reset(); + sentError_ = false; +} + +static bool +XmlParser_StartsWithXmlns(const char *name) { + return name[0] == 'x' && + name[1] == 'm' && + name[2] == 'l' && + name[3] == 'n' && + name[4] == 's'; +} + +void +XmlParser::ExpatStartElement(const char *name, const char **atts) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + const char **att; + context_.StartElement(); + for (att = atts; *att; att += 2) { + if (XmlParser_StartsWithXmlns(*att)) { + if ((*att)[5] == '\0') { + context_.StartNamespace("", *(att + 1)); + } + else if ((*att)[5] == ':') { + if (**(att + 1) == '\0') { + // In XML 1.0 empty namespace illegal with prefix (not in 1.1) + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + context_.StartNamespace((*att) + 6, *(att + 1)); + } + } + } + context_.SetPosition(XML_GetCurrentLineNumber(expat_), + XML_GetCurrentColumnNumber(expat_), + XML_GetCurrentByteIndex(expat_)); + pxph_->StartElement(&context_, name, atts); +} + +void +XmlParser::ExpatEndElement(const char *name) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + context_.EndElement(); + context_.SetPosition(XML_GetCurrentLineNumber(expat_), + XML_GetCurrentColumnNumber(expat_), + XML_GetCurrentByteIndex(expat_)); + pxph_->EndElement(&context_, name); +} + +void +XmlParser::ExpatCharacterData(const char *text, int len) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + context_.SetPosition(XML_GetCurrentLineNumber(expat_), + XML_GetCurrentColumnNumber(expat_), + XML_GetCurrentByteIndex(expat_)); + pxph_->CharacterData(&context_, text, len); +} + +void +XmlParser::ExpatXmlDecl(const char * ver, const char * enc, int standalone) { + if (context_.RaisedError() != XML_ERROR_NONE) + return; + + if (ver && std::string("1.0") != ver) { + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (standalone == 0) { + context_.RaiseError(XML_ERROR_SYNTAX); + return; + } + + if (enc && !((enc[0] == 'U' || enc[0] == 'u') && + (enc[1] == 'T' || enc[1] == 't') && + (enc[2] == 'F' || enc[2] == 'f') && + enc[3] == '-' && enc[4] =='8')) { + context_.RaiseError(XML_ERROR_INCORRECT_ENCODING); + return; + } + +} + +bool +XmlParser::Parse(const char *data, size_t len, bool isFinal) { + if (sentError_) + return false; + + if (XML_Parse(expat_, data, static_cast<int>(len), isFinal) != + XML_STATUS_OK) { + context_.SetPosition(XML_GetCurrentLineNumber(expat_), + XML_GetCurrentColumnNumber(expat_), + XML_GetCurrentByteIndex(expat_)); + context_.RaiseError(XML_GetErrorCode(expat_)); + } + + if (context_.RaisedError() != XML_ERROR_NONE) { + sentError_ = true; + pxph_->Error(&context_, context_.RaisedError()); + return false; + } + + return true; +} + +XmlParser::~XmlParser() { + XML_ParserFree(expat_); +} + +void +XmlParser::ParseXml(XmlParseHandler *pxph, std::string text) { + XmlParser parser(pxph); + parser.Parse(text.c_str(), text.length(), true); +} + +XmlParser::ParseContext::ParseContext() : + xmlnsstack_(), + raised_(XML_ERROR_NONE), + line_number_(0), + column_number_(0), + byte_index_(0) { +} + +void +XmlParser::ParseContext::StartNamespace(const char *prefix, const char *ns) { + xmlnsstack_.AddXmlns(*prefix ? prefix : STR_EMPTY, ns); +} + +void +XmlParser::ParseContext::StartElement() { + xmlnsstack_.PushFrame(); +} + +void +XmlParser::ParseContext::EndElement() { + xmlnsstack_.PopFrame(); +} + +QName +XmlParser::ParseContext::ResolveQName(const char* qname, bool isAttr) { + const char *c; + for (c = qname; *c; ++c) { + if (*c == ':') { + const std::pair<std::string, bool> result = + xmlnsstack_.NsForPrefix(std::string(qname, c - qname)); + if (!result.second) + return QName(); + return QName(result.first, c + 1); + } + } + if (isAttr) + return QName(STR_EMPTY, qname); + + std::pair<std::string, bool> result = xmlnsstack_.NsForPrefix(STR_EMPTY); + if (!result.second) + return QName(); + + return QName(result.first, qname); +} + +void +XmlParser::ParseContext::Reset() { + xmlnsstack_.Reset(); + raised_ = XML_ERROR_NONE; +} + +void +XmlParser::ParseContext::SetPosition(int line, int column, + long byte_index) { + line_number_ = line; + column_number_ = column; + byte_index_ = byte_index; +} + +void +XmlParser::ParseContext::GetPosition(unsigned long * line, + unsigned long * column, + unsigned long * byte_index) { + if (line != NULL) { + *line = static_cast<unsigned long>(line_number_); + } + + if (column != NULL) { + *column = static_cast<unsigned long>(column_number_); + } + + if (byte_index != NULL) { + *byte_index = static_cast<unsigned long>(byte_index_); + } +} + +XmlParser::ParseContext::~ParseContext() { +} + +} // namespace buzz diff --git a/libjingle/xmllite/xmlparser.h b/libjingle/xmllite/xmlparser.h new file mode 100644 index 00000000..131c585e --- /dev/null +++ b/libjingle/xmllite/xmlparser.h @@ -0,0 +1,103 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLPARSER_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLPARSER_H_ + +#include <string> + +#include "webrtc/libjingle/xmllite/xmlnsstack.h" +#ifdef EXPAT_RELATIVE_PATH +#include "expat.h" +#else +#include "third_party/expat/v2_0_1/Source/lib/expat.h" +#endif // EXPAT_RELATIVE_PATH + +struct XML_ParserStruct; +typedef struct XML_ParserStruct* XML_Parser; + +namespace buzz { + +class XmlParseHandler; +class XmlParseContext; +class XmlParser; + +class XmlParseContext { +public: + virtual ~XmlParseContext() {} + virtual QName ResolveQName(const char * qname, bool isAttr) = 0; + virtual void RaiseError(XML_Error err) = 0; + virtual void GetPosition(unsigned long * line, unsigned long * column, + unsigned long * byte_index) = 0; +}; + +class XmlParseHandler { +public: + virtual ~XmlParseHandler() {} + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) = 0; + virtual void EndElement(XmlParseContext * pctx, + const char * name) = 0; + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) = 0; + virtual void Error(XmlParseContext * pctx, + XML_Error errorCode) = 0; +}; + +class XmlParser { +public: + static void ParseXml(XmlParseHandler * pxph, std::string text); + + explicit XmlParser(XmlParseHandler * pxph); + bool Parse(const char * data, size_t len, bool isFinal); + void Reset(); + virtual ~XmlParser(); + + // expat callbacks + void ExpatStartElement(const char * name, const char ** atts); + void ExpatEndElement(const char * name); + void ExpatCharacterData(const char * text, int len); + void ExpatXmlDecl(const char * ver, const char * enc, int standalone); + +private: + + class ParseContext : public XmlParseContext { + public: + ParseContext(); + virtual ~ParseContext(); + virtual QName ResolveQName(const char * qname, bool isAttr); + virtual void RaiseError(XML_Error err) { if (!raised_) raised_ = err; } + virtual void GetPosition(unsigned long * line, unsigned long * column, + unsigned long * byte_index); + XML_Error RaisedError() { return raised_; } + void Reset(); + + void StartElement(); + void EndElement(); + void StartNamespace(const char * prefix, const char * ns); + void SetPosition(int line, int column, long byte_index); + + private: + XmlnsStack xmlnsstack_; + XML_Error raised_; + XML_Size line_number_; + XML_Size column_number_; + XML_Index byte_index_; + }; + + ParseContext context_; + XML_Parser expat_; + XmlParseHandler * pxph_; + bool sentError_; +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLPARSER_H_ diff --git a/libjingle/xmllite/xmlparser_unittest.cc b/libjingle/xmllite/xmlparser_unittest.cc new file mode 100644 index 00000000..a73d81e6 --- /dev/null +++ b/libjingle/xmllite/xmlparser_unittest.cc @@ -0,0 +1,285 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <iostream> +#include <sstream> +#include <string> +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlparser.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::QName; +using buzz::XmlParser; +using buzz::XmlParseContext; +using buzz::XmlParseHandler; + +class XmlParserTestHandler : public XmlParseHandler { + public: + virtual void StartElement(XmlParseContext * pctx, + const char * name, const char ** atts) { + ss_ << "START (" << pctx->ResolveQName(name, false).Merged(); + while (*atts) { + ss_ << ", " << pctx->ResolveQName(*atts, true).Merged() + << "='" << *(atts+1) << "'"; + atts += 2; + } + ss_ << ") "; + } + virtual void EndElement(XmlParseContext * pctx, const char * name) { + RTC_UNUSED(pctx); + RTC_UNUSED(name); + ss_ << "END "; + } + virtual void CharacterData(XmlParseContext * pctx, + const char * text, int len) { + RTC_UNUSED(pctx); + ss_ << "TEXT (" << std::string(text, len) << ") "; + } + virtual void Error(XmlParseContext * pctx, XML_Error code) { + RTC_UNUSED(pctx); + ss_ << "ERROR (" << static_cast<int>(code) << ") "; + } + virtual ~XmlParserTestHandler() { + } + + std::string Str() { + return ss_.str(); + } + + std::string StrClear() { + std::string result = ss_.str(); + ss_.str(""); + return result; + } + + private: + std::stringstream ss_; +}; + + +TEST(XmlParserTest, TestTrivial) { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<testing/>"); + EXPECT_EQ("START (testing) END ", handler.Str()); +} + +TEST(XmlParserTest, TestAttributes) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<testing a='b'/>"); + EXPECT_EQ("START (testing, a='b') END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<testing e='' long='some text'/>"); + EXPECT_EQ("START (testing, e='', long='some text') END ", handler.Str()); + } +} + +TEST(XmlParserTest, TestNesting) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "<top><first/><second><third></third></second></top>"); + EXPECT_EQ("START (top) START (first) END START (second) START (third) " + "END END END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<top><fifth><deeper><and><deeper/></and>" + "<sibling><leaf/></sibling></deeper></fifth><first/><second>" + "<third></third></second></top>"); + EXPECT_EQ("START (top) START (fifth) START (deeper) START (and) START " + "(deeper) END END START (sibling) START (leaf) END END END " + "END START (first) END START (second) START (third) END END END ", + handler.Str()); + } +} + +TEST(XmlParserTest, TestXmlDecl) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<?xml version=\"1.0\"?><testing/>"); + EXPECT_EQ("START (testing) END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "<?xml version=\"1.0\" encoding=\"utf-8\"?><testing/>"); + EXPECT_EQ("START (testing) END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + "<testing/>"); + EXPECT_EQ("START (testing) END ", handler.Str()); + } +} + +TEST(XmlParserTest, TestNamespace) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<top xmlns='my-namespace' a='b'/>"); + EXPECT_EQ("START (my-namespace:top, xmlns='my-namespace', a='b') END ", + handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<foo:top xmlns:foo='my-namespace' " + "a='b' foo:c='d'/>"); + EXPECT_EQ("START (my-namespace:top, " + "http://www.w3.org/2000/xmlns/:foo='my-namespace', " + "a='b', my-namespace:c='d') END ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<top><nested xmlns='my-namespace'><leaf/>" + "</nested><sibling/></top>"); + EXPECT_EQ("START (top) START (my-namespace:nested, xmlns='my-namespace') " + "START (my-namespace:leaf) END END START (sibling) END END ", + handler.Str()); + } +} + +TEST(XmlParserTest, TestIncremental) { + XmlParserTestHandler handler; + XmlParser parser(&handler); + std::string fragment; + + fragment = "<stream:stream"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("", handler.StrClear()); + + fragment = " id=\"abcdefg\" xmlns=\""; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("", handler.StrClear()); + + fragment = "j:c\" xmlns:stream='hm"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("", handler.StrClear()); + + fragment = "ph'><test"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START (hmph:stream, id='abcdefg', xmlns='j:c', " + "http://www.w3.org/2000/xmlns/:stream='hmph') ", handler.StrClear()); + + fragment = "ing/><again/>abracad"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START (j:c:testing) END START (j:c:again) END TEXT (abracad) ", + handler.StrClear()); + + fragment = "abra</stream:"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("TEXT (abra) ", handler.StrClear()); + + fragment = "stream>"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("END ", handler.StrClear()); +} + +TEST(XmlParserTest, TestReset) { + { + XmlParserTestHandler handler; + XmlParser parser(&handler); + std::string fragment; + + fragment = "<top><first/><second><third></third>"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START (top) START (first) END START (second) START (third) END ", + handler.StrClear()); + + parser.Reset(); + fragment = "<tip><first/><second><third></third>"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START (tip) START (first) END START (second) START (third) END ", + handler.StrClear()); + } + { + XmlParserTestHandler handler; + XmlParser parser(&handler); + std::string fragment; + + fragment = "<top xmlns='m'>"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START (m:top, xmlns='m') ", handler.StrClear()); + + fragment = "<testing/><frag"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START (m:testing) END ", handler.StrClear()); + + parser.Reset(); + fragment = "<testing><fragment/"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START (testing) ", handler.StrClear()); + + fragment = ">"; + parser.Parse(fragment.c_str(), fragment.length(), false); + EXPECT_EQ("START (fragment) END ", handler.StrClear()); + } +} + +TEST(XmlParserTest, TestError) { + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "junk"); + EXPECT_EQ("ERROR (2) ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<top/> garbage "); + EXPECT_EQ("START (top) END ERROR (9) ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<-hm->"); + EXPECT_EQ("ERROR (4) ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, "<hello>&foobar;</hello>"); + EXPECT_EQ("START (hello) ERROR (11) ", handler.Str()); + } + { + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "<!DOCTYPE HTML PUBLIC \"foobar\" \"barfoo\">"); + EXPECT_EQ("ERROR (3) ", handler.Str()); + } + { + // XmlParser requires utf-8 + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?><test/>"); + EXPECT_EQ("ERROR (19) ", handler.Str()); + } + { + // XmlParser requires version 1.0 + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "<?xml version=\"2.0\"?><test/>"); + EXPECT_EQ("ERROR (2) ", handler.Str()); + } + { + // XmlParser requires standalone documents + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "<?xml version=\"1.0\" standalone=\"no\"?><test/>"); + EXPECT_EQ("ERROR (2) ", handler.Str()); + } + { + // XmlParser doesn't like empty namespace URIs + XmlParserTestHandler handler; + XmlParser::ParseXml(&handler, + "<test xmlns:foo='' foo:bar='huh?'>"); + EXPECT_EQ("ERROR (2) ", handler.Str()); + } +} diff --git a/libjingle/xmllite/xmlprinter.cc b/libjingle/xmllite/xmlprinter.cc new file mode 100644 index 00000000..27d7cc0b --- /dev/null +++ b/libjingle/xmllite/xmlprinter.cc @@ -0,0 +1,174 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlprinter.h" + +#include <sstream> +#include <string> +#include <vector> + +#include "webrtc/libjingle/xmllite/xmlconstants.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlnsstack.h" + +namespace buzz { + +class XmlPrinterImpl { +public: + XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack); + void PrintElement(const XmlElement* element); + void PrintQuotedValue(const std::string& text); + void PrintBodyText(const std::string& text); + void PrintCDATAText(const std::string& text); + +private: + std::ostream *pout_; + XmlnsStack* ns_stack_; +}; + +void XmlPrinter::PrintXml(std::ostream* pout, const XmlElement* element) { + XmlnsStack ns_stack; + PrintXml(pout, element, &ns_stack); +} + +void XmlPrinter::PrintXml(std::ostream* pout, const XmlElement* element, + XmlnsStack* ns_stack) { + XmlPrinterImpl printer(pout, ns_stack); + printer.PrintElement(element); +} + +XmlPrinterImpl::XmlPrinterImpl(std::ostream* pout, XmlnsStack* ns_stack) + : pout_(pout), + ns_stack_(ns_stack) { +} + +void XmlPrinterImpl::PrintElement(const XmlElement* element) { + ns_stack_->PushFrame(); + + // first go through attrs of pel to add xmlns definitions + const XmlAttr* attr; + for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) { + if (attr->Name() == QN_XMLNS) { + ns_stack_->AddXmlns(STR_EMPTY, attr->Value()); + } else if (attr->Name().Namespace() == NS_XMLNS) { + ns_stack_->AddXmlns(attr->Name().LocalPart(), + attr->Value()); + } + } + + // then go through qnames to make sure needed xmlns definitons are added + std::vector<std::string> new_ns; + std::pair<std::string, bool> prefix; + prefix = ns_stack_->AddNewPrefix(element->Name().Namespace(), false); + if (prefix.second) { + new_ns.push_back(prefix.first); + new_ns.push_back(element->Name().Namespace()); + } + + for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) { + prefix = ns_stack_->AddNewPrefix(attr->Name().Namespace(), true); + if (prefix.second) { + new_ns.push_back(prefix.first); + new_ns.push_back(attr->Name().Namespace()); + } + } + + // print the element name + *pout_ << '<' << ns_stack_->FormatQName(element->Name(), false); + + // and the attributes + for (attr = element->FirstAttr(); attr; attr = attr->NextAttr()) { + *pout_ << ' ' << ns_stack_->FormatQName(attr->Name(), true) << "=\""; + PrintQuotedValue(attr->Value()); + *pout_ << '"'; + } + + // and the extra xmlns declarations + std::vector<std::string>::iterator i(new_ns.begin()); + while (i < new_ns.end()) { + if (*i == STR_EMPTY) { + *pout_ << " xmlns=\"" << *(i + 1) << '"'; + } else { + *pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"'; + } + i += 2; + } + + // now the children + const XmlChild* child = element->FirstChild(); + + if (child == NULL) + *pout_ << "/>"; + else { + *pout_ << '>'; + while (child) { + if (child->IsText()) { + if (element->IsCDATA()) { + PrintCDATAText(child->AsText()->Text()); + } else { + PrintBodyText(child->AsText()->Text()); + } + } else { + PrintElement(child->AsElement()); + } + child = child->NextChild(); + } + *pout_ << "</" << ns_stack_->FormatQName(element->Name(), false) << '>'; + } + + ns_stack_->PopFrame(); +} + +void XmlPrinterImpl::PrintQuotedValue(const std::string& text) { + size_t safe = 0; + for (;;) { + size_t unsafe = text.find_first_of("<>&\"", safe); + if (unsafe == std::string::npos) + unsafe = text.length(); + *pout_ << text.substr(safe, unsafe - safe); + if (unsafe == text.length()) + return; + switch (text[unsafe]) { + case '<': *pout_ << "<"; break; + case '>': *pout_ << ">"; break; + case '&': *pout_ << "&"; break; + case '"': *pout_ << """; break; + } + safe = unsafe + 1; + if (safe == text.length()) + return; + } +} + +void XmlPrinterImpl::PrintBodyText(const std::string& text) { + size_t safe = 0; + for (;;) { + size_t unsafe = text.find_first_of("<>&", safe); + if (unsafe == std::string::npos) + unsafe = text.length(); + *pout_ << text.substr(safe, unsafe - safe); + if (unsafe == text.length()) + return; + switch (text[unsafe]) { + case '<': *pout_ << "<"; break; + case '>': *pout_ << ">"; break; + case '&': *pout_ << "&"; break; + } + safe = unsafe + 1; + if (safe == text.length()) + return; + } +} + +void XmlPrinterImpl::PrintCDATAText(const std::string& text) { + *pout_ << "<![CDATA[" << text << "]]>"; +} + +} // namespace buzz diff --git a/libjingle/xmllite/xmlprinter.h b/libjingle/xmllite/xmlprinter.h new file mode 100644 index 00000000..40cf195f --- /dev/null +++ b/libjingle/xmllite/xmlprinter.h @@ -0,0 +1,32 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_LIBJINGLE_XMLLITE_XMLPRINTER_H_ +#define WEBRTC_LIBJINGLE_XMLLITE_XMLPRINTER_H_ + +#include <iosfwd> +#include <string> + +namespace buzz { + +class XmlElement; +class XmlnsStack; + +class XmlPrinter { + public: + static void PrintXml(std::ostream* pout, const XmlElement* pelt); + + static void PrintXml(std::ostream* pout, const XmlElement* pelt, + XmlnsStack* ns_stack); +}; + +} // namespace buzz + +#endif // WEBRTC_LIBJINGLE_XMLLITE_XMLPRINTER_H_ diff --git a/libjingle/xmllite/xmlprinter_unittest.cc b/libjingle/xmllite/xmlprinter_unittest.cc new file mode 100644 index 00000000..b4850b5a --- /dev/null +++ b/libjingle/xmllite/xmlprinter_unittest.cc @@ -0,0 +1,45 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/libjingle/xmllite/xmlprinter.h" + +#include <sstream> +#include <string> + +#include "webrtc/libjingle/xmllite/qname.h" +#include "webrtc/libjingle/xmllite/xmlelement.h" +#include "webrtc/libjingle/xmllite/xmlnsstack.h" +#include "webrtc/base/common.h" +#include "webrtc/base/gunit.h" + +using buzz::QName; +using buzz::XmlElement; +using buzz::XmlnsStack; +using buzz::XmlPrinter; + +TEST(XmlPrinterTest, TestBasicPrinting) { + XmlElement elt(QName("google:test", "first")); + std::stringstream ss; + XmlPrinter::PrintXml(&ss, &elt); + EXPECT_EQ("<test:first xmlns:test=\"google:test\"/>", ss.str()); +} + +TEST(XmlPrinterTest, TestNamespacedPrinting) { + XmlElement elt(QName("google:test", "first")); + elt.AddElement(new XmlElement(QName("nested:test", "second"))); + std::stringstream ss; + + XmlnsStack ns_stack; + ns_stack.AddXmlns("gg", "google:test"); + ns_stack.AddXmlns("", "nested:test"); + + XmlPrinter::PrintXml(&ss, &elt, &ns_stack); + EXPECT_EQ("<gg:first><second/></gg:first>", ss.str()); +} @@ -24,6 +24,7 @@ 'common.gyp:*', 'common_audio/common_audio.gyp:*', 'common_video/common_video.gyp:*', + 'libjingle/xmllite/xmllite.gyp:*', 'modules/modules.gyp:*', 'system_wrappers/source/system_wrappers.gyp:*', 'video_engine/video_engine.gyp:*', @@ -44,6 +45,7 @@ 'dependencies': [ 'base/base_tests.gyp:*', 'common_video/common_video_unittests.gyp:*', + 'libjingle/xmllite/xmllite_tests.gyp:*', 'sound/sound_tests.gyp:*', 'system_wrappers/source/system_wrappers_tests.gyp:*', 'test/metrics.gyp:*', |