/// <summary>
        /// Distill all useful information of a hyperlink string, and put the found data in a ParsedLinkInfo structure
        /// </summary>
        /// <param name="eclSession">The ECL Session provides access to the external items. </param>
        /// <param name="elementName">The name of the element being processed</param>
        /// <param name="hyperLinkText">The full tag to parse.</param>
        /// <param name="endStartTag">Within the hyperLinkText, where end-tag ends</param>
        /// <returns>A newly created ParsedLinkInfo object with all the found information filled out</returns>
        private ParsedLinkInfo ParseLinkInfo(IEclSession eclSession, string elementName, string hyperLinkText, int endStartTag)
        {
            string elementStartTagOpen = "<" + elementName;
            ParsedLinkInfo linkInfo = new ParsedLinkInfo();

            // Determine hyperlink parts
            string startTag = hyperLinkText.Substring(0, endStartTag + 1);
            string startTagAttributes = hyperLinkText.Substring(elementStartTagOpen.Length, endStartTag - elementStartTagOpen.Length);

            linkInfo.LinkElementName = elementName;

            // Process attributes
            IDictionary<string, string> tridionAttributes = new Dictionary<string, string>();
            linkInfo.RemainingAttributes = ParseTridionTagAttributes(startTagAttributes, tridionAttributes);

            // Determine target URI
            if (!tridionAttributes.ContainsKey(AttributeNameTridionHref))
            {
                throw new InvalidDataException(string.Format("Could not find attribute {1} in tag {0}", startTag, "tridion:href"));
            }
            string tridionHrefValue = tridionAttributes[AttributeNameTridionHref];
            string targetUriString = ProcessExpression(tridionHrefValue);

            // Check link URI and determine link-type
            if (targetUriString.StartsWith("ecl:"))
            {
                // resolve into stub uri
                targetUriString = eclSession.GetOrCreateTcmUriFromEclUri(eclSession.HostServices.CreateEclUri(targetUriString));
            }
            if (!TcmUri.IsValid(targetUriString))
            {
                throw new Exception(string.Format("Could not process href value '{0}' into uri", tridionHrefValue));
            }
            linkInfo.TargetUri = new TcmUri(targetUriString);
            if (linkInfo.TargetUri.ItemType == ItemType.Page)
            {
                linkInfo.LinkType = AttributeTridionTypePage;
            }
            // Check for override of type in link itself
            if (tridionAttributes.ContainsKey(AttributeNameTridionType))
            {
                linkInfo.LinkType = tridionAttributes[AttributeNameTridionType];
            }

            // Check for anchor attribute
            if (tridionAttributes.ContainsKey(AttributeNameTridionAnchor))
            {
                string anchorValue = ProcessExpression(tridionAttributes[AttributeNameTridionAnchor]);
                if (!String.IsNullOrEmpty(anchorValue))
                {
                    linkInfo.Anchor = anchorValue;
                }
            }

            // Output attribute (only for multimedia)
            if (tridionAttributes.ContainsKey(AttributeNameTargetAttribute))
            {
                linkInfo.TargetAttribute = tridionAttributes[AttributeNameTargetAttribute];
            }

            if ((linkInfo.LinkType == AttributeTridionTypeComponent) && String.IsNullOrEmpty(linkInfo.Anchor))
            {
                // for component links where nothing is specified specifically do not add an anchor
                linkInfo.Anchor = "false";
            }

            if (tridionAttributes.ContainsKey(AttributeNameTridionVariantid))
            {
                linkInfo.VariantId = tridionAttributes[AttributeNameTridionVariantid];
            }

            return linkInfo;
        }
        /// <summary>
        /// Distill all useful information of a hyperlink node, and remove the existing tridion: attributes from the node
        /// </summary>
        /// <param name="linkNode">The element node containing a tridion:href attribute being processed</param>
        private ParsedLinkInfo ParseLinkInfo(XmlNode linkNode)
        {
            // Set up data not in attributes
            ParsedLinkInfo linkInfo = new ParsedLinkInfo();
            StringBuilder sb = new StringBuilder();
            XmlAttributeCollection linkAttributeNodes = linkNode.Attributes;
            if (linkAttributeNodes != null)
            {
                IList<XmlAttribute> linkAttributes = linkAttributeNodes.Cast<XmlAttribute>().ToList();

                linkInfo.LinkElementName = linkNode.LocalName;

                foreach (XmlAttribute linkAttribute in linkAttributes)
                {
                    if (linkAttribute.NamespaceURI == TemplateUtilities.TridionNamespace)
                    {
                        // Remove attribute from original node
                        linkAttributeNodes.Remove(linkAttribute);

                        // So what attribute is it?
                        string attName = linkAttribute.LocalName;
                        string value = linkAttribute.Value;
                        switch (attName)
                        {
                            case AttributeNameTridionType:
                                linkInfo.LinkType = value;
                                break;
                            case AttributeNameTridionHref:
                                {
                                    string targetUriString = ProcessExpression(value);
                                    if (!TcmUri.IsValid(targetUriString))
                                    {
                                        throw new InvalidDataException(string.Format("Could not process href value '{0}' into uri", value));
                                    }
                                    linkInfo.TargetUri = new TcmUri(targetUriString);

                                    if (linkInfo.TargetUri.ItemType == ItemType.Page)
                                    {
                                        linkInfo.LinkType = AttributeTridionTypePage;
                                    }
                                }
                                break;
                            case AttributeNameTargetAttribute:
                                linkInfo.TargetAttribute = value;
                                break;
                            case AttributeNameTridionAnchor:
                                linkInfo.Anchor = value;
                                break;
                            case AttributeNameTridionVariantid:
                                linkInfo.VariantId = value;
                                break;
                            default:
                                sb.Append(string.Format("{0}=\"{1}\" ", attName, value));
                                break;
                        }
                    }
                }
            }
            if (sb.Length > 0)
            {
                linkInfo.RemainingAttributes = sb.ToString();
            }

            if (linkInfo.TargetUri == null)
            {
                throw new InvalidDataException(string.Format("Internal error: {0}", "Attribute 'href' must be present"));
            }

            if ((linkInfo.LinkType == AttributeTridionTypeComponent) && String.IsNullOrEmpty(linkInfo.Anchor))
            {
                // For component links where nothing is specified specifically do not add an anchor
                linkInfo.Anchor = "false";
            }

            return linkInfo;
        }
        /// <summary>
        /// Render and possibly publish an ECL item
        /// </summary>
        /// <param name="linkInfo">Parsed link info</param>
        /// <param name="item">EclItemInPackage object</param>
        /// <returns>template fragment for (published) ECL item or fragment with external link</returns>
        private string RenderEclItem(ParsedLinkInfo linkInfo, EclItemInPackage item)
        {
            // parse remaining attributes to determine width and height 
            IDictionary<string, string> remainingAttributes = new Dictionary<string, string>();
            string width;
            string height;
            ParseRemainingAttributes(linkInfo.RemainingAttributes, remainingAttributes, out width, out height);

            // convert remaining attributes into IList<ITemplateAttribute>
            IList<ITemplateAttribute> attributes = new List<ITemplateAttribute>();
            foreach (var attribute in remainingAttributes)
            {
                attributes.Add(new NodeKeyValuePair(new KeyValuePair<string, string>(attribute.Key, attribute.Value)));
            }

            // we need a variantId because we could have to publish the ECL item using AddBinary  
            string variantId = DefaultVariantId;
            if (!string.IsNullOrEmpty(linkInfo.VariantId))
            {
                variantId = linkInfo.VariantId;
            }
            else if (!(string.IsNullOrEmpty(width) && !(string.IsNullOrEmpty(height))))
            {
                variantId = string.Format("{0}x{1}", width, height);
            }

            string itemUrl = ResolveEclItem(item, attributes, variantId, _targetStructureGroup);
            attributes.Add(new NodeKeyValuePair(new KeyValuePair<string, string>(linkInfo.TargetAttribute, itemUrl)));

            if (linkInfo.LinkElementName.ToUpperInvariant() == "IMG")
            {
                string embedFragment = item.EclItem.GetTemplateFragment(attributes);
                if (!string.IsNullOrEmpty(embedFragment))
                {
                    // if provider can embed item we return embed fragment
                    return embedFragment;
                }

                string externalLink = item.EclItem.GetDirectLinkToPublished(attributes);

                if (!string.IsNullOrEmpty(externalLink))
                {
                    StringBuilder result = new StringBuilder();

                    result.AppendFormat("<img ");
                    foreach (var att in attributes)
                    {
                        string value = att.Value;
                        if (att.Name.ToUpperInvariant() == "ALT")
                        {
                            value = item.EclItem.Title;
                        }

                        result.AppendFormat(" {0}=\"{1}\"", att.Name, HttpUtility.HtmlAttributeEncode(value));
                    }

                    result.AppendFormat(" />");
                    return result.ToString();
                }
            }

            // All <a> links and images that do not need special treatment processed by default finish actions
            // because we already resolve Ecl items with ResolveEclItem
            return null;
        }