הבלוג של גרי רשף

20/06/2011

היפוך חכם של טקסט עברי-לועזי

Filed under: Uncategorized — תגיות: , , , , — גרי רשף @ 20:50

בעייה מוכרת למי שעובד עם קבצי טקסט המשלבים עברית ולועזית היא שלעיתים העברית מתהפכת בשל הגדרות שונות של מערכות שונות, ואז יש להפוך את הטקסט בצורה חכמה, כלומר- את תתי המחרוזות העבריים יש להפוך ואת תתי המחרוזות הלועזיים יש להשאיר כמו שהם.
למשל- התקבל הטקסט " 012345ףשר ירג" הכולל את שמי בצורה משובשת ואת מספר חשבון הבנק שלי 012345 בצורה תקינה.
כעת יש להפוך את המחרוזת בצורה חכמה כך ש-ףשר ירג יחזור להיות גרי רשף, אבל 012345 ישאר ללא שינוי.

מכיוון שתקלה זו אופיינית לאתרי אינטרנט, בעיקר בימים בהם הסתבכנו עם עברית לוגית וויזואלית, נהוג היה לכתוב פונקציה ב-Java Script לטיפול בבעייה, אבל אני אנסה לעשות זאת ב-TSQL, וכדרכי- בעזרת CTE רקורסיבי במקום עיבוד ישיר של המחרוזת.

הבעייה מעט יותר מסובכת ממה שנראה במבט ראשון: לכאורה טקסט עברי הוא כל מה שכולל את האותיות א..ת ואת הסופיות ך..ץ, וטקסט לועזי את כל השאר, אך מה עם רווחים? מה קורה אם נקבל "עבש ראב" או אולי "עבש-ראב" (באר שבע או באר-שבע בהתאמה)? האם נהפוך את ה-"עבש" נוסיף רווח או מקף ונהפוך את ה-"ראב" כדי לקבל "שבע באר" או אולי "שבע-באר"? הרי ברור שכל הרצף של האותיות הוא מחרוזת עברית ולא שתי מחרוזות נפרדות!

נצטרך להגדיר שני קבועים – @Ltr שמגדיר את האותיות העבריות (רגילות וסופיות), ו-@Hrg שמגדיר את האותיות המשותפות.

בעיית המשך היא מה דינן של האותיות המשותפות בין מחרוזת עברית או לועזית, למשל "-אבג-abc-": המקף הראשון הוא כנראה חלק מהמחרוזת העברית "אבג", המקף השני ספק עברי ספק לועזי, והמקף האחרון בוודאי לועזי (שייך למחרוזת "abc"). אני אניח להלן שהראשון הוא עברי, אך בכל מקרה שבסמוך אליו – מימין או משאל – תהיה מחרוזת לועזית, הוא יהיה לועזי. במילים אחרות- אם תו משותף נמצא בתוך מחרוזת עברית או בתחילת מחרוזת עברית בתחילת המחרוזת כולה– הוא חלק מהמחרוזת העברית, ובכל מקרה אחר הוא לועזי. נכון שמקף תמים – בין אם הוא לועזי או עברי – נשאר מקף גם אם הופכים אותו, אבל הבעייה היא אם להפוך אותו עם כל הטקסט העברי או מה קורה אם במקום המקף מופיע רצף תווים משותפים כדוגמת "- –" ואז יש להחליט אם להפוך גם אותו ל-"– -" או לא.

לאחר הקדמה משעממת זו לגבי הבעיות וההנחות שהנחתי (כדי שניתן יהיה לדעת מה לשנות בקוד במקרה הצורך), ניגש לקוד:

Declare    @S Varchar(Max);

Set @S='tcd123-דהו-abc';

Declare @Ltr Varchar(max),

@Hrg Varchar(Max);

Select @Ltr='אבגדהוזחטיכלמנסעפצקרשתךםןףץ',

@Hrg=' -';

With T As

(Select S,

Mh1,–המופע הראשון של עברית ברוטו

Mh2,–המופע הראשון של עברית נטו

Mh3,–המופע האחרון של עברית נטו

Me1,–המופע הראשון של לועזית ברוטו

Me2,–המופע הראשון של לועזית נטו

Me3,–המופע האחרון של לועזית נטו

Mh1_,–המופע הראשון של עברית ברוטו, ולא- אורך המחרוזת

Mh2_,–המופע הראשון של עברית נטו, ולא- אורך המחרוזת

Me1_,–המופע הראשון של לועזית ברוטו, ולא- אורך המחרוזת

Me2_,–המופע הראשון של לועזית נטו, ולא- אורך המחרוזת

H,

Case When H=1 Then–עברית

Reverse(Left(S,Mh3))

Else–לועזית

Left(S,Mh2_-1) End S1,–הראש

Case When H=1 Then Right(S,DataLength(S)-Mh3) Else Right(S,DataLength(S)-Mh2_+1) End S2–הזנב

From (Select S,

Mh1,

Mh2,

Me2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S,Me2_-1))) Mh3,

Me1,

Me2,

Mh2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S,Mh2_-1))) Me3,

Mh1_,

Mh2_,

Me1_,

Me2_,

Case When Mh2>0 And (Mh1_<Me1_ Or Mh2_<Me2_) Then 1 Else 0 End H

From (Select @S S,

PatIndex('%['+@Ltr+@Hrg+']%',@S) Mh1,

PatIndex('%['+@Ltr+']%',@S) Mh2,

PatIndex('%[^'+@Ltr+']%',@S) Me1,

PatIndex('%[^'+@Ltr+@Hrg+']%',@S) Me2,

PatIndex('%['+@Ltr+@Hrg+']%',@S+'ע') Mh1_,

PatIndex('%['+@Ltr+']%',@S+'ע') Mh2_,

PatIndex('%[^'+@Ltr+']%',@S+'e') Me1_,

PatIndex('%[^'+@Ltr+@Hrg+']%',@S+'e') Me2_) T1) T2

Union All

Select S,

Mh1,–המופע הראשון של עברית ברוטו

Mh2,–המופע הראשון של עברית נטו

Mh3,–המופע האחרון של עברית נטו

Me1,–המופע הראשון של לועזית ברוטו

Me2,–המופע הראשון של לועזית נטו

Me3,–המופע האחרון של לועזית נטו

Mh1_,–המופע הראשון של עברית ברוטו, ולא- אורך המחרוזת

Mh2_,–המופע הראשון של עברית נטו, ולא- אורך המחרוזת

Me1_,–המופע הראשון של לועזית ברוטו, ולא- אורך המחרוזת

Me2_,–המופע הראשון של לועזית נטו, ולא- אורך המחרוזת

H,

S1+Case When H=1 Then–עברית

Reverse(Left(S2,Mh3))

Else–לועזית

Left(S2,Mh2_-1) End S1,–הראש

Case When H=1 Then Right(S2,Len(S2)-Mh3) Else Right(S2,DataLength(S2)-Mh2_+1) End S2–הזנב

From (Select S,

Mh1,

Mh2,

Me2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S2,Me2_-1))) Mh3,

Me1,

Me2,

Mh2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S2,Mh2_-1))) Me3,

Mh1_,

Mh2_,

Me1_,

Me2_,

S1,

S2,

Case When Mh2>0 And (Mh1_<Me1_ Or Mh2_<Me2_) Then 1 Else 0 End H

From (Select S,

PatIndex('%['+@Ltr+@Hrg+']%',S2) Mh1,

PatIndex('%['+@Ltr+']%',S2) Mh2,

PatIndex('%[^'+@Ltr+']%',S2) Me1,

PatIndex('%[^'+@Ltr+@Hrg+']%',S2) Me2,

PatIndex('%['+@Ltr+@Hrg+']%',S2+'ע') Mh1_,

PatIndex('%['+@Ltr+']%',S2+'ע') Mh2_,

PatIndex('%[^'+@Ltr+']%',S2+'e') Me1_,

PatIndex('%[^'+@Ltr+@Hrg+']%',S2+'e') Me2_,

S1,

S2

From T

Where IsNull(S2,")<>") T1) T2)

Select *

From T

Where IsNull(S2,")="

Option (MaxRecursion 0);

 

clip_image002

כפי שניתן לראות- האותיות החריגות אצלי הן רווח ומקף, אבל ניתן להגדיר עוד (סוגריים? סימן שאלה?..).

לצורכי חישוב אני בודק היכן מופיעה לראשונה עברית/לועזית נטו (ללא המשותפות), היכן ברוטו (עם המשותפות), והיכן המופע האחרון בתוך תת המחרוזת הראשונה ("ףשר ירג 01234" התו השביעי "ג" הוא התו העברי האחרון במחרוזת העברית הפותחת).

כדי למנוע בלבול- S זו המחרוזת המקורית,ו- S1 זו המחרוזת לאחר ההיפוך החכם.

אם נוותר על התנאי S2 Is Null נוכל לראות איך S1 צובר את תתי המחרוזות,

ו-S2 (הזנב) מתקצר עד שהופך ל-Null.

כיצד מתרגמים את הקוד הנ"ל לפונקציית משתמש? בעזרת מספר שינויים קוסמטיים (בשורה השניה יש דוגמה מוערת כיצד להשתמש):

בה):Create Function dbo.F_Reverse(@S Varchar(Max)) Returns Varchar(Max) As

Select dbo.F_Reverse('באר-שבע Beer-Sheva 1234אבגד');

Begin

Declare @Ltr Varchar(max),

@Hrg Varchar(Max),

@Rvrs Varchar(Max);

Select @Ltr='אבגדהוזחטיכלמנסעפצקרשתךםןףץ',

@Hrg=' -';

With T As

(Select S,

Mh1,–המופע הראשון של עברית ברוטו

Mh2,–המופע הראשון של עברית נטו

Mh3,–המופע האחרון של עברית נטו

Me1,–המופע הראשון של לועזית ברוטו

Me2,–המופע הראשון של לועזית נטו

Me3,–המופע האחרון של לועזית נטו

Mh1_,–המופע הראשון של עברית ברוטו, ולא- אורך המחרוזת

Mh2_,–המופע הראשון של עברית נטו, ולא- אורך המחרוזת

Me1_,–המופע הראשון של לועזית ברוטו, ולא- אורך המחרוזת

Me2_,–המופע הראשון של לועזית נטו, ולא- אורך המחרוזת

H,

Case When H=1 Then–עברית

Reverse(Left(S,Mh3))

Else–לועזית

Left(S,Mh2_-1) End S1,–הראש

Case When H=1 Then Right(S,DataLength(S)-Mh3) Else Right(S,DataLength(S)-Mh2_+1) End S2–הזנב

From (Select S,

Mh1,

Mh2,

Me2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S,Me2_-1))) Mh3,

Me1,

Me2,

Mh2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S,Mh2_-1))) Me3,

Mh1_,

Mh2_,

Me1_,

Me2_,

Case When Mh2>0 And (Mh1_<Me1_ Or Mh2_<Me2_) Then 1 Else 0 End H

From (Select @S S,

PatIndex('%['+@Ltr+@Hrg+']%',@S) Mh1,

PatIndex('%['+@Ltr+']%',@S) Mh2,

PatIndex('%[^'+@Ltr+']%',@S) Me1,

PatIndex('%[^'+@Ltr+@Hrg+']%',@S) Me2,

PatIndex('%['+@Ltr+@Hrg+']%',@S+'ע') Mh1_,

PatIndex('%['+@Ltr+']%',@S+'ע') Mh2_,

PatIndex('%[^'+@Ltr+']%',@S+'e') Me1_,

PatIndex('%[^'+@Ltr+@Hrg+']%',@S+'e') Me2_) T1) T2

Union All

Select S,

Mh1,–המופע הראשון של עברית ברוטו

Mh2,–המופע הראשון של עברית נטו

Mh3,–המופע האחרון של עברית נטו

Me1,–המופע הראשון של לועזית ברוטו

Me2,–המופע הראשון של לועזית נטו

Me3,–המופע האחרון של לועזית נטו

Mh1_,–המופע הראשון של עברית ברוטו, ולא- אורך המחרוזת

Mh2_,–המופע הראשון של עברית נטו, ולא- אורך המחרוזת

Me1_,–המופע הראשון של לועזית ברוטו, ולא- אורך המחרוזת

Me2_,–המופע הראשון של לועזית נטו, ולא- אורך המחרוזת

H,

S1+Case When H=1 Then–עברית

Reverse(Left(S2,Mh3))

Else–לועזית

Left(S2,Mh2_-1) End S1,–הראש

Case When H=1 Then Right(S2,DataLength(S2)-Mh3) Else Right(S2,DataLength(S2)-Mh2_+1) End S2–הזנב

From (Select S,

Mh1,

Mh2,

Me2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S2,Me2_-1))) Mh3,

Me1,

Me2,

Mh2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S2,Mh2_-1))) Me3,

Mh1_,

Mh2_,

Me1_,

Me2_,

S1,

S2,

Case When Mh2>0 And (Mh1_<Me1_ Or Mh2_<Me2_) Then 1 Else 0 End H

From (Select S,

PatIndex('%['+@Ltr+@Hrg+']%',S2) Mh1,

PatIndex('%['+@Ltr+']%',S2) Mh2,

PatIndex('%[^'+@Ltr+']%',S2) Me1,

PatIndex('%[^'+@Ltr+@Hrg+']%',S2) Me2,

PatIndex('%['+@Ltr+@Hrg+']%',S2+'ע') Mh1_,

PatIndex('%['+@Ltr+']%',S2+'ע') Mh2_,

PatIndex('%[^'+@Ltr+']%',S2+'e') Me1_,

PatIndex('%[^'+@Ltr+@Hrg+']%',S2+'e') Me2_,

S1,

S2

From T

Where IsNull(S2,")<>") T1) T2)

Select @Rvrs=S1

From T

Where IsNull(S2,")=";

Return @Rvrs;

End

Go

 

ולסיום- כיצד נטפל ברשימת ערכים המופיעה בטבלה?

ניצור טבלה כזו לדוגמה:

Create Table #T(S Varchar(Max));
Go

Insert
Into    #T
Select    'באר-שבע Beer-Sheva 1234אבגד' S Union All
Select    'אבגדה' S Union All
Select    'ABCDE' S Union All
Select    '12345' S Union All
Select    'א-בג דה' S Union All
Select    'AB CD-E' S Union All
Select    '12 34-5' S Union All
Select    '' S Union All
Select    ' ' S Union All
Select    ' ' S Union All
Select    'א1ב2ג3' S Union All
Select    'א 1 ב 2 ג 3 ' S;
Go

 

ונשלוף ממנה את הערכים תוך תיקונם (השליפה המקורית עם כמה שינויים מינוריים):

Declare    @Ltr Varchar(max),
        @Hrg Varchar(Max);
Select    @Ltr='אבגדהוזחטיכלמנסעפצקרשתךםןףץ',
        @Hrg=' -';
With T As
(Select    S,
        Mh1,--המופע הראשון של עברית ברוטו
        Mh2,--המופע הראשון של עברית נטו
        Mh3,--המופע האחרון של עברית נטו
        Me1,--המופע הראשון של לועזית ברוטו
        Me2,--המופע הראשון של לועזית נטו
        Me3,--המופע האחרון של לועזית נטו
        Mh1_,--המופע הראשון של עברית ברוטו, ולא- אורך המחרוזת
        Mh2_,--המופע הראשון של עברית נטו, ולא- אורך המחרוזת
        Me1_,--המופע הראשון של לועזית ברוטו, ולא- אורך המחרוזת
        Me2_,--המופע הראשון של לועזית נטו, ולא- אורך המחרוזת
        H,
        Case When H=1 Then--עברית
                Reverse(Left(S,Mh3))
            Else--לועזית
                Left(S,Mh2_-1) End S1,--הראש
        Case When H=1 Then Right(S,DataLength(S)-Mh3) Else Right(S,DataLength(S)-Mh2_+1) End S2--הזנב
From    (Select    S,
                Mh1,
                Mh2,
                Me2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S,Me2_-1))) Mh3,
                Me1,
                Me2,
                Mh2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S,Mh2_-1))) Me3,
                Mh1_,
                Mh2_,
                Me1_,
                Me2_,
                Case When Mh2>0 And (Mh1_<Me1_ Or Mh2_<Me2_) Then 1 Else 0 End H
        From    (Select    S,
                        PatIndex('%['+@Ltr+@Hrg+']%',S) Mh1,
                        PatIndex('%['+@Ltr+']%',S) Mh2,
                        PatIndex('%[^'+@Ltr+']%',S) Me1,
                        PatIndex('%[^'+@Ltr+@Hrg+']%',S) Me2,
                        PatIndex('%['+@Ltr+@Hrg+']%',S+'ע') Mh1_,
                        PatIndex('%['+@Ltr+']%',S+'ע') Mh2_,
                        PatIndex('%[^'+@Ltr+']%',S+'e') Me1_,
                        PatIndex('%[^'+@Ltr+@Hrg+']%',S+'e') Me2_
                From    #T) T1) T2
Union All
Select    S,
        Mh1,--המופע הראשון של עברית ברוטו
        Mh2,--המופע הראשון של עברית נטו
        Mh3,--המופע האחרון של עברית נטו
        Me1,--המופע הראשון של לועזית ברוטו
        Me2,--המופע הראשון של לועזית נטו
        Me3,--המופע האחרון של לועזית נטו
        Mh1_,--המופע הראשון של עברית ברוטו, ולא- אורך המחרוזת
        Mh2_,--המופע הראשון של עברית נטו, ולא- אורך המחרוזת
        Me1_,--המופע הראשון של לועזית ברוטו, ולא- אורך המחרוזת
        Me2_,--המופע הראשון של לועזית נטו, ולא- אורך המחרוזת
        H,
        S1+Case When H=1 Then--עברית
                Reverse(Left(S2,Mh3))
            Else--לועזית
                Left(S2,Mh2_-1) End S1,--הראש
        Case When H=1 Then Right(S2,Len(S2)-Mh3) Else Right(S2,DataLength(S2)-Mh2_+1) End S2--הזנב
From    (Select    S,
                Mh1,
                Mh2,
                Me2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S2,Me2_-1))) Mh3,
                Me1,
                Me2,
                Mh2_-PatIndex('%['+@Ltr+']%',Reverse(Left(S2,Mh2_-1))) Me3,
                Mh1_,
                Mh2_,
                Me1_,
                Me2_,
                S1,
                S2,
                Case When Mh2>0 And (Mh1_<Me1_ Or Mh2_<Me2_) Then 1 Else 0 End H
        From    (Select    S,
                        PatIndex('%['+@Ltr+@Hrg+']%',S2) Mh1,
                        PatIndex('%['+@Ltr+']%',S2) Mh2,
                        PatIndex('%[^'+@Ltr+']%',S2) Me1,
                        PatIndex('%[^'+@Ltr+@Hrg+']%',S2) Me2,
                        PatIndex('%['+@Ltr+@Hrg+']%',S2+'ע') Mh1_,
                        PatIndex('%['+@Ltr+']%',S2+'ע') Mh2_,
                        PatIndex('%[^'+@Ltr+']%',S2+'e') Me1_,
                        PatIndex('%[^'+@Ltr+@Hrg+']%',S2+'e') Me2_,
                        S1,
                        S2
                From    T
                Where    IsNull(S2,'')<>'') T1) T2)
Select    *
From    T
Where    IsNull(S2,'')='';

 

clip_image004

מודעות פרסומת

2 תגובות »

  1. […] הגעתי לפינה המוזרה הזו? הקוד שפירסמתי בפוסט הקודם- נכתב ורץ במקור ב-2005, אך כשבדקתי במקרה ב-2008 התברר שאינו […]

    פינגבאק של באג מוזר: פונקציית הטקסט Right בסביבת SQL Server 2005 - גרי רשף — 22/06/2011 @ 19:18

  2. […] הגעתי לפינה המוזרה הזו? הקוד שפירסמתי בפוסט הקודם- נכתב ורץ במקור ב-2005, אך כשבדקתי במקרה ב-2008 התברר שאינו […]

    פינגבאק של באג מוזר: פונקציית הטקסט Right בסביבת SQL Server 2005 « הבלוג של גרי רשף — 22/06/2011 @ 19:16


RSS feed for comments on this post. TrackBack URI

להשאיר תגובה

הזינו את פרטיכם בטופס, או לחצו על אחד מהאייקונים כדי להשתמש בחשבון קיים:

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s

יצירה של אתר חינמי או בלוג ב־WordPress.com.

%d בלוגרים אהבו את זה: