Chỉ muc ngữ cảnh

Chỉ mục ngữ cảnh: từ Python đến CSS và JavaScript

Cái từ “concordance” ở đây dùng để chỉ một danh sách các lần xuất hiện của một ngữ đoạn trong một văn bản hoặc ngữ liệu cùng với các từ bao quanh nó. Ở hình thức chữ viết thì ngữ đoạn này có mức nhỏ nhất là một âm tiếng (từ đơn).

Hồi học đại học thì anh cũng được học qua loa về cấu trúc của từ điển giải thích, chứ còn về chuyện ngữ liệu hay chỉ mục ngữ cảnh thì chẳng thấy thầy nói (hoặc có nói nhưng mic không bắt tiếng). Vì thế anh cũng không biết concordance thì tiếng Việt gọi là gì, đành chọn đại một cách tìm thấy trên mạng là “chỉ mục ngữ cảnh”. Để dễ hình dung hơn, như thế này thì được gọi là “chỉ mục ngữ cảnh”:

Tra ngữ liệu ở s.ngonngu.net
Tra ngữ liệu ở s.ngonngu.net

Vậy làm sao để lôi từ cơ sở dữ liệu ra trang web như hình kia?

Bước 1: Tìm kiếm và xử lí bằng Python

Với ngữ liệu là hàng triệu bài báo, anh phải dùng Manticore để tạo chỉ mục. Với mỗi bài báo tìm được, anh sẽ lấy từ khoá làm mốc rồi “bẻ đôi” nội dung thành hai phần trước và sau mốc đó.

"""
sample : str
    One-line plain text (all HTML tags are stripped)
"""

# Regex pattern
pattern = re.compile(rf"(.*)(\b{keyword}\b)(.*)", re.I)

# Split the sample
m = pattern.split(sample)

# Each part is limited to 80 characters
alimit = 80
blimit = alimit * -1

# Pattern matched
if len(m) > 2:

    last = len(m) - 2
    # Avoiding unwanted words caused by the missing one or two characters ;)
    if len(m[1]) > alimit:
        before = re.sub(r"^\S+ (.*)$", "\\1", m[1][blimit:])
    else:
        before = m[1][blimit:]

    if len(m[last]) >= alimit:
        after = re.sub(r"^(.*) \S+$", "\\1", m[last][:alimit])
    else:
        after = m[last][:alimit]

    result = [before, m[2], after]

Vấn đề nảy sinh với chuyện i/y và dấu nối

Nhờ biết cách xử lí dữ liệu đầu vào và cấu hình Manticore nên dù là tìm “hoạ sĩ”, “họa sĩ”, “hoạ sỹ” hay “họa sỹ” cũng ra cùng một kết quả. Nhưng chính điều này lại làm cho tình hình trở nên khá phức tạp vì nếu để nguyên keyword như người dùng nhập vào thì có thể lại không khớp với kết quả. Hay như các trường hợp có dấu nối như “ô xi”, “ô-xi”, “ô-xy”,… cũng vậy. Vì thế, để chắc chắn là bẻ đôi được sample thì biến keyword phải được xử lí thêm để thành một mẫu tìm kiếm như này: ô\s?-\?\s?(xi|xy)

Tuy nhiên, để xi thành xy và ngược lại thì lại là một câu chuyện khá dài khác, nếu có thời gian thì anh sẽ kể sau.

Bước 2: Định dạng bằng CSS

Như vậy, kết quả sau khi “bẻ” xong là một list với 3 phần tử: nội dung trước từ khoá, từ khoá và nội dung sau từ khoá. Anh sử dụng thẻ <table> để hiển thị, mỗi dòng chia làm 2 cột. Với từ khoá, anh cho vào một thẻ <a> để liên kết tới nội dung gốc.

<table id="concordance">
    <tr>
        <td>
            <span>Nội dung trước từ khoá</span>
        </td>
        <td>
            <a href="#nội-dung-gốc">từ khoá</a>
            nội dung sau từ khoá
        </td>
    </tr>
</table>

(Thẻ spantd thứ nhất được bổ sung sau khi phát hiện lỗi với giải pháp direction: rtl)

Nhờ dùng bảng nên các từ khoá sẽ được gióng đúng thành một cột. Do mỗi hàng chỉ được phép hiển thị trên một dòng, nên nếu chiều dài chuỗi lớn hơn độ rộng của mỗi cột thì phải ẩn đi. Vì thế, anh định nghĩa CSS như này:

#concordance {
  width: 100%;
  white-space: nowrap;
}
#concordance td {
  overflow: hidden;
  padding: 3px 2px;
  position: relative;
}
#concordance tr > td:first-child {
  text-align: right;
  width: 45%;
}

Tuy nhiên toàn bộ bảng sẽ bị căn theo lề trái và kéo lệch sang bên phải:

Cột từ khoá bị kéo sang bên phải

Vì thế, anh thêm table-layout: fixed vào #concordance {}. Bảng đã về vị trí cân bằng, mỗi tội là dù đã để text-align: right thì nội dung cần hiển thị (liền trước từ khoá) ở cột trái lại bị ẩn mất:

Cột bên trái bị ẩn một phần dưới cột bên phải

Để giải quyết chuyện này thì phải chơi bẩn một tí bằng cách thêm direction: rtl; vào td:first-child {}.

Cập nhật ngày 26/9/2022:

Anh phát hiện ra là nếu mà để direction: rtl thì chữ số trong đoạn đấy sẽ bị đẩy xuống cuối ô. Vì thế, giải pháp là cho nội dung này vào một thẻ <span> và định nghĩa CSS cho thẻ này là:

#concordance tr > td:first-child span {
  float: right;
}

Sau cùng, để hai đầu mỗi dòng có hiệu ứng mờ dần (fading) thì anh đi chép một codepen và điều chỉnh một chút cho phù hợp. Và đây là toàn bộ phần CSS cho bảng #concordance:

#concordance {
  table-layout: fixed;
  width: 100%;
  white-space: nowrap;
}

#concordance tr:nth-child(even) {
  background-color: #eee;
}

#concordance tr:hover {
  background-color: lightgreen;
}

#concordance td {
  overflow: hidden;
  padding: 3px 2px;
  position: relative;
}

/* Fading effect, ref.: https://codepen.io/43trh/pen/wFuyl */
#concordance td:after {
  content: '';
  position: absolute;
  top: 0;
  width: 2em;
  height: 100%;
  background-image: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,0.9));
}

#concordance tr > td:first-child {
  width: 45%;
}

#concordance tr > td:first-child span {
  float: right;
}

#concordance td:nth-child(even):after {
  right: 0;
  margin-right: -2px;
}

#concordance td:first-child:after {
  left: 0;
  margin-left: -2px;
  background-image: linear-gradient(to right, rgba(255,255,255,0.9), rgba(255,255,255,0));
}

Bước 3: Nêm một chút JavaScript

Vì từ khoá dài ngắn khác nhau nên nếu chỉ để width: 45% cho cột thứ nhất thì đôi khi nó bị lệch khá rõ. Để giải quyết việc này thì anh dùng JavaScript:

function concordance_centering() {
    let table_width = $('#concordance').width(),
    keyword_width = $('#concordance tr td a').first().width(),
    firstcol_width = (table_width - keyword_width - 2) / 2;
        
    $('#concordance tr td').first().css('width', firstcol_width + 'px');
}

$(document).ready(function() {
    concordance_centering();
});

$(window).on('resize', function() {
    concordance_centering();
});

Thử truy cập Dự án S để xem có đúng như thế không.

Có thể vẫn bị lệch một vài điểm ảnh, nhưng cũng không đáng kể. Thật sự là trong chuyện này thì cũng không nên đòi hỏi quá khắt khe đối với một người chụp ảnh chân dung ở Hà Nội.

Một bình luận

  1. […] vậy, không giống như bài trước về chỉ mục ngữ cảnh, bài này thì không cần giải thích chỉ mục trong sách là gì. Vì nhìn tiêu đề […]

Bình luận

Website này sử dụng Akismet để hạn chế spam. Tìm hiểu bình luận của bạn được duyệt như thế nào.