[gnome-builder/wip/slaf/xml-pack: 547/572] xmp-pack: add schema validator



commit 09b1a706621041d03bd0e56319b24f32968ab918
Author: Sebastien Lafargue <slafargue gnome org>
Date:   Wed Feb 15 13:21:27 2017 +0100

    xmp-pack: add schema validator

 plugins/xml-pack/Makefile.am                       |   15 +-
 plugins/xml-pack/ide-xml-analysis.c                |   27 +
 plugins/xml-pack/ide-xml-analysis.h                |    4 +
 plugins/xml-pack/ide-xml-parser-generic.c          |  128 ++++
 ...-tree-builder-ui.h => ide-xml-parser-generic.h} |   22 +-
 plugins/xml-pack/ide-xml-parser-private.h          |  125 ++++
 plugins/xml-pack/ide-xml-parser-ui.c               |  345 ++++++++++
 ...-tree-builder-generic.h => ide-xml-parser-ui.h} |   21 +-
 plugins/xml-pack/ide-xml-parser.c                  |  671 ++++++++++++++++++++
 plugins/xml-pack/ide-xml-parser.h                  |   46 ++
 plugins/xml-pack/ide-xml-sax.c                     |   12 +
 plugins/xml-pack/ide-xml-sax.h                     |   31 +-
 plugins/xml-pack/ide-xml-schema-cache-entry.c      |  103 +++
 plugins/xml-pack/ide-xml-schema-cache-entry.h      |   50 ++
 plugins/xml-pack/ide-xml-service.c                 |   83 +++-
 plugins/xml-pack/ide-xml-service.h                 |    2 +
 plugins/xml-pack/ide-xml-tree-builder-generic.c    |  391 ------------
 plugins/xml-pack/ide-xml-tree-builder-ui.c         |  614 ------------------
 .../xml-pack/ide-xml-tree-builder-utils-private.h  |    3 +
 plugins/xml-pack/ide-xml-tree-builder-utils.c      |   43 ++
 plugins/xml-pack/ide-xml-tree-builder.c            |  521 ++++++++++------
 plugins/xml-pack/ide-xml-tree-builder.h            |   19 +-
 plugins/xml-pack/ide-xml-validator.c               |  298 +++++++++
 plugins/xml-pack/ide-xml-validator.h               |   64 ++
 24 files changed, 2373 insertions(+), 1265 deletions(-)
---
diff --git a/plugins/xml-pack/Makefile.am b/plugins/xml-pack/Makefile.am
index 0254c88..6c22e0a 100644
--- a/plugins/xml-pack/Makefile.am
+++ b/plugins/xml-pack/Makefile.am
@@ -15,10 +15,19 @@ libxml_pack_plugin_la_SOURCES =              \
        ide-xml-highlighter.h                \
        ide-xml-indenter.c                   \
        ide-xml-indenter.h                   \
+       ide-xml-parser.c                     \
+       ide-xml-parser.h                     \
+       ide-xml-parser-private.h             \
+       ide-xml-parser-generic.c             \
+       ide-xml-parser-generic.h             \
+       ide-xml-parser-ui.c                  \
+       ide-xml-parser-ui.h                  \
        ide-xml-sax.c                        \
        ide-xml-sax.h                        \
        ide-xml-service.c                    \
        ide-xml-service.h                    \
+       ide-xml-schema-cache-entry.c         \
+       ide-xml-schema-cache-entry.h         \
        ide-xml-stack.c                      \
        ide-xml-stack.h                      \
        ide-xml-symbol-node.c                \
@@ -29,12 +38,10 @@ libxml_pack_plugin_la_SOURCES =              \
        ide-xml-symbol-tree.h                \
        ide-xml-tree-builder.c               \
        ide-xml-tree-builder.h               \
-       ide-xml-tree-builder-generic.c       \
-       ide-xml-tree-builder-generic.h       \
-       ide-xml-tree-builder-ui.c            \
-       ide-xml-tree-builder-ui.h            \
        ide-xml-tree-builder-utils.c         \
        ide-xml-tree-builder-utils-private.h \
+       ide-xml-validator.c                  \
+       ide-xml-validator.h                  \
        ide-xml.c                            \
        ide-xml.h                            \
        xml-pack-plugin.c                    \
diff --git a/plugins/xml-pack/ide-xml-analysis.c b/plugins/xml-pack/ide-xml-analysis.c
index 804c564..25b14cd 100644
--- a/plugins/xml-pack/ide-xml-analysis.c
+++ b/plugins/xml-pack/ide-xml-analysis.c
@@ -57,6 +57,21 @@ ide_xml_analysis_get_root_node (IdeXmlAnalysis *self)
   return self->root_node;
 }
 
+/**
+ * ide_xml_analysis_get_schemas:
+ * @self: A #GArray.
+ *
+ * Returns: (nullable) (transfer none): The schemas entries #GArray contained by the analysis.
+ *
+ */
+GArray *
+ide_xml_analysis_get_schemas (IdeXmlAnalysis *self)
+{
+  g_return_val_if_fail (self, NULL);
+
+  return self->schemas;
+}
+
 void
 ide_xml_analysis_set_diagnostics (IdeXmlAnalysis *self,
                                   IdeDiagnostics *diagnostics)
@@ -80,6 +95,18 @@ ide_xml_analysis_set_root_node (IdeXmlAnalysis   *self,
 }
 
 void
+ide_xml_analysis_set_schemas (IdeXmlAnalysis *self,
+                              GArray         *schemas)
+{
+  g_return_if_fail (self != NULL);
+
+  g_clear_pointer (&self->schemas, g_array_unref);
+
+  if (schemas != NULL)
+    self->schemas = g_array_ref (schemas);
+}
+
+void
 ide_xml_analysis_set_sequence (IdeXmlAnalysis   *self,
                                gint64            sequence)
 {
diff --git a/plugins/xml-pack/ide-xml-analysis.h b/plugins/xml-pack/ide-xml-analysis.h
index ed86908..a306ccc 100644
--- a/plugins/xml-pack/ide-xml-analysis.h
+++ b/plugins/xml-pack/ide-xml-analysis.h
@@ -35,18 +35,22 @@ struct _IdeXmlAnalysis
   guint             ref_count;
   IdeXmlSymbolNode *root_node;
   IdeDiagnostics   *diagnostics;
+  GArray           *schemas;
   gint64            sequence;
 };
 
 IdeDiagnostics     *ide_xml_analysis_get_diagnostics     (IdeXmlAnalysis   *self);
 IdeXmlSymbolNode   *ide_xml_analysis_get_root_node       (IdeXmlAnalysis   *self);
 gint64              ide_xml_analysis_get_sequence        (IdeXmlAnalysis   *self);
+GArray             *ide_xml_analysis_get_schemas         (IdeXmlAnalysis   *self);
 void                ide_xml_analysis_set_diagnostics     (IdeXmlAnalysis   *self,
                                                           IdeDiagnostics   *diagnostics);
 void                ide_xml_analysis_set_root_node       (IdeXmlAnalysis   *self,
                                                           IdeXmlSymbolNode *root_node);
 void                ide_xml_analysis_set_sequence        (IdeXmlAnalysis   *self,
                                                           gint64            sequence);
+void                ide_xml_analysis_set_schemas         (IdeXmlAnalysis   *self,
+                                                          GArray           *schemas);
 IdeXmlAnalysis     *ide_xml_analysis_new                 (gint64            sequence);
 IdeXmlAnalysis     *ide_xml_analysis_ref                 (IdeXmlAnalysis   *self);
 void                ide_xml_analysis_unref               (IdeXmlAnalysis   *self);
diff --git a/plugins/xml-pack/ide-xml-parser-generic.c b/plugins/xml-pack/ide-xml-parser-generic.c
new file mode 100644
index 0000000..5098b03
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-parser-generic.c
@@ -0,0 +1,128 @@
+/* ide-xml-parser-generic.c
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libxml/parser.h>
+
+#include "ide-xml-parser.h"
+#include "ide-xml-parser-generic.h"
+#include "ide-xml-sax.h"
+#include "ide-xml-stack.h"
+
+#include "ide-xml-parser-generic.h"
+
+static gchar *
+collect_attributes (IdeXmlParser  *self,
+                    const gchar  **attributes)
+{
+  GString *string;
+  gchar *value;
+  const gchar **l = attributes;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  if (attributes == NULL)
+    return NULL;
+
+  string = g_string_new (NULL);
+  while (l [0] != NULL)
+    {
+      value = ide_xml_parser_get_color_tag (self, l [0], COLOR_TAG_ATTRIBUTE, TRUE, TRUE, TRUE);
+      g_string_append (string, value);
+      g_free (value);
+      g_string_append (string, l [1]);
+
+      l += 2;
+    }
+
+  return g_string_free (string, FALSE);
+}
+
+static void
+ide_xml_parser_generic_start_element_sax_cb (ParserState    *state,
+                                             const xmlChar  *name,
+                                             const xmlChar **attributes)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  IdeXmlSymbolNode *node = NULL;
+  g_autofree gchar *attr = NULL;
+  g_autofree gchar *label = NULL;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  attr = collect_attributes (self, (const gchar **)attributes);
+  label = g_strconcat ((const gchar *)name, attr, NULL);
+
+  node = ide_xml_symbol_node_new (label, NULL, NULL, IDE_SYMBOL_XML_ELEMENT, NULL, 0, 0);
+  g_object_set (node, "use-markup", TRUE, NULL);
+
+  ide_xml_parser_state_processing (self, state, (const gchar *)name, node, 
IDE_XML_SAX_CALLBACK_TYPE_START_ELEMENT, FALSE);
+}
+
+static void
+ide_xml_parser_generic_comment_sax_cb (ParserState   *state,
+                                       const xmlChar *name)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  IdeXmlSymbolNode *node = NULL;
+  g_autofree gchar *strip_name = NULL;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  strip_name = g_strstrip (g_strdup ((const gchar *)name));
+  node = ide_xml_symbol_node_new (strip_name, NULL, NULL, IDE_SYMBOL_XML_COMMENT, NULL, 0, 0);
+  ide_xml_parser_state_processing (self, state, "comment", node, IDE_XML_SAX_CALLBACK_TYPE_COMMENT, FALSE);
+}
+
+static void
+ide_xml_parser_generic_cdata_sax_cb (ParserState   *state,
+                                     const xmlChar *value,
+                                     gint           len)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  IdeXmlSymbolNode *node = NULL;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  node = ide_xml_symbol_node_new ("cdata", NULL, NULL, IDE_SYMBOL_XML_CDATA, NULL, 0, 0);
+  ide_xml_parser_state_processing (self, state, "cdata", node, IDE_XML_SAX_CALLBACK_TYPE_CDATA, FALSE);
+}
+
+void
+ide_xml_parser_generic_setup (IdeXmlParser *self,
+                              ParserState  *state)
+{
+  g_assert (IDE_IS_XML_PARSER (self));
+  g_assert (state != NULL);
+
+  ide_xml_sax_clear (self->sax_parser);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_START_ELEMENT, 
ide_xml_parser_generic_start_element_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_END_ELEMENT, 
ide_xml_parser_end_element_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_COMMENT, 
ide_xml_parser_generic_comment_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_CDATA, 
ide_xml_parser_generic_cdata_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_CHAR, 
ide_xml_parser_characters_sax_cb);
+
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_INTERNAL_SUBSET, 
ide_xml_parser_internal_subset_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_EXTERNAL_SUBSET, 
ide_xml_parser_external_subset_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_PROCESSING_INSTRUCTION, 
ide_xml_parser_processing_instruction_sax_cb);
+
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_WARNING, 
ide_xml_parser_warning_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_ERROR, ide_xml_parser_error_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_FATAL_ERROR, 
ide_xml_parser_fatal_error_sax_cb);
+
+  ide_xml_parser_set_post_processing_callback (self, NULL);
+}
diff --git a/plugins/xml-pack/ide-xml-tree-builder-ui.h b/plugins/xml-pack/ide-xml-parser-generic.h
similarity index 53%
rename from plugins/xml-pack/ide-xml-tree-builder-ui.h
rename to plugins/xml-pack/ide-xml-parser-generic.h
index b570f5b..871c95c 100644
--- a/plugins/xml-pack/ide-xml-tree-builder-ui.h
+++ b/plugins/xml-pack/ide-xml-parser-generic.h
@@ -1,4 +1,4 @@
-/* ide-xml-tree-builder-ui.h
+/* ide-xml-parser-generic.h
  *
  * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
  *
@@ -16,26 +16,18 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef IDE_XML_TREE_BUILDER_UI_H
-#define IDE_XML_TREE_BUILDER_UI_H
+#ifndef IDE_XML_PARSER_GENERIC_H
+#define IDE_XML_PARSER_GENERIC_H
 
 #include <glib.h>
-#include <ide.h>
 
-#include "ide-xml-analysis.h"
-#include "ide-xml-sax.h"
-#include "ide-xml-symbol-node.h"
-#include "ide-xml-tree-builder.h"
-#include "xml-reader.h"
+#include "ide-xml-parser-private.h"
 
 G_BEGIN_DECLS
 
-IdeXmlAnalysis *ide_xml_tree_builder_ui_create (IdeXmlTreeBuilder *self,
-                                                IdeXmlSax         *parser,
-                                                GFile             *file,
-                                                const gchar       *data,
-                                                gsize              length);
+void      ide_xml_parser_generic_setup       (IdeXmlParser *self,
+                                              ParserState  *state);
 
 G_END_DECLS
 
-#endif /* IDE_XML_TREE_BUILDER_UI_H */
+#endif /* IDE_XML_PARSER_GENERIC_H */
diff --git a/plugins/xml-pack/ide-xml-parser-private.h b/plugins/xml-pack/ide-xml-parser-private.h
new file mode 100644
index 0000000..6dc35c2
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-parser-private.h
@@ -0,0 +1,125 @@
+/* ide-xml-parser-private.h
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_XML_PARSER_PRIVATE_H
+#define IDE_XML_PARSER_PRIVATE_H
+
+#include <glib.h>
+#include <libxml/parser.h>
+
+#include "ide-xml-analysis.h"
+#include "ide-xml-parser.h"
+#include "ide-xml-sax.h"
+#include "ide-xml-stack.h"
+#include "ide-xml-symbol-node.h"
+
+G_BEGIN_DECLS
+
+typedef gboolean (*PostProcessingCallback)                       (IdeXmlParser           *self,
+                                                                  IdeXmlSymbolNode       *root_node);
+
+typedef enum _BuildState
+{
+  BUILD_STATE_NORMAL,
+  BUILD_STATE_WAIT_END_ELEMENT,
+  BUILD_STATE_GET_CONTENT,
+} BuildState;
+
+typedef enum _ColorTagId
+{
+  COLOR_TAG_LABEL,
+  COLOR_TAG_ID,
+  COLOR_TAG_STYLE_CLASS,
+  COLOR_TAG_TYPE,
+  COLOR_TAG_PARENT,
+  COLOR_TAG_CLASS,
+  COLOR_TAG_ATTRIBUTE,
+} ColorTagId;
+
+struct _IdeXmlParser
+{
+  GObject                 parent_instance;
+  IdeXmlSax              *sax_parser;
+  IdeXmlStack            *stack;
+  GSettings              *settings;
+  GArray                 *color_tags;
+  PostProcessingCallback  post_processing_callback;
+};
+
+typedef struct _ParserState
+{
+  IdeXmlParser      *self;
+  GFile             *file;
+  GBytes            *content;
+  IdeXmlAnalysis    *analysis;
+  GPtrArray         *diagnostics_array;
+  IdeXmlSymbolNode  *root_node;
+  IdeXmlSymbolNode  *parent_node;
+  IdeXmlSymbolNode  *current_node;
+  BuildState         build_state;
+  gint               current_depth;
+  GArray            *schemas;
+  gint64             sequence;
+} ParserState;
+
+void             ide_xml_parser_set_post_processing_callback     (IdeXmlParser           *self,
+                                                                  PostProcessingCallback  callback);
+IdeDiagnostic   *ide_xml_parser_create_diagnostic                (ParserState            *state,
+                                                                  const gchar            *msg,
+                                                                  IdeDiagnosticSeverity   severity);
+gchar           *ide_xml_parser_get_color_tag                    (IdeXmlParser           *self,
+                                                                  const gchar            *str,
+                                                                  ColorTagId              id,
+                                                                  gboolean                space_before,
+                                                                  gboolean                space_after,
+                                                                  gboolean                space_inside);
+void             ide_xml_parser_state_processing                 (IdeXmlParser           *self,
+                                                                  ParserState            *state,
+                                                                  const gchar            *element_name,
+                                                                  IdeXmlSymbolNode       *node,
+                                                                  IdeXmlSaxCallbackType   callback_type,
+                                                                  gboolean                is_internal);
+void             ide_xml_parser_end_element_sax_cb               (ParserState            *state,
+                                                                  const xmlChar          *name);
+void             ide_xml_parser_warning_sax_cb                   (ParserState            *state,
+                                                                  const xmlChar          *name,
+                                                                  ...);
+void             ide_xml_parser_error_sax_cb                     (ParserState            *state,
+                                                                  const xmlChar          *name,
+                                                                  ...);
+void             ide_xml_parser_fatal_error_sax_cb               (ParserState            *state,
+                                                                  const xmlChar          *name,
+                                                                  ...);
+void             ide_xml_parser_internal_subset_sax_cb           (ParserState            *state,
+                                                                  const xmlChar          *name,
+                                                                  const xmlChar          *external_id,
+                                                                  const xmlChar          *system_id);
+void             ide_xml_parser_external_subset_sax_cb           (ParserState            *state,
+                                                                  const xmlChar          *name,
+                                                                  const xmlChar          *external_id,
+                                                                  const xmlChar          *system_id);
+void             ide_xml_parser_processing_instruction_sax_cb    (ParserState            *state,
+                                                                  const xmlChar          *target,
+                                                                  const xmlChar          *data);
+void             ide_xml_parser_characters_sax_cb                (ParserState            *state,
+                                                                  const xmlChar          *name,
+                                                                  gint                    len);
+
+G_END_DECLS
+
+#endif /* IDE_XML_PARSER_PRIVATE_H */
diff --git a/plugins/xml-pack/ide-xml-parser-ui.c b/plugins/xml-pack/ide-xml-parser-ui.c
new file mode 100644
index 0000000..b25a33b
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-parser-ui.c
@@ -0,0 +1,345 @@
+/* ide-xml-parser-ui.c
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-xml-parser.h"
+#include "ide-xml-parser-ui.h"
+#include "ide-xml-sax.h"
+#include "ide-xml-stack.h"
+#include "ide-xml-tree-builder-utils-private.h"
+
+#include "ide-xml-parser-ui.h"
+
+static const gchar *
+get_attribute (const guchar **list,
+               const gchar   *name,
+               const gchar   *replacement)
+{
+  const gchar *value = NULL;
+
+  value = list_get_attribute (list, name);
+  return ide_str_empty0 (value) ? ((replacement != NULL) ? replacement : NULL) : value;
+}
+
+static void
+ide_xml_parser_ui_start_element_sax_cb (ParserState    *state,
+                                        const xmlChar  *name,
+                                        const xmlChar **attributes)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  g_autoptr(GString) string = NULL;
+  g_autofree gchar *label = NULL;
+  const gchar *value = NULL;
+  IdeXmlSymbolNode *node = NULL;
+  const gchar *parent_name;
+  gboolean is_internal = FALSE;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  if (state->build_state == BUILD_STATE_GET_CONTENT)
+    {
+      g_warning ("Wrong xml element, waiting for content\n");
+      return;
+    }
+
+  string = g_string_new (NULL);
+  parent_name = ide_xml_symbol_node_get_element_name (state->parent_node);
+
+  if (ide_str_equal0 (name, "property"))
+    {
+      if (ide_str_equal0 (parent_name, "object") ||
+          ide_str_equal0 (parent_name, "template"))
+        {
+          value = get_attribute (attributes, "name", NULL);
+          node = ide_xml_symbol_node_new (value, NULL, "property",
+                                          IDE_SYMBOL_UI_PROPERTY, NULL, 0, 0);
+          is_internal = TRUE;
+          state->build_state = BUILD_STATE_GET_CONTENT;
+        }
+    }
+  else if (ide_str_equal0 (name, "attribute"))
+    {
+      if (ide_str_equal0 (parent_name, "section") ||
+          ide_str_equal0 (parent_name, "submenu") ||
+          ide_str_equal0 (parent_name, "item"))
+        {
+          value = get_attribute (attributes, "name", NULL);
+          node = ide_xml_symbol_node_new (value, NULL, "attribute",
+                                          IDE_SYMBOL_UI_MENU_ATTRIBUTE, NULL, 0, 0);
+          is_internal = TRUE;
+          state->build_state = BUILD_STATE_GET_CONTENT;
+        }
+    }
+  else if (ide_str_equal0 (name, "class") && ide_str_equal0 (parent_name, "style"))
+    {
+      value = get_attribute (attributes, "name", NULL);
+      node = ide_xml_symbol_node_new (value, NULL, "class",
+                                      IDE_SYMBOL_UI_STYLE_CLASS, NULL, 0, 0);
+      is_internal = TRUE;
+    }
+  else if (ide_str_equal0 (name, "child"))
+    {
+      g_string_append (string, "child");
+
+      if (NULL != (value = get_attribute (attributes, "type", NULL)))
+        {
+          label = ide_xml_parser_get_color_tag (self, "type", COLOR_TAG_TYPE, TRUE, TRUE, TRUE);
+          g_string_append (string, label);
+          g_string_append (string, value);
+        }
+
+      if (NULL != (value = get_attribute (attributes, "internal-child", NULL)))
+        {
+          label = ide_xml_parser_get_color_tag (self, "internal", COLOR_TAG_TYPE, TRUE, TRUE, TRUE);
+          g_string_append (string, label);
+          g_string_append (string, value);
+        }
+
+      node = ide_xml_symbol_node_new (string->str, NULL, "child",
+                                      IDE_SYMBOL_UI_CHILD, NULL, 0, 0);
+      g_object_set (node, "use-markup", TRUE, NULL);
+    }
+  else if (ide_str_equal0 (name, "object"))
+    {
+      value = get_attribute (attributes, "class", "?");
+      label = ide_xml_parser_get_color_tag (self, "class", COLOR_TAG_CLASS, TRUE, TRUE, TRUE);
+      g_string_append (string, label);
+      g_string_append (string, value);
+
+      if (NULL != (value = list_get_attribute (attributes, "id")))
+        {
+          g_free (label);
+          label = ide_xml_parser_get_color_tag (self, "id", COLOR_TAG_ID, TRUE, TRUE, TRUE);
+          g_string_append (string, label);
+          g_string_append (string, value);
+        }
+
+      node = ide_xml_symbol_node_new (string->str, NULL, "object",
+                                      IDE_SYMBOL_UI_OBJECT, NULL, 0, 0);
+      g_object_set (node, "use-markup", TRUE, NULL);
+    }
+  else if (ide_str_equal0 (name, "template"))
+    {
+      value = get_attribute (attributes, "class", "?");
+      label = ide_xml_parser_get_color_tag (self, "class", COLOR_TAG_CLASS, TRUE, TRUE, TRUE);
+      g_string_append (string, label);
+      g_string_append (string, value);
+      g_free (label);
+
+      value = get_attribute (attributes, "parent", "?");
+      label = ide_xml_parser_get_color_tag (self, "parent", COLOR_TAG_PARENT, TRUE, TRUE, TRUE);
+      g_string_append (string, label);
+      g_string_append (string, value);
+
+      node = ide_xml_symbol_node_new (string->str, NULL, (const gchar *)name,
+                                      IDE_SYMBOL_UI_TEMPLATE, NULL, 0, 0);
+      g_object_set (node, "use-markup", TRUE, NULL);
+    }
+  else if (ide_str_equal0 (name, "packing"))
+    {
+      node = ide_xml_symbol_node_new ("packing", NULL, "packing",
+                                      IDE_SYMBOL_UI_PACKING, NULL, 0, 0);
+    }
+  else if (ide_str_equal0 (name, "style"))
+    {
+      node = ide_xml_symbol_node_new ("style", NULL, "style",
+                                      IDE_SYMBOL_UI_STYLE, NULL, 0, 0);
+    }
+  else if (ide_str_equal0 (name, "menu"))
+    {
+      value = get_attribute (attributes, "id", "?");
+      label = ide_xml_parser_get_color_tag (self, "id", COLOR_TAG_ID, TRUE, TRUE, TRUE);
+      g_string_append (string, label);
+      g_string_append (string, value);
+
+      node = ide_xml_symbol_node_new (string->str, NULL, "menu",
+                                      IDE_SYMBOL_UI_MENU, NULL, 0, 0);
+      g_object_set (node, "use-markup", TRUE, NULL);
+    }
+  else if (ide_str_equal0 (name, "submenu"))
+    {
+      value = get_attribute (attributes, "id", "?");
+      label = ide_xml_parser_get_color_tag (self, "id", COLOR_TAG_ID, TRUE, TRUE, TRUE);
+      g_string_append (string, label);
+      g_string_append (string, value);
+
+      node = ide_xml_symbol_node_new (string->str, NULL, "submenu",
+                                      IDE_SYMBOL_UI_SUBMENU, NULL, 0, 0);
+      g_object_set (node, "use-markup", TRUE, NULL);
+    }
+  else if (ide_str_equal0 (name, "section"))
+    {
+      value = get_attribute (attributes, "id", "?");
+      label = ide_xml_parser_get_color_tag (self, "id", COLOR_TAG_ID, TRUE, TRUE, TRUE);
+      g_string_append (string, label);
+      g_string_append (string, value);
+
+      node = ide_xml_symbol_node_new (string->str, NULL, "section",
+                                      IDE_SYMBOL_UI_SECTION, NULL, 0, 0);
+      g_object_set (node, "use-markup", TRUE, NULL);
+    }
+  else if (ide_str_equal0 (name, "item"))
+    {
+      node = ide_xml_symbol_node_new ("item", NULL, "item",
+                                      IDE_SYMBOL_UI_ITEM, NULL, 0, 0);
+    }
+
+  ide_xml_parser_state_processing (self, state, (const gchar *)name, node, 
IDE_XML_SAX_CALLBACK_TYPE_START_ELEMENT, is_internal);
+}
+
+static const gchar *
+get_menu_attribute_value (IdeXmlSymbolNode *node,
+                          const gchar      *name)
+{
+  IdeXmlSymbolNode *child;
+  gint n_children;
+
+  g_assert (IDE_IS_XML_SYMBOL_NODE (node));
+
+  n_children = ide_xml_symbol_node_get_n_internal_children (node);
+  for (gint i = 0; i < n_children; ++i)
+    {
+      child = IDE_XML_SYMBOL_NODE (ide_xml_symbol_node_get_nth_internal_child (node, i));
+      if (ide_symbol_node_get_kind (IDE_SYMBOL_NODE (child)) == IDE_SYMBOL_UI_MENU_ATTRIBUTE &&
+          ide_str_equal0 (ide_symbol_node_get_name (IDE_SYMBOL_NODE (child)), name))
+        {
+          return ide_xml_symbol_node_get_value (child);
+        }
+    }
+
+  return NULL;
+}
+
+static void
+node_post_processing_collect_style_classes (IdeXmlParser      *self,
+                                            IdeXmlSymbolNode  *node)
+{
+  IdeXmlSymbolNode *child;
+  g_autoptr(GString) label = NULL;
+  gint n_children;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+  g_assert (IDE_IS_XML_SYMBOL_NODE (node));
+
+  label = g_string_new (NULL);
+  n_children = ide_xml_symbol_node_get_n_internal_children (node);
+  for (gint i = 0; i < n_children; ++i)
+    {
+      g_autofree gchar *class_tag = NULL;
+
+      child = IDE_XML_SYMBOL_NODE (ide_xml_symbol_node_get_nth_internal_child (node, i));
+      if (ide_symbol_node_get_kind (IDE_SYMBOL_NODE (child)) == IDE_SYMBOL_UI_STYLE_CLASS)
+        {
+          class_tag = ide_xml_parser_get_color_tag (self, ide_symbol_node_get_name (IDE_SYMBOL_NODE (child)),
+                                                    COLOR_TAG_STYLE_CLASS, TRUE, TRUE, TRUE);
+          g_string_append (label, class_tag);
+          g_string_append (label, " ");
+        }
+    }
+
+  g_object_set (node,
+                "name", label->str,
+                "use-markup", TRUE,
+                NULL);
+}
+
+static void
+node_post_processing_add_label (IdeXmlParser      *self,
+                                IdeXmlSymbolNode  *node)
+{
+  const gchar *value;
+  g_autoptr(GString) name = NULL;
+  g_autofree gchar *label = NULL;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+  g_assert (IDE_IS_XML_SYMBOL_NODE (node));
+
+  if (NULL != (value = get_menu_attribute_value (node, "label")))
+    {
+      g_object_get (node, "name", &label, NULL);
+      name = g_string_new (label);
+      g_free (label);
+
+      label = ide_xml_parser_get_color_tag (self, "label", COLOR_TAG_LABEL, TRUE, TRUE, TRUE);
+
+      g_string_append (name, label);
+      g_string_append (name, value);
+      g_object_set (node,
+                    "name", name->str,
+                    "use-markup", TRUE,
+                    NULL);
+    }
+}
+
+static gboolean
+ide_xml_parser_ui_post_processing (IdeXmlParser      *self,
+                                   IdeXmlSymbolNode  *root_node)
+{
+  g_autoptr(GPtrArray) ar = NULL;
+  IdeXmlSymbolNode *node;
+  const gchar *element_name;
+  gint n_children;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+  g_assert (IDE_IS_XML_SYMBOL_NODE (root_node));
+
+  ar = g_ptr_array_new ();
+  g_ptr_array_add (ar, root_node);
+
+  while (ar->len > 0)
+    {
+      node = g_ptr_array_remove_index (ar, ar->len - 1);
+
+      n_children = ide_xml_symbol_node_get_n_children (node);
+      for (gint i = 0; i < n_children; ++i)
+        g_ptr_array_add (ar, ide_xml_symbol_node_get_nth_child (node, i));
+
+      element_name = ide_xml_symbol_node_get_element_name (node);
+
+      if (ide_str_equal0 (element_name, "style"))
+        node_post_processing_collect_style_classes (self, node);
+      else if (ide_str_equal0 (element_name, "item") ||
+               ide_str_equal0 (element_name, "submenu") ||
+               ide_str_equal0 (element_name, "section"))
+        node_post_processing_add_label (self, node);
+    }
+
+  return TRUE;
+}
+
+void
+ide_xml_parser_ui_setup (IdeXmlParser *self,
+                         ParserState  *state)
+{
+  g_assert (IDE_IS_XML_PARSER (self));
+  g_assert (state != NULL);
+
+  ide_xml_sax_clear (self->sax_parser);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_START_ELEMENT, 
ide_xml_parser_ui_start_element_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_END_ELEMENT, 
ide_xml_parser_end_element_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_CHAR, 
ide_xml_parser_characters_sax_cb);
+
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_INTERNAL_SUBSET, 
ide_xml_parser_internal_subset_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_EXTERNAL_SUBSET, 
ide_xml_parser_external_subset_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_PROCESSING_INSTRUCTION, 
ide_xml_parser_processing_instruction_sax_cb);
+
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_WARNING, 
ide_xml_parser_warning_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_ERROR, ide_xml_parser_error_sax_cb);
+  ide_xml_sax_set_callback (self->sax_parser, IDE_XML_SAX_CALLBACK_TYPE_FATAL_ERROR, 
ide_xml_parser_fatal_error_sax_cb);
+
+  ide_xml_parser_set_post_processing_callback (self, ide_xml_parser_ui_post_processing);
+}
diff --git a/plugins/xml-pack/ide-xml-tree-builder-generic.h b/plugins/xml-pack/ide-xml-parser-ui.h
similarity index 52%
rename from plugins/xml-pack/ide-xml-tree-builder-generic.h
rename to plugins/xml-pack/ide-xml-parser-ui.h
index c30bf27..5ece987 100644
--- a/plugins/xml-pack/ide-xml-tree-builder-generic.h
+++ b/plugins/xml-pack/ide-xml-parser-ui.h
@@ -1,4 +1,4 @@
-/* ide-xml-tree-builder-generic.h
+/* ide-xml-parser-ui.h
  *
  * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
  *
@@ -16,25 +16,18 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef IDE_XML_TREE_BUILDER_GENERIC_H
-#define IDE_XML_TREE_BUILDER_GENERIC_H
+#ifndef IDE_XML_PARSER_UI_H
+#define IDE_XML_PARSER_UI_H
 
 #include <glib.h>
 
-#include "ide-xml-analysis.h"
-#include "ide-xml-sax.h"
-#include "ide-xml-symbol-node.h"
-#include "ide-xml-tree-builder.h"
-#include "xml-reader.h"
+#include "ide-xml-parser-private.h"
 
 G_BEGIN_DECLS
 
-IdeXmlAnalysis *ide_xml_tree_builder_generic_create (IdeXmlTreeBuilder *self,
-                                                     IdeXmlSax         *parser,
-                                                     GFile             *file,
-                                                     const gchar       *data,
-                                                     gsize              size);
+void      ide_xml_parser_ui_setup       (IdeXmlParser *self,
+                                         ParserState  *state);
 
 G_END_DECLS
 
-#endif /* IDE_XML_TREE_BUILDER_GENERIC_H */
+#endif /* IDE_XML_PARSER_UI_H */
diff --git a/plugins/xml-pack/ide-xml-parser.c b/plugins/xml-pack/ide-xml-parser.c
new file mode 100644
index 0000000..e66c9e1
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-parser.c
@@ -0,0 +1,671 @@
+/* ide-xml-parser.c
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "ide-xml-parser.h"
+#include "ide-xml-parser-generic.h"
+#include "ide-xml-parser-ui.h"
+#include "ide-xml-parser-private.h"
+#include "ide-xml-sax.h"
+#include "ide-xml-stack.h"
+#include "ide-xml-tree-builder-utils-private.h"
+#include "ide-xml-validator.h"
+
+typedef struct _ColorTag
+{
+  gchar *name;
+  gchar *fg;
+  gchar *bg;
+} ColorTag;
+
+G_DEFINE_TYPE (IdeXmlParser, ide_xml_parser, IDE_TYPE_OBJECT)
+
+static void
+color_tag_free (gpointer *data)
+{
+  ColorTag *tag = (ColorTag *)data;
+
+  g_free (tag->name);
+  g_free (tag->fg);
+  g_free (tag->bg);
+}
+
+/* Keep it in sync with ColorTagId enum */
+static ColorTag default_color_tags [] =
+{
+  { "label",       "#000000", "#D5E7FC" }, // COLOR_TAG_LABEL
+  { "id",          "#000000", "#D9E7BD" }, // COLOR_TAG_ID
+  { "style-class", "#000000", "#DFCD9B" }, // COLOR_TAG_STYLE_CLASS
+  { "type",        "#000000", "#F4DAC3" }, // COLOR_TAG_TYPE
+  { "parent",      "#000000", "#DEBECF" }, // COLOR_TAG_PARENT
+  { "class",       "#000000", "#FFEF98" }, // COLOR_TAG_CLASS
+  { "attribute",   "#000000", "#F0E68C" }, // COLOR_TAG_ATTRIBUTE
+  { NULL },
+};
+
+static void
+parser_state_free (ParserState *state)
+{
+  g_clear_pointer (&state->analysis, ide_xml_analysis_unref);
+  g_clear_pointer (&state->diagnostics_array, g_ptr_array_unref);
+  g_clear_object (&state->file);
+  g_clear_object (&state->root_node);
+
+  g_clear_pointer (&state->content, g_bytes_unref);
+  g_clear_pointer (&state->schemas, g_array_unref);
+}
+
+static gboolean
+ide_xml_parser_file_is_ui (GFile       *file,
+                           const gchar *data,
+                           gsize        size)
+{
+  g_autofree gchar *path = NULL;
+  g_autofree gchar *buffer = NULL;
+  gsize buffer_size;
+
+  g_assert (G_IS_FILE (file));
+  g_assert (data != NULL);
+  g_assert (size > 0);
+
+  path = g_file_get_path (file);
+  if (g_str_has_suffix (path, ".ui") || g_str_has_suffix (path, ".glade"))
+    {
+      buffer_size = (size < 256) ? size : 256;
+      buffer = g_strndup (data, buffer_size);
+      if (NULL != (strstr (buffer, "<interface>")))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+IdeDiagnostic *
+ide_xml_parser_create_diagnostic (ParserState            *state,
+                                  const gchar            *msg,
+                                  IdeDiagnosticSeverity   severity)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  IdeContext *context;
+  IdeDiagnostic *diagnostic;
+  g_autoptr(IdeSourceLocation) loc = NULL;
+  g_autoptr(IdeFile) ifile = NULL;
+  gint line;
+  gint line_offset;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  ide_xml_sax_get_position (self->sax_parser, &line, &line_offset);
+  ifile = ide_file_new (context, state->file);
+  loc = ide_source_location_new (ifile,
+                                 line - 1,
+                                 line_offset - 1,
+                                 0);
+
+  diagnostic = ide_diagnostic_new (severity, msg, loc);
+
+  return diagnostic;
+}
+
+void
+ide_xml_parser_state_processing (IdeXmlParser          *self,
+                                 ParserState           *state,
+                                 const gchar           *element_name,
+                                 IdeXmlSymbolNode      *node,
+                                 IdeXmlSaxCallbackType  callback_type,
+                                 gboolean               is_internal)
+{
+  IdeXmlSymbolNode *parent_node;
+  G_GNUC_UNUSED IdeXmlSymbolNode *popped_node;
+  g_autofree gchar *popped_element_name = NULL;
+  gint line;
+  gint line_offset;
+  gint depth;
+
+  g_assert (IDE_IS_XML_SYMBOL_NODE (node) || node == NULL);
+
+  if (callback_type == IDE_XML_SAX_CALLBACK_TYPE_CHAR)
+    {
+      ide_xml_symbol_node_set_value (state->current_node, element_name);
+      return;
+    }
+
+  depth = ide_xml_sax_get_depth (self->sax_parser);
+
+  if (node == NULL)
+    {
+      if (callback_type == IDE_XML_SAX_CALLBACK_TYPE_START_ELEMENT)
+        ide_xml_stack_push (self->stack, element_name, NULL, state->parent_node, depth);
+      else if (callback_type == IDE_XML_SAX_CALLBACK_TYPE_END_ELEMENT)
+        {
+          /* TODO: compare current with popped */
+          if (ide_xml_stack_is_empty (self->stack))
+            {
+              g_warning ("Xml nodes stack empty\n");
+              return;
+            }
+
+          popped_node = ide_xml_stack_pop (self->stack, &popped_element_name, &parent_node, &depth);
+          state->parent_node = parent_node;
+          g_assert (state->parent_node != NULL);
+        }
+
+      state->current_depth = depth;
+      state->current_node = NULL;
+      return;
+    }
+
+  ide_xml_sax_get_position (self->sax_parser, &line, &line_offset);
+  ide_xml_symbol_node_set_location (node, g_object_ref (state->file), line, line_offset);
+
+  /* TODO: take end elements into account and use:
+   * || ABS (depth - current_depth) > 1
+   */
+  if (depth < 0)
+    {
+      g_warning ("Wrong xml element depth, current:%i new:%i\n", state->current_depth, depth);
+      return;
+    }
+
+  if (callback_type == IDE_XML_SAX_CALLBACK_TYPE_START_ELEMENT)
+    {
+      ide_xml_stack_push (self->stack, element_name, node, state->parent_node, depth);
+      if (is_internal)
+        ide_xml_symbol_node_take_internal_child (state->parent_node, node);
+      else
+        ide_xml_symbol_node_take_child (state->parent_node, node);
+
+      state->parent_node = node;
+    }
+  else if (callback_type == IDE_XML_SAX_CALLBACK_TYPE_END_ELEMENT)
+    {
+      /* TODO: compare current with popped */
+      if (ide_xml_stack_is_empty (self->stack))
+        {
+          g_warning ("Xml nodes stack empty\n");
+          return;
+        }
+
+      popped_node = ide_xml_stack_pop (self->stack, &popped_element_name, &parent_node, &depth);
+      state->parent_node = parent_node;
+      g_assert (state->parent_node != NULL);
+    }
+  else
+    ide_xml_symbol_node_take_child (state->parent_node, node);
+
+  state->current_depth = depth;
+  state->current_node = node;
+}
+
+void
+ide_xml_parser_end_element_sax_cb (ParserState    *state,
+                                   const xmlChar  *name)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  ide_xml_parser_state_processing (self, state, (const gchar *)name, NULL, 
IDE_XML_SAX_CALLBACK_TYPE_END_ELEMENT, FALSE);
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+
+void
+ide_xml_parser_warning_sax_cb (ParserState    *state,
+                               const xmlChar  *name,
+                               ...)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  IdeDiagnostic *diagnostic;
+  g_autofree gchar *msg = NULL;
+  va_list var_args;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  va_start (var_args, name);
+  msg = g_strdup_vprintf ((const gchar *)name, var_args);
+  va_end (var_args);
+
+  diagnostic = ide_xml_parser_create_diagnostic (state, msg, IDE_DIAGNOSTIC_WARNING);
+  g_ptr_array_add (state->diagnostics_array, diagnostic);
+}
+
+void
+ide_xml_parser_error_sax_cb (ParserState    *state,
+                             const xmlChar  *name,
+                             ...)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  IdeDiagnostic *diagnostic;
+  g_autofree gchar *msg = NULL;
+  va_list var_args;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  va_start (var_args, name);
+  msg = g_strdup_vprintf ((const gchar *)name, var_args);
+  va_end (var_args);
+
+  diagnostic = ide_xml_parser_create_diagnostic (state, msg, IDE_DIAGNOSTIC_ERROR);
+  g_ptr_array_add (state->diagnostics_array, diagnostic);
+}
+
+void
+ide_xml_parser_fatal_error_sax_cb (ParserState    *state,
+                                   const xmlChar  *name,
+                                   ...)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  IdeDiagnostic *diagnostic;
+  g_autofree gchar *msg = NULL;
+  va_list var_args;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  va_start (var_args, name);
+  msg = g_strdup_vprintf ((const gchar *)name, var_args);
+  va_end (var_args);
+
+  diagnostic = ide_xml_parser_create_diagnostic (state, msg, IDE_DIAGNOSTIC_FATAL);
+  g_ptr_array_add (state->diagnostics_array, diagnostic);
+}
+
+#pragma GCC diagnostic pop
+
+void
+ide_xml_parser_internal_subset_sax_cb (ParserState   *state,
+                                       const xmlChar *name,
+                                       const xmlChar *external_id,
+                                       const xmlChar *system_id)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  SchemaEntry entry = {0};
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  printf ("internal subset:%s external_id:%s system_id:%s\n", name, external_id, system_id);
+
+  entry.schema_kind = SCHEMA_KIND_DTD;
+  ide_xml_sax_get_position (self->sax_parser, &entry.schema_line, &entry.schema_col);
+  g_array_append_val (state->schemas, entry);
+}
+
+void
+ide_xml_parser_external_subset_sax_cb (ParserState   *state,
+                                       const xmlChar *name,
+                                       const xmlChar *external_id,
+                                       const xmlChar *system_id)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  printf ("external subset:%s external_id:%s system_id:%s\n", name, external_id, system_id);
+}
+
+static GFile *
+get_absolute_schema_file (GFile       *file,
+                          const gchar *schema_url)
+{
+  g_autoptr (GFile) parent = NULL;
+  GFile *abs_file = NULL;
+  g_autofree gchar *scheme = NULL;
+
+  abs_file = g_file_new_for_uri (schema_url);
+  scheme = g_file_get_uri_scheme (abs_file);
+  if (scheme == NULL)
+    {
+      parent = g_file_get_parent (file);
+      printf ("parent:%s\n", g_file_get_uri (parent));
+      if (NULL == (abs_file = g_file_resolve_relative_path (parent, schema_url)))
+        abs_file = g_file_new_for_path (schema_url);
+    }
+
+  printf ("url:%s file url:%s\n", schema_url, (abs_file) ? g_file_get_uri (abs_file) : NULL);
+  return abs_file;
+}
+
+void
+ide_xml_parser_processing_instruction_sax_cb (ParserState   *state,
+                                              const xmlChar *target,
+                                              const xmlChar *data)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  IdeDiagnostic *diagnostic;
+  g_autofree gchar *schema_url = NULL;
+  const gchar *extension;
+  SchemaEntry entry = {0};
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  if (NULL != (schema_url = get_schema_url ((const gchar *)data)))
+    {
+      if (NULL != (extension = strrchr (schema_url, '.')))
+        {
+          ++extension;
+          if (ide_str_equal0 (extension, "rng"))
+            entry.schema_kind = SCHEMA_KIND_RNG;
+          else if (ide_str_equal0 (extension, "xsd"))
+            entry.schema_kind = SCHEMA_KIND_XML_SCHEMA;
+          else
+            goto fail;
+
+          ide_xml_sax_get_position (self->sax_parser, &entry.schema_line, &entry.schema_col);
+          entry.schema_file = get_absolute_schema_file (state->file, schema_url);
+          g_array_append_val (state->schemas, entry);
+
+          return;
+        }
+fail:
+      diagnostic = ide_xml_parser_create_diagnostic (state,
+                                                     "Schema type not supported",
+                                                     IDE_DIAGNOSTIC_WARNING);
+      g_ptr_array_add (state->diagnostics_array, diagnostic);
+    }
+}
+
+void
+ide_xml_parser_characters_sax_cb (ParserState    *state,
+                                  const xmlChar  *name,
+                                  gint            len)
+{
+  IdeXmlParser *self = (IdeXmlParser *)state->self;
+  g_autofree gchar *element_value = NULL;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  if (state->build_state != BUILD_STATE_GET_CONTENT)
+    return;
+
+  element_value = g_strndup ((gchar *)name, len);
+  state->build_state = BUILD_STATE_NORMAL;
+
+  ide_xml_parser_state_processing (self, state, element_value, NULL, IDE_XML_SAX_CALLBACK_TYPE_CHAR, FALSE);
+}
+
+static void
+ide_xml_parser_get_analysis_worker (GTask        *task,
+                                    gpointer      source_object,
+                                    gpointer      task_data,
+                                    GCancellable *cancellable)
+{
+  IdeXmlParser *self = (IdeXmlParser *)source_object;
+  ParserState *state = (ParserState *)task_data;
+  IdeXmlAnalysis *analysis;
+  g_autoptr(IdeDiagnostics) diagnostics = NULL;
+  g_autofree gchar *uri = NULL;
+  const gchar *doc_data;
+  gsize doc_size;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+  g_assert (G_IS_TASK (task));
+  g_assert (state != NULL);
+  g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+  if (g_task_return_error_if_cancelled (task))
+    return;
+
+  doc_data = g_bytes_get_data (state->content, &doc_size);
+
+  if (ide_xml_parser_file_is_ui (state->file, doc_data, doc_size))
+    ide_xml_parser_ui_setup (self, state);
+  else
+    ide_xml_parser_generic_setup (self, state);
+
+  uri = g_file_get_uri (state->file);
+  ide_xml_sax_parse (self->sax_parser, doc_data, doc_size, uri, state);
+
+  if (self->post_processing_callback != NULL)
+    (self->post_processing_callback)(self, state->root_node);
+
+  analysis = g_steal_pointer (&state->analysis);
+  if (analysis == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               _("Failed to create the XML tree."));
+      return;
+    }
+
+  diagnostics = ide_diagnostics_new (g_steal_pointer (&state->diagnostics_array));
+  ide_xml_analysis_set_diagnostics (analysis, diagnostics);
+
+  if (state->schemas != NULL && state->schemas->len > 0)
+    ide_xml_analysis_set_schemas (analysis, g_steal_pointer (&state->schemas));
+
+  ide_xml_analysis_set_sequence (analysis, state->sequence);
+  g_task_return_pointer (task, analysis, (GDestroyNotify)ide_xml_analysis_unref);
+}
+
+static void
+schemas_free (gpointer *data)
+{
+  SchemaEntry *entry = (SchemaEntry *)data;
+
+  g_clear_object (&entry->schema_file);
+  g_clear_pointer (&entry->schema_content, g_bytes_unref);
+  g_clear_pointer (&entry->error_message, g_free);
+}
+
+void
+ide_xml_parser_get_analysis_async (IdeXmlParser        *self,
+                                   GFile               *file,
+                                   GBytes              *content,
+                                   gint64               sequence,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  ParserState *state;
+
+  g_return_if_fail (IDE_IS_XML_PARSER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_xml_parser_get_analysis_async);
+
+  state = g_slice_new0 (ParserState);
+  state->self = self;
+  state->file = g_object_ref (file);
+  state->content = g_bytes_ref (content);
+  state->sequence = sequence;
+  state->diagnostics_array = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_diagnostic_unref);
+  state->schemas = g_array_new (TRUE, TRUE, sizeof (SchemaEntry));
+  g_array_set_clear_func (state->schemas, (GDestroyNotify)schemas_free);
+
+  state->build_state = BUILD_STATE_NORMAL;
+
+  state->analysis = ide_xml_analysis_new (-1);
+  state->root_node = ide_xml_symbol_node_new ("root", NULL, "root", IDE_SYMBOL_NONE, NULL, 0, 0);
+  ide_xml_analysis_set_root_node (state->analysis, state->root_node);
+
+  state->parent_node = state->root_node;
+  ide_xml_stack_push (self->stack, "root", state->root_node, NULL, 0);
+
+  g_task_set_task_data (task, state, (GDestroyNotify)parser_state_free);
+  g_task_run_in_thread (task, ide_xml_parser_get_analysis_worker);
+}
+
+IdeXmlAnalysis *
+ide_xml_parser_get_analysis_finish (IdeXmlParser  *self,
+                                    GAsyncResult  *result,
+                                    GError       **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_XML_PARSER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (task), NULL);
+
+  return g_task_propagate_pointer (task, error);
+}
+
+gchar *
+ide_xml_parser_get_color_tag (IdeXmlParser *self,
+                              const gchar  *str,
+                              ColorTagId    id,
+                              gboolean      space_before,
+                              gboolean      space_after,
+                              gboolean      space_inside)
+{
+  ColorTag *tag;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+  g_assert (self->color_tags != NULL);
+  g_assert (!ide_str_empty0 (str));
+
+  tag = &g_array_index (self->color_tags, ColorTag, id);
+  return g_strdup_printf ("%s<span foreground=\"%s\" background=\"%s\">%s%s%s</span>%s",
+                          space_before ? " " : "",
+                          tag->fg,
+                          tag->bg,
+                          space_inside ? " " : "",
+                          str,
+                          space_inside ? " " : "",
+                          space_after ? " " : "");
+}
+
+static void
+init_color_tags (IdeXmlParser *self)
+{
+  g_autofree gchar *scheme_name = NULL;
+  GtkSourceStyleSchemeManager *manager;
+  GtkSourceStyleScheme *scheme;
+  gchar *tag_name;
+  GtkSourceStyle *style;
+  gchar *foreground;
+  gchar *background;
+  ColorTag tag;
+  ColorTag *tag_ptr;
+  gboolean tag_set;
+
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  scheme_name = g_settings_get_string (self->settings, "style-scheme-name");
+  manager = gtk_source_style_scheme_manager_get_default ();
+  scheme = gtk_source_style_scheme_manager_get_scheme (manager, scheme_name);
+
+  g_array_remove_range (self->color_tags, 0, self->color_tags->len);
+  tag_ptr = default_color_tags;
+  while (tag_ptr->fg != NULL)
+    {
+      tag_set = FALSE;
+      if (scheme != NULL)
+        {
+          tag_name = g_strconcat ("symboltree::", tag_ptr->name, NULL);
+          if (NULL != (style = gtk_source_style_scheme_get_style (scheme, tag_name)))
+            {
+              g_object_get (style, "foreground", &foreground, NULL);
+              g_object_get (style, "background", &background, NULL);
+              if (foreground != NULL && background != NULL)
+                {
+                  tag_set = TRUE;
+                  tag.name = g_strdup (tag_ptr->name);
+                  tag.fg = g_steal_pointer (&foreground);
+                  tag.bg = g_steal_pointer (&background);
+                }
+
+              g_free (foreground);
+              g_free (background);
+            }
+
+          g_free (tag_name);
+        }
+
+      if (!tag_set)
+        {
+          tag.name = g_strdup (tag_ptr->name);
+          tag.fg = g_strdup (tag_ptr->fg);
+          tag.bg = g_strdup (tag_ptr->bg);
+        }
+
+      g_array_append_val (self->color_tags, tag);
+      ++tag_ptr;
+    }
+}
+
+void
+ide_xml_parser_set_post_processing_callback (IdeXmlParser           *self,
+                                             PostProcessingCallback  callback)
+{
+  g_return_if_fail (IDE_IS_XML_PARSER (self));
+
+  self->post_processing_callback = callback;
+}
+
+static void
+editor_settings_changed_cb (IdeXmlParser *self,
+                            gchar        *key,
+                            GSettings    *settings)
+{
+  g_assert (IDE_IS_XML_PARSER (self));
+
+  if (ide_str_equal0 (key, "style-scheme-name"))
+    init_color_tags (self);
+}
+
+IdeXmlParser *
+ide_xml_parser_new (void)
+{
+  return g_object_new (IDE_TYPE_XML_PARSER, NULL);
+}
+
+static void
+ide_xml_parser_finalize (GObject *object)
+{
+  IdeXmlParser *self = (IdeXmlParser *)object;
+
+  g_clear_object (&self->sax_parser);
+  g_clear_object (&self->stack);
+
+  g_clear_pointer (&self->color_tags, g_array_unref);
+  g_clear_object (&self->settings);
+
+  G_OBJECT_CLASS (ide_xml_parser_parent_class)->finalize (object);
+}
+
+static void
+ide_xml_parser_class_init (IdeXmlParserClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_xml_parser_finalize;
+}
+
+static void
+ide_xml_parser_init (IdeXmlParser *self)
+{
+  self->sax_parser = ide_xml_sax_new ();
+  self->stack = ide_xml_stack_new ();
+
+  self->color_tags = g_array_new (TRUE, TRUE, sizeof (ColorTag));
+  g_array_set_clear_func (self->color_tags, (GDestroyNotify)color_tag_free);
+
+  self->settings = g_settings_new ("org.gnome.builder.editor");
+  g_signal_connect_swapped (self->settings,
+                            "changed",
+                            G_CALLBACK (editor_settings_changed_cb),
+                            self);
+
+  init_color_tags (self);
+}
diff --git a/plugins/xml-pack/ide-xml-parser.h b/plugins/xml-pack/ide-xml-parser.h
new file mode 100644
index 0000000..cdecd14
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-parser.h
@@ -0,0 +1,46 @@
+/* ide-xml-parser.h
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_XML_PARSER_H
+#define IDE_XML_PARSER_H
+
+#include <glib-object.h>
+
+#include "ide-xml-analysis.h"
+G_BEGIN_DECLS
+
+#define IDE_TYPE_XML_PARSER (ide_xml_parser_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeXmlParser, ide_xml_parser, IDE, XML_PARSER, IdeObject)
+
+IdeXmlParser      *ide_xml_parser_new                   (void);
+void               ide_xml_parser_get_analysis_async    (IdeXmlParser         *self,
+                                                         GFile                *file,
+                                                         GBytes               *content,
+                                                         gint64                sequence,
+                                                         GCancellable         *cancellable,
+                                                         GAsyncReadyCallback   callback,
+                                                         gpointer              user_data);
+IdeXmlAnalysis    *ide_xml_parser_get_analysis_finish   (IdeXmlParser         *self,
+                                                         GAsyncResult         *result,
+                                                         GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_XML_PARSER_H */
+
diff --git a/plugins/xml-pack/ide-xml-sax.c b/plugins/xml-pack/ide-xml-sax.c
index 916205d..d162fdc 100644
--- a/plugins/xml-pack/ide-xml-sax.c
+++ b/plugins/xml-pack/ide-xml-sax.c
@@ -111,6 +111,18 @@ ide_xml_sax_set_callback (IdeXmlSax             *self,
       handler->entityDecl = callback;
       break;
 
+    case IDE_XML_SAX_CALLBACK_TYPE_INTERNAL_SUBSET:
+      handler->internalSubset = callback;
+      break;
+
+    case IDE_XML_SAX_CALLBACK_TYPE_EXTERNAL_SUBSET:
+      handler->externalSubset = callback;
+      break;
+
+    case IDE_XML_SAX_CALLBACK_TYPE_PROCESSING_INSTRUCTION:
+      handler->processingInstruction = callback;
+      break;
+
     case IDE_XML_SAX_CALLBACK_TYPE_WARNING:
       handler->warning = callback;
       break;
diff --git a/plugins/xml-pack/ide-xml-sax.h b/plugins/xml-pack/ide-xml-sax.h
index cb08cdf..c602c08 100644
--- a/plugins/xml-pack/ide-xml-sax.h
+++ b/plugins/xml-pack/ide-xml-sax.h
@@ -39,25 +39,28 @@ enum _IdeXmlSaxCallbackType {
   IDE_XML_SAX_CALLBACK_TYPE_END_DOCUMENT,
   IDE_XML_SAX_CALLBACK_TYPE_END_ELEMENT,
   IDE_XML_SAX_CALLBACK_TYPE_ENTITY,
+  IDE_XML_SAX_CALLBACK_TYPE_INTERNAL_SUBSET,
+  IDE_XML_SAX_CALLBACK_TYPE_EXTERNAL_SUBSET,
+  IDE_XML_SAX_CALLBACK_TYPE_PROCESSING_INSTRUCTION,
   IDE_XML_SAX_CALLBACK_TYPE_WARNING,
   IDE_XML_SAX_CALLBACK_TYPE_ERROR,
   IDE_XML_SAX_CALLBACK_TYPE_FATAL_ERROR,
 };
 
-void            ide_xml_sax_clear             (IdeXmlSax              *self);
-gint            ide_xml_sax_get_depth         (IdeXmlSax              *self);
-gboolean        ide_xml_sax_get_position      (IdeXmlSax              *self,
-                                               gint                   *line,
-                                               gint                   *line_offset);
-IdeXmlSax      *ide_xml_sax_new               (void);
-gboolean        ide_xml_sax_parse             (IdeXmlSax              *self,
-                                               const gchar            *data,
-                                               gsize                   length,
-                                               const gchar            *uri,
-                                               gpointer                user_data);
-void            ide_xml_sax_set_callback      (IdeXmlSax              *self,
-                                               IdeXmlSaxCallbackType   callback_type,
-                                               gpointer                callback);
+void            ide_xml_sax_clear               (IdeXmlSax              *self);
+gint            ide_xml_sax_get_depth           (IdeXmlSax              *self);
+gboolean        ide_xml_sax_get_position        (IdeXmlSax              *self,
+                                                 gint                   *line,
+                                                 gint                   *line_offset);
+IdeXmlSax      *ide_xml_sax_new                 (void);
+gboolean        ide_xml_sax_parse               (IdeXmlSax              *self,
+                                                 const gchar            *data,
+                                                 gsize                   length,
+                                                 const gchar            *uri,
+                                                 gpointer                user_data);
+void            ide_xml_sax_set_callback        (IdeXmlSax              *self,
+                                                 IdeXmlSaxCallbackType   callback_type,
+                                                 gpointer                callback);
 
 G_END_DECLS
 
diff --git a/plugins/xml-pack/ide-xml-schema-cache-entry.c b/plugins/xml-pack/ide-xml-schema-cache-entry.c
new file mode 100644
index 0000000..3cd3a5e
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-schema-cache-entry.c
@@ -0,0 +1,103 @@
+/* ide-xml-schema-cache-entry.c
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-xml-schema-cache-entry.h"
+
+G_DEFINE_BOXED_TYPE (IdeXmlSchemaCacheEntry, ide_xml_schema_cache_entry, ide_xml_schema_cache_entry_ref, 
ide_xml_schema_cache_entry_unref)
+
+IdeXmlSchemaCacheEntry *
+ide_xml_schema_cache_entry_new (void)
+{
+  IdeXmlSchemaCacheEntry *self;
+
+  self = g_slice_new0 (IdeXmlSchemaCacheEntry);
+  self->ref_count = 1;
+
+  return self;
+}
+
+IdeXmlSchemaCacheEntry *
+ide_xml_schema_cache_entry_new_full (GBytes      *content,
+                                     const gchar *error_message)
+{
+  IdeXmlSchemaCacheEntry *self;
+
+  g_assert ((content != NULL && error_message == NULL) ||
+            (content == NULL && error_message != NULL));
+
+  self = ide_xml_schema_cache_entry_new ();
+
+  if (content != NULL)
+    self->content = g_bytes_ref (content);
+
+  if (error_message != NULL)
+    self->error_message = g_strdup (error_message);
+
+  return self;
+}
+
+IdeXmlSchemaCacheEntry *
+ide_xml_schema_cache_entry_copy (IdeXmlSchemaCacheEntry *self)
+{
+  IdeXmlSchemaCacheEntry *copy;
+
+  g_return_val_if_fail (self, NULL);
+  g_return_val_if_fail (self->ref_count, NULL);
+
+  copy = ide_xml_schema_cache_entry_new ();
+  if (self->content != NULL)
+    copy->content = g_bytes_ref (self->content);
+
+  if (self->error_message != NULL)
+    copy->error_message = g_strdup (self->error_message);
+
+  return copy;
+}
+
+static void
+ide_xml_schema_cache_entry_free (IdeXmlSchemaCacheEntry *self)
+{
+  g_assert (self);
+  g_assert_cmpint (self->ref_count, ==, 0);
+
+  g_clear_pointer (&self->content, g_bytes_unref);
+  g_clear_pointer (&self->error_message, g_free);
+
+  g_slice_free (IdeXmlSchemaCacheEntry, self);
+}
+
+IdeXmlSchemaCacheEntry *
+ide_xml_schema_cache_entry_ref (IdeXmlSchemaCacheEntry *self)
+{
+  g_return_val_if_fail (self, NULL);
+  g_return_val_if_fail (self->ref_count, NULL);
+
+  g_atomic_int_inc (&self->ref_count);
+
+  return self;
+}
+
+void
+ide_xml_schema_cache_entry_unref (IdeXmlSchemaCacheEntry *self)
+{
+  g_return_if_fail (self);
+  g_return_if_fail (self->ref_count);
+
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    ide_xml_schema_cache_entry_free (self);
+}
diff --git a/plugins/xml-pack/ide-xml-schema-cache-entry.h b/plugins/xml-pack/ide-xml-schema-cache-entry.h
new file mode 100644
index 0000000..93a52ca
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-schema-cache-entry.h
@@ -0,0 +1,50 @@
+/* ide-xml-schema-cache-entry.h
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_XML_SCHEMA_CACHE_ENTRY_H
+#define IDE_XML_SCHEMA_CACHE_ENTRY_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_XML_SCHEMA_CACHE_ENTRY (ide_xml_schema_cache_entry_get_type())
+
+typedef struct _IdeXmlSchemaCacheEntry IdeXmlSchemaCacheEntry;
+
+struct _IdeXmlSchemaCacheEntry
+{
+  guint ref_count;
+
+  GBytes *content;
+  gchar  *error_message;
+};
+
+IdeXmlSchemaCacheEntry     *ide_xml_schema_cache_entry_new           (void);
+IdeXmlSchemaCacheEntry     *ide_xml_schema_cache_entry_new_full      (GBytes                 *content,
+                                                                      const gchar            *error_message);
+IdeXmlSchemaCacheEntry     *ide_xml_schema_cache_entry_copy          (IdeXmlSchemaCacheEntry *self);
+IdeXmlSchemaCacheEntry     *ide_xml_schema_cache_entry_ref           (IdeXmlSchemaCacheEntry *self);
+void                        ide_xml_schema_cache_entry_unref         (IdeXmlSchemaCacheEntry *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeXmlSchemaCacheEntry, ide_xml_schema_cache_entry_unref)
+
+G_END_DECLS
+
+#endif /* IDE_XML_SCHEMA_CACHE_ENTRY_H */
+
diff --git a/plugins/xml-pack/ide-xml-service.c b/plugins/xml-pack/ide-xml-service.c
index d0c1301..02d6b57 100644
--- a/plugins/xml-pack/ide-xml-service.c
+++ b/plugins/xml-pack/ide-xml-service.c
@@ -18,12 +18,12 @@
 
 #define G_LOG_DOMAIN "ide-xml-service"
 
-#include <egg-task-cache.h>
 #include <glib/gi18n.h>
 #include <gtksourceview/gtksource.h>
 #include <math.h>
 
 #include "ide-xml-analysis.h"
+#include "ide-xml-schema-cache-entry.h"
 #include "ide-xml-tree-builder.h"
 
 #include "ide-xml-service.h"
@@ -37,6 +37,7 @@ struct _IdeXmlService
   IdeObject          parent_instance;
 
   EggTaskCache      *analyses;
+  EggTaskCache      *schemas;
   IdeXmlTreeBuilder *tree_builder;
   GCancellable      *cancellable;
 };
@@ -104,6 +105,56 @@ ide_xml_service_build_tree_cb (EggTaskCache  *cache,
 }
 
 static void
+ide_xml_service_load_schema_cb2 (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  GFile *file = (GFile *)object;
+  g_autoptr(GTask) task = user_data;
+  IdeXmlSchemaCacheEntry *cache_entry;
+  GError *error = NULL;
+  gchar *content;
+  gsize len;
+
+  g_assert (G_IS_FILE (file));
+  g_assert (G_IS_TASK (result));
+  g_assert (G_IS_TASK (task));
+
+  cache_entry = ide_xml_schema_cache_entry_new ();
+
+  if (!g_file_load_contents_finish (file, result, &content, &len, NULL, &error))
+    cache_entry->error_message = g_strdup (error->message);
+  else
+    cache_entry->content = g_bytes_new_take (content, len);
+
+  g_task_return_pointer (task, cache_entry, (GDestroyNotify)ide_xml_schema_cache_entry_unref);
+}
+
+static void
+ide_xml_service_load_schema_cb (EggTaskCache  *cache,
+                                gconstpointer  key,
+                                GTask         *task,
+                                gpointer       user_data)
+{
+  IdeXmlService *self = user_data;
+  GFile *file = (GFile *)key;
+
+  IDE_ENTRY;
+
+  g_assert (EGG_IS_TASK_CACHE (cache));
+  g_assert (IDE_IS_XML_SERVICE (self));
+  g_assert (G_IS_TASK (task));
+  g_assert (G_IS_FILE (file));
+
+  g_file_load_contents_async (file,
+                              g_task_get_cancellable (task),
+                              ide_xml_service_load_schema_cb2,
+                              g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static void
 ide_xml_service_get_analysis_cb (GObject      *object,
                                  GAsyncResult *result,
                                  gpointer      user_data)
@@ -496,6 +547,20 @@ ide_xml_service_start (IdeService *service)
                                        NULL);
 
   egg_task_cache_set_name (self->analyses, "xml analysis cache");
+
+  /* There's no eviction time on this cache */
+  self->schemas = egg_task_cache_new ((GHashFunc)g_file_hash,
+                                      (GEqualFunc)g_file_equal,
+                                      g_object_ref,
+                                      g_object_unref,
+                                      (GBoxedCopyFunc)ide_xml_schema_cache_entry_ref,
+                                      (GBoxedFreeFunc)ide_xml_schema_cache_entry_unref,
+                                      0,
+                                      ide_xml_service_load_schema_cb,
+                                      self,
+                                      NULL);
+
+  egg_task_cache_set_name (self->schemas, "xml schemas cache");
 }
 
 static void
@@ -510,6 +575,7 @@ ide_xml_service_stop (IdeService *service)
 
   g_clear_object (&self->cancellable);
   g_clear_object (&self->analyses);
+  g_clear_object (&self->schemas);
 }
 
 static void
@@ -606,3 +672,18 @@ ide_xml_service_get_cached_diagnostics (IdeXmlService *self,
 
   return NULL;
 }
+
+/**
+ * ide_xml_service_get_schemas_cache:
+ *
+ * Gets the #EggTaskCache for the xml schemas.
+ *
+ * Returns: (transfer NULL): A #EggTaskCache.
+ */
+EggTaskCache *
+ide_xml_service_get_schemas_cache (IdeXmlService *self)
+{
+  g_return_val_if_fail (IDE_IS_XML_SERVICE (self), NULL);
+
+  return self->schemas;
+}
diff --git a/plugins/xml-pack/ide-xml-service.h b/plugins/xml-pack/ide-xml-service.h
index 1897155..19309ba 100644
--- a/plugins/xml-pack/ide-xml-service.h
+++ b/plugins/xml-pack/ide-xml-service.h
@@ -20,6 +20,7 @@
 #define IDE_XML_SERVICE_H
 
 #include <gtksourceview/gtksource.h>
+#include <egg-task-cache.h>
 
 #include "ide-xml-symbol-node.h"
 #include <ide.h>
@@ -52,6 +53,7 @@ void                ide_xml_service_get_root_node_async       (IdeXmlService
 IdeXmlSymbolNode   *ide_xml_service_get_root_node_finish      (IdeXmlService        *self,
                                                                GAsyncResult         *result,
                                                                GError              **error);
+EggTaskCache       *ide_xml_service_get_schemas_cache         (IdeXmlService        *self);
 
 G_END_DECLS
 
diff --git a/plugins/xml-pack/ide-xml-tree-builder-utils-private.h 
b/plugins/xml-pack/ide-xml-tree-builder-utils-private.h
index 038f5a5..ba7ccce 100644
--- a/plugins/xml-pack/ide-xml-tree-builder-utils-private.h
+++ b/plugins/xml-pack/ide-xml-tree-builder-utils-private.h
@@ -22,9 +22,12 @@
 #include <ide.h>
 
 #include "ide-xml-symbol-node.h"
+#include "ide-xml-validator.h"
 
 G_BEGIN_DECLS
 
+const gchar  *get_schema_kind_string     (SchemaKind          kind);
+gchar        *get_schema_url             (const gchar        *data);
 const gchar  *list_get_attribute         (const guchar      **attributes,
                                           const gchar        *name);
 void          print_node                 (IdeXmlSymbolNode   *node,
diff --git a/plugins/xml-pack/ide-xml-tree-builder-utils.c b/plugins/xml-pack/ide-xml-tree-builder-utils.c
index 6298045..640873e 100644
--- a/plugins/xml-pack/ide-xml-tree-builder-utils.c
+++ b/plugins/xml-pack/ide-xml-tree-builder-utils.c
@@ -16,8 +16,12 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <string.h>
+
 #include "ide-xml-tree-builder-utils-private.h"
 
+#define HREF_LEN 6
+
 void
 print_node (IdeXmlSymbolNode *node,
             guint             depth)
@@ -67,3 +71,42 @@ list_get_attribute (const guchar **attributes,
 
   return NULL;
 }
+
+gchar *
+get_schema_url (const gchar *data)
+{
+  gchar *begin;
+  gchar *end;
+
+  if (NULL != (begin = strstr (data, "href=\"")))
+    {
+      end = begin += HREF_LEN;
+      while (end != NULL)
+        {
+          if (NULL != (end = strchr (begin, '"')))
+            {
+              if (*(end - 1) != '\\')
+                return g_strndup (begin, end - begin);
+            }
+        }
+    }
+
+  return NULL;
+}
+
+const gchar *
+get_schema_kind_string (SchemaKind kind)
+{
+  if (kind == SCHEMA_KIND_NONE)
+    return "No schema";
+  else if (kind == SCHEMA_KIND_DTD)
+    return "DTD schema (.dtd or internal)";
+  else if (kind == SCHEMA_KIND_RNG)
+    return "RNG schema (.rng)";
+  else if (kind == SCHEMA_KIND_XML_SCHEMA)
+    return "XML schema (.xsd)";
+
+  g_assert_not_reached ();
+}
+
+
diff --git a/plugins/xml-pack/ide-xml-tree-builder.c b/plugins/xml-pack/ide-xml-tree-builder.c
index 508a9db..f20719a 100644
--- a/plugins/xml-pack/ide-xml-tree-builder.c
+++ b/plugins/xml-pack/ide-xml-tree-builder.c
@@ -16,71 +16,69 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <egg-task-cache.h>
 #include <glib/gi18n.h>
+#include <libxml/parser.h>
 #include <string.h>
 
+#include <egg-task-cache.h>
+
+#include "ide-xml-parser.h"
 #include "ide-xml-sax.h"
-#include "ide-xml-tree-builder-generic.h"
-#include "ide-xml-tree-builder-ui.h"
+#include "ide-xml-service.h"
+#include "ide-xml-schema-cache-entry.h"
+#include "ide-xml-tree-builder-utils-private.h"
+#include "ide-xml-validator.h"
 
 #include "ide-xml-tree-builder.h"
 
-typedef struct _ColorTag
-{
-  gchar *name;
-  gchar *fg;
-  gchar *bg;
-} ColorTag;
-
-static void
-color_tag_free (gpointer *data)
-{
-  ColorTag *tag = (ColorTag *)data;
-
-  g_free (tag->name);
-  g_free (tag->fg);
-  g_free (tag->bg);
-}
-
-/* Keep it in sync with ColorTagId enum */
-static ColorTag default_color_tags [] =
-{
-  { "label",       "#000000", "#D5E7FC" }, // COLOR_TAG_LABEL
-  { "id",          "#000000", "#D9E7BD" }, // COLOR_TAG_ID
-  { "style-class", "#000000", "#DFCD9B" }, // COLOR_TAG_STYLE_CLASS
-  { "type",        "#000000", "#F4DAC3" }, // COLOR_TAG_TYPE
-  { "parent",      "#000000", "#DEBECF" }, // COLOR_TAG_PARENT
-  { "class",       "#000000", "#FFEF98" }, // COLOR_TAG_CLASS
-  { "attribute",   "#000000", "#F0E68C" }, // COLOR_TAG_ATTRIBUTE
-  { NULL },
-};
-
 struct _IdeXmlTreeBuilder
 {
-  IdeObject  parent_instance;
+  IdeObject        parent_instance;
 
-  GSettings *settings;
-  GArray    *color_tags;
+  IdeXmlParser    *parser;
+  IdeXmlValidator *validator;
 };
 
 typedef struct{
-  IdeXmlSax *parser;
-  GBytes    *content;
-  GFile     *file;
-  gint64     sequence;
-} BuilderState;
+  GBytes         *content;
+  GFile          *file;
+  IdeXmlAnalysis *analysis;
+  gint64          sequence;
+} TreeBuilderState;
 
 static void
-builder_state_free (BuilderState *state)
+tree_builder_state_free (TreeBuilderState *state)
 {
-  g_clear_object (&state->parser);
   g_clear_pointer (&state->content, g_bytes_unref);
+  g_clear_pointer (&state->analysis, ide_xml_analysis_unref);
   g_clear_object (&state->file);
 }
 
 G_DEFINE_TYPE (IdeXmlTreeBuilder, ide_xml_tree_builder, IDE_TYPE_OBJECT)
 
+static IdeDiagnostic *
+create_diagnostic (IdeContext             *context,
+                   const gchar            *msg,
+                   GFile                  *file,
+                   gint                    line,
+                   gint                    col,
+                   IdeDiagnosticSeverity   severity)
+{
+  IdeDiagnostic *diagnostic;
+  g_autoptr(IdeSourceLocation) loc = NULL;
+  g_autoptr(IdeFile) ifile = NULL;
+
+  ifile = ide_file_new (context, file);
+  loc = ide_source_location_new (ifile,
+                                 line - 1,
+                                 col - 1,
+                                 0);
+
+  diagnostic = ide_diagnostic_new (severity, msg, loc);
+
+  return diagnostic;
+}
+
 static GBytes *
 ide_xml_tree_builder_get_file_content (IdeXmlTreeBuilder *self,
                                        GFile             *file,
@@ -115,65 +113,291 @@ ide_xml_tree_builder_get_file_content (IdeXmlTreeBuilder *self,
   return content;
 }
 
+typedef struct _FetchSchemasState
+{
+  IdeXmlTreeBuilder *self;
+  GTask             *task;
+  GArray            *schemas;
+  guint              index;
+} FetchSchemasState;
+
+static void
+fetch_schema_state_free (FetchSchemasState *state)
+{
+  g_object_unref (state->self);
+  g_array_unref (state->schemas);
+
+  g_slice_free (FetchSchemasState, state);
+}
+
+static void
+fetch_schemas_cb (GObject      *object,
+                  GAsyncResult *result,
+                  gpointer      user_data)
+{
+  EggTaskCache *schemas_cache = (EggTaskCache *)object;
+  FetchSchemasState *state = (FetchSchemasState *)user_data;
+  g_autoptr (IdeXmlSchemaCacheEntry) cache_entry = NULL;
+  GTask *task = state->task;
+  guint count;
+  SchemaEntry *entry;
+  GError *error = NULL;
+
+  g_assert (EGG_IS_TASK_CACHE (schemas_cache));
+  g_assert (G_IS_TASK (result));
+  g_assert (G_IS_TASK (task));
+
+  cache_entry = egg_task_cache_get_finish (schemas_cache, result, &error);
+  entry = &g_array_index (state->schemas, SchemaEntry, state->index);
+
+  if (cache_entry->content != NULL)
+    entry->schema_content = g_bytes_ref (cache_entry->content);
+  else
+    entry->error_message = g_strdup (cache_entry->error_message);
+
+  fetch_schema_state_free (state);
+  count = GPOINTER_TO_UINT (g_task_get_task_data (task));
+  --count;
+  g_task_set_task_data (task, GUINT_TO_POINTER (count), NULL);
+
+  if (count == 0)
+    {
+      g_task_return_boolean (task, TRUE);
+      g_object_unref (task);
+    }
+}
+
 static gboolean
-ide_xml_tree_builder_file_is_ui (GFile       *file,
-                                 const gchar *data,
-                                 gsize        size)
+fetch_schemas_async (IdeXmlTreeBuilder   *self,
+                     GArray              *schemas,
+                     GCancellable        *cancellable,
+                     GAsyncReadyCallback  callback,
+                     gpointer             user_data)
 {
-  g_autofree gchar *path = NULL;
-  g_autofree gchar *buffer = NULL;
-  gsize buffer_size;
+  IdeContext *context;
+  IdeXmlService *service;
+  EggTaskCache *schemas_cache;
+  GTask *task;
+  guint count = 0;
+  gboolean has_external_schemas = FALSE;
 
-  g_assert (G_IS_FILE (file));
-  g_assert (data != NULL);
-  g_assert (size > 0);
+  g_assert (IDE_IS_XML_TREE_BUILDER (self));
+  g_assert (schemas != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  path = g_file_get_path (file);
-  if (g_str_has_suffix (path, ".ui") || g_str_has_suffix (path, ".glade"))
+  if (schemas->len == 0)
+    return FALSE;
+
+  /* TODO: use a worker thread */
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  service = ide_context_get_service_typed (context, IDE_TYPE_XML_SERVICE);
+  schemas_cache = ide_xml_service_get_schemas_cache (service);
+
+  for (gint i = 0; i < schemas->len; ++i)
     {
-      buffer_size = (size < 256) ? size : 256;
-      buffer = g_strndup (data, buffer_size);
-      if (NULL != (strstr (buffer, "<interface>")))
-        return TRUE;
+      SchemaEntry *entry;
+      FetchSchemasState *state;
+
+      entry = &g_array_index (schemas, SchemaEntry, i);
+      /* Check if it's an internal schema */
+      if (entry->schema_file == NULL)
+        continue;
+
+      state = g_slice_new0 (FetchSchemasState);
+      state->self = g_object_ref (self);
+      state->schemas = g_array_ref (schemas);
+      state->task = task;
+
+      ++count;
+      g_task_set_task_data (task, GUINT_TO_POINTER (count), NULL);
+      has_external_schemas = TRUE;
+
+      state->index = i;
+      /* TODO: peek schemas if it's already in cache */
+      egg_task_cache_get_async (schemas_cache,
+                                entry->schema_file,
+                                FALSE,
+                                cancellable,
+                                fetch_schemas_cb,
+                                state);
+
+      printf ("fetching schema URL:%s\n", g_file_get_uri (entry->schema_file));
     }
 
-  return FALSE;
+  if (!has_external_schemas)
+    g_task_return_boolean (task, TRUE);
+
+  return TRUE;
+}
+
+static gboolean
+fetch_schemas_finish (IdeXmlTreeBuilder  *self,
+                      GAsyncResult       *result,
+                      GError            **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_XML_TREE_BUILDER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+  g_return_val_if_fail (error != NULL, FALSE);
+
+  return g_task_propagate_boolean (task, error);
 }
 
 static void
-build_tree_worker (GTask        *task,
-                   gpointer      source_object,
-                   gpointer      task_data,
-                   GCancellable *cancellable)
+ide_xml_tree_builder_build_tree_cb2 (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
 {
-  IdeXmlTreeBuilder *self = (IdeXmlTreeBuilder *)source_object;
-  BuilderState *state = (BuilderState *)task_data;
-  IdeXmlAnalysis *analysis = NULL;
-  const gchar *data;
-  gsize size;
+  IdeXmlTreeBuilder *self;
+  TreeBuilderState *state;
+  IdeContext *context;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr (GArray) schemas = NULL;
+  const gchar *doc_data;
+  xmlDoc *doc;
+  gsize doc_size;
+  SchemaKind kind;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (result));
+  g_assert (G_IS_TASK (task));
 
+  self = g_task_get_source_object (task);
   g_assert (IDE_IS_XML_TREE_BUILDER (self));
-  g_assert (G_IS_TASK (task));
-  g_assert (state != NULL);
-  g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
-  data = g_bytes_get_data (state->content, &size);
+  if (!fetch_schemas_finish (self, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  state = g_task_get_task_data (task);
+  schemas = ide_xml_analysis_get_schemas (state->analysis);
+  ide_xml_analysis_set_schemas (state->analysis, NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  doc_data = g_bytes_get_data (state->content, &doc_size);
+  if (NULL != (doc = xmlParseMemory (doc_data, doc_size)))
+    {
+      doc->URL = (guchar *)g_file_get_uri (state->file);
+      for (gint i = 0; i < schemas->len; ++i)
+        {
+          SchemaEntry *entry;
+          const gchar *schema_data;
+          gsize schema_size;
+          g_autoptr (IdeDiagnostics) diagnostics = NULL;
+          g_autoptr (IdeDiagnostic) diagnostic = NULL;
+          gboolean schema_ret;
+
+          entry = &g_array_index (schemas, SchemaEntry, i);
+          kind = entry->schema_kind;
+
+          if (kind == SCHEMA_KIND_RNG || kind == SCHEMA_KIND_XML_SCHEMA)
+            {
+              if (entry->schema_content != NULL)
+                {
+                  schema_data = g_bytes_get_data (entry->schema_content, &schema_size);
+                  schema_ret = ide_xml_validator_set_schema (self->validator,
+                                                             kind,
+                                                             schema_data,
+                                                             schema_size);
+                }
+              else
+                {
+                  g_assert (entry->error_message != NULL);
+
+                  diagnostic = create_diagnostic (context,
+                                                  entry->error_message,
+                                                  state->file,
+                                                  entry->schema_line,
+                                                  entry->schema_col,
+                                                  IDE_DIAGNOSTIC_ERROR);
+                  ide_diagnostics_add (state->analysis->diagnostics, diagnostic);
+                  continue;
+                }
+            }
+          else if (kind == SCHEMA_KIND_DTD)
+            {
+            schema_ret = ide_xml_validator_set_schema (self->validator, SCHEMA_KIND_DTD, NULL, 0);
+            }
+          else
+            g_assert_not_reached ();
 
-  if (ide_xml_tree_builder_file_is_ui (state->file, data, size))
-    analysis = ide_xml_tree_builder_ui_create (self, state->parser, state->file, data, size);
+          if (!schema_ret)
+            {
+              g_autofree gchar *uri = NULL;
+              g_autofree gchar *msg = NULL;
+
+              uri = g_file_get_uri (entry->schema_file);
+              msg = g_strdup_printf ("Can't parse the schema: '%s'", uri);
+              diagnostic = create_diagnostic (context,
+                                              msg,
+                                              state->file,
+                                              entry->schema_line,
+                                              entry->schema_col,
+                                              IDE_DIAGNOSTIC_ERROR);
+              ide_diagnostics_add (state->analysis->diagnostics, diagnostic);
+              continue;
+            }
+
+          if (ide_xml_validator_validate (self->validator, doc, &diagnostics))
+            printf ("validated\n");
+          else
+            printf ("NOT validated\n");
+
+          ide_diagnostics_merge (state->analysis->diagnostics, diagnostics);
+        }
+
+      xmlFreeDoc (doc);
+    }
   else
-    analysis = ide_xml_tree_builder_generic_create (self, state->parser, state->file, data, size);
+    {
+      /* TODO: set error */
+      printf ("can't create xmlDoc\n");
+    }
 
-  if (analysis == NULL)
+  g_task_return_pointer (task, state->analysis, (GDestroyNotify)ide_xml_analysis_unref);
+}
+
+static void
+ide_xml_tree_builder_build_tree_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  IdeXmlTreeBuilder *self;
+  g_autoptr(GTask) task = user_data;
+  TreeBuilderState *state;
+  IdeXmlAnalysis *analysis;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (result));
+  g_assert (G_IS_TASK (task));
+
+  self = g_task_get_source_object (task);
+  g_assert (IDE_IS_XML_TREE_BUILDER (self));
+
+  if (NULL == (analysis = ide_xml_parser_get_analysis_finish (self->parser, result, &error)))
     {
-      g_task_return_new_error (task,
-                               G_IO_ERROR,
-                               G_IO_ERROR_FAILED,
-                               _("Failed to create the XML tree."));
+      g_task_return_error (task, error);
       return;
     }
 
+  state = g_task_get_task_data (task);
+  state->analysis = ide_xml_analysis_ref (analysis);
   ide_xml_analysis_set_sequence (analysis, state->sequence);
+
+  if (analysis->schemas != NULL &&
+      fetch_schemas_async (self,
+                           analysis->schemas,
+                           g_task_get_cancellable (task),
+                           ide_xml_tree_builder_build_tree_cb2,
+                           g_object_ref (task)))
+    return;
+
   g_task_return_pointer (task, analysis, (GDestroyNotify)ide_xml_analysis_unref);
 }
 
@@ -185,7 +409,7 @@ ide_xml_tree_builder_build_tree_async (IdeXmlTreeBuilder   *self,
                                        gpointer             user_data)
 {
   g_autoptr(GTask) task = NULL;
-  BuilderState *state;
+  TreeBuilderState *state;
   GBytes *content = NULL;
   gint64 sequence;
 
@@ -205,14 +429,19 @@ ide_xml_tree_builder_build_tree_async (IdeXmlTreeBuilder   *self,
       return;
     }
 
-  state = g_slice_new0 (BuilderState);
-  state->parser = ide_xml_sax_new ();
-  state->content = content;
+  state = g_slice_new0 (TreeBuilderState);
   state->file = g_object_ref (file);
+  state->content = g_bytes_ref (content);
   state->sequence = sequence;
-
-  g_task_set_task_data (task, state, (GDestroyNotify)builder_state_free);
-  g_task_run_in_thread (task, build_tree_worker);
+  g_task_set_task_data (task, state, (GDestroyNotify)tree_builder_state_free);
+
+  ide_xml_parser_get_analysis_async (self->parser,
+                                     file,
+                                     content,
+                                     sequence,
+                                     cancellable,
+                                     ide_xml_tree_builder_build_tree_cb,
+                                     g_steal_pointer (&task));
 }
 
 IdeXmlAnalysis *
@@ -228,105 +457,11 @@ ide_xml_tree_builder_build_tree_finish (IdeXmlTreeBuilder  *self,
   return g_task_propagate_pointer (task, error);
 }
 
-gchar *
-ide_xml_tree_builder_get_color_tag (IdeXmlTreeBuilder *self,
-                                    const gchar       *str,
-                                    ColorTagId         id,
-                                    gboolean           space_before,
-                                    gboolean           space_after,
-                                    gboolean           space_inside)
-{
-  ColorTag *tag;
-
-  g_assert (IDE_IS_XML_TREE_BUILDER (self));
-  g_assert (self->color_tags != NULL);
-  g_assert (!ide_str_empty0 (str));
-
-  tag = &g_array_index (self->color_tags, ColorTag, id);
-  return g_strdup_printf ("%s<span foreground=\"%s\" background=\"%s\">%s%s%s</span>%s",
-                          space_before ? " " : "",
-                          tag->fg,
-                          tag->bg,
-                          space_inside ? " " : "",
-                          str,
-                          space_inside ? " " : "",
-                          space_after ? " " : "");
-}
-
-static void
-init_color_tags (IdeXmlTreeBuilder *self)
-{
-  g_autofree gchar *scheme_name = NULL;
-  GtkSourceStyleSchemeManager *manager;
-  GtkSourceStyleScheme *scheme;
-  gchar *tag_name;
-  GtkSourceStyle *style;
-  gchar *foreground;
-  gchar *background;
-  ColorTag tag;
-  ColorTag *tag_ptr;
-  gboolean tag_set;
-
-  g_assert (IDE_IS_XML_TREE_BUILDER (self));
-
-  scheme_name = g_settings_get_string (self->settings, "style-scheme-name");
-  manager = gtk_source_style_scheme_manager_get_default ();
-  scheme = gtk_source_style_scheme_manager_get_scheme (manager, scheme_name);
-
-  g_array_remove_range (self->color_tags, 0, self->color_tags->len);
-  tag_ptr = default_color_tags;
-  while (tag_ptr->fg != NULL)
-    {
-      tag_set = FALSE;
-      if (scheme != NULL)
-        {
-          tag_name = g_strconcat ("symboltree::", tag_ptr->name, NULL);
-          if (NULL != (style = gtk_source_style_scheme_get_style (scheme, tag_name)))
-            {
-              g_object_get (style, "foreground", &foreground, NULL);
-              g_object_get (style, "background", &background, NULL);
-              if (foreground != NULL && background != NULL)
-                {
-                  tag_set = TRUE;
-                  tag.name = g_strdup (tag_ptr->name);
-                  tag.fg = g_steal_pointer (&foreground);
-                  tag.bg = g_steal_pointer (&background);
-                }
-
-              g_free (foreground);
-              g_free (background);
-            }
-
-          g_free (tag_name);
-        }
-
-      if (!tag_set)
-        {
-          tag.name = g_strdup (tag_ptr->name);
-          tag.fg = g_strdup (tag_ptr->fg);
-          tag.bg = g_strdup (tag_ptr->bg);
-        }
-
-      g_array_append_val (self->color_tags, tag);
-      ++tag_ptr;
-    }
-}
-
-static void
-editor_settings_changed_cb (IdeXmlTreeBuilder *self,
-                            gchar             *key,
-                            GSettings         *settings)
-{
-  g_assert (IDE_IS_XML_TREE_BUILDER (self));
-
-  if (ide_str_equal0 (key, "style-scheme-name"))
-    init_color_tags (self);
-}
-
 IdeXmlTreeBuilder *
-ide_xml_tree_builder_new (void)
+ide_xml_tree_builder_new (EggTaskCache *schemas)
 {
-  return g_object_new (IDE_TYPE_XML_TREE_BUILDER, NULL);
+  return g_object_new (IDE_TYPE_XML_TREE_BUILDER,
+                       NULL);
 }
 
 static void
@@ -334,8 +469,8 @@ ide_xml_tree_builder_finalize (GObject *object)
 {
   IdeXmlTreeBuilder *self = (IdeXmlTreeBuilder *)object;
 
-  g_clear_pointer (&self->color_tags, g_array_unref);
-  g_clear_object (&self->settings);
+  g_clear_object (&self->parser);
+  g_clear_object (&self->validator);
 
   G_OBJECT_CLASS (ide_xml_tree_builder_parent_class)->finalize (object);
 }
@@ -351,14 +486,12 @@ ide_xml_tree_builder_class_init (IdeXmlTreeBuilderClass *klass)
 static void
 ide_xml_tree_builder_init (IdeXmlTreeBuilder *self)
 {
-  self->color_tags = g_array_new (TRUE, TRUE, sizeof (ColorTag));
-  g_array_set_clear_func (self->color_tags, (GDestroyNotify)color_tag_free);
+  IdeContext *context;
 
-  self->settings = g_settings_new ("org.gnome.builder.editor");
-  g_signal_connect_swapped (self->settings,
-                            "changed",
-                            G_CALLBACK (editor_settings_changed_cb),
-                            self);
+  context = ide_object_get_context (IDE_OBJECT (self));
+  self->parser = g_object_new (IDE_TYPE_XML_PARSER,
+                               "context", context,
+                               NULL);
 
-  init_color_tags (self);
+  self->validator = ide_xml_validator_new (context);
 }
diff --git a/plugins/xml-pack/ide-xml-tree-builder.h b/plugins/xml-pack/ide-xml-tree-builder.h
index 7bc6a33..ebe840b 100644
--- a/plugins/xml-pack/ide-xml-tree-builder.h
+++ b/plugins/xml-pack/ide-xml-tree-builder.h
@@ -31,18 +31,7 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeXmlTreeBuilder, ide_xml_tree_builder, IDE, XML_TREE_BUILDER, IdeObject)
 
-typedef enum _ColorTagId
-{
-  COLOR_TAG_LABEL,
-  COLOR_TAG_ID,
-  COLOR_TAG_STYLE_CLASS,
-  COLOR_TAG_TYPE,
-  COLOR_TAG_PARENT,
-  COLOR_TAG_CLASS,
-  COLOR_TAG_ATTRIBUTE,
-} ColorTagId;
-
-IdeXmlTreeBuilder   *ide_xml_tree_builder_new                    (void);
+IdeXmlTreeBuilder   *ide_xml_tree_builder_new                    ();
 void                 ide_xml_tree_builder_build_tree_async       (IdeXmlTreeBuilder     *self,
                                                                   GFile                 *file,
                                                                   GCancellable          *cancellable,
@@ -51,12 +40,6 @@ void                 ide_xml_tree_builder_build_tree_async       (IdeXmlTreeBuil
 IdeXmlAnalysis      *ide_xml_tree_builder_build_tree_finish      (IdeXmlTreeBuilder     *self,
                                                                   GAsyncResult          *result,
                                                                   GError               **error);
-gchar               *ide_xml_tree_builder_get_color_tag          (IdeXmlTreeBuilder     *self,
-                                                                  const gchar           *str,
-                                                                  ColorTagId             id,
-                                                                  gboolean               space_before,
-                                                                  gboolean               space_after,
-                                                                  gboolean               space_inside);
 
 G_END_DECLS
 
diff --git a/plugins/xml-pack/ide-xml-validator.c b/plugins/xml-pack/ide-xml-validator.c
new file mode 100644
index 0000000..ab52518
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-validator.c
@@ -0,0 +1,298 @@
+/* ide-xml-validator.c
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <libxml/parser.h>
+#include <libxml/parserInternals.h>
+#include <libxml/relaxng.h>
+#include <libxml/tree.h>
+#include <libxml/valid.h>
+#include <libxml/xmlschemas.h>
+
+#include "ide-xml-validator.h"
+
+struct _IdeXmlValidator
+{
+  IdeObject      parent_instance;
+
+  GPtrArray     *diagnostics_array;
+  xmlDtd        *dtd;
+  xmlRelaxNG    *rng;
+  xmlSchema     *xml_schema;
+
+  SchemaKind     kind;
+  guint          dtd_use_subsets : 1;
+};
+
+typedef struct _ValidState
+{
+  IdeXmlValidator *self;
+  SchemaKind       kind;
+  xmlValidCtxt    *dtd_valid_context;
+  xmlDoc          *doc;
+} ValidState;
+
+G_DEFINE_TYPE (IdeXmlValidator, ide_xml_validator, IDE_TYPE_OBJECT)
+
+SchemaKind
+ide_xml_validator_get_kind (IdeXmlValidator *self)
+{
+  g_return_val_if_fail (IDE_IS_XML_VALIDATOR (self), SCHEMA_KIND_NONE);
+
+  return self->kind;
+}
+
+static IdeDiagnostic *
+create_diagnostic (IdeXmlValidator        *self,
+                   GFile                  *file,
+                   xmlError               *error,
+                   IdeDiagnosticSeverity   severity)
+{
+  IdeContext *context;
+  IdeDiagnostic *diagnostic;
+  g_autoptr(IdeSourceLocation) loc = NULL;
+  g_autoptr(IdeFile) ifile = NULL;
+  gint line;
+
+  g_assert (IDE_IS_XML_VALIDATOR (self));
+  g_assert (G_IS_FILE (file));
+  g_assert (error != NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  ifile = ide_file_new (context, file);
+  line = (error->line > 0) ? error->line - 1 : 0;
+  loc = ide_source_location_new (ifile, line, 0, 0);
+
+  diagnostic = ide_diagnostic_new (severity, error->message, loc);
+
+  return diagnostic;
+}
+
+static void
+ide_xml_valid_error (ValidState  *state,
+                     const gchar *msg,
+                     ...)
+{
+  IdeXmlValidator *self = state->self;
+  IdeDiagnostic *diagnostic;
+  g_autoptr (GFile) file = NULL;
+
+  g_assert (IDE_IS_XML_VALIDATOR (self));
+
+  file = g_file_new_for_uri ((gchar *)state->doc->URL);
+  diagnostic = create_diagnostic (self,
+                                  file,
+                                  xmlGetLastError (),
+                                  IDE_DIAGNOSTIC_ERROR);
+
+  g_ptr_array_add (self->diagnostics_array, diagnostic);
+}
+
+static void
+ide_xml_valid_warning (ValidState  *state,
+                       const gchar *msg,
+                       ...)
+{
+  IdeXmlValidator *self = state->self;
+  IdeDiagnostic *diagnostic;
+  g_autoptr (GFile) file = NULL;
+
+  g_assert (IDE_IS_XML_VALIDATOR (self));
+
+  file = g_file_new_for_uri ((gchar *)state->doc->URL);
+  diagnostic = create_diagnostic (self,
+                                  file,
+                                  xmlGetLastError (),
+                                  IDE_DIAGNOSTIC_WARNING);
+
+  g_ptr_array_add (self->diagnostics_array, diagnostic);
+}
+
+/**
+ * ide_xml_validator_validate:
+ * @self: a #IdeXmlValidator instance
+ * @doc: the xmldoc to validate
+ * @diagnostics: (out) (nullable): a location to store a #IdeDiagnostics object
+ *
+ * Returns: %TRUE if the validation succeeded, %FALSE otherwise
+ */
+gboolean
+ide_xml_validator_validate (IdeXmlValidator   *self,
+                            xmlDoc            *doc,
+                            IdeDiagnostics   **diagnostics)
+{
+  xmlValidCtxt *dtd_valid_context;
+  xmlSchemaValidCtxt *xml_schema_valid_context;
+  xmlRelaxNGValidCtxt *rng_valid_context;
+  ValidState state;
+  gboolean ret = FALSE;
+
+  g_assert (IDE_IS_XML_VALIDATOR (self));
+  g_assert (doc != NULL);
+  g_assert ((diagnostics != NULL && *diagnostics == NULL) || diagnostics == NULL);
+
+  xmlLineNumbersDefault (1);
+
+  state.self = self;
+  state.doc = doc;
+  state.kind = self->kind;
+
+  if (self->kind == SCHEMA_KIND_DTD)
+    {
+      if (NULL == (dtd_valid_context = xmlNewValidCtxt ()))
+        goto end;
+
+      state.dtd_valid_context = dtd_valid_context;
+      dtd_valid_context->userData = &state;
+      dtd_valid_context->error = (xmlValidityErrorFunc)ide_xml_valid_error;
+      dtd_valid_context->warning = (xmlValidityWarningFunc)ide_xml_valid_warning;
+
+      if (self->dtd_use_subsets)
+        ret = xmlValidateDocument (dtd_valid_context, doc);
+      else
+        ret = xmlValidateDtd (dtd_valid_context, doc, self->dtd);
+
+      xmlFreeValidCtxt (dtd_valid_context);
+    }
+  else if (self->kind == SCHEMA_KIND_XML_SCHEMA)
+    {
+      if (NULL == (xml_schema_valid_context = xmlSchemaNewValidCtxt (self->xml_schema)))
+        goto end;
+
+      xmlSchemaSetValidErrors (xml_schema_valid_context,
+                               (xmlSchemaValidityErrorFunc)ide_xml_valid_error,
+                               (xmlSchemaValidityWarningFunc)ide_xml_valid_warning,
+                               &state);
+
+      ret = xmlSchemaValidateDoc (xml_schema_valid_context, doc);
+      xmlSchemaFreeValidCtxt (xml_schema_valid_context);
+    }
+  else if (self->kind == SCHEMA_KIND_RNG)
+    {
+      if (NULL == (rng_valid_context = xmlRelaxNGNewValidCtxt (self->rng)))
+        goto end;
+
+      xmlRelaxNGSetValidErrors (rng_valid_context,
+                                (xmlRelaxNGValidityErrorFunc)ide_xml_valid_error,
+                                (xmlRelaxNGValidityWarningFunc)ide_xml_valid_warning,
+                                &state);
+
+      ret = xmlRelaxNGValidateDoc (rng_valid_context, doc);
+      xmlRelaxNGFreeValidCtxt (rng_valid_context);
+    }
+  else
+    g_assert_not_reached ();
+
+end:
+  if (diagnostics != NULL)
+    *diagnostics = ide_diagnostics_new (self->diagnostics_array);
+  else
+    g_clear_pointer (&self->diagnostics_array, g_ptr_array_unref);
+
+  self->diagnostics_array = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_diagnostic_unref);
+
+  return ret;
+}
+
+/* For VALIDATOR_KIND_DTD, if data == NULL, the document
+ * subsets (internal/external) will be used for the validation.
+ */
+gboolean
+ide_xml_validator_set_schema (IdeXmlValidator *self,
+                              SchemaKind       kind,
+                              const gchar     *data,
+                              gsize            size)
+{
+  xmlDoc *dtd_doc;
+  xmlRelaxNGParserCtxt *rng_parser;
+  xmlSchemaParserCtxt *schema_parser;
+  gboolean ret = FALSE;
+
+  g_assert (IDE_IS_XML_VALIDATOR (self));
+
+  if (kind == SCHEMA_KIND_DTD)
+    {
+      if (data == NULL)
+        {
+          self->dtd_use_subsets = TRUE;
+          ret = TRUE;
+        }
+
+      if (NULL != (dtd_doc = xmlParseMemory (data, size)))
+        {
+          if (NULL != (self->dtd = xmlNewDtd (dtd_doc, NULL, NULL, NULL)))
+            ret = TRUE;
+
+          xmlFreeDoc (dtd_doc);
+        }
+    }
+  else if (kind == SCHEMA_KIND_RNG)
+    {
+      if (NULL != (rng_parser = xmlRelaxNGNewMemParserCtxt     (data, size)) &&
+          NULL != (self->rng = xmlRelaxNGParse (rng_parser)))
+        ret = TRUE;
+    }
+  else if (kind == SCHEMA_KIND_XML_SCHEMA)
+    {
+      if (NULL != (schema_parser = xmlSchemaNewMemParserCtxt (data, size)) &&
+          NULL != (self->xml_schema = xmlSchemaParse (schema_parser)))
+        ret = TRUE;
+    }
+  else
+    g_assert_not_reached ();
+
+  self->kind = (ret) ? kind : SCHEMA_KIND_NONE;
+
+  return ret;
+}
+
+IdeXmlValidator *
+ide_xml_validator_new (IdeContext *context)
+{
+  return g_object_new (IDE_TYPE_XML_VALIDATOR,
+                       "context", context,
+                       NULL);
+}
+
+static void
+ide_xml_validator_finalize (GObject *object)
+{
+  IdeXmlValidator *self = (IdeXmlValidator *)object;
+
+  g_clear_pointer (&self->dtd, xmlFreeDtd);
+  g_clear_pointer (&self->rng, xmlRelaxNGFree);
+  g_clear_pointer (&self->xml_schema, xmlSchemaFree);
+  g_clear_pointer (&self->diagnostics_array, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (ide_xml_validator_parent_class)->finalize (object);
+}
+
+static void
+ide_xml_validator_class_init (IdeXmlValidatorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_xml_validator_finalize;
+}
+
+static void
+ide_xml_validator_init (IdeXmlValidator *self)
+{
+  self->diagnostics_array = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_diagnostic_unref);
+}
diff --git a/plugins/xml-pack/ide-xml-validator.h b/plugins/xml-pack/ide-xml-validator.h
new file mode 100644
index 0000000..a01b5f8
--- /dev/null
+++ b/plugins/xml-pack/ide-xml-validator.h
@@ -0,0 +1,64 @@
+/* ide-xml-validator.h
+ *
+ * Copyright (C) 2017 Sebastien Lafargue <slafargue gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_XML_VALIDATOR_H
+#define IDE_XML_VALIDATOR_H
+
+#include <glib-object.h>
+#include <ide.h>
+#include <libxml/parser.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_XML_VALIDATOR (ide_xml_validator_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeXmlValidator, ide_xml_validator, IDE, XML_VALIDATOR, IdeObject)
+
+typedef enum
+{
+  SCHEMA_KIND_NONE,
+  SCHEMA_KIND_DTD,
+  SCHEMA_KIND_RNG,
+  SCHEMA_KIND_XML_SCHEMA,
+} SchemaKind;
+
+typedef struct _SchemaEntry
+{
+  GFile      *schema_file;
+  GBytes     *schema_content;
+  gchar      *error_message;
+  SchemaKind  schema_kind;
+  gint32      schema_line;
+  gint32      schema_col;
+} SchemaEntry;
+
+IdeXmlValidator       *ide_xml_validator_new        (IdeContext       *context);
+SchemaKind             ide_xml_validator_get_kind   (IdeXmlValidator  *self);
+gboolean               ide_xml_validator_set_schema (IdeXmlValidator  *self,
+                                                     SchemaKind        kind,
+                                                     const gchar      *data,
+                                                     gsize             size);
+
+gboolean               ide_xml_validator_validate   (IdeXmlValidator  *self,
+                                                     xmlDoc           *doc,
+                                                     IdeDiagnostics  **diagnostics);
+
+G_END_DECLS
+
+#endif /* IDE_XML_VALIDATOR_H */
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]