@@ -556,6 +556,34 @@ def test_uip_prefix_followed_by_citation_single_chunk(self):
556556 assert cited [0 ].data == "<uip "
557557 assert cited [0 ].citation .end .sources [0 ].url == "https://example.com"
558558
559+ def test_escaped_quotes_in_title (self ):
560+ """Citation with backslash-escaped quotes in title is parsed correctly."""
561+ proc = CitationStreamProcessor ()
562+ text = r'Some text.<uip:cite title="The Peculiar Journey of \"Orange\"" url="https://example.com" />'
563+ events = proc .add_chunk (text )
564+ events .extend (proc .finalize ())
565+ cited = [e for e in events if e .citation is not None ]
566+ assert len (cited ) == 1
567+ assert cited [0 ].data == "Some text."
568+ source = cited [0 ].citation .end .sources [0 ]
569+ assert isinstance (source , UiPathConversationCitationSourceUrl )
570+ assert source .url == "https://example.com"
571+ assert source .title == 'The Peculiar Journey of "Orange"'
572+
573+ def test_escaped_quotes_in_title_streamed (self ):
574+ """Escaped quotes in title still work when streamed across chunks."""
575+ proc = CitationStreamProcessor ()
576+ events = proc .add_chunk (r'Text.<uip:cite title="Say \"hi' )
577+ # "Text." is emitted immediately; the partial tag is buffered
578+ assert len (events ) == 1
579+ assert events [0 ].data == "Text."
580+ assert events [0 ].citation is None
581+ events = proc .add_chunk (r' there\"" url="https://x.com" />' )
582+ events .extend (proc .finalize ())
583+ cited = [e for e in events if e .citation is not None ]
584+ assert len (cited ) == 1
585+ assert cited [0 ].citation .end .sources [0 ].title == 'Say "hi there"'
586+
559587
560588class TestExtractCitationsFromText :
561589 """Test cases for extract_citations_from_text function."""
@@ -689,6 +717,35 @@ def test_reference_without_page_number_skipped(self):
689717 assert cleaned == "UiPath reported earnings"
690718 assert citations == []
691719
720+ def test_escaped_quotes_in_title (self ):
721+ """Citation with escaped quotes in title is parsed and unescaped."""
722+ text = (
723+ r'A fact<uip:cite title="The \"Real\" Story" url="https://example.com" />'
724+ )
725+ cleaned , citations = extract_citations_from_text (text )
726+ assert cleaned == "A fact"
727+ assert len (citations ) == 1
728+ source = citations [0 ].sources [0 ]
729+ assert isinstance (source , UiPathConversationCitationSourceUrl )
730+ assert source .title == 'The "Real" Story'
731+ assert source .url == "https://example.com"
732+
733+ def test_escaped_quotes_in_title_debug_dump_repro (self ):
734+ """Reproduce the exact tag from the debug dump that was failing."""
735+ text = (
736+ r'some text.<uip:cite title="The Peculiar Journey of \"Orange\"" '
737+ r'url="https://www.vocabulary.com/articles/wordroutes/the-peculiar-journey-of-orange/" />'
738+ )
739+ cleaned , citations = extract_citations_from_text (text )
740+ assert cleaned == "some text."
741+ assert len (citations ) == 1
742+ source = citations [0 ].sources [0 ]
743+ assert source .title == 'The Peculiar Journey of "Orange"'
744+ assert (
745+ source .url
746+ == "https://www.vocabulary.com/articles/wordroutes/the-peculiar-journey-of-orange/"
747+ )
748+
692749 def test_different_sources_get_different_numbers (self ):
693750 """Different sources get incrementing numbers."""
694751 text = (
0 commit comments