Công cụ chuyển mã dùng chung - các tiếp cận và cơ hội phát triển!

Liên hệ QC

paulsteigel

Nhi bất hoặc!
Tham gia
25/8/08
Bài viết
306
Được thích
683
Giới tính
Nam
Nghề nghiệp
Governance & Public policy consultant
[FONT=&amp]1[/FONT]Công cụ chuyển mã trong Excel

Dân văn phòng chúng ta hẳn ai cũng đều không phủ nhận các tính năng tuyệt vời của Unikey của bác Phạm Kim Long. Công cụ này đặc biệt mạnh thao tác với văn bản tiếng Việt sử dụng Unicode bên cạnh các công cụ bộ sung mà đến bây giờ chắc nhiều bạn cũng như tôi khó có thể tìm được phần mềm miễn phí nào tốt như vậy.
Với các phần mềm soạn thảo văn bản khác thì không chê vào đâu được nhưng với Excel thì có lẽ còn nhiều vấn đề phải bàn do đặc tính tương tác của Unikey đối với hệ thống. Chính vì lẽ đó mà bộ chuyển mã của Unikey không còn hữu dụng lắm khi trong bảng Excel của ta có nhiều bộ mã khác nhau và chưa kể đến việc văn bản có nhiều định dạng phức tạp.
Đây cũng là lý do nhiều chuyên gia Excel đang phát triển (theo nhóm hoặc đa số là tự phát triển) các công cụ riêng của mình để giải quyết vấn đề chuyển mã.
Là người dùng Excel, tôi thấy tiêng tiếc thế nào ý vì nếu tất cả cùng ngồi lại với nhau và bàn để cùng xây dựng ra một công cụ chung thì hay biết mấy. Cách này, không những vừa tiết kiệm công sức của nhiều người mà còn có thể hoàn thiện được một công cụ hay, hữu dụng, thân thiện và hiệu quả đối với nhiều người.
Vì lý do đấy, tôi mạn phép xin được đi trước bằng việc giải thích đôi chút cách tiếp cận của tôi về công cụ chuyển mã tích hợp. Nếu sau này có anh, chị nào quan tâm, tôi rất mong là ta sẽ hình thành được một đội phát triển để hoàn thiện hơn nữa công cụ này.

[FONT=&amp]1[/FONT]Nguyên tắc thiết kế
Cách tiếp cận tôi sử dụng đối với công cụ chuyển mã là:
+ Tạo ra bộ tương tác mở không hạn chế với bảng mã nào cả: Cho phép người dùng thêm bảng mã mới theo quy tắc nhất định. Các thuật toán chuyển đổi làm việc theo một nguyên tắc chung.
+ Xây dựng các nguyên tắc chuẩn trong tiếp cận lập trình sao cho có thể sử dụng lại qua các phiên bản Office Excel mà không phải điều chỉnh lại mã nguồn quá nhiều.
+ Cô lập các công cụ khác nhau vào các module khác nhau để tiện theo dõi.
[FONT=&amp]1[/FONT]Cách tiếp cận thuật toán.

Tôi phân lập rõ ràng các thuật toán có chức năng khác nhau và chúng chỉ thực hiện một thao tác cụ thể nào đó, tránh lồng ghép quá nhiều tính năng hoặc phân tách quá nhiều thuật toán có cùng nguyên tắc xử lý.
Trong chương trình, tôi hạn chế tối đa việc sử dụng các vòng lặp thừa và chỉ tải thông tin vào bộ nhớ khi không có cách nào tránh khỏi.

[FONT=&amp]1[/FONT]Cách tổ chức các môđun
Toàn bộ chương trình được thiết kế theo 2 nhóm thành phần cơ bản
+ Module chứa dữ liệu: thường có tên mdlResources hoặc frmResources
+ Module thuật toán: được đặt tên riêng rẽ ví dụ mdlConvert, mdlCommandbar ...vv

1. Mô đun khởi tạo thực đơn: mdlCommandBar
Đây là module được thiết kế để khởi tạo các trình đơn và cũng được phân lập thành:
+ Khối dữ liệu chứa các thông tin về thực đơn trong thủ tục InvokeMenuArray(). Hiện tại tôi cũng chưa hài lòng lắm về cách xây dựng mảng chứa thông tin về thực đơn. Có lẽ sau này tôi sẽ đưa chúng vào frmResource để tiện quản lý.
Tôi định nghĩa một Biến kiểu để chứa các thông tin về thực đơn để tiện truy vấn sau này
PHP:
Private Type MenuItem
      MenuId As Long
      ParrentID  As Long
      Caption As String
      Action  As String
      FaceID  As Long
      Tag As String
      IsGroup As Boolean
      Image   As String
      HotKey As String
  End Type
Các thông tin cho thực đơn được tổ chức dưới dạng các biến Ký tự có phân cách dạng _ để tiện chuyển thành mảng dữ liệu, chẳng hạn:
PHP:
mnuParrentID = "0_1_1_1_1_1_1_1_1_1_1_1_1"
Biến mnuHotKey để giữ các ký hiệu phím nóng cho thực đơn
PHP:
mnuHotKey = "N/A_^+c_^+d_^%h_^%t_^+b_^+l_N/A_N/A_N/A_N/A_N/A_^%a"
Thực ra cách làm này có vẻ không ổn lắm vì khó theo dõi cho dân lập trình, vì thế chắc là nên tổ chức lại cho đẹp và dễ theo dõi.
Tất cả các biến này sau đó được lưu vào mảng chứa kiểu dữ liệu đã định nghĩa ở trên thông qua vòng lặp:
PHP:
For i = 0 To UBound(MenuArray)
          With MenuArray(i)
              .ParrentID = ParrentID(i)
              .Caption = Caption(i)
              .Action = Action(i)
              .FaceID = FaceID(i)
              '.Tag = Tag(i)
              .IsGroup = IsGroup(i)
              .Image = Image(i)
              .HotKey = HotKey(i)
          End With
      Next
+ Các thuật toán khởi tạo thực đơn CreateMenu()
Các thuật toán này làm việc không phụ thuộc vào kiểu thực đơn nào cả mà đơn giản là chỉ đọc dữ liệu từ mảng thực đơn sau đó là khởi tạo thực đơn theo các tham số này thôi.
+ Tất nhiên, trong module này còn có nhiều thuật toán con khác phục vụ cho việc kiểm tra thực đơn...vv
Nếu các bạn quan tâm, xin mời xem trong mã nguồn tập tin đính kèm nhé.
2. Module hỗ trợ truy vấn và ghi đăng bạ (Registry) - mdlRegistry

Module này được xây dựng để ghi lại các thông tin cấu hình vào bộ đăng bạ của Windows.
Các thủ tục cụ thể chắc không cần phải bàn nhiều vì không liên quan mấy đến chuyển mã.

3. Module chứa dữ liệu phục vụ chuyển mã - mdlResources
Đây có lẽ sẽ là điều phải bàn nhiều nhất. Tôi xem hầu như gần hết các công cụ của các thành viên GPE như TVEXCEL của bác Long, OverAC Addin của bạn Bình, của Ndu, Dos.net ...vv nhưng đều thấy một điểm giống nhau là: Các bác đều đặc định hóa tính năng chuyển mã (Mỗi kiểu chuyển mã thì làm riêng 1 hàm).
Điều này sẽ gây nhiều trở ngại khi muốn mở rộng tính năng bên cạnh việc có thể làm chậm đáng kể công cuộc chuyển mã.
Thực ra, chúng ta có thể thấy rõ, chuyển mã đơn giản chỉ là việc ánh xạ lại bảng mã từ bảng mã này sang bảng mã khác theo chuỗi thứ tự đã được xác lập. Vì vậy, điểm chung ở đây cần chú ý đó là:
+ Bảng mã nguồn và bảng mã đích với thứ tự các chuỗi giống nhau, còn lại là thủ tục đổi chỗ.
+ Nếu ngon hơn thì thiết kế thêm các tính năng khác như chuyển chữ hoa (Upper case), thường, đổi phông chữ cho đẹp.
Ngay cả khi bỏ dấu thì nguyên tắc cũng đơn giản chỉ là ánh xạ thôi.
Cách tổ chức bảng mã dữ liệu của tôi là lập danh sách chuỗi theo thứ tự. Các bạn có thể lấy được danh sách chuỗi này bằng cách gõ vào một ô trong bảng tính hàm =GetUnicodeString() (Hiện giờ, hàm này không còn được sử dụng nhưng tôi vẫn để lại trong chương trình để các bạn tiện tham khảo khi có nhu cầu muốn làm bảng mã mới).
PHP:
á/à/ả/ã/ạ/ă/ắ/ằ/ẳ/ẵ/ặ/â/ấ/ầ/ẩ/ẫ/ậ/é/è/ẻ/ẽ/ẹ/ê/ế/ề/ể/ễ/ệ/í/ì/ỉ/ĩ/ị/ó/ò/ỏ/õ/ọ/ô/ố/ồ/ổ/ỗ/ộ/ơ/ớ/ờ/ở/ỡ/ợ/ú/ù/ủ/ũ/ụ/ư/ứ/ừ/ử/ữ/ự/ý/ỳ/ỷ/ỹ/ỵ/đ/Á/À/Ả/Ã/Ạ/Ă/Ắ/Ằ/Ẳ/Ẵ/Ặ/Â/Ấ/Ầ/Ẩ/Ẫ/Ậ/É/È/Ẻ/Ẽ/Ẹ/Ê/Ế/Ề/Ể/Ễ/Ệ/Í/Ì/Ỉ/Ĩ/Ị/Ó/Ò/Ỏ/Õ/Ọ/Ô/Ố/Ồ/Ổ/Ỗ/Ộ/Ơ/Ớ/Ờ/Ở/Ỡ/Ợ/Ú/Ù/Ủ/Ũ/Ụ/Ư/Ứ/Ừ/Ử/Ữ/Ự/Ý/Ỳ/Ỷ/Ỹ/Ỵ/Đ
Các bảng mã khác ta cũng làm tương tự như: VNI, TCVN-ABC ...vv
PHP:
aù/aø/aû/aõ/aï/aê/aé/aè/aú/aü/aë/aâ/aá/aà/aå/aã/aä/eù/eø/eû/eõ/eï/eâ/eá/eà/eå/eã/eä/í/ì/æ/ó/ò/où/oø/oû/oõ/oï/oâ/oá/oà/oå/oã/oä/ô/ôù/ôø/ôû/ôõ/ôï/uù/uø/uû/uõ/uï/ö/öù/öø/öû/öõ/öï/yù/yø/yû/yõ/î/ñ/AÙ/AØ/AÛ/AÕ/AÏ/AÊ/AÉ/AÈ/AÚ/AÜ/AË/AÂ/AÁ/AÀ/AÅ/AÃ/AÄ/EÙ/EØ/EÛ/EÕ/EÏ/EÂ/EÁ/EÀ/EÅ/EÃ/EÄ/Í/Ì/Æ/Ó/Ò/OÙ/OØ/OÛ/OÕ/OÏ/OÂ/OÁ/OÀ/OÅ/OÃ/OÄ/Ô/ÔÙ/ÔØ/ÔÛ/ÔÕ/ÔÏ/UÙ/UØ/UÛ/UÕ/UÏ/Ö/ÖÙ/ÖØ/ÖÛ/ÖÕ/ÖÏ/YÙ/YØ/YÛ/YÕ/Î/Đ
(Lý do sử dụng dấu "/" là để chuyển thành mảng một cách nhanh nhất với hàm Split)
Tiếp theo là đưa dãy ký tự này để lưu vào mảng hoặc đối tượng nào có thể lưu được (Code Module hoặc trên bảng tính hay trong form). Trước đây, tôi lưu thẳng vào Code Module (Vì thế trông code đến khiếp) nhưng sau này tôi đưa vào một điều khiển nhãn (Label) và để trong Form frmResource.
Sau đó là việc tạo hàm truy vấn dãy dữ liệu này trong module mdlResources. (xem hàm GetCodetable).

4. Module chứa các thủ tục chuyển mã - mdlConvert

Module này khá quan trọng, cốt lõi của module là dãy các biến toàn cục, thủ tục ConvertRange để xử lý ban đầu đối với vùng chọn trước khi chuyển đổi và hàm ConvertText để thực hiện ánh xạ.

4.1 Thủ tục ConvertRange

Thủ tục này sẽ thực hiện toàn bộ các việc như:
+ Xử lý điều kiện và nạp biến nhớ;
+ Định hướng việc chuyển đổi bảng mã, nạp lại các bảng mã mới khi cần thiết;
+ Chuyển đổi phông chữ;
+ Xác định bảng mã nguồn ...vv
Đây là trái tim của toàn bộ công việc chuyển đổi vì thế trong tương lai tôi sẽ còn sửa đồi nhiều để việc chuyển mã diễn ra tốt nhất.
Có một số thủ tục/ hàm liên quan đến thủ tục này là:
PHP:
InitDestination: Nạp các biến nhớ và khải tạo mảng chứa bảng mã đích;
  InitSource: Nạp biến nhớ của bảng mã nguồn
  GetSourceCodeTable: Lấy bảng mã tương ứng từ module dữ liệu
  GetFontAddUp: Lấy chuỗi chứa tên Font tương đương để xử lý
  SetCellFont: Ấn định phông chữ cho ô xử lý.


4.2 Hàm ConvertText

Hàm này chỉ nhằm mục đích xử lý ánh xạ bảng mã từ đầu vào và chuyển mã.
PHP:
Private Function ConvertText( _
      TextToConvert As String, _
      FrObj As Variant, _
      ToObj As Variant, _
      mFrType As Boolean, _
      mToType As Boolean) As String
      
      'Routine for getting the vowel list of the selected text
      Dim i, j, k, ProcessedList() As String, ReserveList() As String
      Dim RptText As String
      ReDim ProcessedList(133)
      ReDim ReserveList(133)
      If mFrType Then
          For i = 0 To UBound(FrObj)
              If Len(FrObj(i)) = 1 Then
                  ' process it later or it may cause wrong conversion
                  ReserveList(k) = i
                  k = k + 1
              Else
                  If InStr(TextToConvert, FrObj(i)) <> 0 Then
                      ' Replace the occurence of search string with number
                      ProcessedList(j) = i
                      TextToConvert = Replace(TextToConvert, FrObj(i), "[[" & ProcessedList(j) & "]]")
                      j = j + 1
                  End If
              End If
          Next
          If k > 0 Then
              ReDim Preserve ReserveList(k - 1)
              For i = 0 To UBound(ReserveList)
                  If InStr(TextToConvert, FrObj(ReserveList(i))) <> 0 Then
                      ' Replace the occurence of search string with number
                      ProcessedList(j) = ReserveList(i)
                      TextToConvert = Replace(TextToConvert, FrObj(ReserveList(i)), "[[" & ProcessedList(j) & "]]")
                      j = j + 1
                  End If
              Next
          End If
      Else
          For i = 0 To UBound(FrObj)
              If InStr(TextToConvert, FrObj(i)) <> 0 Then
                  ' Replace the occurence of search string with number
                  ProcessedList(j) = i
                  TextToConvert = Replace(TextToConvert, FrObj(i), "[[" & ProcessedList(j) & "]]")
                  j = j + 1
              End If
          Next
      End If
      On Error GoTo errHandle
      ReDim Preserve ProcessedList(j - 1)
      ' now just simple replace all stuff
      For i = 0 To UBound(ProcessedList)
          TextToConvert = Replace(TextToConvert, "[[" & ProcessedList(i) & "]]", ToObj(ProcessedList(i)))
      Next
      ConvertText = TextToConvert
      Exit Function
  errHandle:
      If IsEmpty(j) Or IsNull(j) Then
          ' nothing to do with this text
          ConvertText = TextToConvert
      End If
  End Function
Trong hàm này, chúng ta cần chú ý về cách dùng hàm thay thế (Replace). Thay vì thay thế thẳng thừng, tôi làm việc thay thế bằng một chuỗi trung gian. Điều này giúp loại bỏ những nhầm lẫn khi xảy ra việc sử dụng chung một số ký tự trong các bảng mã khác nhau (Rất hay xảy ra).
PHP:
TextToConvert = Replace(TextToConvert, FrObj(ReserveList(i)), "[[" & ProcessedList(j) & "]]")
Ngoài ra còn chú ý khác là thứ tự xử lý thay thế. Trong bảng mã VNI có một số điểm cần chú ý chẳng hạn 2 ký tự: [ô/ôù/] nằm liền nhau để hiển thị chữ [ơ/ớ/], nếu thay thế ngay các ký tự ô thì ký tự tiếp theo sẽ bị đổi thành ơù và dẫn đến việc chuyển đổi sai. Vì thế các chuỗi ký tự đa bytes sẽ được chuyển đổi trước sau đó đến chuỗi ngắn hơn và ngắn nhất.
PHP:
If Len(FrObj(i)) = 1 Then
                  ' Nhớ lại và xử lý sau để tránh ánh xạ sai
                  ReserveList(k) = i
                  k = k + 1
              Else
                  If InStr(TextToConvert, FrObj(i)) <> 0 Then
                      ' Thay thế từ khóa tìm thấy với số hiệu được đặt trong dấu [[số hiệu]]
                      ProcessedList(j) = i
                      TextToConvert = Replace(TextToConvert, FrObj(i), "[[" & ProcessedList(j) & "]]")
                      j = j + 1
                  End If
              End If
Công việc còn lại chỉ là thêm thắt các tính năng để sao cho thuận tiện và đạt kết quả cao nhất mà thôi.
Tóm lại tôi đã giới thiệu xong cách tiếp cận. Bên cạnh tính năng chuyển mã, hiện có nhiều công cụ khác như đổi số sang chữ, in lịch âm dương, tra từ điển trực tuyến trong Excel... nhưng nói chung chúng không quan trọng lắm.
Trong thời gian tới, nếu có thời gian, tôi sẽ bổ sung thêm các công cụ khác.
Hy vọng các thành viên của diễn đàn có quan tâm cùng tham gia và nếu có thể, cùng bắt tay vào Chiến đấu.
Các bạn quan tâm, xin cùng chia sẻ qua email (ngocdd@sfdp.net) hoặc qua diễn đàn nhé.
Chúc các bạn vui vẻ và thành thật xin lỗi nếu có gì phiền toái.
 

File đính kèm

  • CodeConvert_Apr.rar
    21.1 KB · Đọc: 360
  • Functions.rar
    243 KB · Đọc: 194
Lần chỉnh sửa cuối:
Tôi cũng dùng thủ thuật thay thế và đối với bộ mã VNI cũng bị tình trạng này:
Ngoài ra còn chú ý khác là thứ tự xử lý thay thế. Trong bảng mã VNI có một số điểm cần chú ý chẳng hạn 2 ký tự: [ô/ôù/] nằm liền nhau để phản hiển thị chữ [ơ/ớ/], nếu thay thế ngay các ký tự ô thì ký tự tiếp theo sẽ bị đổi thành ơù và dẫn đến việc chuyển đổi sai.

Thật lạ là thay vì xử lý như Paul:
Vì thế các chuỗi ký tự đa bytes sẽ được chuyển đổi trước sau đó đến chuỗi ngắn hơn và ngắn nhất.

Tôi làm ngược lại: xử lý ký tự 1 byte trước!

Lý do: nếu chuyển đổi "oâ" VNI thành "ô" Unicode trước, thì "ô" Unicode bị lầm tưởng là "ô" VNI và bị chuyển lần nữa thành "ơ" Unicode!

Nếu e rằng thay "ô" VNI thành "ơ" Unicode, rồi "ơù" bị biến thành cái khác nữa, thì tôi không lo:
- Tiếng Việt không có từ nào có nguyên âm đôi "ôù" để bị đổi thành "ơù"
- Bảng mã VNI không có ký tự 2 byte nào bắt đầu là "ơ"

Sau đây là 1 vài thí dụ về mã có thể bị thay thế 2 lần nếu replace ký tự 2 bytes trước:

Chuyển đổi |Mã VNI|Mã Unicode|
lần 1|où|ó|
lần 2|ó|ĩ|
| | |
lần 1|oø|ò|
lần 2|ò|ị|
| | |
lần 1|OÙ|Ó|
lần 2|Ó|Ĩ|
| | |
lần 1|OØ|Ò|
lần 2|Ò|Ị|
| | |
lần 1|oâ|ô|
lần 2|ô|ơ|
| | |
lần 1|OÂ|Ô|
lần 2|Ô|Ơ|

Đối với bảng mã TCVN3 thì cũng có 1 khó khăn tương tự, và tôi đã phải thay thủ thuật replace bằng cách ghép chuỗi, vì TCVN3 mã nào cũng 1 byte.
 
Trong cái chuỗi:
á/à//ã//ă//////â//////é/è////ê/ế/////í/ì//ĩ//ó/ò//õ//ô//////ơ//////ú/ù//ũ//ư//////ý/////đ/Á/À//Ã//Ă//////Â//////É/È////Ê//////Í/Ì//Ĩ//Ó/Ò//Õ//Ô//////Ơ//////Ú/Ù//Ũ//Ư//////Ý/////Đ

đâu có ai bắt buộc bạn phải đặt chữ á trước chữ ì đâu?
Nếu bạn đặt lại thứ tự các chữ cái trong chuỗi đó thì sẽ không bị chuyển mã 2 lần.
 
Đấy là lý do đấy bác Muontennguoi ạ!
Nếu chỉ xử lý riêng với VNI thì ngon quá, nhưng ngặt nỗi các bảng mã khác lại có thứ tự khác vì thế, nếu phải thao tác nhiều bảng mã thì việc chọn cách xếp thứ tự lung tung là cả vấn đề rắc rối.
Trước đây tôi đã thiết kế chuỗi này với việc gắn thêm chỉ số sau đó sử dụng thuật toán để bóc tách ra và đưa vào mảng.
Ví dụ:
001,aø 002 ....
Việc này cũng tạm được nhưng gây ra phiền toái do có quá nhiều các thuật toán xử lý chuỗi sau này. Vì vậy, tiếp cận kiểu mới đã giải quyết dứt điểm vấn đề mà không làm tăng số chu trình xử lý.
Còn về thuật toán của bác ptm
Tôi làm ngược lại: xử lý ký tự 1 byte trước!

Lý do: nếu chuyển đổi "oâ" VNI thành "ô" Unicode trước, thì "ô" Unicode bị lầm tưởng là "ô" VNI và bị chuyển lần nữa thành "ơ" Unicode!

Nếu e rằng thay "ô" VNI thành "ơ" Unicode, rồi "ơù" bị biến thành cái khác nữa, thì tôi không lo:
- Tiếng Việt không có từ nào có nguyên âm đôi "ôù" để bị đổi thành "ơù"
- Bảng mã VNI không có ký tự 2 byte nào bắt đầu là "ơ"
Cả tôi và bác đều không có khác nhau về cách tiếp cận mấy nhưng tôi chú ý loại bỏ các tình huống xử lý gắn với từng bảng mã vì thế tôi chọn ký tự trung gian trước (Hồi đầu tiên khi thiết kế cho Word tôi đã làm thế và mất vài ngày mới phát hiện ra lý do). Như thế, chắc chắn tốc độ sẽ thua của bác nhưng đảm bảo không bị đánh thuế 2 lần.
Chẳng hạn:
Bước 1: chuỗi ban đầu á/à//ã/chuyển thành [[1]]/[[2]]/[[3]]/[[4]]/
Bước 2: Chuỗi kết quả [[1]]/[[2]]/[[3]]/[[4]]/ chuyển thành ////

Mong các bác chia sẻ thêm ý kiến.
 
Lần chỉnh sửa cuối:
Dự án của Paul hơi lớn so với khả năng của tôi:
1. Ngoài chuyển xuôi còn chuyển ngược
2. Nhiểu bảng mã
3. Có thể thêm bảng mã

Thí dụ vấn đề 1: Ngoài chuyển xuôi còn chuyển ngược, thì nếu làm theo cách của tôi là phân ra 1 byte và 2 bytes, thì khi chuyển xuôi phải làm 1 byte trước, 2 byte sau, khi chuyển ngược có lẽ phải làm ngược lại.

Tôi đề xuất 1 thủ thuật khác là thay vì replace lần lượt hết ký tự này đến ký tự khác, thì làm theo kiểu ghép chuỗi:

PHP:
For j = 1 To Len(Mystr)

'kytu_a là ký tự cần chuyển đổi'
'kytu_b là ký tự sẽ chuyển thành'
'nếu chuyển ngược lại thì cũng vậy'

    For i = 1 To 134 ' or other number'
        If Mid(Mystr, j, 1) = kytu_a Then 
            Mystr = Left(Mystr, j - 1) & kytu_b & Right(Mystr, Len(Mystr) - j) 
            Exit For 'chuyển sang ký tự kế tiếp trong chuỗi cần chuyển'
        End If
    Next
Next

Với thủ thuật này thì array {các ký tự cần thay thế} của 1 bảng mã 1 byte bất kỳ có thể sắp xếp theo thứ tự bất kỳ, không lo 1 ký tự bị chuyển đổi 2 lần.

TB: Tôi đã thực hiện thành công với bảng mã TCVN3
 
Lần chỉnh sửa cuối:
1. Về gợi ý của bác Ptm.
PHP:
 For j = 1 To Len(Mystr)

'kytu_a là ký tự cần chuyển đổi'
'kytu_b là ký tự sẽ chuyển thành'
'nếu chuyển ngược lại thì cũng vậy'

    For i = 1 To 134 ' or other number'
        If Mid(Mystr, j, 1) = kytu_a Then 
            Mystr = Left(Mystr, j - 1) & kytu_b & Right(Mystr, Len(Mystr) - j) 
            Exit For 'chuyển sang ký tự kế tiếp trong chuỗi cần chuyển'
        End If
    Next
Cách tiếp cận của bác rất phù hợp và có thể giải quyết đơn giản được bài toán chuyển mã.
Tuy nhiên, với các chuỗi dài, việc kết hợp nhiều hàm xử lý chuỗi sẽ làm giảm đáng kể tốc độ thực hiện toàn diện.

Ta hãy làm khảo nghiệm nho nhỏ với tập tin ví dụ này

http://www.giaiphapexcel.com/forum/attachment.php?attachmentid=29707&d=1247821625
Dùng các công cụ chuyển bảng mã khác nhau đang có trong diễn đàn (tất nhiên Unikey vẫn là vô địch thiên hạ/ nhưng chưa có đất ở Excel thôi)
Kết quả thực hiện như sau:
+ Đỗ Nguyên Bình (AC Convert): 2:23:25'
+ Đặng Đình Ngọc (Functions.xla): 2:41:08'
+ Phạm Duy Long (TVEXCEL): 6:08:93'
Máy tiến hành thử nghiệm là HP6530S Core 2 Duo 2GB RAM, sử dụng với Office 2003, Windows XP professional SP3.
Theo em biết thì hàm Replace của VBE có tốc độ thực hiện chắc chắn nhanh hơn so với việc thực hiện nhiều thao tác xử lý và gắn chuỗi cùng lúc. Đổi lại, điểm yếu của Replace là sẽ dẫn tới những hậu quả chuyển đổi sai nếu có vấn đề về ký tự và thuật toán chuyển đổi. Trong thực tế, việc viết chắc chắn hàm chuyển đổi sẽ có thể giải quyết được điểm này.

2. Về công cụ Functions (Addin) gửi kèm theo bài đầu tiên.
2.1 Về tính năng chuyển mã: Công cụ này đã làm được:
+ Hỗ trợ chuyển đổi 15 bảng mã khác nhau (nói là nhiều nhưng thực tế chỉ cần Unicode/VNI/TCVN/VIQR/VNI-DOS là quá đủ). Các bảng mã hiện hỗ trợ là: "Unicode", "TCVN-ABC", "VISCII", "VPS", "VNI", "B.K. HCM2", "VS1", "Telex Simulator keyboard", "Vietware X for Windows", "Viet Spell", "Vietkey", "3C25 for Windows", "MTDEVA", "Daisy", "VNI for DOS", "VIQR"
+ Hỗ trợ chuyển đổi chéo. Tức là có thể chuyển từ bất kỳ bảng mã nào sang bảng mã khác và ngược lại. Như vậy, về nguyên tắc, công cụ này hỗ trợ 14! (giai thừa) phép hoán chuyển và không phụ thuộc bảng mã là đơn hay đa byte.
+ Hỗ trợ bỏ dấu: Loại bỏ dấu của bảng mã.
+ Hỗ trợ đoán mã tự động dựa vào đặc tính phông chữ - thuật toán này cần bàn thêm nhiều.
+ Hỗ trợ chuyển chữ hoa, thường.
+ Hỗ trợ chuyển phông chữ tương đương. Ví dụ nếu trong TCVN dùng .VnArial, VNI dùng VNI-Helve thì khi chuyển về Unicode sẽ dùng phông Arial và ngược lại.
Tuy nhiên, công cụ này còn nhiều khiếm khuyết do em chưa nghiên cứu kỹ được tất cả các tình huống xử lý.

2.1 Về các tính năng khác, công cụ có tích hợp
+ Chuyển số ra chữ+ In lịch âm dương
+ Sửa đổi tiêu đề, hạ mục văn bản ....vv
+ Tra từ điển trên mạng

Hiện tại còn nhiều khiếm khuyết rõ như:
+ Chưa bẫy lỗi tốt
+ Các thuật toán thiết kế lằng nhằng
+ Chưa tối ưu code.
+ Chưa có hướng dẫn sử dụng hoàn chỉnh

3. Kế hoạch sắp tới
+ Cố gắng hoàn chỉnh các tính năng.
+ Bổ sung thêm bảng mã nếu cần
+ Sửa đổi module Menubar
+ Viết thêm chức năng tự cài đặt
+ Viết thêm chức năng tự cập nhật qua mạng phiên bản mới nhất.
....vv theo yêu cầu của người sử dụng (hihi - nếu có ai đó trót sử dụng ạ)

Em thành thật hy vọng, nếu chúng ta cùng đồng tâm, chắc chắn sẽ xử lý được bộ công cụ này thành một phần mềm hoàn chỉnh.
Tất nhiên, nếu có thể thay đổi tiếp cận hay hơn, em rất mong các bác cùng đóng góp ý kiến ạ.
Xin chân thành cảm ơn sự chú ý của mọi người.
 
Lần chỉnh sửa cuối:
Nhận xét về dùng Replace và ghép chuỗi:
Giả sử ta có 2 mảng:
Madauvao = "á/à/ả ....ỵ/đ" (mã VNI)
Madaura = "á/à/ả ....ỵ/đ" (mã Uni)
Và ta muốn đổi mã của đoạn string sau: "á"

Ờ thì chỉ ngắn gọn vậy thôi.

Như vậy nếu dùng replace thì phải dùng vòng lặp 132 lần, cho hết các thành phần của mảng.
Nhưng nếu ghép chuỗi thì chỉ cần 1 lần so sánh, bởi vì string có độ dài là 1, và ngay lần so sánh với phần tử đầu tiên của mảng là đã tìm ra ký tự cần thay.

Tuy nhiên nếu chuỗi string là 1 văn bản dài cỡ 10 trang thì số lần thực hiện phép so sánh chỉ còn chênh lệch cỡ 2 lần (nhưng tổng thời gian thì không lệch nhiều đến thế).

Nhưng nếu chuỗi string là bảng mã 2 byte như VNI thì phải thêm khó khăn là làm sao phân biệt chữ nào 1 byte, chữ nào 2 byte.
Vì VNI không phải là toàn bộ đều 2 byte cả. Một số chữ cái là 1 byte, số khác lại là 2.
Thành ra dùng replace lại viết code đơn giản hơn.
Nhưng các ô trong Excel lại thưòng chỉ chứa các chuỗi ngắn.

Ta phải cân nhắc cái này thôi.

Tôi vote 2/3 phiếu cho ghép chuỗi và 1/3 phiếu cho dùng replace (vì code nó dễ đọc hơn).

Nhận xét về chuyển nhiều bảng mã:
Dĩ nhiên đơn giản là ta gán lại bảng-mã-nguồn và bảng-mã-đích cho từng trường hợp đổi từ mã XXX sang YYY thôi.
Và như thế thì chuyển xuôi hay chuyển ngược đều như nhau. Nghĩa là ta không định trước Uni là "chuẩn không cần chỉnh".
Thay mặt tổ chức Hòa Bình Xanh kiến nghị: đối xử bình đẳng tất cả các mã.

Và nhận xét của bạn là:
Đấy là lý do đấy bác Muontennguoi ạ!
Nếu chỉ xử lý riêng với VNI thì ngon quá, nhưng ngặt nỗi các bảng mã khác lại có thứ tự khác vì thế, nếu phải thao tác nhiều bảng mã thì việc chọn cách xếp thứ tự lung tung là cả vấn đề rắc rối.
Trước đây tôi đã thiết kế chuỗi này với việc gắn thêm chỉ số sau đó sử dụng thuật toán để bóc tách ra và đưa vào mảng.
Ví dụ:
001,aø 002 ....
Việc này cũng tạm được nhưng gây ra phiền toái do có quá nhiều các thuật toán xử lý chuỗi sau này. Vì vậy, tiếp cận kiểu mới đã giải quyết dứt điểm vấn đề mà không làm tăng số chu trình xử lý.

Theo tôi thì, giả sử chuyển mã giữa 2 bảng mã 1byte với nhau thì chuyện mã trùng lòng vòng khiến cho phải chịu thuế chồng lên thuế là luôn có thể xảy ra (tất nhiên là đang nói cách dùng Replace ấy nhé).

Ví dụ bảng mã XXX có mã chữ a là 97, chữ b là 98 và bảng mã YYY lại có mã chữ a là 98, chữ b là 97 .. thì nó đã tạo vòng.
Viết 1 đoạn code ngắn là ta có thể kiểm tra ngay bảng-mã-nguồn và bảng-mã-đích sẽ bị đổi 2 lần ở chỗ nào.
Căn cứ vào đó ta đảo thứ tự của mảng.
Tôi không nghĩ nó sẽ "nếu phải thao tác nhiều bảng mã thì việc chọn cách xếp thứ tự lung tung là cả vấn đề rắc rối" lắm đâu, phải không?
Bởi vì bản thân việc đổi mã Unicode ra TCVN3 cũng đã có 1 số ký tự trùng mã nhau rồi. Đàng nào cũng phải xử lý.
 
Khuyến cáo:
===========================================================
Công cụ đang trong quá trình hoàn thiện, vì thế sẽ có nhiều lỗi chưa xác định hết
xảy ra. Tập tin đính kèm gửi theo đây chỉ có giá trị dùng thử.
Vì thế, trước khi sử dụng, đề nghị sao lưu tập tin.
Xin cảm phiền mọi người đã quan tâm!

Liên kết tới phiên bản mới nhất có thể tải về từ link dưới đây
http://www.giaiphapexcel.com/forum/attachment.php?attachmentid=47025&d=1275811516http://www.sfdp.net/tai-lieu-khac/bocongcuchuyenma/Functions.rar
===========================================================

Cảm ơn bác muontennguoi đã cho ý kiến!

Tôi không nghĩ nó sẽ "nếu phải thao tác nhiều bảng mã thì việc chọn cách xếp thứ tự lung tung là cả vấn đề rắc rối" lắm đâu, phải không?
Bởi vì bản thân việc đổi mã Unicode ra TCVN3 cũng đã có 1 số ký tự trùng mã nhau rồi. Đàng nào cũng phải xử lý.

Tiếp thu ý kiến của bác và tôi đang nghiên cứu vụ lai ghép 2 cách tiếp cận xử lý để tăng tốc độ.

Tuy nhiên, trước tiên em đã sửa hàm tạo mảng mã nguồn và đích với một số điều chỉnh cách dùng hàm split theo đó gắn thêm ký hiệu số thứ tự chuỗi so sánh vào các bảng mã đa bytes như VNI, VIQR ...vv. Bác xem hàm dưới này nhé.
(Em nghĩ sẽ sửa comment sang tiếng Việt để mọi người tiện theo dõi)

PHP:
Private Function SplitAdvance(txtString As String, TxtDelimiter As String, Optional toMultiChar = False) As Variant
    ' Hàm này mở rộng khả năng sử dụng hàm Split theo đó:
    ' Sắp xếp lại bảng mã chuyển đổi nguồn nếu đó là đa byte:
    ' Chuỗi dài nhất sẽ đứng trước và ngắn nhất sẽ đứng sau
    ' Kiểm tra xem chuỗi đầu vào là đơn hay đa byte
    Dim MultiChar As Boolean, txtArr As Variant, outString As String
    Dim medArr() As String  ' Giữ chuỗi sắp xếp theo độ dài khác nhau vào từng phần tử
    Dim i As Long           ' Biến đếm
    Dim j As Long           ' Độ dài cũ
    
    If Len(txtString) > 267 Then MultiChar = True
    ReDim medArr(0)
    
    'Làm rã mảng phần tử đầu vào
    txtArr = Split(txtString, TxtDelimiter)
    If MultiChar And Not toMultiChar Then
        For i = LBound(txtArr) To UBound(txtArr)
            j = Len(txtArr(i))
'Nếu chuỗi có độ dài lớn hơn kích thước mảng thì tăng kích thước mảng lên tới số phần tử đó
            If j > UBound(medArr) + 1 Then
                ReDim Preserve medArr(j - 1)
            End If
            ' Lưu giữ chuỗi cùng độ dài vào từng phần tử mảng
            medArr(j - 1) = medArr(j - 1) & TxtDelimiter & txtArr(i) & Format(i, "00#")
        Next
        For i = UBound(medArr) To LBound(medArr) Step -1
            If medArr(i) = "" Then Exit For
            outString = outString & medArr(i)
        Next
        outString = Mid(outString, 2)
        txtArr = Split(outString, TxtDelimiter)
    End If
    SplitAdvance = txtArr
End Function
Và Hàm chuyển mã có vài sự cập nhật

PHP:
Private Function ConvertText( _
    TextToConvert As String, _
    FrObj As Variant, _
    ToObj As Variant, _
    mFrType As Boolean, _
    mToType As Boolean) As String    
    
    Dim i, j, k, ProcessedList() As String, ReserveList() As String
    Dim RptText As String
    ReDim ProcessedList(133)
    ReDim ReserveList(133)
    If mFrType Then
        ' Đối với chuỗi đa byte
        For i = 0 To UBound(FrObj)
            If InStr(TextToConvert, Left(FrObj(i), Len(FrObj(i)) - 3)) <> 0 Then
                ' Thay thế chuỗi xuất hiện bằng số theo dạng [[1]], cách này sẽ giúp tránh triệt để việc đánh thuế 2 lần
                ProcessedList(j) = Val(Right(FrObj(i), 3))
                TextToConvert = Replace(TextToConvert, Left(FrObj(i), Len(FrObj(i)) - 3), "[[" & ProcessedList(j) & "]]")
                j = j + 1
            End If
        Next
    Else
        ' Đối với mã đơn byte đầu vào
        For i = 0 To UBound(FrObj)
            If InStr(TextToConvert, FrObj(i)) <> 0 Then
                ProcessedList(j) = i
                TextToConvert = Replace(TextToConvert, FrObj(i), "[[" & ProcessedList(j) & "]]")
                j = j + 1
            End If
        Next
    End If
    On Error GoTo errHandle
    ReDim Preserve ProcessedList(j - 1)
    ' Thay thế ngược lại chuỗi [[xx]] bằng ký tự trong bảng mã đích với số xx là số thứ tự phần tử trong bảng mã đích
    For i = 0 To UBound(ProcessedList)
        TextToConvert = Replace(TextToConvert, "[[" & ProcessedList(i) & "]]", ToObj(ProcessedList(i)))
    Next
    ConvertText = TextToConvert
    Exit Function
errHandle:
    If IsEmpty(j) Or IsNull(j) Then
        ' nothing to do with this text
        ConvertText = TextToConvert
    End If
End Function
Tất nhiên, theo cả 2 cách thì đều có lợi và hại, tuy nhiên với tiếp cận đổi mới đôi chút thì tốc độ khả dĩ hơn và đảm bảo làm việc với các loại bảng mã khác nhau, không cần chú ý nhiều đến các xử lý tình huống.

Em đang viết tiếp phần tự động cập nhật mã nguồn mới, các bác cho ý kiến có nên làm không nhỉ?

Một lần nữa xin cảm ơn các chia sẻ của các bác.
 
Lần chỉnh sửa cuối:
Khuyến cáo về Công cụ chuyển mã tích hợp
==================================================
Công cụ đang trong quá trình hoàn thiện, vì thế sẽ có nhiều lỗi chưa xác định hết
xảy ra. Tập tin đính kèm gửi theo đây chỉ có giá trị dùng thử.
Vì thế, trước khi sử dụng, đề nghị sao lưu tập tin.
Xin cảm phiền mọi người đã quan tâm!

Liên kết tới phiên bản mới nhất có thể tải về từ link dưới đây
http://www.giaiphapexcel.com/forum/attachment.php?attachmentid=47025&d=1275811516http://www.sfdp.net/tai-lieu-khac/bocongcuchuyenma/Functions.rar
==================================================

Phiên bản cập nhập mới với thuật toán xử lý phông chữ tương đương đã được thiết kế lại cho phép xử lý các phông chữ tốt hơn.
Hiện tại tính năng chuyến đổi phông chữ đang làm việc tốt với các bảng mã cơ bản: TCVN/ UNICODE / VNI Windows/ VIQR.
Có thể với một số bảng mã khác thì có thể có lỗi chuyển đổi vì trong quá trình tạo bảng danh sách nguyên âm, tôi chưa kiểm tra được chính xác các nguyên âm tương đương hoặc do việc lưu bảng nguyên âm so sánh vào đối tượng Label trong form vô tình tạo ra dấu xuống dòng nên có thể còn có lỗi.
Hy vọng trong thời gian tới, khi sưu tập đủ bộ phông chữ, tôi sẽ khắc phục triệt để vấn đề này.
Cảm ơn các bạn đã chú ý
 
Lần chỉnh sửa cuối:
Hướng dẫn sử dụng công cụ chuyển mã ....

Khuyến cáo về Công cụ chuyển mã tích hợp
==================================================
Công cụ đang trong quá trình hoàn thiện, vì thế sẽ có nhiều lỗi chưa xác định hết
xảy ra. Tập tin đính kèm gửi theo đây chỉ có giá trị dùng thử.
Vì thế, trước khi sử dụng, đề nghị sao lưu tập tin.
Xin cảm phiền mọi người đã quan tâm!

Liên kết tới phiên bản mới nhất có thể tải về từ link dưới đây
http://www.giaiphapexcel.com/forum/attachment.php?attachmentid=47025&d=1275811516http://www.sfdp.net/tai-lieu-khac/bocongcuchuyenma/Functions.rar
==================================================
Tôi đã hoàn thành bản hướng dẫn sơ lược sử dụng công cụ chuyển mã. Các bạn có thể tải về trong tập tin đính kèm.
Xin chân thành cảm ơn sự chú ý của mọi người và rất mong sự thông cảm nếu có bất kỳ phiền toái hay trục trặc nào xảy ra khi sử dụng chương trình.
Paulsteigel.
 

File đính kèm

  • Manuals.rar
    93.6 KB · Đọc: 567
Lần chỉnh sửa cuối:
Chương trình của bạn rất hay, nhưng khi để chế độ "tự động đoán mã" thì chương trình không đoán được nếu dùng bảng mã Vietware X for Window, còn lại các mã khác như TCVN3, VNI chương trình nhận biết rất tốt. Mong bạn hoàn thiện thêm để mọi người có một công cụ Convert Font tốt. Cám ơn rất nhiều vì những đóng góp của bạn.
 
Cảm ơn bạn đã góp ý, mặc dù biết đây là việc mang tính xử lý tình huống nhưng tôi dự định sẽ sủa triệt để tính năng này nhờ việc cải thiện thuật toán đoán bảng mã.
Phiên bản vừa cập nhật có vài sửa đổi tính năng đoán phông chữ.
Tiện thể, tôi xin giới thiệu một tiếp cận đang dự định hoàn chỉnh về việc đoán phông chữ như sau:
Tiếp cận cũ: Đoán bảng mã dựa vào phông chữ được sử dụng trong đoạn, cách này nhanh nhưng tính chính xác thấp, đặc biệt như với tình huống có nhiều phông chữ lạ không theo chuẩn.
Tiếp cận mới: Xử lý trực tiếp chuỗi ký tự và thử các cách chuyển đổi khác nhau để kiểm nghiệm bảng mã đồng thời nhớ lại tập quán sử dụng bảng mã nhằm tăng tốc độ.
Cách này tăng được một chút độ chính xác của xử lý đoán bảng mã nhưng làm tốc độ chậm đi rất nhiều.
Tuy thế, tôi mong sẽ có thể kết hợp 2 cách nhằm vừa tăng tốc độ xử lý, vừa tăng độ chính xác.
Trong diễn đàn có nhiều cao thủ về xử lý thuật toán, tôi rất mong các cao thủ cho ý kiến góp ý để chúng ta cùng cải thiện công cụ.
Chân thành cảm ơn sự chú ý của các bạn.
 
Gần đây, nhờ đi làm tư vấn về lập kế hoạch tại một số nơi, mình đã viết thêm được một công cụ nhỏ hỗ trợ cập nhật thông tin kế hoạch đồng thời cho phép trích xuất kết quả ngay ra Word.
Mình nghĩ là có thể một số bạn sẽ quan tâm xem nên mạo muội xin gửi lên đây để chia sẻ. Một số tính năng của Excel được sử dụng trong công cụ này là:
1. Sử dụng Validation để kiểm tra số liệu đầu vào
2. Sử dụng Conditional Formating
Các tính năng khác bao gồm:
1. Kết nối với Word để tạo tài liệu dự thảo kế hoạch trong Word.
2. Sử dụng Validation trong hộp nhập liệu chỉ cho phép người dùng nhập một số dạng ký tự nào đó.
Vì mục tiêu là cho cán bộ xã nên mình bảo vệ đơn giản bằng mật khẩu. Các bạn quan tâm có thể dùng mật khẩu d1ndh1sk để bỏ chế độ bảo vệ bảng tính nhé, trong VBA cũng dùng mật khẩu tương tự.
(Các bạn muốn xem tính năng chuyển văn bản sang Word thì dùng nút Tạo Dự thảo)
Để tải về, các bạn nhấn vào đây nhé
 
Tôi sử dụng thấy có báo lỗi sau đây, tác giả thử xem lại giúp.
untitled.jpg
 
Gần đây, nhờ đi làm tư vấn về lập kế hoạch tại một số nơi, mình đã viết thêm được một công cụ nhỏ hỗ trợ cập nhật thông tin kế hoạch đồng thời cho phép trích xuất kết quả ngay ra Word.
Mình nghĩ là có thể một số bạn sẽ quan tâm xem nên mạo muội xin gửi lên đây để chia sẻ. Một số tính năng của Excel được sử dụng trong công cụ này là:
1. Sử dụng Validation để kiểm tra số liệu đầu vào
2. Sử dụng Conditional Formating
Các tính năng khác bao gồm:
1. Kết nối với Word để tạo tài liệu dự thảo kế hoạch trong Word.
2. Sử dụng Validation trong hộp nhập liệu chỉ cho phép người dùng nhập một số dạng ký tự nào đó.
Vì mục tiêu là cho cán bộ xã nên mình bảo vệ đơn giản bằng mật khẩu. Các bạn quan tâm có thể dùng mật khẩu d1ndh1sk để bỏ chế độ bảo vệ bảng tính nhé, trong VBA cũng dùng mật khẩu tương tự.
(Các bạn muốn xem tính năng chuyển văn bản sang Word thì dùng nút Tạo Dự thảo)
Để tải về, các bạn nhấn vào đây nhé
Đêm đêm ngồi buồn, đào mộ phát.
Mình thấy tool này rất hay. Nhưng vấn đề auto mã nguồn vẫn bị lỗi, thêm nữa là việc chọn bảng mã không tận dụng được con lăn chuột lên xuống thì lại lười.
Hy vọng có update bản mới, đặc biệt là auto để cho thiệt là hoàn hảo.
 

File đính kèm

  • file test.xlsx
    10.4 KB · Đọc: 6
Nhờ vào bài "Sử dụng con lăn của chuột trong combox list và list box". Mình chỉnh sửa code này:
- lăn chuột được ở combobox.
- không cho code này chạy. " 'If Not chkAutoCodeDetection Then RemoveItem toEncoding, fromEncoding"
Còn việc auto mã nguồn thì chịu chết, mình ko biết sửa code, chờ version mới vậy.
Bài đã được tự động gộp:

 

File đính kèm

  • Functions.xla
    665 KB · Đọc: 14
  • file test.xlsx
    12.6 KB · Đọc: 7
Tình hình là mình hiểu ra cách auto mã nguồn như sau:
- text viết là unicode thì font định dạng phải là unicode (VD: Times New Roman).
- text viết là TCVN thì font định dạng phải là TCVN (VD: .VNarial).
- text viết là VNI thì font định dạng phải là VNI (VD: VNI-Times).
Sau khi convert, chương trình sẽ tự động đổi luôn format cell về mã đích.

Còn 2 vấn đề nhỏ có thể bỏ qua:
- Nếu cells có text viết bằng unicode, nhưng định dạng là VNI thì auto mã nguồn hiểu là VNI nên ko chuyển.
- test với chữ "mù" nếu trong cell là đoạn text dài thì auto mã nguồn sẽ bị lỗi khi convert (auto từ uni -> TCVN thành chữ "mự", auto từ uni -> VNI thành chữ "m' "), nếu cells chỉ có duy nhất 1 chữ "mù" thì convert thành công.

Vậy là tốt rồi, chương trình này đạt 99.9999% so với nhu cầu. Xin cảm ơn paulsteigel và tất cả mọi người.
 
Web KT
Back
Top Bottom