<commit_before>﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Ivony.Fluent;


namespace Ivony.Html
{
  /// <summary>
  /// 提供应用于 Document 特有的扩展方法
  /// </summary>
  public static class DocumentExtensions
  {

    /// <summary>
    /// 在文档中通过ID来查找元素
    /// </summary>
    /// <param name="document">要查找元素的文档</param>
    /// <param name="id">元素ID</param>
    /// <returns>找到的元素，没有符合要求的则返回null</returns>
    /// <exception cref="System.InvalidOperationException">找到多个ID相同的元素</exception>
    public static IHtmlElement GetElementById( this IHtmlDocument document, string id )
    {
      if ( document == null )
        throw new ArgumentNullException( "document" );
      if ( id == null )
        throw new ArgumentNullException( "id" );

      return AllElements( document ).SingleOrDefault( element => element.Attribute( "id" ).Value() == id );
    }


    /// <summary>
    /// 返回元素的唯一ID，如果没有ID属性，或者有但非唯一，返回null
    /// </summary>
    /// <param name="element">要标识的元素</param>
    /// <returns>元素的唯一ID。</returns>
    public static string Identity( this IHtmlElement element )
    {
      return Identity( element, false );
    }

    /// <summary>
    /// 返回元素的唯一ID，如果没有ID属性，或者有但非唯一，返回null
    /// </summary>
    /// <param name="element">要标识的元素</param>
    /// <param name="create">指示当没有唯一ID时是否创建一个</param>
    /// <returns>元素的唯一ID。</returns>
    public static string Identity( this IHtmlElement element, bool create )
    {
      return Identity( element, create, false );
    }

    /// <summary>
    /// 返回元素的唯一ID，没有ID属性，或者有但非唯一，返回null
    /// </summary>
    /// <param name="element">要标识的元素</param>
    /// <param name="create">指示当没有唯一ID时是否创建一个</param>
    /// <param name="ancestorsCreate">在创建ID的过程中，是否为没有唯一ID的父级也创建ID</param>
    /// <returns>元素的唯一ID。</returns>
    public static string Identity( this IHtmlElement element, bool create, bool ancestorsCreate )
    {

      if ( element == null )
        throw new ArgumentNullException( "element" );


      EnsureAllocated( element );

      var id = element.Attribute( "id" ).Value();

      if ( string.IsNullOrEmpty( id ) || !element.Document.AllElements().Where( e => e.Attribute( "id" ).Value() == id ).IsSingle() )
        id = null;

      if ( create && id == null )
        element.SetAttribute( "id", id = CreateIdentity( element, ancestorsCreate ) );


      return id;
    }


    /// <summary>
    /// 获取元素的唯一标识
    /// </summary>
    /// <param name="element">要获取标识的元素</param>
    /// <returns>唯一标识</returns>
    /// <remarks>
    /// 元素的唯一标识仅在文档结构不被修改时唯一，当文档结构变化时，元素的唯一标识将会改变，也不能确保唯一性
    /// </remarks>
    public static string Unique( this IHtmlElement element )
    {
      var id = element.Identity();

      if ( id == null )
        return CreateIdentity( element, false );
      else
        return id;
    }

    private static string CreateIdentity( IHtmlElement element, bool ancestorsCreate )
    {
      string parentId;

      var parentElement = element.Parent();
      if ( parentElement != null )
      {
        parentId = Identity( parentElement );
        if ( parentId == null )
        {
          if ( ancestorsCreate )
            parentId = Identity( parentElement, true, true );
          else
            parentId = CreateIdentity( parentElement, false );
        }
      }
      else
      {
        if ( element.Container is IHtmlDocument )
          parentId = null;
        else
          throw new InvalidOperationException();
      }

      var name = GetElementName( element );

      var builder = new StringBuilder();

      if ( !string.IsNullOrEmpty( parentId ) )
        builder.AppendFormat( "{0}_", parentId );

      builder.Append( name );

      if ( element.Siblings().Where( e => GetElementName( e ).EqualsIgnoreCase( GetElementName( element ) ) ).IsSingle() )
        return builder.ToString();



      var index = element.SiblingsBeforeSelf().Count( e => GetElementName( e ).EqualsIgnoreCase( GetElementName( element ) ) );

      builder.Append( index + 1 );

      var identity = builder.ToString();

      return EnsureUniqueness( identity, element.Document );
    }

    private static string EnsureUniqueness( string identity, IHtmlDocument document )
    {
      return EnsureUniqueness( identity, document.Descendants().Select( element => element.Attribute( "id" ).Value() ).NotNull() );
    }

    private static string EnsureUniqueness( string identity, IEnumerable<string> ExistsIdentities )
    {
      var id = identity;
      var postfix = 0;

      while ( ExistsIdentities.Contains( id ) )
        id = identity + "_" + postfix++;

      return id;
    }


    /// <summary>
    /// 确保节点是已分配在一个固定的文档上（未从 DOM 移除，也不是以碎片形式存在）
    /// </summary>
    /// <param name="node">要检查的节点</param>
    /// <exception cref="System.InvalidOperationException">如果节点没有被分配在一个固定的文档。</exception>
    public static void EnsureAllocated( this IHtmlNode node )<commit_msg>修正 CreateIdentity 的一个Bug<commit_after>﻿using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Ivony.Fluent;


namespace Ivony.Html
{
  /// <summary>
  /// 提供应用于 Document 特有的扩展方法
  /// </summary>
  public static class DocumentExtensions
  {

    /// <summary>
    /// 在文档中通过ID来查找元素
    /// </summary>
    /// <param name="document">要查找元素的文档</param>
    /// <param name="id">元素ID</param>
    /// <returns>找到的元素，没有符合要求的则返回null</returns>
    /// <exception cref="System.InvalidOperationException">找到多个ID相同的元素</exception>
    public static IHtmlElement GetElementById( this IHtmlDocument document, string id )
    {
      if ( document == null )
        throw new ArgumentNullException( "document" );
      if ( id == null )
        throw new ArgumentNullException( "id" );

      return AllElements( document ).SingleOrDefault( element => element.Attribute( "id" ).Value() == id );
    }


    /// <summary>
    /// 返回元素的唯一ID，如果没有ID属性，或者有但非唯一，返回null
    /// </summary>
    /// <param name="element">要标识的元素</param>
    /// <returns>元素的唯一ID。</returns>
    public static string Identity( this IHtmlElement element )
    {
      return Identity( element, false );
    }

    /// <summary>
    /// 返回元素的唯一ID，如果没有ID属性，或者有但非唯一，返回null
    /// </summary>
    /// <param name="element">要标识的元素</param>
    /// <param name="create">指示当没有唯一ID时是否创建一个</param>
    /// <returns>元素的唯一ID。</returns>
    public static string Identity( this IHtmlElement element, bool create )
    {
      return Identity( element, create, false );
    }

    /// <summary>
    /// 返回元素的唯一ID，没有ID属性，或者有但非唯一，返回null
    /// </summary>
    /// <param name="element">要标识的元素</param>
    /// <param name="create">指示当没有唯一ID时是否创建一个</param>
    /// <param name="ancestorsCreate">在创建ID的过程中，是否为没有唯一ID的父级也创建ID</param>
    /// <returns>元素的唯一ID。</returns>
    public static string Identity( this IHtmlElement element, bool create, bool ancestorsCreate )
    {

      if ( element == null )
        throw new ArgumentNullException( "element" );


      EnsureAllocated( element );

      var id = element.Attribute( "id" ).Value();

      if ( string.IsNullOrEmpty( id ) || !element.Document.AllElements().Where( e => e.Attribute( "id" ).Value() == id ).IsSingle() )
        id = null;

      if ( create && id == null )
        element.SetAttribute( "id", id = CreateIdentity( element, ancestorsCreate ) );


      return id;
    }


    /// <summary>
    /// 获取元素的唯一标识
    /// </summary>
    /// <param name="element">要获取标识的元素</param>
    /// <returns>唯一标识</returns>
    /// <remarks>
    /// 元素的唯一标识仅在文档结构不被修改时唯一，当文档结构变化时，元素的唯一标识将会改变，也不能确保唯一性
    /// </remarks>
    public static string Unique( this IHtmlElement element )
    {
      var id = element.Identity();

      if ( id == null )
        return CreateIdentity( element, false );
      else
        return id;
    }

    private static string CreateIdentity( IHtmlElement element, bool ancestorsCreate )
    {
      string parentId;

      var parentElement = element.Parent();
      if ( parentElement != null )
      {
        parentId = Identity( parentElement );
        if ( parentId == null )
        {
          if ( ancestorsCreate )
            parentId = Identity( parentElement, true, true );
          else
            parentId = CreateIdentity( parentElement, false );
        }
      }
      else
      {
        if ( element.Container is IHtmlDocument )
          parentId = null;
        else
          throw new InvalidOperationException();
      }

      var name = GetElementName( element );

      var builder = new StringBuilder();

      if ( !string.IsNullOrEmpty( parentId ) )
        builder.AppendFormat( "{0}_", parentId );

      builder.Append( name );



      string identity;

      if ( element.Siblings().Where( e => GetElementName( e ).EqualsIgnoreCase( GetElementName( element ) ) ).IsSingle() )
        identity = builder.ToString();



      var index = element.SiblingsBeforeSelf().Count( e => GetElementName( e ).EqualsIgnoreCase( GetElementName( element ) ) );
      builder.Append( index + 1 );

      identity = builder.ToString();
      return EnsureUniqueness( identity, element.Document );
    }

    private static string EnsureUniqueness( string identity, IHtmlDocument document )
    {
      return EnsureUniqueness( identity, document.Descendants().Select( element => element.Attribute( "id" ).Value() ).NotNull() );
    }

    private static string EnsureUniqueness( string identity, IEnumerable<string> ExistsIdentities )
    {
      var id = identity;
      var postfix = 0;

      while ( ExistsIdentities.Contains( id ) )
        id = identity + "_" + postfix++;

      return id;
    }


    /// <summary>
    /// 确保节点是已分配在一个固定的文档上（未从 DOM 移除，也不是以碎片形式存在）
    /// </summary>
    /// <param name="node">要检查的节点</param>
    /// <exception cref="System.InvalidOperationException">如果节点没有被分配在一个固定的文档。</exception>
    public static void EnsureAllocated( this IHtmlNode node )
{'fraction_non_alphanumeric': 0.13436846819946252, 'fraction_numerical': 0.0005971931919976113, 'mean_word_length': 2.553041018387553, 'pattern_counts': {'":': 0, '<': 105, '<?xml version=': 0, '>': 115, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3095562', 'n_tokens_mistral': 3301, 'n_tokens_neox': 3419, 'n_words': 687}