handle internal refs properly
diff --git a/bin/unittestschema/idandref.json b/bin/unittestschema/idandref.json
new file mode 100644
index 0000000..ad485d2
--- /dev/null
+++ b/bin/unittestschema/idandref.json
@@ -0,0 +1,69 @@
+{
+  "id": "http://example.com/root.json",
+  "definitions": {
+    "A": {
+      "id": "#foo",
+      "type": "integer"
+    },
+    "B": {
+      "id": "other.json",
+      "definitions": {
+        "X": {
+          "id": "#bar",
+          "type": "boolean"
+        },
+        "Y": {
+          "$ref": "#/definitions/X"
+        },
+        "W": {
+          "$ref": "#/definitions/Y"
+        },
+        "Z": {
+          "$ref": "#bar"
+        },
+        "N": {
+          "properties": {
+            "NX": {
+              "$ref": "#/definitions/X"
+            }
+          }
+        }
+      }
+    }
+  },
+  "properties": {
+    "PA1": {
+      "$ref": "http://example.com/root.json#/definitions/A"
+    },
+    "PA2": {
+      "$ref": "#/definitions/A"
+    },
+    "PA3": {
+      "$ref": "#foo"
+    },
+    "PX1": {
+      "$ref": "#/definitions/B/definitions/X"
+    },
+    "PX2Y": {
+      "$ref": "#/definitions/B/definitions/Y"
+    },
+    "PX3Z": {
+      "$ref": "#/definitions/B/definitions/Z"
+    },
+    "PX4": {
+      "$ref": "http://example.com/other.json#/definitions/X"
+    },
+    "PX5": {
+      "$ref": "other.json#/definitions/X"
+    },
+    "PX6": {
+      "$ref": "other.json#bar"
+    },
+    "PX7W": {
+      "$ref": "#/definitions/B/definitions/W"
+    },
+    "PX8N": {
+      "$ref": "#/definitions/B/definitions/N"
+    }
+  }
+}
\ No newline at end of file
diff --git a/include/rapidjson/pointer.h b/include/rapidjson/pointer.h
index 90e5903..b5e952b 100644
--- a/include/rapidjson/pointer.h
+++ b/include/rapidjson/pointer.h
@@ -16,6 +16,7 @@
 #define RAPIDJSON_POINTER_H_
 
 #include "document.h"
+#include "uri.h"
 #include "internal/itoa.h"
 
 #ifdef __clang__
@@ -80,6 +81,8 @@
 public:
     typedef typename ValueType::EncodingType EncodingType;  //!< Encoding type from Value
     typedef typename ValueType::Ch Ch;                      //!< Character type from Value
+    typedef GenericUri<ValueType, Allocator> UriType;
+
 
     //! A token is the basic units of internal representation.
     /*!
@@ -520,6 +523,69 @@
 
     //@}
 
+    //!@name Compute URI
+    //@{
+
+    //! Compute the in-scope URI for a subtree.
+    //  For use with JSON pointers into JSON schema documents.
+    /*!
+        \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root.
+        \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token.
+        \return Uri if it can be resolved. Otherwise null.
+
+        \note
+        There are only 3 situations when a URI cannot be resolved:
+        1. A value in the path is not an array nor object.
+        2. An object value does not contain the token.
+        3. A token is out of range of an array value.
+
+        Use unresolvedTokenIndex to retrieve the token index.
+    */
+    UriType GetUri(ValueType& root, const UriType& rootUri, size_t* unresolvedTokenIndex = 0) const {
+        static const Ch kIdString[] = { 'i', 'd', '\0' };
+        static const ValueType kIdValue(kIdString, 2);
+        UriType base = rootUri;
+        RAPIDJSON_ASSERT(IsValid());
+        ValueType* v = &root;
+        for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) {
+            switch (v->GetType()) {
+                case kObjectType:
+                {
+                    // See if we have an id, and if so resolve with the current base
+                    typename ValueType::MemberIterator m = v->FindMember(kIdValue);
+                    if (m != v->MemberEnd() && (m->value).IsString()) {
+                        UriType here = UriType(m->value);
+                        here.Resolve(base);
+                        base = here;
+                    }
+                    m = v->FindMember(GenericValue<EncodingType>(GenericStringRef<Ch>(t->name, t->length)));
+                    if (m == v->MemberEnd())
+                        break;
+                    v = &m->value;
+                }
+                  continue;
+                case kArrayType:
+                    if (t->index == kPointerInvalidIndex || t->index >= v->Size())
+                        break;
+                    v = &((*v)[t->index]);
+                    continue;
+                default:
+                    break;
+            }
+
+            // Error: unresolved token
+            if (unresolvedTokenIndex)
+                *unresolvedTokenIndex = static_cast<size_t>(t - tokens_);
+            return UriType();
+        }
+        return base;
+    }
+
+    UriType GetUri(const ValueType& root, const UriType& rootUri, size_t* unresolvedTokenIndex = 0) const {
+      return GetUri(const_cast<ValueType&>(root), rootUri, unresolvedTokenIndex);
+    }
+
+
     //!@name Query value
     //@{
 
diff --git a/include/rapidjson/schema.h b/include/rapidjson/schema.h
index e9ed19d..e290fc1 100644
--- a/include/rapidjson/schema.h
+++ b/include/rapidjson/schema.h
@@ -1,6 +1,7 @@
 // Tencent is pleased to support the open source community by making RapidJSON available->
 // 
 // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved->
+// Portions (C) Copyright IBM Corporation 2021
 //
 // Licensed under the MIT License (the "License"); you may not use this file except
 // in compliance with the License-> You may obtain a copy of the License at
@@ -19,6 +20,7 @@
 #include "pointer.h"
 #include "stringbuffer.h"
 #include "error/en.h"
+#include "uri.h"
 #include <cmath> // abs, floor
 
 #if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX)
@@ -150,9 +152,6 @@
 template <typename ValueType, typename Allocator>
 class GenericSchemaDocument;
 
-template <typename SchemaDocumentType>
-class Uri;
-
 namespace internal {
 
 template <typename SchemaDocumentType>
@@ -435,7 +434,7 @@
     typedef Schema<SchemaDocumentType> SchemaType;
     typedef GenericValue<EncodingType, AllocatorType> SValue;
     typedef IValidationErrorHandler<Schema> ErrorHandler;
-    typedef Uri<SchemaDocumentType> UriType;
+    typedef GenericUri<ValueType, AllocatorType> UriType;
     friend class GenericSchemaDocument<ValueType, AllocatorType>;
 
     Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator, const UriType& id = UriType()) :
@@ -496,7 +495,6 @@
         // If we have an id property, resolve it with the in-scope id
         if (const ValueType* v = GetMember(value, GetIdString())) {
             if (v->IsString()) {
-                //std::cout << "Resolving local id '" << v->.GetString() << "' with in-scope id '" << id.GetString() << "'" << std::endl;
                 UriType local = UriType(*v);
                 local.Resolve(id_);
                 id_ = local;
@@ -727,7 +725,7 @@
         return id_;
     }
 
-   const PointerType& GetPointer() const {
+    const PointerType& GetPointer() const {
         return pointer_;
     }
 
@@ -806,7 +804,7 @@
             foundEnum:;
         }
 
-    // Only check allOf etc if we have validators
+        // Only check allOf etc if we have validators
         if (context.validatorCount > 0) {
             if (allOf_.schemas)
                 for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++)
@@ -1593,219 +1591,18 @@
 } // namespace internal
 
 ///////////////////////////////////////////////////////////////////////////////
-// Uri
-
-template <typename SchemaDocumentType>
-class Uri {
-public:
-    typedef typename SchemaDocumentType::Ch Ch;
-    typedef typename SchemaDocumentType::AllocatorType AllocatorType;
-    typedef internal::Schema<SchemaDocumentType> SchemaType;
-    typedef std::basic_string<Ch> String;
-
-    // Constructors
-    Uri() {}
-
-    Uri(const String& uri) {
-        Parse(uri);
-    }
-
-    Uri(const Ch* uri, SizeType len) {
-        Parse(String(uri, len));
-    }
-
-    // Use with specializations of GenericValue
-    template<typename T> Uri(const T& uri) {
-        Parse(uri.template Get<String>());
-    }
-
-    // Getters
-    const String& Get() {
-        // Create uri_ on-demand
-        if (uri_.empty()) uri_ = this->GetDoc() + frag_;
-        return uri_; }
-
-    // Use with specializations of GenericValue
-    template<typename T> void Get(T& uri, AllocatorType& allocator) {
-        uri.template Set<String>(this->Get(), allocator);
-    }
-
-    const String& GetDoc() {
-        // Create doc_ on-demand
-        if (doc_.empty()) doc_ = scheme_ + auth_ + path_ + query_;
-        return doc_;
-    }
-    const String& GetScheme() const { return scheme_; }
-    const String& GetAuth() const { return auth_; }
-    const String& GetPath() const { return path_; }
-    const String& GetQuery() const { return query_; }
-    const String& GetFrag() const { return frag_; }
-
-    const Ch* GetString() { return this->Get().c_str(); }
-    SizeType GetStringLength() { return static_cast<SizeType>(this->Get().length()); }
-
-    const Ch* GetDocString() { return this->GetDoc().c_str(); }
-    SizeType GetDocStringLength() { return static_cast<SizeType>(this->GetDoc().length()); }
-
-    const Ch* GetFragString() const { return frag_.c_str(); }
-    SizeType GetFragStringLength() const { return static_cast<SizeType>(frag_.length()); }
-
-    // Resolve this URI against a base URI in accordance with URI resolution rules at
-    // https://tools.ietf.org/html/rfc3986
-    // Use for resolving an id or $ref with an in-scope id.
-    // This URI is updated in place where needed from the base URI.
-    Uri& Resolve(const Uri& base)
-    {
-        if (!scheme_.empty()) {
-            // Use all of this URI
-            RemoveDotSegments(path_);
-        } else {
-            if (!auth_.empty()) {
-              RemoveDotSegments(path_);
-            } else {
-                if (path_.empty()) {
-                    path_ = base.GetPath();
-                    if (query_.empty()) {
-                        query_ = base.GetQuery();
-                    }
-                } else {
-                    static const String slash = SchemaType::GetSlashString().GetString();
-                    if (path_.find(slash) == 0) {
-                        // Absolute path - replace all the path
-                        RemoveDotSegments(path_);
-                    } else {
-                        // Relative path - append to path after last slash
-                        String p;
-                        if (!base.GetAuth().empty() && base.GetPath().empty()) p = slash;
-                        std::size_t lastslashpos = base.GetPath().find_last_of(slash);
-                        path_ = p + base.GetPath().substr(0, lastslashpos + 1) + path_;
-                        RemoveDotSegments(path_);
-                    }
-                }
-                auth_ = base.GetAuth();
-            }
-            scheme_ = base.GetScheme();
-        }
-        //std::cout << " Resolved uri: " <<  this->GetString() << std::endl;
-        return *this;
-    }
-
-private:
-    // Parse a URI into constituent scheme, authority, path, query, fragment
-    // Supports URIs that match regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? as per
-    // https://tools.ietf.org/html/rfc3986
-    void Parse(const String& uri) {
-        std::size_t start = 0, pos1 = 0, pos2 = 0;
-        const std::size_t len = uri.length();
-        static const String schemeEnd = SchemaType::GetSchemeEndString().GetString();
-        static const String authStart = SchemaType::GetAuthStartString().GetString();
-        static const String pathStart = SchemaType::GetSlashString().GetString();
-        static const String queryStart = SchemaType::GetQueryStartString().GetString();
-        static const String fragStart = SchemaType::GetFragStartString().GetString();
-        // Look for scheme ([^:/?#]+):)?
-        if (start < len) {
-            pos1 = uri.find(schemeEnd);
-            if (pos1 != std::string::npos) {
-                pos2 = uri.find_first_of(pathStart + queryStart + fragStart);
-                if (pos1 < pos2) {
-                    pos1 += schemeEnd.length();
-                    scheme_ = uri.substr(start, pos1);
-                    start = pos1;
-                }
-            }
-        }
-        // Look for auth (//([^/?#]*))?
-        if (start < len) {
-            pos1 = uri.find(authStart, start);
-            if (pos1 == start) {
-                pos2 = uri.find_first_of(pathStart + queryStart + fragStart, start + authStart.length());
-                auth_ = uri.substr(start, pos2 - start);
-                start = pos2;
-            }
-        }
-        // Look for path ([^?#]*)
-        if (start < len) {
-            pos2 = uri.find_first_of(queryStart + fragStart, start);
-            if (start != pos2) {
-                path_ = uri.substr(start, pos2 - start);
-                if (path_.find(pathStart) == 0) {  // absolute path - normalize
-                    RemoveDotSegments(path_);
-                }
-                start = pos2;
-            }
-        }
-        // Look for query (\?([^#]*))?
-        if (start < len) {
-            pos2 = uri.find(fragStart, start);
-            if (start != pos2) {
-                query_ = uri.substr(start, pos2 - start);
-                start = pos2;
-            }
-        }
-        // Look for fragment (#(.*))?
-        if (start < len) {
-            frag_ = uri.substr(start);
-        }
-        //std::cout << " Parsed uri: " <<  "s: " << scheme_.c_str() << " a: " << auth_.c_str() << " p: " << path_.c_str() << " q: " << query_.c_str() << " f: " << frag_.c_str() << std::endl;
-    }
-
-    // Remove . and .. segments from a path
-    // https://tools.ietf.org/html/rfc3986
-    void RemoveDotSegments(String& path) {
-        String temp = path;
-        path.clear();
-        static const String slash = SchemaType::GetSlashString().GetString();
-        static const String dot = SchemaType::GetDotString().GetString();
-        std::size_t pos = 0;
-        // Loop through each path segment
-        while (pos != std::string::npos) {
-            //std::cout << "Temp: '" << temp.c_str() << "' Path: '" << path.c_str() << "'" << std::endl;
-            pos = temp.find_first_of(slash);
-            // Get next segment
-            String seg = temp.substr(0, pos);
-            if (seg == dot) {
-                // Discard . segment
-            } else if (seg == dot + dot) {
-                // Backup a .. segment
-                // We expect to find a previously added slash at the end or nothing
-                std::size_t pos1 = path.find_last_of(slash);
-                // Make sure we don't go beyond the start
-                if (pos1 != std::string::npos && pos1 != 0) {
-                    // Find the next to last slash and back up to it
-                    pos1 = path.find_last_of(slash, pos1 - 1);
-                    path = path.substr(0, pos1 + 1);
-                }
-            } else {
-                  // Copy segment and add slash if not at end
-                  path += seg;
-                  if (pos != std::string::npos) path += slash;
-            }
-            // Move to next segment if not at end
-            if (pos != std::string::npos) temp = temp.substr(pos + 1);
-        }
-        //std::cout << "Final Temp: '" << temp.c_str() << "' Final Path: '" << path.c_str() << "'" << std::endl;
-    }
-
-    String uri_;    // Created on-demand
-    String doc_;    // Created on-demand
-    String scheme_; // Includes the :
-    String auth_;   // Includes the //
-    String path_;   // Absolute if starts with /
-    String query_;  // Includes the ?
-    String frag_;   // Includes the #
-};
-
-///////////////////////////////////////////////////////////////////////////////
 // IGenericRemoteSchemaDocumentProvider
 
 template <typename SchemaDocumentType>
 class IGenericRemoteSchemaDocumentProvider {
 public:
     typedef typename SchemaDocumentType::Ch Ch;
+    typedef typename SchemaDocumentType::ValueType ValueType;
+    typedef typename SchemaDocumentType::AllocatorType AllocatorType;
 
     virtual ~IGenericRemoteSchemaDocumentProvider() {}
     virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0;
-    virtual const SchemaDocumentType* GetRemoteDocument(Uri<SchemaDocumentType> uri) { return GetRemoteDocument(uri.GetDocString(), uri.GetDocStringLength()); }
+    virtual const SchemaDocumentType* GetRemoteDocument(GenericUri<ValueType, AllocatorType> uri) { return GetRemoteDocument(uri.GetBaseString(), uri.GetBaseStringLength()); }
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1831,7 +1628,7 @@
     typedef internal::Schema<GenericSchemaDocument> SchemaType;
     typedef GenericPointer<ValueType, Allocator> PointerType;
     typedef GenericValue<EncodingType, AllocatorType> SValue;
-    typedef Uri<GenericSchemaDocument> UriType;
+    typedef GenericUri<ValueType, Allocator> UriType;
     friend class internal::Schema<GenericSchemaDocument>;
     template <typename, typename, typename>
     friend class GenericSchemaValidator;
@@ -1863,20 +1660,20 @@
 
         Ch noUri[1] = {0};
         uri_.SetString(uri ? uri : noUri, uriLength, *allocator_);
-        UriType baseId(uri_);
+        docId_ = UriType(uri_);
 
         typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType)));
-        new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, baseId);
+        new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_, docId_);
 
         // Generate root schema, it will call CreateSchema() to create sub-schemas,
         // And call HandleRefSchema() if there are $ref.
         // PR #1393 use input pointer if supplied
         root_ = typeless_;
         if (pointer.GetTokenCount() == 0) {
-            CreateSchemaRecursive(&root_, pointer, document, document, baseId);
+            CreateSchemaRecursive(&root_, pointer, document, document, docId_);
         }
         else if (const ValueType* v = pointer.Get(document)) {
-            CreateSchema(&root_, pointer, *v, document, baseId);
+            CreateSchema(&root_, pointer, *v, document, docId_);
         }
 
         RAPIDJSON_ASSERT(root_ != 0);
@@ -1894,7 +1691,8 @@
         typeless_(rhs.typeless_),
         schemaMap_(std::move(rhs.schemaMap_)),
         schemaRef_(std::move(rhs.schemaRef_)),
-        uri_(std::move(rhs.uri_))
+        uri_(std::move(rhs.uri_)),
+        docId_(rhs.docId_),
     {
         rhs.remoteProvider_ = 0;
         rhs.allocator_ = 0;
@@ -1945,10 +1743,10 @@
     // Changed by PR #1393
     void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
         if (v.GetType() == kObjectType) {
-            CreateSchema(schema, pointer, v, document, id);
+            UriType newid = CreateSchema(schema, pointer, v, document, id);
 
             for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr)
-                CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, id);
+                CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document, newid);
         }
         else if (v.GetType() == kArrayType)
             for (SizeType i = 0; i < v.Size(); i++)
@@ -1956,20 +1754,20 @@
     }
 
     // Changed by PR #1393
-    void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
+    const UriType& CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document, const UriType& id) {
         RAPIDJSON_ASSERT(pointer.IsValid());
         if (v.IsObject()) {
-             if (const SchemaType* sc = GetSchema(pointer)) {
+            if (const SchemaType* sc = GetSchema(pointer)) {
                 if (schema)
                     *schema = sc;
-               //std::cout << "Using Schema with id " << sc->GetId().GetString() << std::endl;
-               AddSchemaRefs(const_cast<SchemaType*>(sc));
+                AddSchemaRefs(const_cast<SchemaType*>(sc));
             }
             else if (!HandleRefSchema(pointer, schema, v, document, id)) {
-                // The new schema adds itself and its $ref(s) to schemaMap_
+                // The new schema constructor adds itself and its $ref(s) to schemaMap_
                 SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_, id);
                 if (schema)
                     *schema = s;
+                return s->GetId();
             }
         }
         else {
@@ -1977,61 +1775,95 @@
                 *schema = typeless_;
             AddSchemaRefs(typeless_);
         }
+        return id;
     }
 
     // Changed by PR #1393
+    // TODO should this return a UriType& ?
     bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document, const UriType& id) {
-        //std::cout << "HandleRefSchema called with id " << id.GetString() << std::endl;
         typename ValueType::ConstMemberIterator itr = v.FindMember(SchemaType::GetRefString());
         if (itr == v.MemberEnd())
             return false;
 
-       // Resolve the source pointer to the $ref'ed schema (finally)
+        // Resolve the source pointer to the $ref'ed schema (finally)
         new (schemaRef_.template Push<SchemaRefPtr>()) SchemaRefPtr(&source);
 
         if (itr->value.IsString()) {
             SizeType len = itr->value.GetStringLength();
             if (len > 0) {
-                const Ch* s = itr->value.GetString();
-                if (s[0] != '#') { // Remote reference - resolve $ref against the in-scope id
+                // First resolve $ref against the in-scope id
+                UriType scopeId = id;
+                UriType ref = UriType(itr->value);
+                ref.Resolve(scopeId);
+                // See if the resolved $ref minus the fragment matches a resolved id in this document
+                // Search from the root. Returns the subschema in the document and its absolute JSON pointer.
+                PointerType basePointer = PointerType();
+                const ValueType *base = FindId(document, ref, basePointer, docId_, false);
+                if (!base) {
+                    // Remote reference - call the remote document provider
                     if (remoteProvider_) {
-                        UriType ref = UriType(itr->value);
-                        ref.Resolve(id);
-                        //std::cout << "Resolved $ref '" << s << "' against in-scope id '" << id.GetString() << "' giving '" << ref.GetDocString() << "'" << std::endl;
                         if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(ref)) {
-                            // Create a pointer from the # onwards
-                            const PointerType pointer(ref.GetFragString(), ref.GetFragStringLength(), allocator_);
-                            if (pointer.IsValid()) {
-                                if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) {
-                                    if (schema)
-                                        *schema = sc;
-                                    AddSchemaRefs(const_cast<SchemaType*>(sc));
-                                    return true;
+                            const Ch* s = ref.GetFragString();
+                            len = ref.GetFragStringLength();
+                            if (len <= 1 || s[1] == '/') {
+                                // JSON pointer fragment, absolute in the remote schema
+                                const PointerType pointer(s, len, allocator_);
+                                if (pointer.IsValid()) {
+                                    // Get the subschema
+                                    if (const SchemaType *sc = remoteDocument->GetSchema(pointer)) {
+                                        if (schema)
+                                            *schema = sc;
+                                        AddSchemaRefs(const_cast<SchemaType *>(sc));
+                                        return true;
+                                    }
                                 }
-                            }
+                          } else {
+                            // Plain name fragment, not allowed
+                          }
                         }
                     }
                 }
                 else { // Local reference
-                  if (len == 1 || s[1] == '/') {
-                    // JSON pointer
-                    const PointerType pointer(s, len, allocator_);
-                    if (pointer.IsValid() && !IsCyclicRef(pointer)) {
-                      if (const ValueType *nv = pointer.Get(document)) {
-                        CreateSchema(schema, pointer, *nv, document, id);
-                        return true;
-                      }
+                    const Ch* s = ref.GetFragString();
+                    len = ref.GetFragStringLength();
+                    if (len <= 1 || s[1] == '/') {
+                        // JSON pointer fragment, relative to the resolved URI
+                        const PointerType relPointer(s, len, allocator_);
+                        if (relPointer.IsValid()) {
+                            // Get the subschema
+                            if (const ValueType *v = relPointer.Get(*base)) {
+                                // Now get the absolute JSON pointer by adding relative to base
+                                PointerType pointer(basePointer);
+                                for (SizeType i = 0; i < relPointer.GetTokenCount(); i++)
+                                    pointer = pointer.Append(relPointer.GetTokens()[i], allocator_);
+                                //GenericStringBuffer<EncodingType> sb;
+                                //pointer.StringifyUriFragment(sb);
+                                if (pointer.IsValid() && !IsCyclicRef(pointer)) {
+                                    // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there
+                                    // TODO: cache pointer <-> id mapping
+                                    scopeId = pointer.GetUri(document, docId_);
+                                    CreateSchema(schema, pointer, *v, document, scopeId);
+                                    return true;
+                                }
+                            }
+                        }
+                    } else {
+                        // Plain name fragment, relative to the resolved URI
+                        // See if the fragment matches an id in this document.
+                        // Search from the base we just established. Returns the subschema in the document and its absolute JSON pointer.
+                        PointerType pointer = PointerType();
+                        if (const ValueType *v = FindId(*base, ref, pointer, UriType(ref.GetBase()), true, basePointer)) {
+                            if (v && !IsCyclicRef(pointer)) {
+                                //GenericStringBuffer<EncodingType> sb;
+                                //pointer.StringifyUriFragment(sb);
+                                // Call CreateSchema recursively, but first compute the in-scope id for the $ref target as we have jumped there
+                                // TODO: cache pointer <-> id mapping
+                                scopeId = pointer.GetUri(document, docId_);
+                                CreateSchema(schema, pointer, *v, document, scopeId);
+                                return true;
+                            }
+                        }
                     }
-                  } else {
-                    // Internal reference to an id
-                    const ValueType val(s, len);
-                    PointerType pointer = PointerType();
-                    ValueType *nv = FindId(document, val, pointer);
-                    if (nv && !IsCyclicRef(pointer)) {
-                      CreateSchema(schema, pointer, *nv, document, id);
-                      return true;
-                    }
-                  }
                 }
             }
         }
@@ -2043,36 +1875,46 @@
         return true;
     }
 
-    //! Find the first subschema with 'id' string property matching the specified value.
-    // Return a pointer to the subschema and its JSON pointer.
-    ValueType* FindId(const ValueType& doc, const ValueType& findval, PointerType& resptr, const PointerType& here = PointerType()) const {
+    //! Find the first subschema with a resolved 'id' that matches the specified URI.
+    // If full specified use all URI else ignore fragment.
+    // If found, return a pointer to the subschema and its JSON pointer.
+    // TODO cache pointer <-> id mapping
+    ValueType* FindId(const ValueType& doc, const UriType& finduri, PointerType& resptr, const UriType& baseuri, bool full, const PointerType& here = PointerType()) const {
         SizeType i = 0;
         ValueType* resval = 0;
-        switch(doc.GetType()) {
-            case kObjectType:
-                for (typename ValueType::ConstMemberIterator m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) {
-                    if (m->name == SchemaType::GetIdString() && m->value.GetType() == kStringType && m->value == findval) {
-                        // Found the 'id' with the value
-                        resval = const_cast<ValueType*>(&doc);
-                        resptr = here;
-                    } else if (m->value.GetType() == kObjectType || m->value.GetType() == kArrayType) {
-                        resval = FindId(m->value, findval, resptr, here.Append(m->name.GetString(), m->name.GetStringLength(), allocator_));
-                    }
-                    if (resval) break;
+        UriType tempuri = finduri;
+        UriType localuri = baseuri;
+        if (doc.GetType() == kObjectType) {
+            // Establish the base URI of this object
+            typename ValueType::ConstMemberIterator m = doc.FindMember(SchemaType::GetIdString());
+            if (m != doc.MemberEnd() && m->value.GetType() == kStringType) {
+                localuri = UriType(m->value);
+                localuri.Resolve(baseuri);
+            }
+            // See if it matches
+            if (localuri.Match(finduri, full)) {
+                resval = const_cast<ValueType *>(&doc);
+                resptr = here;
+                return resval;
+            }
+            // No match, continue looking
+            for (typename ValueType::ConstMemberIterator m = doc.MemberBegin(); m != doc.MemberEnd(); ++m) {
+                if (m->value.GetType() == kObjectType || m->value.GetType() == kArrayType) {
+                    resval = FindId(m->value, finduri, resptr, localuri, full, here.Append(m->name.GetString(), m->name.GetStringLength(), allocator_));
                 }
-                return resval;
-            case kArrayType:
-                for (typename ValueType::ConstValueIterator v = doc.Begin(); v != doc.End(); ++v) {
-                    if (v->GetType() == kObjectType || v->GetType() == kArrayType) {
-                        resval = FindId(*v, findval, resptr, here.Append(i, allocator_));
-                    }
-                    if (resval) break;
-                    i++;
+                if (resval) break;
+            }
+        } else if (doc.GetType() == kArrayType) {
+            // Continue looking
+            for (typename ValueType::ConstValueIterator v = doc.Begin(); v != doc.End(); ++v) {
+                if (v->GetType() == kObjectType || v->GetType() == kArrayType) {
+                    resval = FindId(*v, finduri, resptr, localuri, full, here.Append(i, allocator_));
                 }
-                return resval;
-            default:
-                return resval;
+                if (resval) break;
+                i++;
+            }
         }
+        return resval;
     }
 
     // Added by PR #1393
@@ -2119,6 +1961,7 @@
     internal::Stack<Allocator> schemaMap_;  // Stores created Pointer -> Schemas
     internal::Stack<Allocator> schemaRef_;  // Stores Pointer(s) from $ref(s) until resolved
     SValue uri_;                            // Schema document URI
+    UriType docId_;
 };
 
 //! GenericSchemaDocument using Value type.
diff --git a/include/rapidjson/uri.h b/include/rapidjson/uri.h
new file mode 100644
index 0000000..04bd92e
--- /dev/null
+++ b/include/rapidjson/uri.h
@@ -0,0 +1,259 @@
+// Tencent is pleased to support the open source community by making RapidJSON available.
+// 
+// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
+//
+// Licensed under the MIT License (the "License"); you may not use this file except
+// in compliance with the License. You may obtain a copy of the License at
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless required by applicable law or agreed to in writing, software distributed 
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the 
+// specific language governing permissions and limitations under the License.
+
+#ifndef RAPIDJSON_URI_H_
+#define RAPIDJSON_URI_H_
+
+#if RAPIDJSON_HAS_CXX11_RVALUE_REFS
+#include <utility> // std::move
+#endif
+
+#if defined(__clang__)
+RAPIDJSON_DIAG_PUSH
+RAPIDJSON_DIAG_OFF(c++98-compat)
+#endif
+
+RAPIDJSON_NAMESPACE_BEGIN
+
+///////////////////////////////////////////////////////////////////////////////
+// GenericUri
+
+template <typename ValueType, typename Allocator=CrtAllocator>
+class GenericUri {
+public:
+    typedef typename ValueType::Ch Ch;
+    typedef std::basic_string<Ch> String;
+
+    // Constructors
+    GenericUri() {}
+
+    GenericUri(const String& uri) {
+        Parse(uri);
+    }
+
+    GenericUri(const Ch* uri, SizeType len) {
+        Parse(String(uri, len));
+    }
+
+    // Use with specializations of GenericValue
+    template<typename T> GenericUri(const T& uri) {
+        Parse(uri.template Get<String>());
+    }
+
+    // Getters
+    const String& Get() const { return uri_; }
+
+    // Use with specializations of GenericValue
+    template<typename T> void Get(T& uri, Allocator& allocator) {
+        uri.template Set<String>(this->Get(), allocator);
+    }
+
+    const String& GetBase() const { return base_; }
+    const String& GetScheme() const { return scheme_; }
+    const String& GetAuth() const { return auth_; }
+    const String& GetPath() const { return path_; }
+    const String& GetQuery() const { return query_; }
+    const String& GetFrag() const { return frag_; }
+
+    const Ch* GetString() const { return uri_.c_str(); }
+    SizeType GetStringLength() const { return static_cast<SizeType>(uri_.length()); }
+
+    const Ch* GetBaseString() const { return base_.c_str(); }
+    SizeType GetBaseStringLength() const { return static_cast<SizeType>(base_.length()); }
+
+    const Ch* GetFragString() const { return frag_.c_str(); }
+    SizeType GetFragStringLength() const { return static_cast<SizeType>(frag_.length()); }
+
+    // Resolve this URI against another URI in accordance with URI resolution rules at
+    // https://tools.ietf.org/html/rfc3986
+    // Use for resolving an id or $ref with an in-scope id.
+    // This URI is updated in place where needed from the base URI.
+    GenericUri& Resolve(const GenericUri& uri) {
+        if (!scheme_.empty()) {
+            // Use all of this URI
+            RemoveDotSegments(path_);
+        } else {
+            if (!auth_.empty()) {
+                RemoveDotSegments(path_);
+            } else {
+                if (path_.empty()) {
+                    path_ = uri.GetPath();
+                    if (query_.empty()) {
+                        query_ = uri.GetQuery();
+                    }
+                } else {
+                    static const String slash = GetSlashString().GetString();
+                    if (path_.find(slash) == 0) {
+                        // Absolute path - replace all the path
+                        RemoveDotSegments(path_);
+                    } else {
+                        // Relative path - append to path after last slash
+                        String p;
+                        if (!uri.GetAuth().empty() && uri.GetPath().empty()) p = slash;
+                        std::size_t lastslashpos = uri.GetPath().find_last_of(slash);
+                        path_ = p + uri.GetPath().substr(0, lastslashpos + 1) + path_;
+                        RemoveDotSegments(path_);
+                    }
+                }
+                auth_ = uri.GetAuth();
+            }
+            scheme_ = uri.GetScheme();
+        }
+        base_ = scheme_ + auth_ + path_ + query_;
+        uri_ = base_ + frag_;
+        //std::cout << " Resolved uri: " <<  uri_ << std::endl;
+        return *this;
+    }
+
+    bool Match(const GenericUri& uri, bool full) const {
+        if (full)
+            return uri_ == uri.Get();
+        else
+            return base_ == uri.GetBase();
+    }
+
+  // Generate functions for string literal according to Ch
+#define RAPIDJSON_STRING_(name, ...) \
+    static const ValueType& Get##name##String() {\
+        static const Ch s[] = { __VA_ARGS__, '\0' };\
+        static const ValueType v(s, static_cast<SizeType>(sizeof(s) / sizeof(Ch) - 1));\
+        return v;\
+    }
+
+    RAPIDJSON_STRING_(SchemeEnd, ':')
+    RAPIDJSON_STRING_(AuthStart, '/', '/')
+    RAPIDJSON_STRING_(QueryStart, '?')
+    RAPIDJSON_STRING_(FragStart, '#')
+    RAPIDJSON_STRING_(Slash, '/')
+    RAPIDJSON_STRING_(Dot, '.')
+
+#undef RAPIDJSON_STRING_
+
+private:
+    // Parse a URI into constituent scheme, authority, path, query, fragment
+    // Supports URIs that match regex ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? as per
+    // https://tools.ietf.org/html/rfc3986
+    void Parse(const String& uri) {
+        std::size_t start = 0, pos1 = 0, pos2 = 0;
+        const std::size_t len = uri.length();
+        static const String schemeEnd = GetSchemeEndString().GetString();
+        static const String authStart = GetAuthStartString().GetString();
+        static const String pathStart = GetSlashString().GetString();
+        static const String queryStart = GetQueryStartString().GetString();
+        static const String fragStart = GetFragStartString().GetString();
+        // Look for scheme ([^:/?#]+):)?
+        if (start < len) {
+            pos1 = uri.find(schemeEnd);
+            if (pos1 != std::string::npos) {
+                pos2 = uri.find_first_of(pathStart + queryStart + fragStart);
+                if (pos1 < pos2) {
+                    pos1 += schemeEnd.length();
+                    scheme_ = uri.substr(start, pos1);
+                    start = pos1;
+                }
+            }
+        }
+        // Look for auth (//([^/?#]*))?
+        if (start < len) {
+            pos1 = uri.find(authStart, start);
+            if (pos1 == start) {
+                pos2 = uri.find_first_of(pathStart + queryStart + fragStart, start + authStart.length());
+                auth_ = uri.substr(start, pos2 - start);
+                start = pos2;
+            }
+        }
+        // Look for path ([^?#]*)
+        if (start < len) {
+            pos2 = uri.find_first_of(queryStart + fragStart, start);
+            if (start != pos2) {
+                path_ = uri.substr(start, pos2 - start);
+                if (path_.find(pathStart) == 0) {  // absolute path - normalize
+                    RemoveDotSegments(path_);
+                }
+                start = pos2;
+            }
+        }
+        // Look for query (\?([^#]*))?
+        if (start < len) {
+            pos2 = uri.find(fragStart, start);
+            if (start != pos2) {
+                query_ = uri.substr(start, pos2 - start);
+                start = pos2;
+            }
+        }
+        // Look for fragment (#(.*))?
+        if (start < len) {
+            frag_ = uri.substr(start);
+        }
+        base_ = scheme_ + auth_ + path_ + query_;
+        uri_ = base_ + frag_;
+        //std::cout << " Parsed uri: " <<  "s: " << scheme_.c_str() << " a: " << auth_.c_str() << " p: " << path_.c_str() << " q: " << query_.c_str() << " f: " << frag_.c_str() << std::endl;
+    }
+
+    // Remove . and .. segments from a path
+    // https://tools.ietf.org/html/rfc3986
+    void RemoveDotSegments(String& path) {
+        String temp = path;
+        path.clear();
+        static const String slash = GetSlashString().GetString();
+        static const String dot = GetDotString().GetString();
+        std::size_t pos = 0;
+        // Loop through each path segment
+        while (pos != std::string::npos) {
+            //std::cout << "Temp: '" << temp.c_str() << "' Path: '" << path.c_str() << "'" << std::endl;
+            pos = temp.find_first_of(slash);
+            // Get next segment
+            String seg = temp.substr(0, pos);
+            if (seg == dot) {
+                // Discard . segment
+            } else if (seg == dot + dot) {
+                // Backup a .. segment
+                // We expect to find a previously added slash at the end or nothing
+                std::size_t pos1 = path.find_last_of(slash);
+                // Make sure we don't go beyond the start
+                if (pos1 != std::string::npos && pos1 != 0) {
+                    // Find the next to last slash and back up to it
+                    pos1 = path.find_last_of(slash, pos1 - 1);
+                    path = path.substr(0, pos1 + 1);
+                }
+            } else {
+                // Copy segment and add slash if not at end
+                path += seg;
+                if (pos != std::string::npos) path += slash;
+            }
+            // Move to next segment if not at end
+            if (pos != std::string::npos) temp = temp.substr(pos + 1);
+        }
+        //std::cout << "Final Temp: '" << temp.c_str() << "' Final Path: '" << path.c_str() << "'" << std::endl;
+    }
+
+    String uri_ = String();    // Full uri
+    String base_ = String();   // Everything except fragment
+    String scheme_ = String(); // Includes the :
+    String auth_ = String();   // Includes the //
+    String path_ = String();   // Absolute if starts with /
+    String query_ = String();  // Includes the ?
+    String frag_ = String();   // Includes the #
+};
+
+//! GenericUri for Value (UTF-8, default allocator).
+typedef GenericUri<Value> Uri;
+
+RAPIDJSON_NAMESPACE_END
+
+#if defined(__clang__)
+RAPIDJSON_DIAG_POP
+#endif
+
+#endif // RAPIDJSON_URI_H_
diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt
index fc8803e..6439c36 100644
--- a/test/unittest/CMakeLists.txt
+++ b/test/unittest/CMakeLists.txt
@@ -26,6 +26,7 @@
     stringbuffertest.cpp
     strtodtest.cpp
     unittest.cpp
+    uritest.cpp
     valuetest.cpp
     writertest.cpp)
 
diff --git a/test/unittest/pointertest.cpp b/test/unittest/pointertest.cpp
index 4371803..c693b8f 100644
--- a/test/unittest/pointertest.cpp
+++ b/test/unittest/pointertest.cpp
@@ -648,6 +648,48 @@
     }
 }
 
+static const char kJsonIds[] = "{\n"
+   "    \"id\": \"/root/\","
+   "    \"foo\":[\"bar\", \"baz\", {\"id\": \"inarray\", \"child\": 1}],\n"
+   "    \"int\" : 2,\n"
+   "    \"str\" : \"val\",\n"
+   "    \"obj\": {\"id\": \"inobj\", \"child\": 3},\n"
+   "    \"jbo\": {\"id\": true, \"child\": 4}\n"
+   "}";
+
+
+TEST(Pointer, GetUri) {
+    typedef std::basic_string<Value::Ch> String;
+    Document d;
+    d.Parse(kJsonIds);
+
+    String doc = String("http://doc");
+    EXPECT_TRUE((Pointer("").GetUri(d, Pointer::UriType(doc)).Get()) == doc);
+    EXPECT_TRUE((Pointer("/foo").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/");
+    EXPECT_TRUE((Pointer("/foo/0").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/");
+    EXPECT_TRUE((Pointer("/foo/2").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/");
+    EXPECT_TRUE((Pointer("/foo/2/child").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/inarray");
+    EXPECT_TRUE((Pointer("/int").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/");
+    EXPECT_TRUE((Pointer("/str").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/");
+    EXPECT_TRUE((Pointer("/obj").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/");
+    EXPECT_TRUE((Pointer("/obj/child").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/inobj");
+    EXPECT_TRUE((Pointer("/jbo").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/");
+    EXPECT_TRUE((Pointer("/jbo/child").GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/"); // id not string
+
+    size_t unresolvedTokenIndex;
+    EXPECT_TRUE((Pointer("/foo/3").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // Out of boundary
+    EXPECT_EQ(1u, unresolvedTokenIndex);
+    EXPECT_TRUE((Pointer("/foo/a").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // "/foo" is an array, cannot query by "a"
+    EXPECT_EQ(1u, unresolvedTokenIndex);
+    EXPECT_TRUE((Pointer("/foo/0/0").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // "/foo/0" is an string, cannot further query
+    EXPECT_EQ(2u, unresolvedTokenIndex);
+    EXPECT_TRUE((Pointer("/foo/0/a").GetUri(d, Pointer::UriType(doc), &unresolvedTokenIndex).Get()) == ""); // "/foo/0" is an string, cannot further query
+    EXPECT_EQ(2u, unresolvedTokenIndex);
+
+    Pointer::Token tokens[] = { { "foo ...", 3, kPointerInvalidIndex } };
+    EXPECT_TRUE((Pointer(tokens, 1).GetUri(d, Pointer::UriType(doc)).Get()) == "http://doc/root/");
+}
+
 TEST(Pointer, Get) {
     Document d;
     d.Parse(kJson);
diff --git a/test/unittest/schematest.cpp b/test/unittest/schematest.cpp
index a076389..1b25e2f 100644
--- a/test/unittest/schematest.cpp
+++ b/test/unittest/schematest.cpp
@@ -2300,7 +2300,7 @@
     Document e;
     e.Parse(
         "{ \"maxLength\": {"
-"            \"errorCode\": 6,"
+        "     \"errorCode\": 6,"
         "    \"instanceRef\": \"#\", \"schemaRef\": \"#\","
         "    \"expected\": 3, \"actual\": \"ABCD\""
         "}}");
@@ -2578,6 +2578,37 @@
         kValidateDefaultFlags, SchemaValidatorType, PointerType);
 }
 
+// Test that $refs are correctly resolved when intermediate multiple ids are present
+// Includes $ref to a part of the document with a different in-scope id, which also contains $ref..
+TEST(SchemaValidator, Ref_internal_multiple_ids) {
+    typedef GenericSchemaDocument<Value, MemoryPoolAllocator<> > SchemaDocumentType;
+    //RemoteSchemaDocumentProvider<SchemaDocumentType> provider;
+    CrtAllocator allocator;
+    char* schema = ReadFile("unittestschema/idandref.json", allocator);
+    Document sd;
+    sd.Parse(schema);
+    ASSERT_FALSE(sd.HasParseError());
+    SchemaDocumentType s(sd, "http://xyz", 10/*, &provider*/);
+    typedef GenericSchemaValidator<SchemaDocumentType, BaseReaderHandler<UTF8<> >, MemoryPoolAllocator<> > SchemaValidatorType;
+    typedef GenericPointer<Value, MemoryPoolAllocator<> > PointerType;
+    INVALIDATE_(s, "{\"PA1\": \"s\", \"PA2\": \"t\", \"PA3\": \"r\", \"PX1\": 1, \"PX2Y\": 2, \"PX3Z\": 3, \"PX4\": 4, \"PX5\": 5, \"PX6\": 6, \"PX7W\": 7, \"PX8N\": { \"NX\": 8}}", "#", "errors", "#",
+        "{ \"type\": ["
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PA1\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PA2\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PA3\", \"schemaRef\": \"http://xyz#/definitions/A\", \"expected\": [\"integer\"], \"actual\": \"string\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX1\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX2Y\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX3Z\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX4\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX5\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX6\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX7W\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"},"
+        "    {\"errorCode\": 20, \"instanceRef\": \"#/PX8N/NX\", \"schemaRef\": \"http://xyz#/definitions/B/definitions/X\", \"expected\": [\"boolean\"], \"actual\": \"integer\"}"
+        "]}",
+        kValidateDefaultFlags | kValidateContinueOnErrorFlag, SchemaValidatorType, PointerType);
+    CrtAllocator::Free(schema);
+}
+
 TEST(SchemaValidator, Ref_remote_issue1210) {
     class SchemaDocumentProvider : public IRemoteSchemaDocumentProvider {
         SchemaDocument** collection;
@@ -2916,250 +2947,6 @@
     ASSERT_TRUE(SchemaValidator::SchemaType::GetValidateErrorKeyword(kValidateErrors).GetString() == std::string("null"));
 }
 
-TEST(SchemaValidator, Uri_Parse) {
-    typedef std::basic_string<Value::Ch> String;
-    typedef Uri<GenericSchemaDocument<Value, MemoryPoolAllocator<> > > Uri;
-    MemoryPoolAllocator<CrtAllocator> allocator;
-
-    String s = "http://auth/path?query#frag";
-    Value v;
-    v.SetString(s, allocator);
-    Uri u = Uri(v);
-    EXPECT_TRUE(u.GetScheme() == "http:");
-    EXPECT_TRUE(u.GetAuth() == "//auth");
-    EXPECT_TRUE(u.GetPath() == "/path");
-    EXPECT_TRUE(u.GetDoc() == "http://auth/path?query");
-    EXPECT_TRUE(u.GetQuery() == "?query");
-    EXPECT_TRUE(u.GetFrag() == "#frag");
-    Value w;
-    u.Get(w, allocator);
-    EXPECT_TRUE(*w.GetString() == *v.GetString());
-
-    s = "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f";
-    v.SetString(s, allocator);
-    u = Uri(v);
-    EXPECT_TRUE(u.GetScheme() == "urn:");
-    EXPECT_TRUE(u.GetAuth() == "");
-    EXPECT_TRUE(u.GetPath() == "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
-    EXPECT_TRUE(u.GetDoc() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
-    EXPECT_TRUE(u.GetQuery() == "");
-    EXPECT_TRUE(u.GetFrag() == "");
-    u.Get(w, allocator);
-    EXPECT_TRUE(*w.GetString() == *v.GetString());
-
-    s = "";
-    v.SetString(s, allocator);
-    u = Uri(v);
-    EXPECT_TRUE(u.GetScheme() == "");
-    EXPECT_TRUE(u.GetAuth() == "");
-    EXPECT_TRUE(u.GetPath() == "");
-    EXPECT_TRUE(u.GetDoc() == "");
-    EXPECT_TRUE(u.GetQuery() == "");
-    EXPECT_TRUE(u.GetFrag() == "");
-
-    s = "http://auth/";
-    v.SetString(s, allocator);
-    u = Uri(v);
-    EXPECT_TRUE(u.GetScheme() == "http:");
-    EXPECT_TRUE(u.GetAuth() == "//auth");
-    EXPECT_TRUE(u.GetPath() == "/");
-    EXPECT_TRUE(u.GetDoc() == "http://auth/");
-    EXPECT_TRUE(u.GetQuery() == "");
-    EXPECT_TRUE(u.GetFrag() == "");
-
-    s = "/path/sub";
-    u = Uri(s);
-    EXPECT_TRUE(u.GetScheme() == "");
-    EXPECT_TRUE(u.GetAuth() == "");
-    EXPECT_TRUE(u.GetPath() == "/path/sub");
-    EXPECT_TRUE(u.GetDoc() == "/path/sub");
-    EXPECT_TRUE(u.GetQuery() == "");
-    EXPECT_TRUE(u.GetFrag() == "");
-
-    // absolute path gets normalized
-    s = "/path/../sub/";
-    u = Uri(s);
-    EXPECT_TRUE(u.GetScheme() == "");
-    EXPECT_TRUE(u.GetAuth() == "");
-    EXPECT_TRUE(u.GetPath() == "/sub/");
-    EXPECT_TRUE(u.GetDoc() == "/sub/");
-    EXPECT_TRUE(u.GetQuery() == "");
-    EXPECT_TRUE(u.GetFrag() == "");
-
-    // relative path does not
-    s = "path/../sub";
-    u = Uri(s);
-    EXPECT_TRUE(u.GetScheme() == "");
-    EXPECT_TRUE(u.GetAuth() == "");
-    EXPECT_TRUE(u.GetPath() == "path/../sub");
-    EXPECT_TRUE(u.GetDoc() == "path/../sub");
-    EXPECT_TRUE(u.GetQuery() == "");
-    EXPECT_TRUE(u.GetFrag() == "");
-
-    s = "http://auth#frag/stuff";
-    u = Uri(s);
-    EXPECT_TRUE(u.GetScheme() == "http:");
-    EXPECT_TRUE(u.GetAuth() == "//auth");
-    EXPECT_TRUE(u.GetPath() == "");
-    EXPECT_TRUE(u.GetDoc() == "http://auth");
-    EXPECT_TRUE(u.GetQuery() == "");
-    EXPECT_TRUE(u.GetFrag() == "#frag/stuff");
-    EXPECT_TRUE(u.Get() == s);
-
-    s = "#frag/stuff";
-    u = Uri(s);
-    EXPECT_TRUE(u.GetScheme() == "");
-    EXPECT_TRUE(u.GetAuth() == "");
-    EXPECT_TRUE(u.GetPath() == "");
-    EXPECT_TRUE(u.GetDoc() == "");
-    EXPECT_TRUE(u.GetQuery() == "");
-    EXPECT_TRUE(u.GetFrag() == "#frag/stuff");
-    EXPECT_TRUE(u.Get() == s);
-
-    Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'};
-    u = Uri(c, 11);
-    EXPECT_TRUE(String(u.GetString()) == "#frag/stuff");
-    EXPECT_TRUE(u.GetStringLength() == 11);
-    EXPECT_TRUE(String(u.GetDocString()) == "");
-    EXPECT_TRUE(u.GetDocStringLength() == 0);
-    EXPECT_TRUE(String(u.GetFragString()) == "#frag/stuff");
-    EXPECT_TRUE(u.GetFragStringLength() == 11);
-}
-
-TEST(SchemaValidator, Uri_Resolve) {
-    typedef std::basic_string<Value::Ch> String;
-    typedef Uri<GenericSchemaDocument<Value, MemoryPoolAllocator<> > > Uri;
-
-    // ref is full uri
-    Uri base = Uri(String("http://auth/path/#frag"));
-    Uri ref = Uri(String("http://newauth/newpath#newfrag"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag");
-
-    base = Uri(String("/path/#frag"));
-    ref = Uri(String("http://newauth/newpath#newfrag"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag");
-
-    // ref is alternate uri
-    base = Uri(String("http://auth/path/#frag"));
-    ref = Uri(String("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
-
-    // ref is absolute path
-    base = Uri(String("http://auth/path/#"));
-    ref = Uri(String("/newpath#newfrag"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newpath#newfrag");
-
-    // ref is relative path
-    base = Uri(String("http://auth/path/file.json#frag"));
-    ref = Uri(String("newfile.json#"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#");
-
-    base = Uri(String("http://auth/path/file.json#frag/stuff"));
-    ref = Uri(String("newfile.json#newfrag/newstuff"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#newfrag/newstuff");
-
-    base = Uri(String("file.json"));
-    ref = Uri(String("newfile.json"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
-
-    base = Uri(String("file.json"));
-    ref = Uri(String("./newfile.json"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
-
-    base = Uri(String("file.json"));
-    ref = Uri(String("parent/../newfile.json"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
-
-    base = Uri(String("file.json"));
-    ref = Uri(String("parent/./newfile.json"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "parent/newfile.json");
-
-    base = Uri(String("file.json"));
-    ref = Uri(String("../../parent/.././newfile.json"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
-
-    base = Uri(String("http://auth"));
-    ref = Uri(String("newfile.json"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newfile.json");
-
-    // ref is fragment
-    base = Uri(String("#frag/stuff"));
-    ref = Uri(String("#newfrag/newstuff"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "#newfrag/newstuff");
-
-    // test ref fragment always wins
-    base = Uri(String("/path#frag"));
-    ref = Uri(String(""));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "/path");
-
-    // Examples from RFC3896
-    base = Uri(String("http://a/b/c/d;p?q"));
-    ref = Uri(String("g:h"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "g:h");
-    ref = Uri(String("g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g");
-    ref = Uri(String("./g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g");
-    ref = Uri(String("g/"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g/");
-    ref = Uri(String("/g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
-    ref = Uri(String("//g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://g");
-    ref = Uri(String("?y"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?y");
-    ref = Uri(String("g?y"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y");
-    ref = Uri(String("#s"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q#s");
-    ref = Uri(String("g#s"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s");
-    ref = Uri(String("g?y#s"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y#s");
-    ref = Uri(String(";x"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/;x");
-    ref = Uri(String("g;x"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x");
-    ref = Uri(String("g;x?y#s"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x?y#s");
-    ref = Uri(String(""));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q");
-    ref = Uri(String("."));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/");
-    ref = Uri(String("./"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/");
-    ref = Uri(String(".."));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/");
-    ref = Uri(String("../"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/");
-    ref = Uri(String("../g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/g");
-    ref = Uri(String("../.."));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/");
-    ref = Uri(String("../../"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/");
-     ref = Uri(String("../../g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
-    ref = Uri(String("../../../g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
-    ref = Uri(String("../../../../g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
-    ref = Uri(String("/./g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
-    ref = Uri(String("/../g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
-    ref = Uri(String("g."));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g.");
-    ref = Uri(String(".g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/.g");
-    ref = Uri(String("g.."));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g..");
-    ref = Uri(String("..g"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/..g");
-    ref = Uri(String("g#s/../x"));
-    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s/../x");
-}
-
 #if defined(_MSC_VER) || defined(__clang__)
 RAPIDJSON_DIAG_POP
 #endif
diff --git a/test/unittest/uritest.cpp b/test/unittest/uritest.cpp
new file mode 100644
index 0000000..d8a78b8
--- /dev/null
+++ b/test/unittest/uritest.cpp
@@ -0,0 +1,278 @@
+// Tencent is pleased to support the open source community by making RapidJSON available.
+// 
+// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.
+//
+// Licensed under the MIT License (the "License"); you may not use this file except
+// in compliance with the License. You may obtain a copy of the License at
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless required by applicable law or agreed to in writing, software distributed 
+// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
+// CONDITIONS OF ANY KIND, either express or implied. See the License for the 
+// specific language governing permissions and limitations under the License.
+
+#define RAPIDJSON_SCHEMA_VERBOSE 0
+#define RAPIDJSON_HAS_STDSTRING 1
+
+#include "unittest.h"
+#include "rapidjson/document.h"
+#include "rapidjson/uri.h"
+
+#ifdef __clang__
+RAPIDJSON_DIAG_PUSH
+RAPIDJSON_DIAG_OFF(variadic-macros)
+#elif defined(_MSC_VER)
+RAPIDJSON_DIAG_PUSH
+RAPIDJSON_DIAG_OFF(4822) // local class member function does not have a body
+#endif
+
+using namespace rapidjson;
+
+TEST(Uri, Parse) {
+    typedef std::basic_string<Value::Ch> String;
+    typedef GenericUri<Value, MemoryPoolAllocator<> > Uri;
+    MemoryPoolAllocator<CrtAllocator> allocator;
+
+    String s = "http://auth/path?query#frag";
+    Value v;
+    v.SetString(s, allocator);
+    Uri u = Uri(v);
+    EXPECT_TRUE(u.GetScheme() == "http:");
+    EXPECT_TRUE(u.GetAuth() == "//auth");
+    EXPECT_TRUE(u.GetPath() == "/path");
+    EXPECT_TRUE(u.GetBase() == "http://auth/path?query");
+    EXPECT_TRUE(u.GetQuery() == "?query");
+    EXPECT_TRUE(u.GetFrag() == "#frag");
+    Value w;
+    u.Get(w, allocator);
+    EXPECT_TRUE(*w.GetString() == *v.GetString());
+
+    s = "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f";
+    v.SetString(s, allocator);
+    u = Uri(v);
+    EXPECT_TRUE(u.GetScheme() == "urn:");
+    EXPECT_TRUE(u.GetAuth() == "");
+    EXPECT_TRUE(u.GetPath() == "uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
+    EXPECT_TRUE(u.GetBase() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
+    EXPECT_TRUE(u.GetQuery() == "");
+    EXPECT_TRUE(u.GetFrag() == "");
+    u.Get(w, allocator);
+    EXPECT_TRUE(*w.GetString() == *v.GetString());
+
+    s = "";
+    v.SetString(s, allocator);
+    u = Uri(v);
+    EXPECT_TRUE(u.GetScheme() == "");
+    EXPECT_TRUE(u.GetAuth() == "");
+    EXPECT_TRUE(u.GetPath() == "");
+    EXPECT_TRUE(u.GetBase() == "");
+    EXPECT_TRUE(u.GetQuery() == "");
+    EXPECT_TRUE(u.GetFrag() == "");
+
+    s = "http://auth/";
+    v.SetString(s, allocator);
+    u = Uri(v);
+    EXPECT_TRUE(u.GetScheme() == "http:");
+    EXPECT_TRUE(u.GetAuth() == "//auth");
+    EXPECT_TRUE(u.GetPath() == "/");
+    EXPECT_TRUE(u.GetBase() == "http://auth/");
+    EXPECT_TRUE(u.GetQuery() == "");
+    EXPECT_TRUE(u.GetFrag() == "");
+
+    s = "/path/sub";
+    u = Uri(s);
+    EXPECT_TRUE(u.GetScheme() == "");
+    EXPECT_TRUE(u.GetAuth() == "");
+    EXPECT_TRUE(u.GetPath() == "/path/sub");
+    EXPECT_TRUE(u.GetBase() == "/path/sub");
+    EXPECT_TRUE(u.GetQuery() == "");
+    EXPECT_TRUE(u.GetFrag() == "");
+
+    // absolute path gets normalized
+    s = "/path/../sub/";
+    u = Uri(s);
+    EXPECT_TRUE(u.GetScheme() == "");
+    EXPECT_TRUE(u.GetAuth() == "");
+    EXPECT_TRUE(u.GetPath() == "/sub/");
+    EXPECT_TRUE(u.GetBase() == "/sub/");
+    EXPECT_TRUE(u.GetQuery() == "");
+    EXPECT_TRUE(u.GetFrag() == "");
+
+    // relative path does not
+    s = "path/../sub";
+    u = Uri(s);
+    EXPECT_TRUE(u.GetScheme() == "");
+    EXPECT_TRUE(u.GetAuth() == "");
+    EXPECT_TRUE(u.GetPath() == "path/../sub");
+    EXPECT_TRUE(u.GetBase() == "path/../sub");
+    EXPECT_TRUE(u.GetQuery() == "");
+    EXPECT_TRUE(u.GetFrag() == "");
+
+    s = "http://auth#frag/stuff";
+    u = Uri(s);
+    EXPECT_TRUE(u.GetScheme() == "http:");
+    EXPECT_TRUE(u.GetAuth() == "//auth");
+    EXPECT_TRUE(u.GetPath() == "");
+    EXPECT_TRUE(u.GetBase() == "http://auth");
+    EXPECT_TRUE(u.GetQuery() == "");
+    EXPECT_TRUE(u.GetFrag() == "#frag/stuff");
+    EXPECT_TRUE(u.Get() == s);
+
+    s = "#frag/stuff";
+    u = Uri(s);
+    EXPECT_TRUE(u.GetScheme() == "");
+    EXPECT_TRUE(u.GetAuth() == "");
+    EXPECT_TRUE(u.GetPath() == "");
+    EXPECT_TRUE(u.GetBase() == "");
+    EXPECT_TRUE(u.GetQuery() == "");
+    EXPECT_TRUE(u.GetFrag() == "#frag/stuff");
+    EXPECT_TRUE(u.Get() == s);
+
+    Value::Ch c[] = { '#', 'f', 'r', 'a', 'g', '/', 's', 't', 'u', 'f', 'f', '\0'};
+    u = Uri(c, 11);
+    EXPECT_TRUE(String(u.GetString()) == "#frag/stuff");
+    EXPECT_TRUE(u.GetStringLength() == 11);
+    EXPECT_TRUE(String(u.GetBaseString()) == "");
+    EXPECT_TRUE(u.GetBaseStringLength() == 0);
+    EXPECT_TRUE(String(u.GetFragString()) == "#frag/stuff");
+    EXPECT_TRUE(u.GetFragStringLength() == 11);
+}
+
+TEST(Uri, Resolve) {
+    typedef std::basic_string<Value::Ch> String;
+    typedef GenericUri<Value, MemoryPoolAllocator<> > Uri;
+
+    // ref is full uri
+    Uri base = Uri(String("http://auth/path/#frag"));
+    Uri ref = Uri(String("http://newauth/newpath#newfrag"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag");
+
+    base = Uri(String("/path/#frag"));
+    ref = Uri(String("http://newauth/newpath#newfrag"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://newauth/newpath#newfrag");
+
+    // ref is alternate uri
+    base = Uri(String("http://auth/path/#frag"));
+    ref = Uri(String("urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f");
+
+    // ref is absolute path
+    base = Uri(String("http://auth/path/#"));
+    ref = Uri(String("/newpath#newfrag"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newpath#newfrag");
+
+    // ref is relative path
+    base = Uri(String("http://auth/path/file.json#frag"));
+    ref = Uri(String("newfile.json#"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#");
+
+    base = Uri(String("http://auth/path/file.json#frag/stuff"));
+    ref = Uri(String("newfile.json#newfrag/newstuff"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/path/newfile.json#newfrag/newstuff");
+
+    base = Uri(String("file.json"));
+    ref = Uri(String("newfile.json"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
+
+    base = Uri(String("file.json"));
+    ref = Uri(String("./newfile.json"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
+
+    base = Uri(String("file.json"));
+    ref = Uri(String("parent/../newfile.json"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
+
+    base = Uri(String("file.json"));
+    ref = Uri(String("parent/./newfile.json"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "parent/newfile.json");
+
+    base = Uri(String("file.json"));
+    ref = Uri(String("../../parent/.././newfile.json"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "newfile.json");
+
+    base = Uri(String("http://auth"));
+    ref = Uri(String("newfile.json"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://auth/newfile.json");
+
+    // ref is fragment
+    base = Uri(String("#frag/stuff"));
+    ref = Uri(String("#newfrag/newstuff"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "#newfrag/newstuff");
+
+    // test ref fragment always wins
+    base = Uri(String("/path#frag"));
+    ref = Uri(String(""));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "/path");
+
+    // Examples from RFC3896
+    base = Uri(String("http://a/b/c/d;p?q"));
+    ref = Uri(String("g:h"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "g:h");
+    ref = Uri(String("g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g");
+    ref = Uri(String("./g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g");
+    ref = Uri(String("g/"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g/");
+    ref = Uri(String("/g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
+    ref = Uri(String("//g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://g");
+    ref = Uri(String("?y"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?y");
+    ref = Uri(String("g?y"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y");
+    ref = Uri(String("#s"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q#s");
+    ref = Uri(String("g#s"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s");
+    ref = Uri(String("g?y#s"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g?y#s");
+    ref = Uri(String(";x"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/;x");
+    ref = Uri(String("g;x"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x");
+    ref = Uri(String("g;x?y#s"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g;x?y#s");
+    ref = Uri(String(""));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/d;p?q");
+    ref = Uri(String("."));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/");
+    ref = Uri(String("./"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/");
+    ref = Uri(String(".."));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/");
+    ref = Uri(String("../"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/");
+    ref = Uri(String("../g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/g");
+    ref = Uri(String("../.."));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/");
+    ref = Uri(String("../../"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/");
+     ref = Uri(String("../../g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
+    ref = Uri(String("../../../g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
+    ref = Uri(String("../../../../g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
+    ref = Uri(String("/./g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
+    ref = Uri(String("/../g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/g");
+    ref = Uri(String("g."));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g.");
+    ref = Uri(String(".g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/.g");
+    ref = Uri(String("g.."));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g..");
+    ref = Uri(String("..g"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/..g");
+    ref = Uri(String("g#s/../x"));
+    EXPECT_TRUE(ref.Resolve(base).Get() == "http://a/b/c/g#s/../x");
+}
+
+#if defined(_MSC_VER) || defined(__clang__)
+RAPIDJSON_DIAG_POP
+#endif