/// <summary> /// Load the project settings for a specific transform file and return it /// </summary> /// <param name="sourceFile">The full path to the data file</param> /// <param name="baseDirectory">The path for the base directory (with a trailing \)</param> /// <param name="arguments">Argument list to pass to the returned transform</param> /// <param name="project">The context project</param> /// <param name="automationObjectName">The name of an automation object supported by the live document. Used /// to get a live version of the file stream.</param> /// <returns>The transform file name. Existence will have been verified.</returns> private string LoadProjectSettings(string sourceFile, string baseDirectory, XsltArgumentList arguments, EnvDTE.Project project, out string automationObjectName) { #if VerifyUIThread Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); #endif Debug.Assert(arguments != null); // Allocate before call string transformFile = null; automationObjectName = null; string plixProjectSettingsFile = baseDirectory + PlixProjectSettingsFile; if (File.Exists(plixProjectSettingsFile)) { // Use the text from the live document if possible string liveText = null; EnvDTE.Document settingsDoc = null; try { settingsDoc = project.DTE.Documents.Item(plixProjectSettingsFile); } catch (ArgumentException) { // swallow } catch (InvalidCastException) { // swallow } if (settingsDoc != null) { EnvDTE.TextDocument textDoc = settingsDoc.Object("TextDocument") as EnvDTE.TextDocument; if (textDoc != null) { liveText = textDoc.StartPoint.CreateEditPoint().GetText(textDoc.EndPoint); } } string sourceFileIdentifier = sourceFile.Substring(baseDirectory.Length); PlixLoaderNameTable names = PlixLoaderSchema.Names; using (FileStream plixSettingsStream = (liveText == null) ? new FileStream(plixProjectSettingsFile, FileMode.Open, FileAccess.Read) : null) { using (XmlTextReader settingsReader = new XmlTextReader((liveText == null) ? new StreamReader(plixSettingsStream) as TextReader : new StringReader(liveText), names)) { using (XmlReader reader = XmlReader.Create(settingsReader, PlixLoaderSchema.ReaderSettings)) { References references = null; bool finished = false; while (!finished && reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (!reader.IsEmptyElement) { while (reader.Read()) { XmlNodeType nodeType = reader.NodeType; if (nodeType == XmlNodeType.Element) { Debug.Assert(XmlUtility.TestElementName(reader.LocalName, names.SourceFileElement)); // Only value allowed by the validating loader string testFileName = reader.GetAttribute(names.FileAttribute); if (0 == string.Compare(testFileName, sourceFileIdentifier, true, CultureInfo.CurrentCulture)) { finished = true; // Stop looking string attrValue = reader.GetAttribute(names.TransformFileAttribute); if (attrValue != null && attrValue.Length != 0) { transformFile = baseDirectory + attrValue; } attrValue = reader.GetAttribute(names.LiveDocumentObjectAttribute); if (attrValue != null && attrValue.Length != 0) { automationObjectName = attrValue; } if (!reader.IsEmptyElement) { while (reader.Read()) { nodeType = reader.NodeType; if (nodeType == XmlNodeType.Element) { string localName = reader.LocalName; if (XmlUtility.TestElementName(localName, names.TransformParameterElement)) { // Add an argument for the transform arguments.AddParam(reader.GetAttribute(names.NameAttribute), "", reader.GetAttribute(names.ValueAttribute)); } else if (XmlUtility.TestElementName(localName, names.ExtensionClassElement)) { // Load an extension class and associate it with an extension namespace // used by the transform arguments.AddExtensionObject(reader.GetAttribute(names.XslNamespaceAttribute), Type.GetType(reader.GetAttribute(names.ClassNameAttribute), true, false).GetConstructor(Type.EmptyTypes).Invoke(new object[0])); } else if (XmlUtility.TestElementName(localName, names.ProjectReferenceElement)) { // The generated code requires project references, add them if (null == references) { references = ((VSProject)project.Object).References; } if (references.Item(reader.GetAttribute(names.NamespaceAttribute)) == null) { references.Add(reader.GetAttribute(names.AssemblyAttribute)); } } else { Debug.Assert(false); // Not allowed by schema definition } XmlUtility.PassEndElement(reader); } else if (nodeType == XmlNodeType.EndElement) { break; } } } break; } XmlUtility.PassEndElement(reader); } else if (nodeType == XmlNodeType.EndElement) { break; } } } } } } } } } bool verifiedExistence = false; if (transformFile == null) { string fileBase = sourceFile.Substring(0, sourceFile.LastIndexOf('.')); transformFile = fileBase + ".xslt"; if (File.Exists(transformFile)) { verifiedExistence = true; } else { transformFile = fileBase + ".xsl"; } } if (!verifiedExistence && !File.Exists(transformFile)) { transformFile = null; } return(transformFile); }
/// <summary> /// Generate a code file for the current xml file contents. Loads /// settings for the file off the Plix.xml settings file in the project to /// get the generation transform and other settings to apply to the specific file. /// </summary> /// <param name="fileContents">Contents of an xml file to transform</param> /// <param name="defaultNamespace">The namespace provided in the property grid</param> /// <returns>Contents of the corresponding code file</returns> private string GenerateCode(string fileContents, string defaultNamespace) { #if VerifyUIThread Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); #endif // Make sure we have a CodeDomProvider CodeDomProvider provider = CodeDomProvider; if (provider == null) { return(string.Empty); } // Get the current project item and project information EnvDTE.ProjectItem projectItem = CurrentProjectItem; string sourceFile = (string)projectItem.Properties.Item("LocalPath").Value; EnvDTE.Project project = projectItem.ContainingProject; string projectFile = (string)project.Properties.Item("LocalPath").Value; string projectLocation = projectFile.Substring(0, projectFile.LastIndexOf('\\') + 1); // If this is the Plix.xml settings file, then regenerate all other mentioned NUPlixLoader files if (0 == string.Compare(projectItem.Name, PlixProjectSettingsFile, true, CultureInfo.InvariantCulture)) { RunCustomTool( project.ProjectItems, projectItem, delegate(EnvDTE.ProjectItem matchItem) { #if VerifyUIThread Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); #endif VSProjectItem vsProjItem = matchItem.Object as VSProjectItem; if (vsProjItem != null) { vsProjItem.RunCustomTool(); } }); StringWriter writer = new StringWriter(); provider.GenerateCodeFromStatement(new CodeCommentStatement( @"Empty file generated by NUPlixLoader for Plix.xml. Setting NUPlixLoader as the custom tool on the Plix.xml settings file enables automatic regeneration of other NUPlixLoader files in the project when the settings file is changed. There is no way to both successfully trigger regeneration and avoid writing this file."), writer, null); return(writer.ToString()); } // Load a language formatter for this file extension string fileExtension = CodeDomProvider.FileExtension; if (fileExtension.StartsWith(".")) { fileExtension = fileExtension.Substring(1); } XslCompiledTransform formatter = FormatterManager.GetFormatterTransform(fileExtension); if (formatter == null) { StringWriter writer = new StringWriter(); provider.GenerateCodeFromStatement(new CodeCommentStatement(string.Format(CultureInfo.InvariantCulture, "A PLiX formatter transform for the '{0}' language was not found.", fileExtension)), writer, null); return(writer.ToString()); } // Get options for this xml file XsltArgumentList arguments = new XsltArgumentList(); string automationObjectName; string transformFile = LoadProjectSettings(sourceFile, projectLocation, arguments, project, out automationObjectName); // MSBUG: Beta2 There's a nasty bug with single-file generators right now where // the text for an open file that is not in a text document is // passed through as encoded bytes in the string. EnvDTE.Document itemDocument = projectItem.Document; if (itemDocument != null && "XML" != itemDocument.Language) { if (fileContents.Length > 1) { char[] leadChars = fileContents.ToCharArray(0, 2); byte[] leadBytes = new byte[2 * sizeof(char)]; GCHandle handle = GCHandle.Alloc(leadBytes, GCHandleType.Pinned); Marshal.Copy(leadChars, 0, Marshal.UnsafeAddrOfPinnedArrayElement(leadBytes, 0), 2); handle.Free(); EncodingInfo[] encodingInfos = Encoding.GetEncodings(); int encodingsCount = encodingInfos.Length; for (int i = 0; i < encodingsCount; ++i) { EncodingInfo encodingInfo = encodingInfos[i]; Encoding encoding = encodingInfo.GetEncoding(); byte[] preamble = encoding.GetPreamble(); int preambleByteCount = preamble.Length; if (preambleByteCount != 0) { Debug.Assert(preambleByteCount <= 4); int j; for (j = 0; j < preambleByteCount; ++j) { if (preamble[j] != leadBytes[j]) { break; } } if (j == preambleByteCount) { Decoder decoder = encoding.GetDecoder(); leadChars = fileContents.ToCharArray(); int startCharCount = leadChars.Length; leadBytes = new byte[startCharCount * sizeof(char)]; int byteCount = leadBytes.Length - preambleByteCount; GCHandle handle2 = GCHandle.Alloc(leadBytes, GCHandleType.Pinned); Marshal.Copy(leadChars, 0, Marshal.UnsafeAddrOfPinnedArrayElement(leadBytes, 0), startCharCount); handle2.Free(); int finalCharCount = decoder.GetCharCount(leadBytes, preambleByteCount, byteCount, true); char[] finalChars = new char[finalCharCount + 1]; decoder.GetChars(leadBytes, preambleByteCount, byteCount, finalChars, 0, true); // Hack within a hack to make sure that the Xml element has a trailing >, // byte data in a string has a tendency to lose the last byte char testChar = finalChars[finalCharCount - 1];; if (testChar != '>' && !char.IsWhiteSpace(testChar)) { finalChars[finalCharCount] = '>'; ++finalCharCount; } fileContents = new string(finalChars, 0, finalCharCount); } } } } } // Resolve any file redirections here. File redirection allows the same source file // to generate multiple outputs via multiple transforms. string alternateSourceFile = null; using (StringReader stringReader = new StringReader(fileContents)) { try { using (XmlTextReader reader = new XmlTextReader(stringReader)) { if (XmlNodeType.Element == reader.MoveToContent()) { if (reader.NamespaceURI == RedirectNamespace && reader.LocalName == RedirectElementName) { string relativeTargetSourceFile = reader.GetAttribute(RedirectTargetAttribute); FileInfo targetSourceFileInfo = new FileInfo(sourceFile.Substring(0, sourceFile.LastIndexOf('\\') + 1) + relativeTargetSourceFile); if (targetSourceFileInfo.Exists) { alternateSourceFile = targetSourceFileInfo.FullName; sourceFile = alternateSourceFile; try { itemDocument = null; itemDocument = project.DTE.Documents.Item(alternateSourceFile); } catch (ArgumentException) { // Swallow if the document is not open } } else { StringWriter writer = new StringWriter(); provider.GenerateCodeFromStatement(new CodeCommentStatement(string.Format(CultureInfo.InvariantCulture, "Redirection target file '{0}' not found", relativeTargetSourceFile)), writer, null); return(writer.ToString()); } } } } } catch (XmlException ex) { return(GenerateExceptionInformation(ex, provider)); } } // Add standard defined attributes to the argument list string projectNamespace = (string)project.Properties.Item("DefaultNamespace").Value; if (null == arguments.GetParam("ProjectPath", "")) { arguments.AddParam("ProjectPath", "", projectLocation); } if (null == arguments.GetParam("SourceFile", "")) { arguments.AddParam("SourceFile", "", sourceFile.Substring(projectLocation.Length)); } if (null == arguments.GetParam("CustomToolNamespace", "")) { if (defaultNamespace == null || defaultNamespace.Length == 0) { defaultNamespace = projectNamespace; } arguments.AddParam("CustomToolNamespace", "", defaultNamespace); } if (null == arguments.GetParam("ProjectNamespace", "")) { arguments.AddParam("ProjectNamespace", "", projectNamespace); } try { XslCompiledTransform transform = null; if (transformFile != null) { transform = new XslCompiledTransform(); using (FileStream transformStream = new FileStream(transformFile, FileMode.Open, FileAccess.Read)) { using (StreamReader reader = new StreamReader(transformStream)) { transform.Load(new XmlTextReader(reader), XsltSettings.TrustedXslt, XmlUtility.CreateFileResolver(transformFile)); } } } MemoryStream plixStream = (transform != null) ? new MemoryStream() : null; using (XmlWriter xmlTextWriter = (transform != null) ? XmlWriter.Create(plixStream, transform.OutputSettings) : null) { // Variables that need to be disposed TextReader reader = null; Stream docStream = null; try { // First try to get data from the live object string docText = null; if (itemDocument != null) { if (automationObjectName != null) { docStream = itemDocument.Object(automationObjectName) as Stream; if (docStream != null) { reader = new StreamReader(docStream); } } // Fall back on getting the contents of the text buffer from the live document if (reader == null) { EnvDTE.TextDocument textDoc = itemDocument.Object("TextDocument") as EnvDTE.TextDocument; if (textDoc != null) { docText = textDoc.StartPoint.CreateEditPoint().GetText(textDoc.EndPoint); reader = new StringReader(docText); } } } // If this is a redirection, then pull direction from the file if (reader == null && alternateSourceFile != null) { reader = new StreamReader(alternateSourceFile); } // Fallback on the default reading mechanism if (reader == null) { docText = fileContents; reader = new StringReader(fileContents); } if (transform == null) { XmlReaderSettings testPlixDocumentReaderSettings = new XmlReaderSettings(); testPlixDocumentReaderSettings.CloseInput = false; bool plixDocument = false; try { using (XmlReader testPlixDocumentReader = XmlReader.Create(reader, testPlixDocumentReaderSettings)) { testPlixDocumentReader.MoveToContent(); if (testPlixDocumentReader.NodeType == XmlNodeType.Element && testPlixDocumentReader.NamespaceURI == PlixSchemaNamespace) { plixDocument = true; } } } catch (XmlException ex) { return(GenerateExceptionInformation(ex, provider)); } if (!plixDocument) { StringWriter writer = new StringWriter(); provider.GenerateCodeFromStatement(new CodeCommentStatement("Transform file not found"), writer, null); GenerateNUPlixLoaderExceptionLine(writer, provider); return(writer.ToString()); } if (docText != null) { reader.Dispose(); reader = new StringReader(docText); } else { StreamReader streamReader = (StreamReader)reader; streamReader.BaseStream.Position = 0; } } else { // Use an XmlTextReader here instead of an XPathDocument // so that our transforms support the xsl:preserve-space element transform.Transform(new XmlTextReader(reader), arguments, xmlTextWriter, XmlUtility.CreateFileResolver(sourceFile)); plixStream.Position = 0; } // From the plix stream, generate the code using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture)) { using (XmlReader plixReader = (plixStream != null) ? XmlReader.Create(plixStream, PlixReaderSettings) : XmlReader.Create(reader, PlixReaderSettings)) { formatter.Transform(plixReader, new XsltArgumentList(), writer); } return(writer.ToString()); } } finally { if (reader != null) { (reader as IDisposable).Dispose(); } if (docStream != null) { (docStream as IDisposable).Dispose(); } } } } catch (Exception ex) { return(GenerateExceptionInformation(ex, provider)); } finally { // Regardless of how we finish process this file, we need to find files redirected to this // one and regenerate them. if (alternateSourceFile == null) // We only redirect one level { FileInfo sourceFileInfo = new FileInfo(sourceFile); RunCustomTool( project.ProjectItems, projectItem, delegate(EnvDTE.ProjectItem matchItem) { #if VerifyUIThread Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); #endif VSProjectItem vsProjItem = matchItem.Object as VSProjectItem; if (vsProjItem != null) { string itemFile = (string)matchItem.Properties.Item("LocalPath").Value; EnvDTE.Document liveDoc; EnvDTE.TextDocument textDoc; string liveText = null; if (null != (liveDoc = matchItem.Document) && null != (textDoc = liveDoc.Object("TextDocument") as EnvDTE.TextDocument)) { liveText = textDoc.StartPoint.CreateEditPoint().GetText(textDoc.EndPoint); } try { using (FileStream fileStream = (liveText == null) ? new FileStream(itemFile, FileMode.Open, FileAccess.Read) : null) { using (XmlTextReader reader = new XmlTextReader((liveText == null) ? new StreamReader(fileStream) as TextReader : new StringReader(liveText))) { if (XmlNodeType.Element == reader.MoveToContent()) { if (reader.NamespaceURI == RedirectNamespace && reader.LocalName == RedirectElementName) { FileInfo targetSourceFileInfo = new FileInfo(itemFile.Substring(0, itemFile.LastIndexOf('\\') + 1) + reader.GetAttribute(RedirectTargetAttribute)); if (0 == string.Compare(sourceFileInfo.FullName, targetSourceFileInfo.FullName, true, CultureInfo.CurrentCulture)) { vsProjItem.RunCustomTool(); } } } } } } catch (XmlException) { // Swallow anything that Xml gripes about } } }); } } }