diff --git a/strictdoc/backend/sdoc_source_code/grammar.py b/strictdoc/backend/sdoc_source_code/grammar.py
index d28df43bc..b8ff20039 100644
--- a/strictdoc/backend/sdoc_source_code/grammar.py
+++ b/strictdoc/backend/sdoc_source_code/grammar.py
@@ -40,7 +40,7 @@
;
Req[noskipws]:
- uid = /(?!scope=)[A-Za-z][A-Za-z0-9\\-]+/
+ uid = /(?!scope=)[A-Za-z][A-Za-z0-9\\-\\_]+/
;
SingleLineString[noskipws]:
diff --git a/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/example.sdoc b/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/example.sdoc
new file mode 100644
index 000000000..7a19621bd
--- /dev/null
+++ b/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/example.sdoc
@@ -0,0 +1,29 @@
+[DOCUMENT]
+TITLE: Example: Traceability between requirements and source files
+
+[REQUIREMENT]
+UID: REQ-001
+TITLE: Requirement to source reference
+STATEMENT: This requirement references the whole file from requirements.
+RELATIONS:
+- TYPE: File
+ VALUE: file.st
+
+[REQUIREMENT]
+UID: REQ-002
+TITLE: Source to requirement range reference
+STATEMENT: This requirement references a range in the file.
+
+[REQUIREMENT]
+UID: REQ-003
+TITLE: Requirement to source range reference
+STATEMENT: This requirement references a range in the file from the requirement.
+RELATIONS:
+- TYPE: File
+ VALUE: file.st
+ LINE_RANGE: 2, 4
+
+[REQUIREMENT]
+UID: SF_REQ-001
+TITLE: Custom requirement prefix range reference
+STATEMENT: This requirement has a custom prefix, and is referenced in the file.
diff --git a/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/file.st b/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/file.st
new file mode 100644
index 000000000..e96b25144
--- /dev/null
+++ b/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/file.st
@@ -0,0 +1,18 @@
+// @relation(SF_REQ-001, scope=file)
+FUNCTION_BLOCK FB_Test
+VAR_INPUT
+ i_xA: BOOL;
+ i_xB: BOOL;
+END_VAR
+VAR_OUTPUT
+ o_xX: BOOL;
+END_VAR
+
+// --- BEGIN IMPLEMENTATION ---
+// @relation(REQ-002, scope=range_start)
+IF i_xA AND i_xB THEN
+ o_xX := TRUE;
+ELSE
+ o_xX := FALSE;
+END_IF
+// @relation(REQ-002, scope=range_end)
diff --git a/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/strictdoc.toml b/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/strictdoc.toml
new file mode 100644
index 000000000..bf7608a9a
--- /dev/null
+++ b/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/strictdoc.toml
@@ -0,0 +1,9 @@
+[project]
+
+features = [
+ "REQUIREMENT_TO_SOURCE_TRACEABILITY",
+]
+
+include_source_paths = [
+ "file.st",
+]
diff --git a/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/test.itest b/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/test.itest
new file mode 100644
index 000000000..29063b1f6
--- /dev/null
+++ b/tests/integration/features/source_code_traceability/_file_types/66_st_structured_text_user_report/test.itest
@@ -0,0 +1,31 @@
+#
+# This test verifies that the Structured Text (PLCs) format is properly recognized
+# by StrictDoc. In addition, based on a user report, this test verifies that the
+# markers with mixed slashes and underscored are recognized correctly, e.g., SF_REQ-001.
+#
+# Original issue: https://github.com/strictdoc-project/strictdoc/discussions/2568.
+#
+# @relation(SDOC-SRS-142, scope=file)
+#
+
+RUN: %strictdoc export %S --output-dir %T | filecheck %s --dump-input=fail
+CHECK: Published: Example: Traceability between requirements and source files
+
+RUN: %check_exists --file "%T/html/_source_files/file.st.html"
+
+RUN: %cat %T/html/%THIS_TEST_FOLDER/example.html | filecheck %s --dump-input=fail --check-prefix CHECK-HTML
+CHECK-HTML:
+CHECK-HTML:
+CHECK-HTML:
+CHECK-HTML:
+
+RUN: %cat %T/html/_source_files/file.st.html | filecheck %s --dump-input=fail --check-prefix CHECK-SOURCE-FILE
+
+# Verify that the Text lexer is selected for this file as Pygments does not
+# support the ST syntax at the moment.
+CHECK-SOURCE-FILE: /* Lexer: Text only */
+# Verify that the link that points back to requirement is displayed correctly.
+CHECK-SOURCE-FILE: href="../66_st_structured_text_user_report/example.html#REQ-001"
+# Verify that the range link is displayed correctly.
+CHECK-SOURCE-FILE: href="../_source_files/file.st.html#REQ-001#1#18"
+CHECK-SOURCE-FILE: href="../_source_files/file.st.html#SF_REQ-001#1#18"
diff --git a/tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_general.py b/tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_general.py
index b61d78c1e..2ddc27ee9 100644
--- a/tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_general.py
+++ b/tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_general.py
@@ -78,6 +78,31 @@ def test_002_two_range_markers():
assert marker_4.ng_range_line_begin == 6
+def test_003_marker_with_dashes_and_underscores():
+ """
+ Verifies that SF_REQ-001 markers can be parsed (identifiers with mixed _ and -).
+
+ Bug report: https://github.com/strictdoc-project/strictdoc/discussions/2568.
+ """
+
+ source_input = """\
+# @relation(SF_REQ-001, scope=file)
+CONTENT 1
+CONTENT 2
+CONTENT 3
+""".lstrip()
+
+ reader = SourceFileTraceabilityReader()
+
+ document = reader.read(source_input)
+ markers = document.markers
+ assert markers[0].reqs == ["SF_REQ-001"]
+ assert markers[0].is_begin()
+ assert markers[0].ng_source_line_begin == 1
+ assert markers[0].ng_range_line_begin == 1
+ assert markers[0].ng_range_line_end == 4
+
+
def test_005_no_markers():
source_input = """
def hello_world_2():