Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 6465)
+++ django/conf/global_settings.py	(working copy)
@@ -209,9 +209,14 @@
 # loudly.
 SECRET_KEY = ''
 
-# Path to the "jing" executable -- needed to validate XMLFields
+# Either JING or RNV exectable is needed to validate XMLFields (JING is preferred)
+
+# Path to the "jing" executable
 JING_PATH = "/usr/bin/jing"
 
+# Path to the "rnv" executable
+RNV_PATH = "/usr/bin/rnv"
+
 # Absolute path to the directory that holds media.
 # Example: "/home/media/media.lawrence.com/"
 MEDIA_ROOT = ''
Index: django/core/validators.py
===================================================================
--- django/core/validators.py	(revision 6465)
+++ django/core/validators.py	(working copy)
@@ -556,47 +556,60 @@
         fp = open(filename, 'w')
         fp.write(field_data)
         fp.close()
-        if not os.path.exists(settings.JING_PATH):
-            raise Exception, "%s not found!" % settings.JING_PATH
-        p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
-        errors = [line.strip() for line in p.readlines()]
-        p.close()
-        os.unlink(filename)
-        display_errors = []
-        lines = field_data.split('\n')
-        for error in errors:
-            ignored, line, level, message = error.split(':', 3)
-            # Scrape the Jing error messages to reword them more nicely.
-            m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
-            if m:
-                display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
-                    {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
-                continue
-            if message.strip() == 'text not allowed here':
-                display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
-                    {'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
-            if m:
-                display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
-                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            m = re.search(r'\s*unknown element "(.*?)"', message)
-            if m:
-                display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
-                    {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            if message.strip() == 'required attributes missing':
-                display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
-                    {'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            m = re.search(r'\s*bad value for attribute "(.*?)"', message)
-            if m:
-                display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
-                    {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
-                continue
-            # Failing all those checks, use the default error message.
-            display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
-            display_errors.append(display_error)
+        if os.path.exists(settings.JING_PATH):
+            p = os.popen('%s -c %s %s' % (settings.JING_PATH, self.schema_path, filename))
+            errors = [line.strip() for line in p.readlines()]
+            p.close()
+            os.unlink(filename)
+            display_errors = []
+            lines = field_data.split('\n')
+            for error in errors:
+                ignored, line, level, message = error.split(':', 3)
+                # Scrape the Jing error messages to reword them more nicely.
+                m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
+                if m:
+                    display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
+                        {'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
+                    continue
+                if message.strip() == 'text not allowed here':
+                    display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
+                        {'line':line, 'start':lines[int(line) - 1][:30]})
+                    continue
+                m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
+                if m:
+                    display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
+                        {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+                    continue
+                m = re.search(r'\s*unknown element "(.*?)"', message)
+                if m:
+                    display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
+                        {'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+                    continue
+                if message.strip() == 'required attributes missing':
+                    display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
+                        {'line':line, 'start':lines[int(line) - 1][:30]})
+                    continue
+                m = re.search(r'\s*bad value for attribute "(.*?)"', message)
+                if m:
+                    display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
+                        {'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
+                    continue
+                # Failing all those checks, use the default error message.
+                display_error = 'Line %s: %s [%s]' % (line, message, level.strip())
+                display_errors.append(display_error)
+        elif os.path.exists(settings.RNV_PATH):
+            p = os.popen('%s -q %s %s 2>&1' % (settings.RNV_PATH, self.schema_path, filename))
+            errors = [line.strip() for line in p.readlines()]
+            p.close()
+            os.unlink(filename)
+            display_errors = []
+            lines = field_data.split('\n')
+            for error in errors:
+                fname, line, col, message = error.split(':', 3)
+                start = lines[int(line) - 1][:max(int(col) + 10, 30)]
+                display_error = 'Line %s:%s - %s (Line starts with "%s")' % (line, col, message, start)
+                display_errors.append(display_error)
+        else:
+            raise Exception, "Neither %s nor %s found!" % (settings.JING_PATH, settings.RNV_PATH)
         if len(display_errors) > 0:
             raise ValidationError, display_errors
