2
// XmlTextEditorExtension.cs
6
// Michael Hutchinson <mhutchinson@novell.com>
10
// (C) 2008 Novell, Inc (http://www.novell.com)
13
// Derived from LGPL files (Matt Ward)
14
// All code since then is MIT/X11
18
using System.Collections.Generic;
20
using System.Xml.Schema;
22
using MonoDevelop.Core;
23
using MonoDevelop.Components.Commands;
24
using MonoDevelop.Projects.Gui;
25
using MonoDevelop.Projects.Gui.Completion;
26
using MonoDevelop.Ide.Gui.Content;
27
using MonoDevelop.XmlEditor.Completion;
28
using MonoDevelop.Xml.StateEngine;
30
namespace MonoDevelop.XmlEditor
34
public class XmlTextEditorExtension : MonoDevelop.XmlEditor.Gui.BaseXmlEditorExtension
36
const string TextXmlMimeType = "text/xml";
37
const string ApplicationXmlMimeType = "application/xml";
38
string stylesheetFileName;
39
XmlSchemaCompletionData defaultSchemaCompletionData;
40
string defaultNamespacePrefix;
42
bool showSchemaAnnotation;
44
public XmlTextEditorExtension() : base ()
48
public override bool ExtendsEditor (MonoDevelop.Ide.Gui.Document doc, IEditableTextBuffer editor)
52
return IsFileNameHandled (doc.Name);
55
protected override IEnumerable<string> SupportedExtensions {
56
get { throw new System.InvalidOperationException (); }
60
public override void Initialize ()
63
XmlEditorAddInOptions.PropertyChanged += XmlEditorPropertyChanged;
64
XmlSchemaManager.UserSchemaAdded += UserSchemaAdded;
65
XmlSchemaManager.UserSchemaRemoved += UserSchemaRemoved;
68
MonoDevelop.SourceEditor.SourceEditorView view =
69
Document.GetContent<MonoDevelop.SourceEditor.SourceEditorView> ();
70
if (view != null && view.Document.SyntaxMode == null) {
71
Mono.TextEditor.Highlighting.SyntaxMode mode =
72
Mono.TextEditor.Highlighting.SyntaxModeService.GetSyntaxMode (ApplicationXmlMimeType);
74
view.Document.SyntaxMode = mode;
76
LoggingService.LogWarning ("XmlTextEditorExtension could not get SyntaxMode for mimetype '"
77
+ ApplicationXmlMimeType + "'.");
82
public override void Dispose()
86
XmlEditorAddInOptions.PropertyChanged -= XmlEditorPropertyChanged;
87
XmlSchemaManager.UserSchemaAdded -= UserSchemaAdded;
88
XmlSchemaManager.UserSchemaRemoved -= UserSchemaRemoved;
93
#region Code completion
95
IEditableTextBuffer GetBuffer ()
97
IEditableTextBuffer buf = Document.GetContent<IEditableTextBuffer> ();
98
System.Diagnostics.Debug.Assert (buf != null);
102
XmlElementPath GetCurrentPath ()
104
return ConvertPath (Tracker.Engine.Nodes.OfType<XElement> ().Reverse ());
107
static XmlElementPath ConvertPath (IEnumerable<XElement> path)
109
XmlElementPath elementPath = new XmlElementPath ();
110
var namespaces = new Dictionary<string, string> ();
111
string defaultNamespace = null;
112
foreach (XElement el in path) {
113
foreach (XAttribute att in el.Attributes) {
114
if (att.Name.HasPrefix) {
115
if (att.Name.Prefix == "xmlns")
116
namespaces [att.Name.Name] = att.Value;
118
if (att.Name.Name == "xmlns")
119
defaultNamespace = att.Value;
123
if (el.Name.HasPrefix)
124
namespaces.TryGetValue (el.Name.Prefix, out ns);
126
ns = defaultNamespace;
127
QualifiedName qn = new QualifiedName (el.Name.Name, ns, el.Name.Prefix ?? String.Empty);
128
elementPath.Elements.Add (qn);
133
protected override void GetElementCompletions (CompletionDataList list)
135
XmlElementPath path = GetCurrentPath ();
136
if (path.Elements.Count > 0) {
137
XmlSchemaCompletionData schema = FindSchema (path);
138
if (schema != null) {
139
ICompletionData[] completionData = schema.GetChildElementCompletionData (path);
140
if (completionData != null)
141
list.AddRange (completionData);
143
} else if (defaultSchemaCompletionData != null) {
144
list.AddRange (defaultSchemaCompletionData.GetElementCompletionData (defaultNamespacePrefix));
148
protected override void GetAttributeCompletions (CompletionDataList list, IAttributedXObject attributedOb,
149
Dictionary<string, string> existingAtts)
151
XmlElementPath path = GetCurrentPath ();
152
if (path.Elements.Count > 0) {
153
XmlSchemaCompletionData schema = FindSchema (path);
154
if (schema != null) {
155
ICompletionData[] completionData = schema.GetAttributeCompletionData (path);
156
if (completionData != null)
157
list.AddRange (completionData);
162
protected override void GetAttributeValueCompletions (CompletionDataList list, IAttributedXObject attributedOb,
165
XmlElementPath path = GetCurrentPath ();
166
if (path.Elements.Count > 0) {
167
XmlSchemaCompletionData schema = FindSchema (path);
168
if (schema != null) {
169
ICompletionData[] completionData = schema.GetAttributeValueCompletionData (path, att.Name.FullName);
170
if (completionData != null)
171
list.AddRange (completionData);
178
#region From XmlCompletionDataProvider.cs
180
public XmlSchemaCompletionData FindSchemaFromFileName (string fileName)
182
return XmlSchemaManager.SchemaCompletionDataItems.GetSchemaFromFileName (fileName);
185
public XmlSchemaCompletionData FindSchema (string namespaceUri)
187
return XmlSchemaManager.SchemaCompletionDataItems[namespaceUri];
190
public XmlSchemaCompletionData FindSchema (XmlElementPath path)
192
return FindSchema (XmlSchemaManager.SchemaCompletionDataItems, path);
196
/// Finds the schema given the xml element path.
198
public XmlSchemaCompletionData FindSchema (IXmlSchemaCompletionDataCollection schemaCompletionDataItems, XmlElementPath path)
200
if (path.Elements.Count > 0) {
201
string namespaceUri = path.Elements[0].Namespace;
202
if (namespaceUri.Length > 0) {
203
return schemaCompletionDataItems[namespaceUri];
204
} else if (defaultSchemaCompletionData != null) {
206
// Use the default schema namespace if none
207
// specified in a xml element path, otherwise
208
// we will not find any attribute or element matches
210
foreach (QualifiedName name in path.Elements) {
211
if (name.Namespace.Length == 0) {
212
name.Namespace = defaultSchemaCompletionData.NamespaceUri;
215
return defaultSchemaCompletionData;
223
#region Schema resolution
226
/// Gets the XmlSchemaObject that defines the currently selected xml element or attribute.
228
/// <param name="currentSchemaCompletionData">This is the schema completion data for the schema currently being
229
/// displayed. This can be null if the document is not a schema.</param>
230
public XmlSchemaObject GetSchemaObjectSelected (XmlSchemaCompletionData currentSchemaCompletionData)
232
// Find element under cursor.
233
XmlElementPath path = GetCurrentPath ();
235
//attribute name under cursor, if valid
236
string attributeName = null;
237
XAttribute xatt = Tracker.Engine.Nodes.Peek (0) as XAttribute;
239
XName xattName = xatt.Name;
240
if (Tracker.Engine.CurrentState is XmlNameState) {
241
xattName = GetCompleteName ();
243
attributeName = xattName.FullName;
246
// Find schema definition object.
247
XmlSchemaCompletionData schemaCompletionData = FindSchema (path);
248
XmlSchemaObject schemaObject = null;
249
if (schemaCompletionData != null) {
250
XmlSchemaElement element = schemaCompletionData.FindElement(path);
251
schemaObject = element;
252
if (element != null) {
253
if (attributeName.Length > 0) {
254
XmlSchemaAttribute attribute = schemaCompletionData.FindAttribute(element, attributeName);
255
if (attribute != null) {
256
if (currentSchemaCompletionData != null) {
257
schemaObject = GetSchemaObjectReferenced (currentSchemaCompletionData, element, attribute);
259
schemaObject = attribute;
270
/// If the attribute value found references another item in the schema
271
/// return this instead of the attribute schema object. For example, if the
272
/// user can select the attribute value and the code will work out the schema object pointed to by the ref
273
/// or type attribute:
275
/// xs:element ref="ref-name"
276
/// xs:attribute type="type-name"
279
/// The <paramref name="attribute"/> if no schema object was referenced.
281
XmlSchemaObject GetSchemaObjectReferenced (XmlSchemaCompletionData currentSchemaCompletionData, XmlSchemaElement element, XmlSchemaAttribute attribute)
283
XmlSchemaObject schemaObject = null;
284
if (IsXmlSchemaNamespace(element)) {
285
// Find attribute value.
287
string attributeValue = "";// XmlParser.GetAttributeValueAtIndex(xml, index);
288
if (attributeValue.Length == 0) {
292
if (attribute.Name == "ref") {
293
schemaObject = FindSchemaObjectReference(attributeValue, currentSchemaCompletionData, element.Name);
294
} else if (attribute.Name == "type") {
295
schemaObject = FindSchemaObjectType(attributeValue, currentSchemaCompletionData, element.Name);
299
if (schemaObject != null) {
306
/// Checks whether the element belongs to the XSD namespace.
308
static bool IsXmlSchemaNamespace (XmlSchemaElement element)
310
XmlQualifiedName qualifiedName = element.QualifiedName;
311
if (qualifiedName != null) {
312
return XmlSchemaManager.IsXmlSchemaNamespace (qualifiedName.Namespace);
318
/// Attempts to locate the reference name in the specified schema.
320
/// <param name="name">The reference to look up.</param>
321
/// <param name="schemaCompletionData">The schema completion data to use to
322
/// find the reference.</param>
323
/// <param name="elementName">The element to determine what sort of reference it is
324
/// (e.g. group, attribute, element).</param>
325
/// <returns><see langword="null"/> if no match can be found.</returns>
326
XmlSchemaObject FindSchemaObjectReference(string name, XmlSchemaCompletionData schemaCompletionData, string elementName)
328
QualifiedName qualifiedName = schemaCompletionData.CreateQualifiedName(name);
329
XmlSchemaCompletionData qualifiedNameSchema = FindSchema(qualifiedName.Namespace);
330
if (qualifiedNameSchema != null) {
331
schemaCompletionData = qualifiedNameSchema;
333
switch (elementName) {
335
return schemaCompletionData.FindElement(qualifiedName);
337
return schemaCompletionData.FindAttribute(qualifiedName.Name);
339
return schemaCompletionData.FindGroup(qualifiedName.Name);
340
case "attributeGroup":
341
return schemaCompletionData.FindAttributeGroup(qualifiedName.Name);
347
/// Attempts to locate the type name in the specified schema.
349
/// <param name="name">The type to look up.</param>
350
/// <param name="schemaCompletionData">The schema completion data to use to
351
/// find the type.</param>
352
/// <param name="elementName">The element to determine what sort of type it is
353
/// (e.g. group, attribute, element).</param>
354
/// <returns><see langword="null"/> if no match can be found.</returns>
355
XmlSchemaObject FindSchemaObjectType(string name, XmlSchemaCompletionData schemaCompletionData, string elementName)
357
QualifiedName qualifiedName = schemaCompletionData.CreateQualifiedName(name);
358
XmlSchemaCompletionData qualifiedNameSchema = FindSchema(qualifiedName.Namespace);
359
if (qualifiedNameSchema != null) {
360
schemaCompletionData = qualifiedNameSchema;
362
switch (elementName) {
364
return schemaCompletionData.FindComplexType(qualifiedName);
366
return schemaCompletionData.FindSimpleType(qualifiedName.Name);
373
#region Settings handling
375
void SetDefaultSchema (string fileName)
377
if (fileName == null) {
380
string extension = System.IO.Path.GetExtension (fileName).ToLower ();
381
defaultSchemaCompletionData = XmlSchemaManager.GetSchemaCompletionData (extension);
382
defaultNamespacePrefix = XmlSchemaManager.GetNamespacePrefix (extension);
385
/// Updates the default schema association since the schema may have been added.
386
void UserSchemaAdded (object source, EventArgs e)
388
SetDefaultSchema (Document.Name);
391
// Updates the default schema association since the schema may have been removed.
392
void UserSchemaRemoved (object source, EventArgs e)
394
SetDefaultSchema (Document.Name);
397
void XmlEditorPropertyChanged (object sender, PropertyChangedEventArgs args)
400
case "AutoCompleteElements":
401
AutoCompleteClosingTags = XmlEditorAddInOptions.AutoCompleteElements;
403
case "ShowSchemaAnnotation":
404
showSchemaAnnotation = XmlEditorAddInOptions.ShowSchemaAnnotation;
407
string extension = System.IO.Path.GetExtension (Document.Name).ToLower ();
408
if (args.Key == extension) {
409
SetDefaultSchema (Document.Name);
411
LoggingService.LogError ("Unhandled property change in XmlTextEditorExtension: " + args.Key);
417
void SetInitialValues()
419
showSchemaAnnotation = XmlEditorAddInOptions.ShowSchemaAnnotation;
420
AutoCompleteClosingTags = XmlEditorAddInOptions.AutoCompleteElements;
421
SetDefaultSchema (Document.Name);
426
#region Stylesheet handling
429
/// Gets or sets the stylesheet associated with this xml file.
431
public string StylesheetFileName {
432
get { return stylesheetFileName; }
433
set { stylesheetFileName = value; }
438
#region Filetype/schema detection
440
public bool IsSchema {
442
string extension = System.IO.Path.GetExtension (FileName);
443
if (extension != null)
444
return String.Compare (extension, ".xsd", true) == 0;
450
/// Determines whether the file can be displayed by
453
public static bool IsFileNameHandled (string fileName)
455
if (fileName == null)
458
if (System.IO.Path.IsPathRooted (fileName)) {
459
string vfsname = fileName.Replace ("%", "%25").Replace ("#", "%23").Replace ("?", "%3F");
460
string mimeType = MonoDevelop.Core.Gui.Services.PlatformService.GetMimeTypeForUri (vfsname);
461
if (IsMimeTypeHandled (mimeType))
465
return XmlFileExtensions.IsXmlFileExtension (System.IO.Path.GetExtension (fileName));
468
public static bool IsMimeTypeHandled (string mimeType)
470
return (mimeType != null && (mimeType == TextXmlMimeType || mimeType == ApplicationXmlMimeType));
477
public override bool KeyPress (Gdk.Key key, char keyChar, Gdk.ModifierType modifier)
481
if (TextEditorProperties.IndentStyle == IndentStyle.Smart && key == Gdk.Key.Return) {
482
result = base.KeyPress (key, keyChar, modifier);
483
SmartIndentLine (Editor.CursorLine);
486
return base.KeyPress (key, keyChar, modifier);
489
void SmartIndentLine (int line)
491
//FIXME: implement this
494
string GetLineIndent (int line)
496
string indent = string.Empty;
497
int start = Editor.GetPositionFromLineColumn (line, 1);
499
while (i < Editor.TextLength) {
500
char c = Editor.GetCharAt (i);
501
if (c == '\n' || c == '\r')
503
if (!char.IsWhiteSpace (c))
508
indent = Editor.GetText (start, i);
512
//gets the indent of the line containing this position, up to the position index
513
string GetPositionIndent (int position)
515
int indentEnd = position;
516
int i = position - 1;
518
char c = Editor.GetCharAt (i);
519
if (c == '\n' || c == '\r')
520
return Editor.GetText (i + 1, indentEnd);
521
if (!char.IsWhiteSpace (c))
530
#region Command handlers
532
[CommandUpdateHandler (MonoDevelop.Ide.Commands.EditCommands.ToggleCodeComment)]
533
protected void ToggleCodeCommentCommandUpdate (CommandInfo info)
535
info.Enabled = false;
538
[CommandHandler (MonoDevelop.Ide.Commands.EditCommands.ToggleCodeComment)]
539
public void ToggleCodeCommentCommand ()
544
[CommandHandler (Commands.Format)]
545
public void FormatCommand ()
547
MonoDevelop.Ide.Gui.IdeApp.Services.TaskService.ClearExceptCommentTasks ();
549
using (IProgressMonitor monitor = XmlEditorService.GetMonitor ()) {
550
bool selection = (Editor.SelectionEndPosition - Editor.SelectionStartPosition) > 0;
551
string xml = selection? Editor.SelectedText : Editor.Text;
552
XmlDocument doc = XmlEditorService.ValidateWellFormedness (monitor, xml, FileName);
556
//if there's a line indent at the current location, prepend that to all new lines
557
string extraIndent = null;
559
extraIndent = GetPositionIndent (Editor.SelectionStartPosition);
561
string formattedXml = XmlEditorService.IndentedFormat (xml);
563
//convert newlines and prepend extra indents to each line if needed
564
bool nonNativeNewline = (Editor.NewLine != Environment.NewLine);
565
bool hasExtraIndent = !string.IsNullOrEmpty (extraIndent);
566
if (hasExtraIndent || nonNativeNewline) {
567
System.Text.StringBuilder builder = new System.Text.StringBuilder (formattedXml);
569
if (nonNativeNewline)
570
builder.Replace (Environment.NewLine, Editor.NewLine);
572
if (hasExtraIndent) {
573
builder.Replace (Editor.NewLine, Editor.NewLine + extraIndent);
574
if (formattedXml.EndsWith (Environment.NewLine))
575
builder.Remove (builder.Length - 1 - extraIndent.Length, extraIndent.Length);
577
formattedXml = builder.ToString ();
580
Editor.BeginAtomicUndo ();
582
Editor.SelectedText = formattedXml;
584
Editor.DeleteText (0, Editor.TextLength);
585
Editor.InsertText (0, formattedXml);
587
Editor.EndAtomicUndo ();
591
[CommandHandler (Commands.CreateSchema)]
592
public void CreateSchemaCommand ()
595
MonoDevelop.Ide.Gui.IdeApp.Services.TaskService.ClearExceptCommentTasks ();
596
string xml = Editor.Text;
597
using (IProgressMonitor monitor = XmlEditorService.GetMonitor ()) {
598
XmlDocument doc = XmlEditorService.ValidateWellFormedness (monitor, xml, FileName);
601
monitor.BeginTask (GettextCatalog.GetString ("Creating schema..."), 0);
603
string schema = XmlEditorService.CreateSchema (xml);
604
string fileName = XmlEditorService.GenerateFileName (FileName, "{0}.xsd");
605
MonoDevelop.Ide.Gui.IdeApp.Workbench.NewDocument (fileName, "application/xml", schema);
606
monitor.ReportSuccess (GettextCatalog.GetString ("Schema created."));
607
} catch (Exception ex) {
608
string msg = GettextCatalog.GetString ("Error creating XML schema.");
609
LoggingService.LogError (msg, ex);
610
monitor.ReportError (msg, ex);
613
} catch (Exception ex) {
614
MonoDevelop.Core.Gui.MessageService.ShowError(ex.Message);
618
[CommandHandler (Commands.OpenStylesheet)]
619
public void OpenStylesheetCommand ()
621
if (!string.IsNullOrEmpty (stylesheetFileName)) {
623
MonoDevelop.Ide.Gui.IdeApp.Workbench.OpenDocument (stylesheetFileName);
624
} catch (Exception ex) {
625
MonoDevelop.Core.LoggingService.LogError ("Could not open document.", ex);
626
MonoDevelop.Core.Gui.MessageService.ShowException (ex, "Could not open document.");
631
[CommandUpdateHandler (Commands.OpenStylesheet)]
632
public void UpdateOpenStylesheetCommand (CommandInfo info)
634
info.Enabled = !string.IsNullOrEmpty (stylesheetFileName);
637
[CommandHandler (Commands.GoToSchemaDefinition)]
638
public void GoToSchemaDefinitionCommand ()
641
//try to resolve the schema
642
XmlSchemaCompletionData currentSchemaCompletionData = FindSchemaFromFileName (FileName);
643
XmlSchemaObject schemaObject = GetSchemaObjectSelected (currentSchemaCompletionData);
645
// Open schema if resolved
646
if (schemaObject != null && schemaObject.SourceUri != null && schemaObject.SourceUri.Length > 0) {
647
string schemaFileName = schemaObject.SourceUri.Replace ("file:/", String.Empty);
648
MonoDevelop.Ide.Gui.IdeApp.Workbench.OpenDocument (
650
Math.Max (1, schemaObject.LineNumber),
651
Math.Max (1, schemaObject.LinePosition), true);
653
} catch (Exception ex) {
654
MonoDevelop.Core.LoggingService.LogError ("Could not open document.", ex);
655
MonoDevelop.Core.Gui.MessageService.ShowException (ex, "Could not open document.");
659
[CommandHandler (Commands.Validate)]
660
public void ValidateCommand ()
662
MonoDevelop.Ide.Gui.IdeApp.Services.TaskService.ClearExceptCommentTasks ();
663
using (IProgressMonitor monitor = XmlEditorService.GetMonitor()) {
665
XmlEditorService.ValidateSchema (monitor, Editor.Text, FileName);
667
XmlEditorService.ValidateXml (monitor, Editor.Text, FileName);
671
[CommandHandler (Commands.AssignStylesheet)]
672
public void AssignStylesheetCommand ()
674
// Prompt user for filename.
675
string fileName = XmlEditorService.BrowseForStylesheetFile ();
676
if (!string.IsNullOrEmpty (stylesheetFileName))
677
stylesheetFileName = fileName;
680
[CommandHandler (Commands.RunXslTransform)]
681
public void RunXslTransformCommand ()
683
if (string.IsNullOrEmpty (stylesheetFileName)) {
684
stylesheetFileName = XmlEditorService.BrowseForStylesheetFile ();
685
if (string.IsNullOrEmpty (stylesheetFileName))
689
using (IProgressMonitor monitor = XmlEditorService.GetMonitor()) {
693
xsltContent = GetFileContent (stylesheetFileName);
694
} catch (System.IO.IOException) {
695
monitor.ReportError (
696
GettextCatalog.GetString ("Error reading file '{0}'.", stylesheetFileName), null);
699
System.Xml.Xsl.XslTransform xslt =
700
XmlEditorService.ValidateStylesheet (monitor, xsltContent, stylesheetFileName);
704
XmlDocument doc = XmlEditorService.ValidateXml (monitor, Editor.Text, FileName);
708
string newFileName = XmlEditorService.GenerateFileName (FileName, "-transformed{0}.xml");
710
monitor.BeginTask (GettextCatalog.GetString ("Executing transform..."), 1);
711
using (XmlTextWriter output = XmlEditorService.CreateXmlTextWriter()) {
712
xslt.Transform (doc, null, output);
713
MonoDevelop.Ide.Gui.IdeApp.Workbench.NewDocument (
714
newFileName, "application/xml", output.ToString ());
716
monitor.ReportSuccess (GettextCatalog.GetString ("Transform completed."));
718
} catch (Exception ex) {
719
string msg = GettextCatalog.GetString ("Could not run transform.");
720
monitor.ReportError (msg, ex);
726
string GetFileContent (string fileName)
728
MonoDevelop.Projects.Text.IEditableTextFile tf =
729
MonoDevelop.DesignerSupport.OpenDocumentFileProvider.Instance.GetEditableTextFile (fileName);
732
System.IO.StreamReader reader = new System.IO.StreamReader (fileName, true);
733
return reader.ReadToEnd();