| File: XmlElementContext.cs | Web Access |
| Project: src\xdt\src\Microsoft.Web.XmlTransform\Microsoft.Web.XmlTransform.csproj (Microsoft.Web.XmlTransform) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.Text.RegularExpressions; using System.Diagnostics; using System.IO; using System.Globalization; using Microsoft.Web.XmlTransform.Properties; namespace Microsoft.Web.XmlTransform { internal class XmlElementContext : XmlNodeContext { #region private data members private XmlElementContext parentContext; private string xpath = null; private string parentXPath = null; private XmlDocument xmlTargetDoc; private IServiceProvider serviceProvider; private XmlNode transformNodes = null; private XmlNodeList targetNodes = null; private XmlNodeList targetParents = null; private XmlAttribute transformAttribute = null; private XmlAttribute locatorAttribute = null; private XmlNamespaceManager namespaceManager = null; #endregion public XmlElementContext(XmlElementContext parent, XmlElement element, XmlDocument xmlTargetDoc, IServiceProvider serviceProvider) : base(element) { this.parentContext = parent; this.xmlTargetDoc = xmlTargetDoc; this.serviceProvider = serviceProvider; } public T GetService<T>() where T : class { if (serviceProvider != null) { T service = serviceProvider.GetService(typeof(T)) as T; // now it is legal to return service that's null -- due to SetTokenizeAttributeStorage //Debug.Assert(service != null, String.Format(CultureInfo.InvariantCulture, "Service provider didn't provide {0}", typeof(ServiceType).Name)); return service; } else { Debug.Fail("No ServiceProvider"); return null; } } #region data accessors public XmlElement Element { get { return Node as XmlElement; } } public string XPath { get { if (xpath == null) { xpath = ConstructXPath(); } return xpath; } } public string ParentXPath { get { if (parentXPath == null) { parentXPath = ConstructParentXPath(); } return parentXPath; } } public Transform ConstructTransform(out string argumentString) { try { return CreateObjectFromAttribute<Transform>(out argumentString, out transformAttribute); } catch (Exception ex) { throw WrapException(ex); } } public int TransformLineNumber { get { IXmlLineInfo lineInfo = transformAttribute as IXmlLineInfo; if (lineInfo != null) { return lineInfo.LineNumber; } else { return LineNumber; } } } public int TransformLinePosition { get { IXmlLineInfo lineInfo = transformAttribute as IXmlLineInfo; if (lineInfo != null) { return lineInfo.LinePosition; } else { return LinePosition; } } } public XmlAttribute TransformAttribute { get { return transformAttribute; } } public XmlAttribute LocatorAttribute { get { return locatorAttribute; } } #endregion #region XPath construction private string ConstructXPath() { try { string argumentString; string parentPath = parentContext == null ? String.Empty : parentContext.XPath; Locator locator = CreateLocator(out argumentString); return locator.ConstructPath(parentPath, this, argumentString); } catch (Exception ex) { throw WrapException(ex); } } private string ConstructParentXPath() { try { string argumentString; string parentPath = parentContext == null ? String.Empty : parentContext.XPath; Locator locator = CreateLocator(out argumentString); return locator.ConstructParentPath(parentPath, this, argumentString); } catch (Exception ex) { throw WrapException(ex); } } private Locator CreateLocator(out string argumentString) { Locator locator = CreateObjectFromAttribute<Locator>(out argumentString, out locatorAttribute); if (locator == null) { argumentString = null; //avoid using singleton of "DefaultLocator.Instance", so unit tests can run parallel locator = new DefaultLocator(); } return locator; } #endregion #region Context information internal XmlNode TransformNode { get { if (transformNodes == null) { transformNodes = CreateCloneInTargetDocument(Element); } return transformNodes; } } internal XmlNodeList TargetNodes { get { if (targetNodes == null) { targetNodes = GetTargetNodes(XPath); } return targetNodes; } } internal XmlNodeList TargetParents { get { if (targetParents == null && parentContext != null) { targetParents = GetTargetNodes(ParentXPath); } return targetParents; } } #endregion #region Node helpers private XmlDocument TargetDocument { get { return xmlTargetDoc; } } private XmlNode CreateCloneInTargetDocument(XmlNode sourceNode) { XmlFileInfoDocument infoDocument = TargetDocument as XmlFileInfoDocument; XmlNode clonedNode; if (infoDocument != null) { clonedNode = infoDocument.CloneNodeFromOtherDocument(sourceNode); } else { XmlReader reader = new XmlTextReader(new StringReader(sourceNode.OuterXml)); clonedNode = TargetDocument.ReadNode(reader); } ScrubTransformAttributesAndNamespaces(clonedNode); return clonedNode; } private void ScrubTransformAttributesAndNamespaces(XmlNode node) { if (node.Attributes != null) { List<XmlAttribute> attributesToRemove = new List<XmlAttribute>(); foreach (XmlAttribute attribute in node.Attributes) { if (attribute.NamespaceURI == XmlTransformation.TransformNamespace) { attributesToRemove.Add(attribute); } else if (attribute.Prefix.Equals("xmlns") || attribute.Name.Equals("xmlns")) { attributesToRemove.Add(attribute); } else { attribute.Prefix = null; } } foreach (XmlAttribute attributeToRemove in attributesToRemove) { node.Attributes.Remove(attributeToRemove); } } // Do the same recursively for child nodes foreach (XmlNode childNode in node.ChildNodes) { ScrubTransformAttributesAndNamespaces(childNode); } } private XmlNodeList GetTargetNodes(string xpath) { XmlNamespaceManager mgr = GetNamespaceManager(); return TargetDocument.SelectNodes(xpath, GetNamespaceManager()); } private Exception WrapException(Exception ex) { return XmlNodeException.Wrap(ex, Element); } private Exception WrapException(Exception ex, XmlNode node) { return XmlNodeException.Wrap(ex, node); } private XmlNamespaceManager GetNamespaceManager() { if (namespaceManager == null) { XmlNodeList localNamespaces = Element.SelectNodes("namespace::*"); if (localNamespaces.Count > 0) { namespaceManager = new XmlNamespaceManager(Element.OwnerDocument.NameTable); foreach (XmlAttribute nsAttribute in localNamespaces) { string prefix = String.Empty; int index = nsAttribute.Name.IndexOf(':'); if (index >= 0) { prefix = nsAttribute.Name.Substring(index + 1); } else { prefix = "_defaultNamespace"; } namespaceManager.AddNamespace(prefix, nsAttribute.Value); } } else { namespaceManager = new XmlNamespaceManager(GetParentNameTable()); } } return namespaceManager; } private XmlNameTable GetParentNameTable() { if (parentContext == null) { return Element.OwnerDocument.NameTable; } else { return parentContext.GetNamespaceManager().NameTable; } } #endregion #region Named object creation private static Regex nameAndArgumentsRegex = null; private Regex NameAndArgumentsRegex { get { if (nameAndArgumentsRegex == null) { nameAndArgumentsRegex = new Regex(@"\A\s*(?<name>\w+)(\s*\((?<arguments>.*)\))?\s*\Z", RegexOptions.Compiled|RegexOptions.Singleline); } return nameAndArgumentsRegex; } } private string ParseNameAndArguments(string name, out string arguments) { arguments = null; System.Text.RegularExpressions.Match match = NameAndArgumentsRegex.Match(name); if (match.Success) { if (match.Groups["arguments"].Success) { CaptureCollection argumentCaptures = match.Groups["arguments"].Captures; if (argumentCaptures.Count == 1 && !String.IsNullOrEmpty(argumentCaptures[0].Value)) { arguments = argumentCaptures[0].Value; } } return match.Groups["name"].Captures[0].Value; } else { throw new XmlTransformationException(Resources.XMLTRANSFORMATION_BadAttributeValue); } } private ObjectType CreateObjectFromAttribute<ObjectType>(out string argumentString, out XmlAttribute objectAttribute) where ObjectType : class { objectAttribute = Element.Attributes.GetNamedItem(typeof(ObjectType).Name, XmlTransformation.TransformNamespace) as XmlAttribute; try { if (objectAttribute != null) { string typeName = ParseNameAndArguments(objectAttribute.Value, out argumentString); if (!String.IsNullOrEmpty(typeName)) { NamedTypeFactory factory = GetService<NamedTypeFactory>(); return factory.Construct<ObjectType>(typeName); } } } catch (Exception ex) { throw WrapException(ex, objectAttribute); } argumentString = null; return null; } #endregion #region Error reporting helpers internal bool HasTargetNode(out XmlElementContext failedContext, out bool existedInOriginal) { failedContext = null; existedInOriginal = false; if (TargetNodes.Count == 0) { failedContext = this; while (failedContext.parentContext != null && failedContext.parentContext.TargetNodes.Count == 0) { failedContext = failedContext.parentContext; } existedInOriginal = ExistedInOriginal(failedContext.XPath); return false; } return true; } internal bool HasTargetParent(out XmlElementContext failedContext, out bool existedInOriginal) { failedContext = null; existedInOriginal = false; if (TargetParents.Count == 0) { failedContext = this; while (failedContext.parentContext != null && !String.IsNullOrEmpty(failedContext.parentContext.ParentXPath) && failedContext.parentContext.TargetParents.Count == 0) { failedContext = failedContext.parentContext; } existedInOriginal = ExistedInOriginal(failedContext.XPath); return false; } return true; } private bool ExistedInOriginal(string xpath) { IXmlOriginalDocumentService service = GetService<IXmlOriginalDocumentService>(); if (service != null) { XmlNodeList nodeList = service.SelectNodes(xpath, GetNamespaceManager()); if (nodeList != null && nodeList.Count > 0) { return true; } } return false; } #endregion } }