Index: /trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(revision 12666)
+++ /trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(revision 12667)
@@ -105,5 +105,6 @@
                 org.openstreetmap.josm.io.GeoJSONExporter.class,
                 org.openstreetmap.josm.io.WMSLayerExporter.class,
-                org.openstreetmap.josm.io.NoteExporter.class
+                org.openstreetmap.josm.io.NoteExporter.class,
+                org.openstreetmap.josm.io.ValidatorErrorExporter.class
         );
 
Index: /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 12666)
+++ /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 12667)
@@ -17,9 +17,13 @@
 import java.util.Collection;
 import java.util.Collections;
+import java.util.EnumMap;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import javax.swing.JOptionPane;
@@ -62,4 +66,5 @@
 import org.openstreetmap.josm.gui.layer.ValidatorLayer;
 import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
+import org.openstreetmap.josm.tools.AlphanumComparator;
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
@@ -389,3 +394,20 @@
     }
 
+    /**
+     * Groups the given collection of errors by severity, then message, then description.
+     * @param errors list of errors to group
+     * @param filterToUse optional filter
+     * @return collection of errors grouped by severity, then message, then description
+     * @since 12667
+     */
+    public static Map<Severity, Map<String, Map<String, List<TestError>>>> getErrorsBySeverityMessageDescription(
+            Collection<TestError> errors, Predicate<? super TestError> filterToUse) {
+        return errors.stream().filter(filterToUse).collect(
+                Collectors.groupingBy(TestError::getSeverity, () -> new EnumMap<>(Severity.class),
+                        Collectors.groupingBy(TestError::getMessage, () -> new TreeMap<>(AlphanumComparator.getInstance()),
+                                Collectors.groupingBy(e -> e.getDescription() == null ? "" : e.getDescription(),
+                                        () -> new TreeMap<>(AlphanumComparator.getInstance()),
+                                        Collectors.toList()
+                                ))));
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/validation/Severity.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/Severity.java	(revision 12666)
+++ /trunk/src/org/openstreetmap/josm/data/validation/Severity.java	(revision 12667)
@@ -14,10 +14,13 @@
     // CHECKSTYLE.OFF: SingleSpaceSeparator
     /** Error messages */
-    ERROR(tr("Errors"), /* ICON(data/) */"error",       new ColorProperty(marktr("validation error"), Color.RED).get()),
+    ERROR(1, tr("Errors"), /* ICON(data/) */"error",       new ColorProperty(marktr("validation error"), Color.RED).get()),
     /** Warning messages */
-    WARNING(tr("Warnings"), /* ICON(data/) */"warning", new ColorProperty(marktr("validation warning"), Color.YELLOW).get()),
+    WARNING(2, tr("Warnings"), /* ICON(data/) */"warning", new ColorProperty(marktr("validation warning"), Color.YELLOW).get()),
     /** Other messages */
-    OTHER(tr("Other"), /* ICON(data/) */"other",        new ColorProperty(marktr("validation other"), Color.CYAN).get());
+    OTHER(3, tr("Other"), /* ICON(data/) */"other",        new ColorProperty(marktr("validation other"), Color.CYAN).get());
     // CHECKSTYLE.ON: SingleSpaceSeparator
+
+    /** Numeric ordering of the severities, 1 being major, 3 being minor */
+    private final int level;
 
     /** Description of the severity code */
@@ -33,9 +36,11 @@
      * Constructor
      *
+     * @param level Numeric ordering, 1 being major, 3 being minor
      * @param message Description
      * @param icon Associated icon
      * @param color The color of this severity
      */
-    Severity(String message, String icon, Color color) {
+    Severity(int level, String message, String icon, Color color) {
+        this.level = level;
         this.message = message;
         this.icon = icon;
@@ -72,3 +77,12 @@
         return color;
     }
+
+    /**
+     * Gets the severity level, 1 being major, 3 being minor
+     * @return The severity level
+     * @since 12667
+     */
+    public int getLevel() {
+        return level;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/data/validation/Test.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/Test.java	(revision 12666)
+++ /trunk/src/org/openstreetmap/josm/data/validation/Test.java	(revision 12667)
@@ -39,5 +39,5 @@
  * @author frsantos
  */
-public class Test extends AbstractVisitor {
+public class Test extends AbstractVisitor implements Comparable<Test> {
 
     protected static final Predicate<OsmPrimitive> IN_DOWNLOADED_AREA = new NotOutsideDataSourceArea();
@@ -359,3 +359,8 @@
                Objects.equals(description, test.description);
     }
+
+    @Override
+    public int compareTo(Test t) {
+        return name.compareTo(t.name);
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java	(revision 12666)
+++ /trunk/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java	(revision 12667)
@@ -9,5 +9,4 @@
 import java.util.Collection;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.Enumeration;
 import java.util.HashSet;
@@ -15,7 +14,5 @@
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 import javax.swing.JTree;
@@ -40,4 +37,5 @@
 import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
+import org.openstreetmap.josm.data.validation.OsmValidator;
 import org.openstreetmap.josm.data.validation.Severity;
 import org.openstreetmap.josm.data.validation.TestError;
@@ -45,5 +43,4 @@
 import org.openstreetmap.josm.gui.MainApplication;
 import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.tools.AlphanumComparator;
 import org.openstreetmap.josm.tools.Destroyable;
 import org.openstreetmap.josm.tools.ListenerList;
@@ -191,11 +188,5 @@
         }
         Map<Severity, Map<String, Map<String, List<TestError>>>> errorsBySeverityMessageDescription
-            = errors.stream().filter(filterToUse).collect(
-                    Collectors.groupingBy(TestError::getSeverity, () -> new EnumMap<>(Severity.class),
-                            Collectors.groupingBy(TestError::getMessage, () -> new TreeMap<>(AlphanumComparator.getInstance()),
-                                    Collectors.groupingBy(e -> e.getDescription() == null ? "" : e.getDescription(),
-                                            () -> new TreeMap<>(AlphanumComparator.getInstance()),
-                                            Collectors.toList()
-                                    ))));
+            = OsmValidator.getErrorsBySeverityMessageDescription(errors, filterToUse);
 
         final List<TreePath> expandedPaths = new ArrayList<>();
Index: /trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java	(revision 12666)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java	(revision 12667)
@@ -275,5 +275,5 @@
     @Override
     public File createAndOpenSaveFileChooser() {
-        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save GPX file"), NoteExporter.FILE_FILTER);
+        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Note file"), NoteExporter.FILE_FILTER);
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/layer/ValidatorLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/ValidatorLayer.java	(revision 12666)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/ValidatorLayer.java	(revision 12667)
@@ -5,4 +5,5 @@
 
 import java.awt.Graphics2D;
+import java.io.File;
 import java.util.Collections;
 import java.util.Enumeration;
@@ -15,4 +16,5 @@
 
 import org.openstreetmap.josm.actions.RenameLayerAction;
+import org.openstreetmap.josm.actions.SaveActionBase;
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
@@ -29,4 +31,5 @@
 import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
 import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
+import org.openstreetmap.josm.io.ValidatorErrorExporter;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.MultiMap;
@@ -140,5 +143,12 @@
                 new RenameLayerAction(null, this),
                 SeparatorLayerAction.INSTANCE,
-                new LayerListPopup.InfoAction(this) };
+                new LayerListPopup.InfoAction(this),
+                new LayerSaveAsAction(this)
+                };
+    }
+
+    @Override
+    public File createAndOpenSaveFileChooser() {
+        return SaveActionBase.createAndOpenSaveFileChooser(tr("Save Validation errors file"), ValidatorErrorExporter.FILE_FILTER);
     }
 
Index: /trunk/src/org/openstreetmap/josm/io/ValidatorErrorExporter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/ValidatorErrorExporter.java	(revision 12667)
+++ /trunk/src/org/openstreetmap/josm/io/ValidatorErrorExporter.java	(revision 12667)
@@ -0,0 +1,51 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.openstreetmap.josm.actions.ExtensionFileFilter;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.layer.ValidatorLayer;
+import org.openstreetmap.josm.tools.Logging;
+
+/**
+ * Exporter to write validator errors to an XML file.
+ * @since 12667
+ */
+public class ValidatorErrorExporter extends FileExporter {
+
+    /** File extension filter for .xml files */
+    public static final ExtensionFileFilter FILE_FILTER = new ExtensionFileFilter(
+            "xml", "xml", tr("Validation Error Files") + " (*.xml)");
+
+    /** Create a new validator error exporter with the default .xml file filter */
+    public ValidatorErrorExporter() {
+        super(FILE_FILTER);
+    }
+
+    @Override
+    public boolean acceptFile(File pathname, Layer layer) {
+        if (!(layer instanceof ValidatorLayer))
+            return false;
+        return super.acceptFile(pathname, layer);
+    }
+
+    @Override
+    public void exportData(File file, Layer layer) throws IOException {
+        OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
+        if (layer instanceof ValidatorLayer && editLayer != null) {
+            Logging.info("exporting validation errors to file: " + file);
+            try (OutputStream os = new FileOutputStream(file);
+                 ValidatorErrorWriter writer = new ValidatorErrorWriter(os)) {
+                writer.write(editLayer.validationErrors);
+            }
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/ValidatorErrorWriter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/ValidatorErrorWriter.java	(revision 12667)
+++ /trunk/src/org/openstreetmap/josm/io/ValidatorErrorWriter.java	(revision 12667)
@@ -0,0 +1,145 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.openstreetmap.josm.command.AddPrimitivesCommand;
+import org.openstreetmap.josm.command.ChangePropertyCommand;
+import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.validation.OsmValidator;
+import org.openstreetmap.josm.data.validation.Severity;
+import org.openstreetmap.josm.data.validation.Test;
+import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.tools.LanguageInfo;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.date.DateUtils;
+
+/**
+ * Class to write a collection of validator errors out to XML.
+ * The format is inspired by the
+ * <a href="https://wiki.openstreetmap.org/wiki/Osmose#Issues_file_format">Osmose API issues file format</a>
+ * @since 12667
+ */
+public class ValidatorErrorWriter extends XmlWriter {
+
+    /**
+     * Constructs a new {@code ValidatorErrorWriter} that will write to the given {@link PrintWriter}.
+     * @param out PrintWriter to write XML to
+     */
+    public ValidatorErrorWriter(PrintWriter out) {
+        super(out);
+    }
+
+    /**
+     * Constructs a new {@code ValidatorErrorWriter} that will write to a given {@link OutputStream}.
+     * @param out OutputStream to write XML to
+     */
+    public ValidatorErrorWriter(OutputStream out) {
+        super(new PrintWriter(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))));
+    }
+
+    /**
+     * Write validator errors to designated output target
+     * @param validationErrors Test error collection to write
+     */
+    public void write(Collection<TestError> validationErrors) {
+        Set<Test> analysers = validationErrors.stream().map(TestError::getTester).collect(Collectors.toCollection(TreeSet::new));
+        String timestamp = DateUtils.fromDate(new Date());
+
+        out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        out.println("<analysers generator='JOSM' timestamp=\""+timestamp+"\">");
+
+        OsmWriter osmWriter = OsmWriterFactory.createOsmWriter(out, true, OsmChangeBuilder.DEFAULT_API_VERSION);
+        String lang = LanguageInfo.getJOSMLocaleCode();
+
+        for (Test test : analysers) {
+            out.println("  <analyser timestamp=\""+timestamp+"\" name=\""+test.getName()+"\">");
+            // Build map of test error classes for the current test
+            Map<ErrorClass, List<TestError>> map = new HashMap<>();
+            for (Entry<Severity, Map<String, Map<String, List<TestError>>>> e1 :
+                OsmValidator.getErrorsBySeverityMessageDescription(validationErrors, e -> e.getTester() == test).entrySet()) {
+                for (Entry<String, Map<String, List<TestError>>> e2 : e1.getValue().entrySet()) {
+                    ErrorClass errorClass = new ErrorClass(e1.getKey(), e2.getKey());
+                    List<TestError> list = map.get(errorClass);
+                    if (list == null) {
+                        list = new ArrayList<>();
+                        map.put(errorClass, list);
+                    }
+                    e2.getValue().values().stream().forEach(list::addAll);
+                }
+            }
+            // Write classes
+            for (ErrorClass ec : map.keySet()) {
+                out.println("    <class id=\""+ec.id+"\" level=\""+ec.severity.getLevel()+"\">");
+                out.println("      <classtext lang=\""+lang+"\" title=\""+ec.message+"\"/>");
+                out.println("    </class>");
+            }
+
+            // Write errors
+            for (Entry<ErrorClass, List<TestError>> entry : map.entrySet()) {
+                for (TestError error : entry.getValue()) {
+                    LatLon ll = error.getPrimitives().iterator().next().getBBox().getCenter();
+                    out.println("    <error class=\""+entry.getKey().id+"\">");
+                    out.println("      <location lat=\""+ll.lat()+"\" lon=\""+ll.lon()+"\">");
+                    for (OsmPrimitive p : error.getPrimitives()) {
+                        p.accept(osmWriter);
+                    }
+                    out.println("      <text lang=\""+lang+"\" value=\""+error.getDescription()+"\">");
+                    if (error.isFixable()) {
+                        out.println("      <fixes>");
+                        Command fix = error.getFix();
+                        if (fix instanceof AddPrimitivesCommand) {
+                            Logging.info("TODO: {0}", fix);
+                        } else if (fix instanceof DeleteCommand) {
+                            Logging.info("TODO: {0}", fix);
+                        } else if (fix instanceof ChangePropertyCommand) {
+                            Logging.info("TODO: {0}", fix);
+                        } else if (fix instanceof ChangePropertyKeyCommand) {
+                            Logging.info("TODO: {0}", fix);
+                        } else {
+                            Logging.warn("Unsupported command type: {0}", fix);
+                        }
+                        out.println("      </fixes>");
+                    }
+                    out.println("    </error>");
+                }
+            }
+
+            out.println("  </analyser>");
+        }
+
+        out.println("</analysers>");
+        out.flush();
+    }
+
+    private static class ErrorClass {
+        static int idCounter = 0;
+        final Severity severity;
+        final String message;
+        final int id;
+
+        ErrorClass(Severity severity, String message) {
+            this.severity = severity;
+            this.message = message;
+            this.id = ++idCounter;
+        }
+    }
+}
