<commit_before>/// Contains project file comparion tools for MSBuild project files.
module Fake.MSBuild.ProjectSystem

open Fake
open System.Xml
open System.Xml.Linq
open XMLHelper

/// A small abstraction over MSBuild project files.
type ProjectFile(projectFileName:string,documentContent : string) =
    let document = XMLDoc documentContent

    let nsmgr = 
        let nsmgr = new XmlNamespaceManager(document.NameTable)
        nsmgr.AddNamespace("default", document.DocumentElement.NamespaceURI)
        nsmgr

    let compileNodesXPath = "/default:Project/default:ItemGroup/default:Compile"
    let getCompileNodes (document:XmlDocument) =         
        [for node in document.SelectNodes(compileNodesXPath,nsmgr) -> node]

    let getFileAttributes (document:XmlDocument) = 
        let xpath = compileNodesXPath + "/@Include"
        [for attr in document.SelectNodes(xpath,nsmgr) -> attr]

    let files = getFileAttributes document |> List.map (fun attr -> attr.InnerText)
    
    /// Read a Project from a FileName
    static member FromFile(projectFileName) = new ProjectFile(projectFileName,ReadFileAsString projectFileName)

    /// Saves the project file
    member x.Save() = document.Save(projectFileName)

    /// Add a file to the Compile nodes
    member x.AddFile fileName =        
        let document = XMLDoc documentContent // we create a copy and work immutable
        let node = getCompileNodes document |> Seq.last
        let newNode = node.CloneNode(false) :?> XmlElement
        newNode.SetAttribute("Include",fileName)
        
        node.ParentNode.AppendChild(newNode) |> ignore
        new ProjectFile(projectFileName,document.OuterXml)

    /// Removes a file from the Compile nodes
    member x.RemoveFile fileName =        
        let document = XMLDoc documentContent // we create a copy and work immutable
        let node = 
            getCompileNodes document 
            |> List.filter (fun node -> node.Attributes.["Include"].InnerText = fileName) 
            |> Seq.last  // we remove the last one to make easier to remove duplicates

        node.ParentNode.RemoveChild node |> ignore

        new ProjectFile(projectFileName,document.OuterXml)

    /// All files which are in "Compile" sections
    member x.Files = files

    /// Finds duplicate files which are in "Compile" sections
    member x.FindDuplicateFiles() = 
        [let d = System.Collections.Generic.Dictionary()
         for i in x.Files do
            match d.TryGetValue(i) with
            | false,_    -> d.[i] <- false         // first observance
            | true,false -> d.[i] <- true; yield i // second observance
            | true,true  -> ()                     // already seen at least twice
        ]

    member x.RemoveDuplicates() =
        x.FindDuplicateFiles()
        |> List.fold (fun (project:ProjectFile) duplicate -> project.RemoveFile duplicate) x

    /// The project file name
    member x.ProjectFileName = projectFileName
<commit_msg>Smaller cleanup in the ProjectSystem.fs<commit_after>/// Contains project file comparion tools for MSBuild project files.
module Fake.MSBuild.ProjectSystem

open Fake
open System.Collections.Generic
open System.Xml
open System.Xml.Linq
open XMLHelper

/// A small abstraction over MSBuild project files.
type ProjectFile(projectFileName:string,documentContent : string) =
    let document = XMLDoc documentContent

    let nsmgr = 
        let nsmgr = new XmlNamespaceManager(document.NameTable)
        nsmgr.AddNamespace("default", document.DocumentElement.NamespaceURI)
        nsmgr

    let compileNodesXPath = "/default:Project/default:ItemGroup/default:Compile"
    let getCompileNodes (document:XmlDocument) =         
        [for node in document.SelectNodes(compileNodesXPath,nsmgr) -> node]

    let getFileAttribute (node:XmlNode) = node.Attributes.["Include"].InnerText

    /// Read a Project from a FileName
    static member FromFile(projectFileName) = new ProjectFile(projectFileName,ReadFileAsString projectFileName)

    /// Saves the project file
    member x.Save(?fileName) = document.Save(defaultArg fileName projectFileName)

    /// Add a file to the Compile nodes
    member x.AddFile fileName =        
        let document = XMLDoc documentContent // we create a copy and work immutable
        let node = getCompileNodes document |> Seq.last
        let newNode = node.CloneNode(false) :?> XmlElement
        newNode.SetAttribute("Include",fileName)
        
        node.ParentNode.AppendChild(newNode) |> ignore
        new ProjectFile(projectFileName,document.OuterXml)

    /// Removes a file from the Compile nodes
    member x.RemoveFile fileName =        
        let document = XMLDoc documentContent // we create a copy and work immutable
        let node = 
            getCompileNodes document 
            |> List.filter (fun node -> getFileAttribute node = fileName) 
            |> Seq.last  // we remove the last one to make easier to remove duplicates

        node.ParentNode.RemoveChild node |> ignore

        new ProjectFile(projectFileName,document.OuterXml)

    /// All files which are in "Compile" sections
    member x.Files = getCompileNodes document |> List.map getFileAttribute

    /// Finds duplicate files which are in "Compile" sections
    member x.FindDuplicateFiles() = 
        [let d = Dictionary()
         for i in x.Files do
            match d.TryGetValue(i) with
            | false,_    -> d.[i] <- false         // first observance
            | true,false -> d.[i] <- true; yield i // second observance
            | true,true  -> ()                     // already seen at least twice
        ]

    member x.RemoveDuplicates() =
        x.FindDuplicateFiles()
        |> List.fold (fun (project:ProjectFile) duplicate -> project.RemoveFile duplicate) x

    /// The project file name
    member x.ProjectFileName = projectFileName

{'fraction_non_alphanumeric': 0.08391252754704187, 'fraction_numerical': 0.0, 'mean_word_length': 2.621853898096992, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 33, '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': '3507007', 'n_tokens_mistral': 1572, 'n_tokens_neox': 1508, 'n_words': 478}