Repository: SubtitleEdit/subtitleedit (2026-07-23T16:57:41Z)

Issue:
"fix dash dialog" does not work if the dash is a non-ASCII
[en.srt.txt](https://github.com/user-attachments/files/30299884/en.srt.txt)

I had this subtitle which seemed immune for the "fix dash dialog" I want to apply.

There is no way to see the character used is not an ASCII-dash.
There for I made this entry in `eng_OCRFixReplaceList_User.xml` and the other languages I'm using.

`    <RegEx find="–" replaceWith="-" />`

I don't know how it got in there, but I see no reason why I shouldn't do it.

Maybe you want to apply the rule for this character as well.

I got my fix with the translation.


Discussion:
    <!-- 
      turn these to - (ASCII 45) 
      – = En dash (Unicode U+2013, UTF-8 bytes E2 80 93)
      — = Em dash (Unicode U+2014)
      − = Minus sign (Unicode U+2212) -->
    <RegEx find="[–—−]" replaceWith="-" />

With ChatGPT I found more.


It is an en dash:

```
- = Hyphen-minus (ASCII, U+002D, decimal 45)

– = En dash (Unicode U+2013, UTF-8 bytes E2 80 93)
— = Em dash (Unicode U+2014)
− = Minus sign (Unicode U+2212)
```
Are you talking about Fix common errors and this rule???

<img width="859" height="36" alt="Image" src="https://github.com/user-attachments/assets/19e33191-6b84-4566-a411-cbe4d9f80bdc" />
Yes...
The dash is more than ascii 45.

I convert them (as of today) to ASCII 45, so have a fix.
I leave it up to you if you like to have it working for the other dashes.
thanks



Patch:
diff --git a/src/libse/Forms/FixCommonErrors/RemoveDialogFirstLineInNonDialogs.cs b/src/libse/Forms/FixCommonErrors/RemoveDialogFirstLineInNonDialogs.cs
index 916a8b8e2d..d480d58f27 100644
--- a/src/libse/Forms/FixCommonErrors/RemoveDialogFirstLineInNonDialogs.cs
+++ b/src/libse/Forms/FixCommonErrors/RemoveDialogFirstLineInNonDialogs.cs
@@ -1,4 +1,4 @@
-﻿using Nikse.SubtitleEdit.Core.Common;
+using Nikse.SubtitleEdit.Core.Common;
 using Nikse.SubtitleEdit.Core.Interfaces;
 
 namespace Nikse.SubtitleEdit.Core.Forms.FixCommonErrors
@@ -10,30 +10,35 @@ public static class Language
             public static string RemoveDialogFirstInNonDialogs { get; set; } = "Remove start dash in non dialogs";
         }
 
+        /// <summary>
+        /// Dash characters that can be used as dialog marker - see issue #12748.
+        /// </summary>
+        private static readonly char[] DashChars =
+        {
+            '-', // Hyphen-Minus (-, U+002D)
+            '‐', // Hyphen (‐, U+2010)
+            '‑', // Non-breaking hyphen (‑, U+2011)
+            '‒', // Figure dash (‒, U+2012)
+            '–', // En dash (–, U+2013)
+            '—', // Em dash (—, U+2014)
+            '―', // Horizontal bar (―, U+2015)
+            '−', // Minus sign (−, U+2212)
+        };
+
         public void Fix(Subtitle subtitle, IFixCallbacks callbacks)
         {
             var fixAction = Language.RemoveDialogFirstInNonDialogs;
             var noOfFixes = 0;
-            const char hyphenMinus = '-'; // Hyphen-Minus (-, U+002D)
-            const char hyphen = '‐'; // Hyphen (‐, U+2010)
-            for (int i = 0; i < subtitle.Paragraphs.Count; i++)
+            foreach (var p in subtitle.Paragraphs)
             {
-                var p = subtitle.Paragraphs[i];
                 var oldText = p.Text;
-                var text = p.Text;
-                var noHtml = HtmlUtil.RemoveHtmlTags(text, true).TrimStart();
-
-
-                var count = Utilities.CountTagInText(text, hyphenMinus) + Utilities.CountTagInText(text, hyphen);
-                if (count == 0 || !noHtml.StartsWith(hyphenMinus) && !noHtml.StartsWith(hyphen))
+                var noHtml = HtmlUtil.RemoveHtmlTags(oldText, true).TrimStart();
+                if (noHtml.Length == 0 || !IsDash(noHtml[0]))
                 {
                     continue;
                 }
 
-                // test the two different dashes
-                text = RemoveDash(text, noHtml, hyphenMinus); 
-                text = RemoveDash(text, noHtml, hyphen);
-
+                var text = RemoveDash(oldText, noHtml, noHtml[0]);
                 if (oldText != text && callbacks.AllowFix(p, fixAction))
                 {
                     p.Text = text;
@@ -41,50 +46,49 @@ public void Fix(Subtitle subtitle, IFixCallbacks callbacks)
                     callbacks.AddFixToListView(p, fixAction, oldText, p.Text);
                 }
             }
+
             callbacks.UpdateFixStatus(noOfFixes, Language.RemoveDialogFirstInNonDialogs);
         }
 
         private static string RemoveDash(string text, string noHtml, char dashChar)
         {
-            var count = Utilities.CountTagInText(text, dashChar);
-            if (count == 1)
+            if (CountDashes(noHtml) == 1)
             {
-                text = RemoveFirstDash(text, dashChar);
+                return RemoveFirstDash(text, dashChar);
             }
-            else if (count > 1)
+
+            // more than one dash - only remove the first one if the others cannot be a dialog marker
+            if (StartsSentenceWithDash(noHtml))
             {
-                var lines = noHtml.SplitToLines();
-                if (lines.Count == 1)
-                {
-                    if (!noHtml.Contains(". " + dashChar) && !noHtml.Contains("! " + dashChar) && !noHtml.Contains("? " + dashChar))
-                    {
-                        text = RemoveFirstDash(text, dashChar);
-                    }
-                }
-                else if (lines.Count == 2)
-                {
-                    if (!noHtml.Contains(". " + dashChar) && !noHtml.Contains("! " + dashChar) && !noHtml.Contains("? " + dashChar) && !lines[1].StartsWith(dashChar))
-                    {
-                        text = RemoveFirstDash(text, dashChar);
-                    }
-                }
-                else if (lines.Count == 3)
+                return text;
+            }
+
+            var lines = noHtml.SplitToLines();
+            if (lines.Count > 3)
+            {
+                return text;
+            }
+
+            for (var i = 1; i < lines.Count; i++)
+            {
+                var line = lines[i].TrimStart();
+                if (line.Length > 0 && IsDash(line[0]))
                 {
-                    if (!noHtml.Contains(". " + dashChar) && !noHtml.Contains("! " + dashChar) && !noHtml.Contains("? " + dashChar) &&
-                        !lines[1].StartsWith(dashChar) &&
-                        !lines[2].StartsWith(dashChar))
-                    {
-                        text = RemoveFirstDash(text, dashChar);
-                    }
+                    return text;
                 }
             }
 
-            return text;
+            return RemoveFirstDash(text, dashChar);
         }
 
         private static string RemoveFirstDash(string text, char dashChar)
         {
-            var idx = text.IndexOf(dashChar);
+            var idx = IndexOfDashOutsideTag(text, dashChar);
+            if (idx < 0)
+            {
+                return text;
+            }
+
             if (idx + 1 < text.Length && text[idx + 1] == ' ')
             {
                 text = text.Remove(idx + 1, 1);
@@ -99,5 +103,75 @@ private static string RemoveFirstDash(string text, char dashChar)
 
             return text;
         }
+
+        /// <summary>
+        /// Index of the first dash which is not inside an html/assa tag.
+        /// </summary>
+        private static int IndexOfDashOutsideTag(string text, char dashChar)
+        {
+            var insideTag = false;
+            for (var i = 0; i < text.Length; i++)
+            {
+                var ch = text[i];
+                if (ch == '<' || ch == '{')
+                {
+                    insideTag = true;
+                }
+                else if (ch == '>' || ch == '}')
+                {
+                    insideTag = false;
+                }
+                else if (!insideTag && ch == dashChar)
+                {
+                    return i;
+                }
+            }
+
+            return -1;
+        }
+
+        /// <summary>
+        /// True if a dash follows the end of a sentence, like "Hi there. - Hello!".
+        /// </summary>
+        private static bool StartsSentenceWithDash(string text)
+        {
+            for (var i = 2; i < text.Length; i++)
+            {
+                if (text[i - 1] == ' ' && IsDash(text[i]) &&
+                    (text[i - 2] == '.' || text[i - 2] == '!' || text[i - 2] == '?'))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        private static int CountDashes(string text)
+        {
+            var count = 0;
+            foreach (var ch in text)
+            {
+                if (IsDash(ch))
+                {
+                    count++;
+                }
+            }
+
+            return count;
+        }
+
+        private static bool IsDash(char ch)
+        {
+            foreach (var dashChar in DashChars)
+            {
+                if (ch == dashChar)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
     }
 }


Test patch:
diff --git a/tests/libse/Forms/FixCommonErrors/RemoveDialogFirstLineInNonDialogsTest.cs b/tests/libse/Forms/FixCommonErrors/RemoveDialogFirstLineInNonDialogsTest.cs
new file mode 100644
index 0000000000..9accb47b78
--- /dev/null
+++ b/tests/libse/Forms/FixCommonErrors/RemoveDialogFirstLineInNonDialogsTest.cs
@@ -0,0 +1,77 @@
+using Nikse.SubtitleEdit.Core.Common;
+using Nikse.SubtitleEdit.Core.Forms.FixCommonErrors;
+
+namespace LibSETests.Forms.FixCommonErrors;
+
+public class RemoveDialogFirstLineInNonDialogsTest
+{
+    private static string Fix(string input)
+    {
+        var subtitle = new Subtitle();
+        subtitle.Paragraphs.Add(new Paragraph(input, 0, 2000));
+        subtitle.Renumber();
+        new RemoveDialogFirstLineInNonDialogs().Fix(subtitle, new EmptyFixCallback());
+        return subtitle.Paragraphs[0].Text;
+    }
+
+    [Theory]
+    [InlineData("-")] // Hyphen-Minus (U+002D)
+    [InlineData("‐")] // Hyphen (U+2010)
+    [InlineData("‑")] // Non-breaking hyphen (U+2011)
+    [InlineData("‒")] // Figure dash (U+2012)
+    [InlineData("–")] // En dash (U+2013)
+    [InlineData("—")] // Em dash (U+2014)
+    [InlineData("―")] // Horizontal bar (U+2015)
+    [InlineData("−")] // Minus sign (U+2212)
+    public void RemovesStartDash_ForAllDashCharacters(string dash)
+    {
+        // issue #12748 - the dash is more than ASCII 45
+        Assert.Equal("Hi there!", Fix(dash + " Hi there!"));
+        Assert.Equal("Hi there!", Fix(dash + "Hi there!"));
+    }
+
+    [Theory]
+    [InlineData("-")]
+    [InlineData("–")]
+    [InlineData("—")]
+    public void KeepsStartDash_WhenTwoLineDialog(string dash)
+    {
+        var input = dash + " Hi there!" + Environment.NewLine + dash + " Hello!";
+        Assert.Equal(input, Fix(input));
+    }
+
+    [Theory]
+    [InlineData("-")]
+    [InlineData("–")]
+    [InlineData("—")]
+    public void KeepsStartDash_WhenOneLineDialog(string dash)
+    {
+        var input = dash + " Hi there! " + dash + " Hello!";
+        Assert.Equal(input, Fix(input));
+    }
+
+    [Fact]
+    public void KeepsStartDash_WhenSecondLineUsesAnotherDashCharacter()
+    {
+        var input = "- Hi there!" + Environment.NewLine + "– Hello!";
+        Assert.Equal(input, Fix(input));
+    }
+
+    [Fact]
+    public void RemovesStartDash_WhenTextContainsHyphenatedWord()
+    {
+        Assert.Equal("It's a well-known fact.", Fix("– It's a well-known fact."));
+    }
+
+    [Fact]
+    public void RemovesStartDash_InsideItalicTag()
+    {
+        Assert.Equal("<i>Hi there!</i>", Fix("<i>– Hi there!</i>"));
+    }
+
+    [Fact]
+    public void KeepsText_WhenNoStartDash()
+    {
+        Assert.Equal("Hi there – or not!", Fix("Hi there – or not!"));
+    }
+}