@@ -181,7 +181,7 @@ def convert_tei_file(self, tei_file: Union[Path, BinaryIO], stream: bool = False
181181 head = item .head
182182 label = item .label
183183 if item .has_attr ("type" ) and item .attrs ["type" ] == "table" :
184- content = xml_table_to_markdown (item .table ) if item .table else None
184+ json_content = xml_table_to_json (item .table ) if item .table else None
185185 note = item .note
186186 figures_and_tables .append (
187187 {
@@ -190,7 +190,7 @@ def convert_tei_file(self, tei_file: Union[Path, BinaryIO], stream: bool = False
190190 "head" : head .text if head else "" ,
191191 "type" : "table" ,
192192 "desc" : desc .text if desc else "" ,
193- "content" : content ,
193+ "content" : json_content ,
194194 "note" : note .text if note else "" ,
195195 "coords" : [
196196 box_to_dict (coord .split ("," ))
@@ -228,35 +228,46 @@ def _iter_passages_from_soup(self, soup: BeautifulSoup, passage_level: str) -> I
228228
229229 def _iter_passages_from_soup_for_text (self , text_node : Tag , passage_level : str ) -> Iterator [Dict [str , Union [str , Dict [str , str ]]]]:
230230 head_paragraph = None
231- div_nodes = text_node .find_all ("div" )
232- for id_div , div in enumerate (div_nodes ):
233- head = div .find ("head" )
234- p_nodes = div .find_all ("p" )
235- head_section = None
236-
237- if head :
238- if len (p_nodes ) == 0 :
239- head_paragraph = head .text
231+
232+ # Process body and back sections, but only their direct child divs
233+ for section in text_node .find_all (['body' , 'back' ]):
234+ # Only get direct child divs of this section
235+ div_nodes = [child for child in section .children if hasattr (child , 'name' ) and child .name == "div" ]
236+
237+ for id_div , div in enumerate (div_nodes ):
238+ head = div .find ("head" )
239+ p_nodes = div .find_all ("p" )
240+ head_section = None
241+
242+ if head :
243+ if len (p_nodes ) == 0 :
244+ head_paragraph = head .text
245+ else :
246+ head_section = head .text
240247 else :
241- head_section = head .text
242-
243- for id_p , p in enumerate (p_nodes ):
244- paragraph_id = get_random_id (prefix = "p_" )
245- if passage_level == "sentence" :
246- for id_s , sentence in enumerate (p .find_all ("s" )):
247- struct = get_formatted_passage (head_paragraph , head_section , paragraph_id , sentence )
248+ # If no head element, try to use the type attribute as head_section
249+ div_type = div .get ("type" )
250+ if div_type and len (p_nodes ) > 0 :
251+ head_section = div_type .title () # Capitalize first letter
252+
253+ for id_p , p in enumerate (p_nodes ):
254+ paragraph_id = get_random_id (prefix = "p_" )
255+
256+ if passage_level == "sentence" :
257+ for id_s , sentence in enumerate (p .find_all ("s" )):
258+ struct = get_formatted_passage (head_paragraph , head_section , paragraph_id , sentence )
259+ if self .validate_refs :
260+ for ref in struct ['refs' ]:
261+ assert ref ['offset_start' ] < ref ['offset_end' ]
262+ assert struct ['text' ][ref ['offset_start' ]:ref ['offset_end' ]] == ref ['text' ]
263+ yield struct
264+ else :
265+ struct = get_formatted_passage (head_paragraph , head_section , paragraph_id , p )
248266 if self .validate_refs :
249267 for ref in struct ['refs' ]:
250268 assert ref ['offset_start' ] < ref ['offset_end' ]
251269 assert struct ['text' ][ref ['offset_start' ]:ref ['offset_end' ]] == ref ['text' ]
252270 yield struct
253- else :
254- struct = get_formatted_passage (head_paragraph , head_section , paragraph_id , p )
255- if self .validate_refs :
256- for ref in struct ['refs' ]:
257- assert ref ['offset_start' ] < ref ['offset_end' ]
258- assert struct ['text' ][ref ['offset_start' ]:ref ['offset_end' ]] == ref ['text' ]
259- yield struct
260271
261272 def process_directory (self , directory : Union [str , Path ], pattern : str = "*.tei.xml" , parallel : bool = True , workers : int = None ) -> Iterator [Dict ]:
262273 """Process a directory of TEI files and yield converted documents.
@@ -289,7 +300,7 @@ def _convert_file_worker(path: str):
289300 from bs4 import BeautifulSoup
290301 import dateparser
291302 # Reuse existing top-level helpers from this module by importing here
292- from grobid_client .format .TEI2LossyJSON import box_to_dict , get_random_id , get_formatted_passage , get_refs_with_offsets , xml_table_to_markdown
303+ from grobid_client .format .TEI2LossyJSON import box_to_dict , get_random_id , get_formatted_passage , get_refs_with_offsets , xml_table_to_markdown , xml_table_to_json
293304 content = open (path , 'r' ).read ()
294305 soup = BeautifulSoup (content , 'xml' )
295306 converter = TEI2LossyJSONConverter ()
@@ -377,6 +388,56 @@ def xml_table_to_markdown(table_element):
377388 return "\n " .join (markdown_lines ) if markdown_lines else None
378389
379390
391+ def xml_table_to_json (table_element ):
392+ """Convert XML table to JSON format."""
393+ if not table_element :
394+ return None
395+
396+ table_data = {
397+ "headers" : [],
398+ "rows" : [],
399+ "metadata" : {}
400+ }
401+
402+ # Check if table has a header row (thead)
403+ thead = table_element .find ("thead" )
404+ if thead :
405+ header_row = thead .find ("row" )
406+ if header_row :
407+ for cell in header_row .find_all ("cell" ):
408+ cell_text = cell .get_text ().strip ()
409+ table_data ["headers" ].append (cell_text )
410+
411+ # Process table body rows
412+ tbody = table_element .find ("tbody" )
413+ if tbody :
414+ rows = tbody .find_all ("row" )
415+ else :
416+ # If no tbody, get all rows
417+ rows = table_element .find_all ("row" )
418+ # Skip first row if we already processed it as header
419+ if thead and rows :
420+ rows = rows [1 :]
421+
422+ for row in rows :
423+ row_data = []
424+ for cell in row .find_all ("cell" ):
425+ cell_text = cell .get_text ().strip ()
426+ row_data .append (cell_text )
427+
428+ if row_data :
429+ table_data ["rows" ].append (row_data )
430+
431+ # Add metadata
432+ table_data ["metadata" ] = {
433+ "row_count" : len (table_data ["rows" ]),
434+ "column_count" : len (table_data ["headers" ]) if table_data ["headers" ] else (len (table_data ["rows" ][0 ]) if table_data ["rows" ] else 0 ),
435+ "has_headers" : len (table_data ["headers" ]) > 0
436+ }
437+
438+ return table_data if table_data ["rows" ] else None
439+
440+
380441# Backwards compatible top-level function that uses the class
381442def convert_tei_file (tei_file : Union [Path , BinaryIO ], stream : bool = False ):
382443 converter = TEI2LossyJSONConverter ()
0 commit comments