Thứ Tư, 31 tháng 10, 2018

Parse tài liệu Xml không Well-Formed sử dụng StAX parser với các trường hợp thiếu thẻ đóng tag và thẻ sai thẻ đóng tag

Parse tài liệu Xml không Well-Formed sử dụng StAX parser với các trường hợp thiếu thẻ đóng tag và thẻ sai thẻ đóng tag

Tác giả: Nguyễn Sinh Cung

Mục đích: Bài viết này nhằm hỗ trợ việc phân tích dữ liệu từ tài liệu xml chưa well-formed, cụ thể là việc crawl dữ liệu từ các trang website sử dụng html. Bài viết này sử dụng bộ StAX parser để xử lý các trường hợp trong quá trình parse tài liệu chưa well-form khi thiếu thẻ đóng và sai thẻ đóng, tiếp tục quá trình xử lý mà không dừng chương trình lại.

 

Kiến thức tổng quát

StAX – là bộ parser thực hiện parsing tài liệu XML theo cơ chế như sau

·      Khi tài liệu XML được đưa vào bộ parser, toàn bộ tài liệu được kiểm tra well-formed

·      Khi nội dung tài liệu kiểm tra xong và đóng, toàn bộ tài liệu được truy cập dưới dạng Stream và được nạp vào bộ nhớ từng phần tùy theo tiến trình duyệt tài liệu

·      Sau đó, bộ StAX parser sẽ thực hiện việc đọc các thành phần theo chiều đi tới trong stream tùy theo sự điều khiển của ứng dụng và lấy giá trị theo yêu cầu của người dùng

·      StAX parser có đặc tính tối ưu là bộ parse dạng pull, nó cho phép người sử dụng điều khiển quá trình xử lý. Nó cung cấp khá năng cho người dùng có thể dừng bộ parser bất cứ khi nào, có thể bỏ qua một số phần tử nhất định, có thể tạm dừng quá trình parse, có thể tiếp tục quá trình parse đã dừng

·      StAX parser hỗ trợ multitple thread đối với ứng dụng và hỗ trợ nhiều người sử dụng cùng một lúc

·      Cú pháp API của StAX rất đơn giản bởi vì nó hỗ trợ di chuyển trong Stream và đưa ra dữ liệu dạng tổng quát nhất cho người sử dụng

·      StAX là bộ parser duy nhất hỗ trợ cơ chế đọc và ghi trên tài liệu XML

·      StAX hỗ trợ cơ chế đọc tài liệu XML cực kỳ lớn và hỗ trợ xử lý với tốc độ duyệt khá tốt và phù hợp với thiết bị có bộ nhớ nhỏ và ít

Các nội dung quan trọng trong bài viết này

·      HTML là một tài liệu XML không được well-form (VD: thẻ input và meta không có chứa thẻ đóng, sai tên thẻ đóng ). Vì vậy, khi sử dụng bộ parse StAX, chúng ta cần lưu ý để bộ parse không bị dừng khi gặp lỗi không well-form. 

·      StAX là bộ parse tài liệu XML có thể cung cấp những thông tin liên quan đến tài liệu HTML như

o   Loại thẻ element theo tuần tự từ trên xuống từ trái qua

o   Tên thẻ

o   Giá trị thuộc tính của thẻ (nếu có).

·      Vì vậy, để trích xuất được những dữ liệu cần thiết trên tài liệu HTML, chúng ta cần quan tâm đến vị trí và thuộc tính của thẻ chứa những dữ liệu đó trong tài liệu HTML.

Các giải pháp xử lý

·      Phương pháp mà chúng ta tiếp cận ở đây để xử lý trong trường hợp tài liệu XML không well-form

o   Thứ nhất bỏ qua nội dung trong thẻ hiện hành đang bị thiếu thẻ đóng hoặc sai tên và tiếp tục parse

o   Thứ hai là gán giá trị cho thẻ thiếu bằng null.

·      Ở trong bài viết dưới đây, chúng ta sẽ tiếp cận theo cách thứ nhất.

·      Khi bộ STAX parser gặp được XML chưa được well-formed, bộ STAX sẽ ném ra lỗi XMLStreamException và ở đây sẽ xảy ra 2 trường hợp:

o   Trường hợp 1: Bộ parse sẽ vẫn cho chúng ta di chuyển con trỏ cursor tiếp tục sau khi chúng ta đã catch và xử lý lỗi.

§  Đây là trường hợp bị thiếu thẻ đóng.

§  Để xử lí trường hợp này chúng ta cần khởi tạo một List lưu giữ các thẻ tag và nội dung trong qua trình parse.

§  Ngay khi gặp lỗi chúng ta sẽ tiến hành tìm ngược lại vi trí mở thẻ của thẻ bị thiếu thẻ đóng trong List ở trên để tiến hành remove à đến vị trí hiện tại.

§  Sau đó, chúng ta tiếp tục di chuyển con trỏ đến vị trí parse tiếp theo

o   Trường hợp 2: Bộ parse sẽ không cho phép ta di chuyển con trỏ cho tới khi nó thấy tài liệu XML đã Well-Formed.

§  Đây là trường hợp bị sai thẻ đóng

§  Bộ parse sẽ không cho phép chúng ta di chuyển con trỏ sau khi chúng ta đã catch lỗi xong, nó sẽ báo lỗi tới khi  nó thấy được tài liệu XML đã được WellFormed.

§  Vì vậy chúng ta cần phải tiến hành chỉnh sửa file XML bằng cách lấy vị trí  của thẻ mở và vị trí của thẻ đóng đang bị lỗi , tiến hành remove hết nội dung trong khoảng này, sau đó tiến hành parse lại.

Áp dụng định hướng nêu trên trong việc parse dữ liệu tài liệu chưa được well-formed

·      Tool và các công nghệ sử dụng

o   JDK 8

o   JAXP (TrAX)

o   Các plugin hỗ trợ inspect element trong các browser

·      Các bước thực hiện

o   Chúng ta chuẩn bị một tài liệu chưa được well-formed theo các trường hợp như bên dưới

§  Case 1

·        Tài liệu chuẩn bị sẽ thiếu thẻ đóng. Trong bài viết này, chúng ta làm thiếu thẻ đóng của thẻ <Cost>

·        Mục tiêu của tài liệu này sẽ làm cho bộ StAX parse ném lỗi StreamException.

o   Trong ví dụ như hình dưới là thẻ Cost được mở nhưng chưa được đóng

o   Thẻ ISBN đóng sai thẻ

o   Cấu trúc của project được xây dựng cho ứng dụng như sau

 

§  Bước 1: Khởi tạo class hỗ trợ quá trình parse

·        Chúng ta xây dựng class XMLUtilities chứa phương thức parseFileToXMLEvent hỗ trợ parse xml thành STAX cursor.

§  Bước 2: Khởi tạo class xử lý parse XML not well-form

·        Chúng ta xây dựng class ParseXMLNotWellFormed

o   Chúng ta khởi tạo attribute List chứa các XMLevent (lEvent) trả về. Dựa trên tập List này, chúng ta sẽ thao tác với dữ liệu đã được parse và well-formed  dữ liệu.

o   Chúng ta xây phương thức autoParseFileEvenNotWellform

o   Trong phương thức này, chúng ta thực hiện parse tài liệu XML sử dụng StAX cursor với phương thức đã xây dựng ở các bước trên

o   Chúng ta tiếp tục thực hiện khởi tạo hai List quan trọng

§  Một List lưu giữ vị trí thẻ mở gần nhất (lStartTagPogs)

§  Một List lưu giữ các XMLEvent ở đây là các loại thẻ tag như thẻ character, startelement, endElment (lEvent).

o   Chúng ta thực hiện duyệt tuần tự từng Event. Nếu việc duyệt thực hiện đến cuối, kiểm tra XMLEvent khác null thì lặp tức lEvent sẽ Add thêm tag đó vào.

o   Đối với lStartTagPogs, chúng ta tiến hành kiểm tra nếu gặp thẻ Start sẽ Add vào list vị trí của thẻ đó  trong lEvent và khi gặp được thẻ End àRemove vị trí thẻ Start nằm cuối trong lStartTagPogs, ra khỏi List vì đây chính là thẻ Start của thẻ End này à Thẻ đó đã WellForm.

o   VD: lEvents [Start1,Start2] , lStartTagPogs [0,1], 0 ,1 là vị trí của thẻ Start1 và Start2 được thêm vào khi gặp 2 thẻ này.

o   Khi End2 được thêm vào lEvents  thì lStartTagPogs cũng sẽ remove vị trí số 1 tức là thẻ Start2 đã Well-Form, tiếp tục khi gặp End1 à Remove vị trí còn lại trong List là Sô 0.

o   Trong trường hợp gặp lỗi sẽ được miêu tả cách xử lý trong phần tiếp theo của bài viết này.

o   Dưới đây là toàn bộ quy trình parse và catch lỗi , chúng ta sẽ tiến hành phân tích rõ từng Case.

§  Bước 3: Tiến hành catch lỗi và phân tích lỗi xảy ra

o   Chúng ta sử dụng try catch để catch lỗi XMLStreamException

§  Số 1: Là nơi xử lý lỗi cho trường hợp thứ nhất. (Case 1)

§  Số 2: Là nơi xử lý lỗi cho trường hợp thứ hai. (Case 2)

o   Biến flagRemoved ở đây được sử dụng để phân loại Case xảy ra trong 2 trường hợp trên. Biến này sẽ được kích hoạt thành true khi tài liệu xử lý có lỗi.

o   Như đã nói ở trên đối với Case 1, chúng ta vẫn có thể dịch chuyển con trỏ đi tiếp mà không ảnh hưởng gì đến bộ parse, còn đối với Case 2 sẽ không thể xảy ra điều đó.

o   Vì vậy, khi lỗi được phát hiện thì chương trình sẽ chưa thực hiện xử lý lỗi ngay, mà chỉ bật cờ lên (flagRemove) để báo cho chương trình biết là đã có lỗi xảy ra và khi quay lại sau lệnh nextEvent() vẫn tiếp tục catch lỗi thì chúng ta đang nằm ở Case 2. Ngược lại, chúng ta sẽ rơi vào Case 1 như hình ở trên.

§  Bước 4: Tiến hành xử lý lỗi xảy ra cho trường hợp 1(Case 1)

·        Như đã trình bày ở trên khi lỗi xảy ra, chúng ta sẽ không lấy nội dung và thẻ tag của thẻ bị lỗi.

o   VD: lEvents [Start1,Start2,…,End1] tới khi gặp thẻ End1 chương trình sẽ ném lỗi vì không tìm thấy thẻ đóng của Start2 và lúc này trong lStartTagPogs đang giữ 2 giá trị là 0 và 1 chúng ta tiến hành lấy ra giá trị cuối list ra là 1 tương ứng với vị trí của thẻ Start2 trong lEvents .

·        Chúng ta sẽ tiến hành remove trong lEvents từ vị trí 1 (vị trí thẻ Start2) đến cuối à Loại bỏ tất cả nội dung liên quan tới Start2.

o   Để phục vụ việc xóa List chúng  ta bổ sung thêm hàm removeListFrom trong class XMLUtilities.

·        Sau khi xử lý lỗi xong, chúng ta tiến hành tiếp tục Parse và không gặp phải trở ngại nào và bộ lEvent đã xóa hoàn toàn thẻ lỗi.

§  Bước 5: Tiến hành xử lí lỗi cho trường hợp 2 (Case 2)

o   Như đã miêu tả ở trên khi flagRemove đang được bật tức đã gặp được lỗi, và nếu như lại tiếp tục Catch được lỗi thì tức là bộ parser không cho phép ta di chuyển cursor à chúng ta đang ở trong trường hợp 2.

o   Đầu tiên, chúng ta cần xác định được vị trí của thẻ mở và thẻ đóng (nơi gặp được lỗi).

§  Lưu ý: vị trí thẻ mở ở đây là vị trí trong dòng và cột của thẻ đó trong File XML, khác so với vị trí thẻ mở trong lEvents ở trên, thẻ đóng cũng vậy.

§  Vị trí thẻ mở:

·        Để lấy được vị trí của thẻ mở trong file XML, chúng ta cần lấy thẻ Start lỗi trong lEvents tương tự ở Case 1 lấy vị giá trị cuối trong lStartTagPogs tương ứng vị trí thẻ lỗi và search vị trí đó trong lEvents .

·        Sau khi có được element, chúng ta sẽ getLocation của thẻ đó và lặp tức Location sẽ trả về cho chúng ta dữ liệu của colNumber và lineNumber.

o   Lưu ý : đối với colNumber của thẻ Start sẽ là vị trí sau dấu ‘>’ đóng thẻ nên ta sẽ trừ đi 1 để có vị trí mốc là dấu ‘>’.

§  Vị trí thẻ đóng:

·        Đối với thẻ đóng, chúng ta cần thực hiện việc phân tích chuỗi Messge Errror trả về để lấy ra được dòng và cột của nó trong file XML.

·        Lưu ý: đối với vị trí col lấy được từ messgeError sẽ bắt đầu tại kí tự đầu tiên của tên tag nên chúng ta sẽ lùi về 3 đơn vị để lấy mốc là vị trí của dấu ‘<’ vd: </abc> messgeError sẽ trả về vị trí của chữ ‘a’.

o   Sau khi chúng ta đã có vị trí của hai thẻ Start và End tiếp theo chúng ta sẽ truyền 4 thông số trên cho hàm remakeFile để tiến hành xóa dữ liệu trong file XML.

o   Vì sau phải -1 và -3 đã được nêu rõ ở phía trên.

o   Nội dung và cách xử lý của hàm remarkFile được thể hiện trong nội dung của bước tiếp theo

§  Bước 6: Tiến hành Remake lại File XML

o   Trong class XMLUtilities khởi tạo hàm remakeFile

o   Trong nội dung của code này, chúng ta sẽ có:

§  startRow: tương ứng vị trí dòng của thẻ Start.

§  endRow: tương ứng vị trí dòng của thẻ End.

§  posClose: vị trí dấu ‘>’ của thẻ Start.

§  posOpen: vị trí dấu ‘<’ của thẻ End.

§  lines: list<String> dùng để thêm line hợp lệ vào trong list.

§   FlagRemoved:  dùng để khóa việc Add line vào trong list.

§  curRow : vị trí dòng hiện tại.

o   Thực hiện xử lý

§  Tiến hành duyệt file XML và kiểm tra vị trí dòng. Khi vị trí dòng = với startRow tức là chúng ta đang ở line của thẻ Start và chúng ta sẽ tiến hành remove dữ liệu trong line này và đồng thời chúng ta sẽ bật cờ để khóa việc add thêm dữ liệu vào listLine đồng nghĩa content của thẻ này sẽ bị xóa bỏ.

§  Lưu ý: Vì sao chúng ta không remove cả line mà phải remove dữ liệu cần thiết. bởi vị trên line này không chỉ có mỗi dữ liệu của thẻ Start mà còn có thể chứa dữ liệu character hoặc có các thẻ khác.
VD: abc<Price><Cost>cdf .

§  Ví dụ Với line chứa dữ liệu như sau : abc<Cost>cdf. Chúng ta cần xóa <Cost>cdf và giữ lại ‘abc’ .

§  Bởi vì dữ liệu chúng ta lấy được từ thẻ Start là posClose (vị trí dấu ‘>’) không phải vị trí dấu ‘<’ nên ta sẽ tiến hành đi tìm vị trí dấu ‘<’ gần nhất với posClose từ đó ta mới remove được.

§  Ta tiến hành duyệt từ vị trí posClose ‘>’về trước để tìm vị trí dấu ‘<’ và ngay khi tìm được sẽ break vòng lặp và gọi substring để tiến hành remove.

§  Tương khi gặp được vị trí endRow chúng ta sẽ tiến hành remove content cho line đó và đồng thời mở khóa flagRemoved để có thể Add dữ liệu lại vào listLine.

§  Ví dụ Với line chứa dữ liệu như sau : abc</Cost>cdf. Chúng ta cần xóa abc</Cost> và giữ lại cdf.

§  Sau khi có listLine, chúng ta tiến hành ghi ra file và gọi hàm parse để trả về crusor để tiếp tục quá trình parse file.

§  Lưu ý: Chính vì Case 2 này độ phức tạp và ảnh hưởng hiệu năng nhiều hơn Case 1 nên chúng ta cần phải tách ra làm 2 Case như ở trên bằng biến (flagRemoved) để tăng hiệu năng của ứng dụng.

§  Dưới đây là nơi xử lí chính của hàm này.

§  Để thực hiện việc xóa thẻ Start trên dòng dữ liệu xác định, chúng ta xây dựng phương thức removeErrorWithPosClose trong cùng class XMLUtilities.

§  Chúng ta xây dựng phương thức removeErrorTagWithPosOpen đối với thẻ End. Phương thức này sẽ hỗ trợ chúng ta có vị trí của dấu ‘<’ sau khi catch từ messgeError ở trên và tiến hành tìm vị trí dấu ‘>’ để remove từ đầu đến khi gặp được vị trí dấu ‘>’.

o   Để hổ trợ thuận lợi cho việc test dữ liệu, chúng ta xây dựng hàm printAllData để in ra màn hình.

§  Hàm Main Test

§  Tập tin xml dùng để test cho trường hợp thứ 1

§  Kết quả chạy hàm test

·        Đã mất dữ liệu thẻ <Cost>

§  Sử dụng XML cho trường hợp thứ 2

§  Kết quả thực thi của việc test

·        Sai tên thẻ Date nên sẽ mất dữ liệu của thẻ Date.

Chúc mừng quí vị đã hoàn thành xong việc parse và lấy thông tin của tài XML chưa well-formed sử dụng StAX parser. Thông qua bài viết này, quí vị sẽ có kinh nghiệm đối với việc parse tài liệu không well-formed, cụ thể là crawl dữ liệu từ các trang web về - các trang web đang thể hiện bằng định dạng html – một xml 99% không well-formed.

 

Hy vọng nội dung bài viết này sẽ hỗ trợ quí vị trong việc lấy dữ liệu và xây dựng ứng dụng. Rất mong sự đóng góp của quí vị

 

Không có nhận xét nào:

Đăng nhận xét