Modify GetPath and GetRelativePath.

GetRelativePath now handles relative paths between any two nodes
in an element tree, even if one is not a direct descendant of the
other.
diff --git a/etree.go b/etree.go
index 9c04ecb..cfcc909 100644
--- a/etree.go
+++ b/etree.go
@@ -135,25 +135,92 @@
 	}
 }
 
-// GetPath returns the absolute path of the element
+// GetPath returns the absolute path of the element.
 func (e *Element) GetPath() string {
-	path := e.GetRelativePath(nil)
-
-	return path[1:]
-}
-
-// GetPath returns the path of the element corresponding to the source element or document root
-func (e *Element) GetRelativePath(source *Element) string {
-	parts := []string{}
-
-	for element := e; element != nil && element.Tag != "" && element != source; element = element.Parent() {
-		parts = append(parts, "")
-		copy(parts[1:], parts)
-		parts[0] = element.Tag
+	path := []string{}
+	for seg := e; seg != nil; seg = seg.Parent() {
+		if seg.Tag != "" {
+			path = append(path, seg.Tag)
+		}
 	}
 
-	path := strings.Join(parts, "/")
-	return "./" + path
+	// Reverse the path.
+	for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 {
+		path[i], path[j] = path[j], path[i]
+	}
+
+	return "/" + strings.Join(path, "/")
+}
+
+// GetRelativePath returns the path of the element relative to the source
+// element. If the two elements are not part of the same element tree, then
+// GetRelativePath returns the empty string.
+func (e *Element) GetRelativePath(source *Element) string {
+	var path []*Element
+
+	if source == nil {
+		return ""
+	}
+
+	// Build a reverse path from the element toward the root. Stop if the
+	// source element is encountered.
+	var seg *Element
+	for seg = e; seg != nil && seg != source; seg = seg.Parent() {
+		path = append(path, seg)
+	}
+
+	// If we found the source element, reverse the path and compose the
+	// string.
+	if seg == source {
+		if len(path) == 0 {
+			return "."
+		}
+		parts := []string{}
+		for i := len(path) - 1; i >= 0; i-- {
+			parts = append(parts, path[i].Tag)
+		}
+		return "./" + strings.Join(parts, "/")
+	}
+
+	// The source wasn't encountered, so climb from the source element toward
+	// the root of the tree until an element in the reversed path is
+	// encountered.
+
+	findPathIndex := func(e *Element, path []*Element) int {
+		for i, ee := range path {
+			if e.Tag == ee.Tag {
+				return i
+			}
+		}
+		return -1
+	}
+
+	climb := 0
+	for seg = source; seg != nil; seg = seg.Parent() {
+		i := findPathIndex(seg, path)
+		if i >= 0 {
+			path = path[:i] // truncate at found segment
+			break
+		}
+		climb++
+	}
+
+	// No element in the reversed path was encountered, so the two elements
+	// must not be part of the same tree.
+	if seg == nil {
+		return ""
+	}
+
+	// Reverse the (possibly truncated) path and prepend ".." segments to
+	// climb.
+	parts := []string{}
+	for i := 0; i < climb; i++ {
+		parts = append(parts, "..")
+	}
+	for i := len(path) - 1; i >= 0; i-- {
+		parts = append(parts, path[i].Tag)
+	}
+	return strings.Join(parts, "/")
 }
 
 // Copy returns a recursive, deep copy of the document.
@@ -445,7 +512,8 @@
 }
 
 // SelectAttr finds an element attribute matching the requested key and
-// returns it if found. The key may be prefixed by a namespace and a colon.
+// returns it if found. Returns nil if no matching attribute is found. The key
+// may be prefixed by a namespace and a colon.
 func (e *Element) SelectAttr(key string) *Attr {
 	space, skey := spaceDecompose(key)
 	for i, a := range e.Attr {
@@ -481,7 +549,8 @@
 }
 
 // SelectElement returns the first child element with the given tag. The tag
-// may be prefixed by a namespace and a colon.
+// may be prefixed by a namespace and a colon. Returns nil if no element with
+// a matching tag was found.
 func (e *Element) SelectElement(tag string) *Element {
 	space, stag := spaceDecompose(tag)
 	for _, t := range e.Child {
@@ -506,13 +575,14 @@
 }
 
 // FindElement returns the first element matched by the XPath-like path
-// string. Panics if an invalid path string is supplied.
+// string. Returns nil if no element is found using the path. Panics if an
+// invalid path string is supplied.
 func (e *Element) FindElement(path string) *Element {
 	return e.FindElementPath(MustCompilePath(path))
 }
 
 // FindElementPath returns the first element matched by the XPath-like path
-// string.
+// string. Returns nil if no element is found using the path.
 func (e *Element) FindElementPath(path Path) *Element {
 	p := newPather()
 	elements := p.traverse(e, path)
diff --git a/etree_test.go b/etree_test.go
index 3cc059a..e20d28b 100644
--- a/etree_test.go
+++ b/etree_test.go
@@ -10,12 +10,9 @@
 )
 
 func checkEq(t *testing.T, got, want string) {
-	if got == want {
-		return
+	if got != want {
+		t.Errorf("etree: unexpected result.\nGot:\n%s\nWanted:\n%s\n", got, want)
 	}
-	t.Errorf(
-		"etree: unexpected result.\nGot:\n%s\nWanted:\n%s\n",
-		got, want)
 }
 
 func TestDocument(t *testing.T) {
@@ -257,26 +254,70 @@
 	if s1 == s2 {
 		t.Error("etree: incorrect result after RemoveElement")
 	}
+}
 
-	pa1 := e1.GetPath()
-	checkEq(t, pa1, "/title")
+func TestGetPath(t *testing.T) {
+	testdoc := `<a>
+    <b1>
+        <c1>
+            <d1>
+            </d1>
+        </c1>
+    </b1>
+    <b2>
+        <c2>
+            <d2>
+            </d2>
+        </c2>
+    </b2>
+</a>`
 
-	pa2 := e2.GetPath()
-	checkEq(t, pa2, "/store/book/title")
+	doc := NewDocument()
+	err := doc.ReadFromString(testdoc)
+	if err != nil {
+		t.Fatalf("etree ReadFromString: %v\n", err)
+	}
 
-	p1 := e1.GetRelativePath(nil)
-	checkEq(t, p1, "./title")
+	cases := []struct {
+		from    string
+		to      string
+		relpath string
+		topath  string
+	}{
+		{"a", ".", "..", "/"},
+		{".", "a", "./a", "/a"},
+		{"a/b1/c1/d1", ".", "../../../..", "/"},
+		{".", "a/b1/c1/d1", "./a/b1/c1/d1", "/a/b1/c1/d1"},
+		{"a", "a", ".", "/a"},
+		{"a/b1", "a/b1/c1", "./c1", "/a/b1/c1"},
+		{"a/b1/c1", "a/b1", "..", "/a/b1"},
+		{"a/b1/c1", "a/b1/c1", ".", "/a/b1/c1"},
+		{"a", "a/b1", "./b1", "/a/b1"},
+		{"a/b1", "a", "..", "/a"},
+		{"a", "a/b1/c1", "./b1/c1", "/a/b1/c1"},
+		{"a/b1/c1", "a", "../..", "/a"},
+		{"a/b1/c1/d1", "a", "../../..", "/a"},
+		{"a", "a/b1/c1/d1", "./b1/c1/d1", "/a/b1/c1/d1"},
+		{"a/b1", "a/b2", "../b2", "/a/b2"},
+		{"a/b2", "a/b1", "../b1", "/a/b1"},
+		{"a/b1/c1/d1", "a/b2/c2/d2", "../../../b2/c2/d2", "/a/b2/c2/d2"},
+		{"a/b2/c2/d2", "a/b1/c1/d1", "../../../b1/c1/d1", "/a/b1/c1/d1"},
+	}
 
-	p2 := e1.GetRelativePath(e1)
-	checkEq(t, p2, "./")
+	for _, c := range cases {
+		fe := doc.FindElement(c.from)
+		te := doc.FindElement(c.to)
 
-	e3 := doc.FindElement("./store")
-	e4 := e3.FindElement("./book/author")
-	p3 := e4.GetRelativePath(e3)
-	checkEq(t, p3, "./book/author")
+		rp := te.GetRelativePath(fe)
+		if rp != c.relpath {
+			t.Errorf("GetRelativePath from '%s' to '%s'. Expected '%s', got '%s'.\n", c.from, c.to, c.relpath, rp)
+		}
 
-	p4 := e4.GetRelativePath(nil)
-	checkEq(t, p4, "./store/book/author")
+		p := te.GetPath()
+		if p != c.topath {
+			t.Errorf("GetPath for '%s'. Expected '/%s', got '%s'.\n", c.to, c.topath, p)
+		}
+	}
 }
 
 func TestInsertChild(t *testing.T) {