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”:
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ẻ span
ở td
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:
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:
Để 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.
Bình luận