[coding] Add Uri class.

This commit is contained in:
Yury Melnichek 2012-11-28 17:50:24 +01:00 committed by Alex Zolotarev
parent 9ae450222c
commit f93a18c9bd
5 changed files with 225 additions and 0 deletions

View file

@ -30,6 +30,7 @@ SOURCES += \
reader_writer_ops.cpp \
blob_indexer.cpp \
blob_storage.cpp \
uri.cpp \
HEADERS += \
internal/xmlparser.h \
@ -87,3 +88,4 @@ HEADERS += \
reader_wrapper.hpp \
blob_indexer.hpp \
blob_storage.hpp \
uri.hpp \

View file

@ -39,6 +39,7 @@ SOURCES += ../../testing/testingmain.cpp \
trie_test.cpp \
reader_writer_ops_test.cpp \
blob_storage_test.cpp \
uri_test.cpp \
HEADERS += \
reader_test.hpp \

View file

@ -0,0 +1,117 @@
#include "../../testing/testing.hpp"
#include "../uri.hpp"
#include "../../base/macros.hpp"
#include "../../std/bind.hpp"
#include "../../std/queue.hpp"
#include "../../std/utility.hpp"
using url_scheme::Uri;
namespace
{
class TestUri
{
public:
TestUri(string const & uri) { m_uri = uri; }
TestUri & Scheme(string const &scheme) { m_scheme = scheme; return *this; }
TestUri & Path(string const & path) { m_path = path; return *this; }
TestUri & KV(string const & key, string const & value)
{
m_keyValuePairs.push(make_pair(key, value));
return *this;
}
~TestUri()
{
Uri uri(&m_uri[0], m_uri.size());
TEST_EQUAL(uri.GetScheme(), m_scheme, ());
TEST_EQUAL(uri.GetPath(), m_path, ());
TEST(!m_scheme.empty() || !uri.IsValid(), ("Scheme is empty if and only if uri is invalid!"));
uri.ForEachKeyValue(bind(&TestUri::AddTestValue, this, _1, _2));
}
private:
void AddTestValue(string const & key, string const & value)
{
TEST(!m_keyValuePairs.empty(), ("Check that key/value pair is expected"));
TEST_EQUAL(m_keyValuePairs.front().first, key, ());
TEST_EQUAL(m_keyValuePairs.front().second, value, ());
m_keyValuePairs.pop();
}
string m_uri, m_scheme, m_path;
queue<pair<string, string> > m_keyValuePairs;
};
} // unnamed namespace
UNIT_TEST(UriValidScheme)
{
char const uriS[] = "mapswithme://map?ll=10.3,12.3223&n=Hello%20World";
Uri uri(uriS, ARRAY_SIZE(uriS) - 1);
TEST_EQUAL(uri.GetScheme(), "mapswithme", ());
}
UNIT_TEST(UriInvalidSchemeNoColon)
{
TEST_EQUAL(Uri("mapswithme:").GetScheme(), "mapswithme", ());
}
UNIT_TEST(UriTestValidScheme2)
{
TestUri("mapswithme://map?ll=10.3,12.3223&n=Hello%20World")
.Scheme("mapswithme")
.Path("map")
.KV("ll", "10.3,12.3223")
.KV("n", "Hello World");
}
UNIT_TEST(UriComprehensive)
{
TestUri("");
TestUri("scheme:").Scheme("scheme");
TestUri("scheme:/").Scheme("scheme");
TestUri("scheme://").Scheme("scheme");
TestUri("sometext");
TestUri(":noscheme");
TestUri("://noscheme?");
TestUri("mwm://?").Scheme("mwm");
TestUri("http://path/to/something").Scheme("http").Path("path/to/something");
TestUri("http://path?").Scheme("http").Path("path");
TestUri("maps://path?&&key=&").Scheme("maps").Path("path").KV("key", "");
TestUri("mapswithme://map?ll=1.2,3.4&z=15").Scheme("mapswithme").Path("map")
.KV("ll", "1.2,3.4").KV("z", "15");
TestUri("nopathnovalues://?key1&key2=val2").Scheme("nopathnovalues").Path("")
.KV("key1", "").KV("key2", "val2");
TestUri("s://?key1&key2").Scheme("s").Path("").KV("key1", "").KV("key2", "");
TestUri("g://p?key1=val1&key2=").Scheme("g").Path("p").KV("key1", "val1").KV("key2", "");
TestUri("g://p?=val1&key2=").Scheme("g").Path("p").KV("", "val1").KV("key2", "");
TestUri("g://?k&key2").Scheme("g").KV("k", "").KV("key2", "");
TestUri("m:?%26Amp%26%3D%26Amp%26&name=%31%20%30").Scheme("m")
.KV("&Amp&=&Amp&", "").KV("name", "1 0");
TestUri("s://?key1=value1&key1=value2&key1=value3&key2&key2&key3=value1&key3&key3=value2")
.Scheme("s")
.KV("key1", "value1").KV("key1", "value2").KV("key1", "value3")
.KV("key2", "").KV("key2", "")
.KV("key3", "value1").KV("key3", "").KV("key3", "value2");
}

69
coding/uri.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "uri.hpp"
#include "url_encode.hpp"
#include "../base/logging.hpp"
#include "../std/algorithm.hpp"
using namespace url_scheme;
void Uri::Init()
{
if (!Parse())
{
m_scheme.clear();
m_path.clear();
}
}
bool Uri::Parse()
{
// get url scheme
size_t pathStart = m_url.find(':');
if (pathStart == string::npos || pathStart == 0)
return false;
m_scheme.assign(m_url, 0, pathStart);
// skip slashes
while (++pathStart < m_url.size() && m_url[pathStart] == '/') {};
// get path
m_queryStart = m_url.find('?', pathStart);
m_path.assign(m_url, pathStart, m_queryStart - pathStart);
// url without query
if (m_queryStart == string::npos)
m_queryStart = m_url.size();
else
++m_queryStart;
return true;
}
void Uri::ForEachKeyValue(CallbackT const & callback) const
{
// parse query for keys and values
for (size_t start = m_queryStart; start < m_url.size(); )
{
// TODO: Unoptimal search here, since it goes until the end of the string.
size_t const end = min(m_url.size(), m_url.find('&', start));
// Skip empty keys.
if (end - start > 0)
{
size_t const eq = m_url.find('=', start);
string key, value;
if (eq < end)
{
key = UrlDecode(m_url.substr(start, eq - start));
value = UrlDecode(m_url.substr(eq + 1, end - eq - 1));
}
else
{
key = UrlDecode(m_url.substr(start, end - start));
}
callback(key, value);
}
start = end + 1;
}
}

36
coding/uri.hpp Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include "../base/base.hpp"
#include "../std/function.hpp"
#include "../std/string.hpp"
namespace url_scheme
{
// Uri in format: 'scheme://path?key1=value1&key2&key3=&key4=value4'
class Uri
{
public:
typedef function<void (string const &, string const &)> CallbackT;
explicit Uri(string const & uri) : m_url(uri) { Init(); }
Uri(char const * uri, size_t size) : m_url(uri, uri + size) { Init(); }
string GetScheme() const { return m_scheme; }
string GetPath() const { return m_path; }
bool IsValid() const { return !m_scheme.empty(); }
void ForEachKeyValue(CallbackT const & callback) const;
private:
void Init();
bool Parse();
string m_url;
string m_scheme;
string m_path;
size_t m_queryStart;
};
} // namespace url_scheme