Làm thế nào để làm trường hợp so sánh chuỗi không nhạy cảm?


1056

Làm cách nào để thực hiện so sánh chuỗi không phân biệt chữ hoa chữ thường trong JavaScript?


25
xem .localeCompare()phương pháp javascript mới được thêm vào . Chỉ được hỗ trợ bởi các trình duyệt hiện đại tại thời điểm viết (IE11 +). xem developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
mẹo


5
@AdrienBe "A".localeCompare( "a" );trả về 1trong Bảng điều khiển Chrome 48.
manuell

3
@manuell có nghĩa là "a"đến trước "A"khi sắp xếp. Giống như "a"đến trước "b". Nếu hành vi này không muốn, người ta có thể muốn .toLowerCase()từng chữ cái / chuỗi. I E. "A".toLowerCase().localeCompare( "a".toLowerCase() )xem developer.mozilla.org/en/docs/Web/JavaScript/Reference/
mẹo

2
Bởi vì so sánh thường là một thuật ngữ được sử dụng để sắp xếp / sắp xếp chuỗi tôi cho rằng. Tôi đã nhận xét ở đây một thời gian dài bây giờ. ===sẽ kiểm tra sự bằng nhau nhưng sẽ không đủ tốt để sắp xếp / sắp xếp chuỗi (xem câu hỏi ban đầu tôi liên kết đến).
Adrien Be

Câu trả lời:


1163

Cách đơn giản nhất để làm điều đó (nếu bạn không lo lắng về các ký tự Unicode đặc biệt) là gọi toUpperCase:

var areEqual = string1.toUpperCase() === string2.toUpperCase();

44
Chuyển đổi sang chữ hoa hoặc chữ thường không cung cấp so sánh trường hợp không nhạy cảm chính xác trong tất cả các ngôn ngữ. i18nguy.com/unicode/turkish-i18n.html
Samuel Neff

57
@sam: Tôi biết. Đó là lý do tại sao tôi viết if you're not worried about special Unicode characters.
SLaks

141
Có một lý do để thích toUpperCasehơn toLowerCase?
jpmc26


19
Đây thực sự là JS tốt nhất phải cung cấp?
Kugel

210

EDIT : Câu trả lời này ban đầu được thêm vào 9 năm trước. Hôm nay bạn nên sử dụng localeComparevới sensitivity: 'accent'tùy chọn:

function ciEquals(a, b) {
    return typeof a === 'string' && typeof b === 'string'
        ? a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0
        : a === b;
}

console.log("'a' = 'a'?", ciEquals('a', 'a'));
console.log("'AaA' = 'aAa'?", ciEquals('AaA', 'aAa'));
console.log("'a' = 'á'?", ciEquals('a', 'á'));
console.log("'a' = 'b'?", ciEquals('a', 'b'));

Các { sensitivity: 'accent' }kể localeCompare()để điều trị hai biến thể của bức thư cơ sở tương tự như giống nhau trừ khi họ có điểm nhấn khác nhau (như trong ví dụ thứ ba) ở trên.

Ngoài ra, bạn có thể sử dụng { sensitivity: 'base' }, xử lý hai ký tự tương đương miễn là ký tự cơ sở của chúng giống nhau (do đó Asẽ được coi là tương đương với á).

Lưu ý rằng tham số thứ ba localeComparekhông được hỗ trợ trong IE10 hoặc thấp hơn hoặc một số trình duyệt di động nhất định (xem biểu đồ tương thích trên trang được liên kết ở trên), vì vậy nếu bạn cần hỗ trợ các trình duyệt đó, bạn sẽ cần một số dự phòng:

function ciEqualsInner(a, b) {
    return a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0;
}

function ciEquals(a, b) {
    if (typeof a !== 'string' || typeof b !== 'string') {
        return a === b;
    }

    //      v--- feature detection
    return ciEqualsInner('A', 'a')
        ? ciEqualsInner(a, b)
        : /*  fallback approach here  */;
}

Câu trả lời gốc

Cách tốt nhất để thực hiện so sánh không phân biệt chữ hoa chữ thường trong JavaScript là sử dụng match()phương thức RegExp với icờ.

Tìm kiếm không phân biệt chữ hoa chữ thường

Khi cả hai chuỗi được so sánh là các biến (không phải là hằng), thì nó phức tạp hơn một chút vì bạn cần tạo RegExp từ chuỗi nhưng việc chuyển chuỗi sang hàm tạo RegExp có thể dẫn đến kết quả khớp không chính xác hoặc khớp không thành công nếu chuỗi có regex đặc biệt nhân vật trong đó.

Nếu bạn quan tâm đến việc quốc tế hóa, đừng sử dụng toLowerCase()hoặc toUpperCase()vì nó không cung cấp các so sánh không phân biệt chữ hoa chữ thường trong tất cả các ngôn ngữ.

http://www.i18nguy.com/unicode/turkish-i18n.html


5
@Quandary, vâng, đó là những gì tôi đã nói phải được xử lý - "bạn cần tạo RegExp từ chuỗi nhưng việc chuyển chuỗi sang hàm tạo RegExp có thể dẫn đến kết quả khớp không chính xác hoặc không khớp nếu chuỗi có ký tự regex đặc biệt trong đó"
Samuel Neff

21
Sử dụng điều này là giải pháp tốn kém nhất để so sánh chuỗi không phân biệt chữ hoa chữ thường. RegExp có nghĩa là để khớp mẫu phức tạp, do đó, nó cần xây dựng một cây quyết định cho mỗi mẫu, sau đó thực thi điều đó đối với các chuỗi đầu vào. Trong khi nó sẽ hoạt động, nó tương đương với việc đi máy bay phản lực để đi mua sắm trên khối tiếp theo. tl; dr: xin đừng làm điều này
Agoston Horvath

2
tôi có thể sử dụng localeCompare (), nhưng nó trả về -1 cho 'a'.localeCompare('A')và giống như op tôi đang tìm kiếm so sánh chuỗi không phân biệt chữ hoa chữ thường.
StingyJack

3
@StingyJack để so sánh không phân biệt chữ hoa chữ thường bằng localeCompare, bạn nên làm 'a'.localeCompare (' A ', không xác định, {độ nhạy:' cơ sở '})
Judah Gabriel Himango

1
Lưu ý: Các localeComparephiên bản đòi hỏi động cơ JavaScript hỗ trợ các API ECMAScript® Quốc tế hóa , mà nó được không cần phải làm gì. Vì vậy, trước khi dựa vào nó, bạn có thể muốn kiểm tra xem nó có hoạt động trong môi trường bạn đang sử dụng không. Ví dụ: const compareInsensitive = "x".localeCompare("X", undefined, {sensitivity: "base"}) === 0 ? (a, b) => a.localeCompare(b, undefined, {sensitivity: "base"}) : (a, b) => a.toLowerCase().localeCompare(b.toLowerCase());hoặc một số như vậy.
TJ Crowder

46

Như đã nói trong các ý kiến ​​gần đây, string::localeComparehỗ trợ so sánh trường hợp không nhạy cảm (trong số những thứ mạnh mẽ khác).

Đây là một ví dụ đơn giản

'xyz'.localeCompare('XyZ', undefined, { sensitivity: 'base' }); // returns 0

Và một chức năng chung bạn có thể sử dụng

function equalsIgnoringCase(text, other) {
    return text.localeCompare(other, undefined, { sensitivity: 'base' }) === 0;
}

Lưu ý rằng thay vì undefinedbạn có thể nên nhập địa điểm cụ thể mà bạn đang làm việc. Điều này quan trọng như được biểu thị trong các tài liệu MDN

trong tiếng Thụy Điển, ä và a là các chữ cái cơ sở riêng biệt

Tùy chọn độ nhạy

Tùy chọn độ nhạy được lập bảng từ MDN

Hỗ trợ trình duyệt

Tính đến thời điểm đăng bài, UC Browser cho Android và Opera Mini không hỗ trợ các tham số tùy chọnngôn ngữ . Vui lòng kiểm tra https://caniuse.com/#search=localeCompare để biết thông tin cập nhật.


35

Với sự giúp đỡ của biểu hiện thường xuyên, chúng ta cũng có thể đạt được.

(/keyword/i).test(source)

/ilà cho trường hợp bỏ qua. Nếu không cần thiết, chúng tôi có thể bỏ qua và kiểm tra KHÔNG khớp trường hợp nhạy cảm như

(/keyword/).test(source)

17
Sử dụng một regex như thế này sẽ phù hợp với chất nền! Trong ví dụ của bạn, chuỗi keyWORDsẽ tạo ra kết quả trùng khớp. Nhưng chuỗi this is a keyword yohoặc keywordscũng sẽ dẫn đến một kết quả khớp tích cực. Hãy nhận biết điều đó :-)
Elmer

6
Điều này không trả lời kiểm tra Bình đẳng (trường hợp không nhạy cảm) như được hỏi trong câu hỏi! Nhưng, đây là một kiểm tra Chứa ! Đừng sử dụng nó
S.Serpooshan 6/2/19

4
Tất nhiên, để khớp với toàn bộ chuỗi, có thể thay đổi regrec thành /^keyword$/.test(source), nhưng 1) nếu keywordkhông phải là hằng số, bạn cần phải làm new RegExp('^' + x + '$').test(source)và 2) sử dụng một biểu thức chính quy để kiểm tra một cái gì đó đơn giản như bình đẳng chuỗi không phân biệt chữ hoa chữ thường hoàn toàn không hiệu quả
JHH

28

Hãy nhớ rằng vỏ là một hoạt động cụ thể địa phương. Tùy thuộc vào kịch bản bạn có thể muốn đưa nó vào tài khoản. Ví dụ: nếu bạn đang so sánh tên của hai người, bạn có thể muốn xem xét ngôn ngữ nhưng nếu bạn đang so sánh các giá trị được tạo bằng máy như UUID thì bạn có thể không. Đây là lý do tại sao tôi sử dụng chức năng sau trong thư viện utils của mình (lưu ý rằng việc kiểm tra loại không được bao gồm vì lý do hiệu suất).

function compareStrings (string1, string2, ignoreCase, useLocale) {
    if (ignoreCase) {
        if (useLocale) {
            string1 = string1.toLocaleLowerCase();
            string2 = string2.toLocaleLowerCase();
        }
        else {
            string1 = string1.toLowerCase();
            string2 = string2.toLowerCase();
        }
    }

    return string1 === string2;
}

Có một lý do bạn sử dụng "!!" để thực hiện chuyển đổi boolean rõ ràng, thay vì cho phép mệnh đề if đánh giá tính trung thực của các giá trị?
Celos

Nó không bắt buộc. Tôi đoán tôi đã có nó từ phiên bản khác của mã phức tạp hơn. Tôi đã cập nhật câu trả lời.
Shital Shah

@thekodester chức năng của bạn có lỗi. Điều này compareStrings("", "")sẽ cung cấp cho falsemặc dù thực tế các chuỗi bằng nhau.
Serge

@Sergey Làm điều đó trả lại truecho tôi. Có lẽ đó là một lỗi với trình duyệt của bạn?
Jenna Sloan

14

Gần đây tôi đã tạo một thư viện vi mô cung cấp các trình trợ giúp chuỗi không phân biệt chữ hoa chữ thường: https://github.com/nickuraltsev/ignore-case . (Nó sử dụng toUpperCasenội bộ.)

var ignoreCase = require('ignore-case');

ignoreCase.equals('FOO', 'Foo'); // => true
ignoreCase.startsWith('foobar', 'FOO'); // => true
ignoreCase.endsWith('foobar', 'BaR'); // => true
ignoreCase.includes('AbCd', 'c'); // => true
ignoreCase.indexOf('AbCd', 'c'); // => 2

12

nếu bạn lo ngại về hướng của bất đẳng thức (có lẽ bạn muốn sắp xếp danh sách), bạn sẽ phải thực hiện chuyển đổi trường hợp và vì có nhiều ký tự chữ thường trong unicode hơn chữ hoa có thể là chuyển đổi tốt nhất để sử dụng.

function my_strcasecmp( a, b ) 
{
    if((a+'').toLowerCase() > (b+'').toLowerCase()) return 1  
    if((a+'').toLowerCase() < (b+'').toLowerCase()) return -1
    return 0
}

Javascript dường như sử dụng ngôn ngữ "C" để so sánh chuỗi, vì vậy thứ tự kết quả sẽ xấu nếu các chuỗi chứa khác với các chữ cái ASCII. không có nhiều thứ có thể được thực hiện về điều đó mà không cần kiểm tra chi tiết hơn về chuỗi.


7

Giả sử chúng ta muốn tìm biến chuỗi needletrong biến chuỗi haystack. Có ba vấn đề:

  1. Các ứng dụng quốc tế nên tránh string.toUpperCasestring.toLowerCase. Sử dụng một biểu thức thông thường mà bỏ qua trường hợp thay thế. Ví dụ, var needleRegExp = new RegExp(needle, "i");theo sau needleRegExp.test(haystack).
  2. Nói chung, bạn có thể không biết giá trị của needle. Hãy cẩn thận needlekhông chứa bất kỳ ký tự đặc biệt biểu thức chính quy . Thoát khỏi những bằng cách sử dụng needle.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");.
  3. Trong các trường hợp khác, nếu bạn muốn khớp chính xác needlehaystack, chỉ cần bỏ qua trường hợp, hãy đảm bảo thêm "^"vào lúc bắt đầu và "$"ở cuối hàm tạo biểu thức chính quy của bạn.

Cân nhắc các điểm (1) và (2), một ví dụ sẽ là:

var haystack = "A. BAIL. Of. Hay.";
var needle = "bail.";
var needleRegExp = new RegExp(needle.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), "i");
var result = needleRegExp.test(haystack);
if (result) {
    // Your code here
}

Bạn đặt cược! Tất cả những gì bạn cần làm là thay thế new RegExp(...)phần trong dòng 3 bằng : new RegExp("^" + needle.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "$", "i");. Điều này đảm bảo rằng không có ký tự nào khác trước hoặc sau chuỗi tìm kiếm của bạn needle.
Chris Chute

4

Có hai cách để so sánh trường hợp không nhạy cảm:

  1. Chuyển đổi chuỗi thành chữ hoa và sau đó so sánh chúng bằng toán tử nghiêm ngặt ( ===). Cách toán tử nghiêm ngặt xử lý toán hạng đọc công cụ tại: http://www.thesstech.com/javascript/relational-logical-operators
  2. Khớp mẫu bằng các phương thức chuỗi:

Sử dụng phương pháp chuỗi "tìm kiếm" cho tìm kiếm không phân biệt chữ hoa chữ thường. Đọc về tìm kiếm và các phương thức chuỗi khác tại: http://www.thesstech.com/potype-matching-USE-opes-methods

<!doctype html>
  <html>
    <head>
      <script>

        // 1st way

        var a = "apple";
        var b = "APPLE";  
        if (a.toUpperCase() === b.toUpperCase()) {
          alert("equal");
        }

        //2nd way

        var a = " Null and void";
        document.write(a.search(/null/i)); 

      </script>
    </head>
</html>

4

Có rất nhiều câu trả lời ở đây, nhưng tôi muốn thêm một câu hỏi dựa trên việc mở rộng chuỗi lib:

String.prototype.equalIgnoreCase = function(str)
{
    return (str != null 
            && typeof str === 'string'
            && this.toUpperCase() === str.toUpperCase());
}

Theo cách này, bạn có thể sử dụng nó giống như bạn làm trong Java!

Thí dụ:

var a = "hello";
var b = "HeLLo";
var c = "world";

if (a.equalIgnoreCase(b)) {
    document.write("a == b");
}
if (a.equalIgnoreCase(c)) {
    document.write("a == c");
}
if (!b.equalIgnoreCase(c)) {
    document.write("b != c");
}

Đầu ra sẽ là:

"a == b"
"b != c"

String.prototype.equalIgnoreCase = function(str) {
  return (str != null &&
    typeof str === 'string' &&
    this.toUpperCase() === str.toUpperCase());
}


var a = "hello";
var b = "HeLLo";
var c = "world";

if (a.equalIgnoreCase(b)) {
  document.write("a == b");
  document.write("<br>");
}
if (a.equalIgnoreCase(c)) {
  document.write("a == c");
}
if (!b.equalIgnoreCase(c)) {
  document.write("b != c");
}


4

Sử dụng RegEx để so khớp chuỗi hoặc so sánh.

Trong JavaScript, bạn có thể sử dụng match()để so sánh chuỗi, đừng quên đặti vào RegEx.

Thí dụ:

var matchString = "Test";
if (matchString.match(/test/i)) {
  alert('String matched');
}
else {
 alert('String not matched');
}

1
Hãy chắc chắn rằng bạn ổn với các trận đấu một phần, nếu không matchString.match(/^test$/i).
hackel

Thay vì "kiểm tra" chữ thường bạn có var x = 'test', sẽ matchString.match(/x/i)hoạt động như thế nào? Nếu không, những gì sẽ làm việc?
Razvan Zamfir

3
str = 'Lol', str2 = 'lOl', regex = new RegExp('^' + str + '$', 'i');
if (regex.test(str)) {
    console.log("true");
}

3

Nếu cả hai chuỗi có cùng miền đã biết, bạn có thể muốn sử dụng Intl.Collatorđối tượng như thế này:

function equalIgnoreCase(s1: string, s2: string) {
    return new Intl.Collator("en-US", { sensitivity: "base" }).compare(s1, s2) === 0;
}

Rõ ràng, bạn có thể muốn lưu trữ bộ đệm Collatorcho hiệu quả tốt hơn.

Ưu điểm của phương pháp này là nó sẽ nhanh hơn nhiều so với sử dụng RegExps và dựa trên một bộ tham số cực kỳ tùy biến (xem mô tả localesoptionscác tham số của hàm tạo trong bài viết ở trên) của bộ collators sẵn sàng sử dụng.


Một lựa chọn khác cho độ nhạy là accent, nó giữ cho trường hợp không nhạy cảm, nhưng xử lý aánhư các ký tự riêng biệt. Vì vậy, basehoặc accentcả hai có thể phù hợp tùy thuộc vào nhu cầu chính xác.
Matthew Crumley

2

Tôi đã viết một phần mở rộng. rất tầm thường

if (typeof String.prototype.isEqual!= 'function') {
    String.prototype.isEqual = function (str){
        return this.toUpperCase()==str.toUpperCase();
     };
}

1
Điều gì xảy ra với hai cơ sở mã với các ý tưởng khác nhau về cách String # isEqual hoạt động nên cố gắng tồn tại cùng một lúc?
Ryan Cavanaugh

3
@KhanSharp Rất nhiều người coi đó là một mô hình chống để sửa đổi nguyên mẫu của các kiểu dựng sẵn. Đây là lý do tại sao mọi người có thể bỏ phiếu trả lời của bạn.
jt000

1
Nó không được coi là thích định nghĩa phương thức chưa biết? Ví dụ: ngay khi một số trình duyệt quyết định triển khai String#isEqualhoặc Object#isEqualtất cả các trang của bạn hoạt động khác đi và có thể làm những điều kỳ lạ nếu thông số kỹ thuật không khớp chính xác với bạn.
Robert

2

Ngay cả câu hỏi này đã được trả lời. Tôi có một cách tiếp cận khác để sử dụng RegExp và khớp để bỏ qua phân biệt chữ hoa chữ thường. Vui lòng xem liên kết của tôi https://jsfiddle.net/marchdave/7v8bd7dq/27/

$("#btnGuess").click(guessWord);

  function guessWord() {

   var letter = $("#guessLetter").val();
   var word = 'ABC';
   var pattern = RegExp(letter, 'gi'); // pattern: /a/gi

   var result = word.match(pattern);
   alert('Ignore case sensitive:' + result);

  }

1

Làm thế nào về việc KHÔNG ném ngoại lệ và KHÔNG sử dụng regex chậm?

return str1 != null && str2 != null 
    && typeof str1 === 'string' && typeof str2 === 'string'
    && str1.toUpperCase() === str2.toUpperCase();

Đoạn mã trên giả định rằng bạn không muốn khớp nếu chuỗi đó là null hoặc không xác định.

Nếu bạn muốn khớp null / không xác định, thì:

return (str1 == null && str2 == null)
    || (str1 != null && str2 != null 
        && typeof str1 === 'string' && typeof str2 === 'string'
        && str1.toUpperCase() === str2.toUpperCase());

Nếu vì lý do nào đó bạn quan tâm đến không xác định so với null:

return (str1 === undefined && str2 === undefined)
    || (str1 === null && str2 === null)
    || (str1 != null && str2 != null 
        && typeof str1 === 'string' && typeof str2 === 'string'
        && str1.toUpperCase() === str2.toUpperCase());

Hoặc chỉstr1 == str2 || ...
SLaks

1

Vì không có câu trả lời rõ ràng cung cấp một đoạn mã đơn giản để sử dụng RegExp, đây là nỗ lực của tôi:

function compareInsensitive(str1, str2){ 
  return typeof str1 === 'string' && 
    typeof str2 === 'string' && 
    new RegExp("^" + str1.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + "$", "i").test(str2);
}

Nó có một số lợi thế:

  1. Xác minh loại tham số ( undefinedví dụ: bất kỳ tham số không phải chuỗi nào , sẽ phá vỡ biểu thức nhưstr1.toUpperCase() ).
  2. Không bị các vấn đề quốc tế hóa có thể.
  3. Thoát khỏi RegExpchuỗi.

Nhưng bị thiếu regex thoát.
Qwertiy

@Qwertiy điểm công bằng, đã thêm thoát trên mỗi stackoverflow.com/a/3561711/67824 .
Ohad Schneider

0

Đây là một phiên bản cải tiến của câu trả lời này .

String.equal = function (s1, s2, ignoreCase, useLocale) {
    if (s1 == null || s2 == null)
        return false;

    if (!ignoreCase) {
        if (s1.length !== s2.length)
            return false;

        return s1 === s2;
    }

    if (useLocale) {
        if (useLocale.length)
            return s1.toLocaleLowerCase(useLocale) === s2.toLocaleLowerCase(useLocale)
        else
            return s1.toLocaleLowerCase() === s2.toLocaleLowerCase()
    }
    else {
        if (s1.length !== s2.length)
            return false;

        return s1.toLowerCase() === s2.toLowerCase();
    }
}



Tập quán & kiểm tra:


0

Chuyển đổi cả hai thành thấp hơn (chỉ một lần vì lý do hiệu suất) và so sánh chúng với toán tử ternary trong một dòng duy nhất:

function strcasecmp(s1,s2){
    s1=(s1+'').toLowerCase();
    s2=(s2+'').toLowerCase();
    return s1>s2?1:(s1<s2?-1:0);
}

Ai nói C đã chết? : D
Seth

0

Nếu bạn biết bạn đang xử lý asciivăn bản thì bạn chỉ có thể sử dụng phép so sánh ký tự chữ hoa / chữ thường.

Chỉ cần đảm bảo chuỗi "hoàn hảo" của bạn (chuỗi bạn muốn khớp với) là chữ thường:

const CHARS_IN_BETWEEN = 32;
const LAST_UPPERCASE_CHAR = 90; // Z
function strMatchesIgnoreCase(lowercaseMatch, value) {
    let i = 0, matches = lowercaseMatch.length === value.length;
    while (matches && i < lowercaseMatch.length) {
        const a = lowercaseMatch.charCodeAt(i);
        const A = a - CHARS_IN_BETWEEN;
        const b = value.charCodeAt(i);
        const B = b + ((b > LAST_UPPERCASE_CHAR) ? -CHARS_IN_BETWEEN : CHARS_IN_BETWEEN);
        matches = a === b // lowerA === b
            || A === b // upperA == b
            || a === B // lowerA == ~b
            || A === B; // upperA == ~b
        i++;
    }
    return matches;
}

0

Tôi thích biến thể tốc ký nhanh này -

export const equalsIgnoreCase = (str1, str2) => {
    return (!str1 && !str2) || (str1 && str2 && str1.toUpperCase() == str2.toUpperCase())
}

Nhanh chóng trong việc xử lý, và làm những gì nó được dự định.


0

Điều này javascriptThư viện dường như cung cấp rất nhiều hoạt động chuỗi. Nó rất thuận tiện để sử dụng

Cài đặt thế nào

npm install --save string

Nhập khẩu

var S = require('string');

Chuỗi so sánh bỏ qua

var isEqual = S('ignoreCase').equalsIgnoreCase('IGNORECASE')
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.