<commit_before>import Foundation

extension Libxml2 {
    /// Text nodes.  Cannot have child nodes (for now, not sure if we will need them).
    ///
    class TextNode: Node, EditableNode, LeafNode {

        fileprivate var contents: String

        // MARK: - CustomReflectable
        
        override public var customMirror: Mirror {
            get {
                return Mirror(self, children: ["type": "text", "name": name, "text": contents, "parent": parent.debugDescription], ancestorRepresentation: .suppressed)
            }
        }
        
        // MARK: - Initializers
        
        init(text: String, editContext: EditContext? = nil) {
            contents = text

            super.init(name: "text", editContext: editContext)
        }

        /// Node length.
        ///
        override func length() -> Int {
            return contents.characters.count
        }
        
        // MARK: - Editing: Atomic Operations
        
        /// Appends the specified string.  The input data is assumed to be sanitized, which means
        /// this method does not perform verifications or cleanups on it.
        ///
        /// - Parameters:
        ///     - string: the string to append to the node.
        ///
        private func append(sanitizedString string: String) {
            registerUndoForAppend(appendedLength: string.characters.count)
            contents.append(string)
        }
        
        /// Prepends the specified string.  The input data is assumed to be sanitized, which means
        /// this method does not perform verifications or cleanups on it.
        ///
        /// - Parameters:
        ///     - string: the string to prepend to the node.
        ///
        private func prepend(sanitizedString string: String) {
            registerUndoForPrepend(prependedLength: string.characters.count)
            contents = "\(string)\(contents)"
        }

        // MARK: - EditableNode

        func append(_ string: String) {
            
            let components = string.components(separatedBy: String(.newline))
            
            if components.count == 1 {
                append(sanitizedString: string)
            } else {
                
                guard let parent = parent else {
                    assertionFailure("This method cannot process newlines if the node's parent isn't set.")
                    return
                }

                var insertionIndex = parent.indexOf(childNode: self)

                for (componentIndex, component) in components.enumerated() {
                    if componentIndex == 0 {
                        append(sanitizedString: component)
                        
                        insertionIndex = insertionIndex + 1
                    } else {
                        let breakNode = ElementNode.break()
                        let textNode = TextNode(text: component, editContext: editContext)
                        
                        parent.insert(breakNode, at: insertionIndex)
                        parent.insert(textNode, at: insertionIndex + 1)
                        
                        insertionIndex = insertionIndex + 2
                    }
                }
            }
        }

        func deleteCharacters(inRange range: NSRange) {

            guard let range = contents.rangeFromNSRange(range) else {
                fatalError("The specified range is out of bounds.")
            }
            
            deleteCharacters(inRange: range)
        }
        
        func deleteCharacters(inRange range: Range<String.Index>) {
            
            registerUndoForDeleteCharacters(inRange: range)
            contents.removeSubrange(range)
        }
        
        func prepend(_ string: String) {
            registerUndoForPrepend(prependedLength: string.characters.count)
            contents = "\(string)\(contents)"
        }
<commit_msg>TextNode's prepend now parses newlines.<commit_after>import Foundation

extension Libxml2 {
    /// Text nodes.  Cannot have child nodes (for now, not sure if we will need them).
    ///
    class TextNode: Node, EditableNode, LeafNode {

        fileprivate var contents: String

        // MARK: - CustomReflectable
        
        override public var customMirror: Mirror {
            get {
                return Mirror(self, children: ["type": "text", "name": name, "text": contents, "parent": parent.debugDescription], ancestorRepresentation: .suppressed)
            }
        }
        
        // MARK: - Initializers
        
        init(text: String, editContext: EditContext? = nil) {
            contents = text

            super.init(name: "text", editContext: editContext)
        }

        /// Node length.
        ///
        override func length() -> Int {
            return contents.characters.count
        }
        
        // MARK: - Editing: Atomic Operations
        
        /// Appends the specified string.  The input data is assumed to be sanitized, which means
        /// this method does not perform verifications or cleanups on it.
        ///
        /// - Parameters:
        ///     - string: the string to append to the node.
        ///
        private func append(sanitizedString string: String) {
            registerUndoForAppend(appendedLength: string.characters.count)
            contents.append(string)
        }
        
        /// Prepends the specified string.  The input data is assumed to be sanitized, which means
        /// this method does not perform verifications or cleanups on it.
        ///
        /// - Parameters:
        ///     - string: the string to prepend to the node.
        ///
        private func prepend(sanitizedString string: String) {
            registerUndoForPrepend(prependedLength: string.characters.count)
            contents = "\(string)\(contents)"
        }

        // MARK: - EditableNode

        func append(_ string: String) {
            let components = string.components(separatedBy: String(.newline))
            
            if components.count == 1 {
                append(sanitizedString: string)
            } else {
                
                guard let parent = parent else {
                    assertionFailure("This method cannot process newlines if the node's parent isn't set.")
                    return
                }

                var insertionIndex = parent.indexOf(childNode: self)

                for (componentIndex, component) in components.enumerated() {
                    if componentIndex == 0 {
                        append(sanitizedString: component)
                        
                        insertionIndex = insertionIndex + 1
                    } else {
                        let breakNode = ElementNode.break()
                        let textNode = TextNode(text: component, editContext: editContext)
                        
                        parent.insert(breakNode, at: insertionIndex)
                        parent.insert(textNode, at: insertionIndex + 1)
                        
                        insertionIndex = insertionIndex + 2
                    }
                }
            }
        }

        func deleteCharacters(inRange range: NSRange) {

            guard let range = contents.rangeFromNSRange(range) else {
                fatalError("The specified range is out of bounds.")
            }
            
            deleteCharacters(inRange: range)
        }
        
        func deleteCharacters(inRange range: Range<String.Index>) {
            
            registerUndoForDeleteCharacters(inRange: range)
            contents.removeSubrange(range)
        }
        
        func prepend(_ string: String) {
            let components = string.components(separatedBy: String(.newline))
            
            if components.count == 1 {
                prepend(sanitizedString: string)
            } else {
                
                guard let parent = parent else {
                    assertionFailure("This method cannot process newlines if the node's parent isn't set.")
                    return
                }
                
                let componentsCount = components.count
                var insertionIndex = parent.indexOf(childNode: self)
                
                for (componentIndex, component) in components.enumerated() {
                    if componentIndex == componentsCount - 1 {
                        prepend(sanitizedString: component)
                    } else {
                        let textNode = TextNode(text: component, editContext: editContext)
                        let breakNode = ElementNode.break()
                        
                        parent.insert(textNode, at: insertionIndex)
                        parent.insert(breakNode, at: insertionIndex + 1)
                        
                        insertionIndex = insertionIndex + 2
                    }
                }
            }
        }

{'fraction_non_alphanumeric': 0.07770870337477798, 'fraction_numerical': 0.0017761989342806395, 'mean_word_length': 1.350991649269311, 'pattern_counts': {'":': 8, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, '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': '512740', 'n_tokens_mistral': 2146, 'n_tokens_neox': 1987, 'n_words': 590}