diff --git a/TeXmacs/progs/convert/latex/init-latex.scm b/TeXmacs/progs/convert/latex/init-latex.scm index cbf152b375..f6fe21d282 100644 --- a/TeXmacs/progs/convert/latex/init-latex.scm +++ b/TeXmacs/progs/convert/latex/init-latex.scm @@ -27,22 +27,23 @@ ((format-test? s pos "\\appendix") #t) ((format-test? s pos "\\section") #t) ((format-test? s pos "\\begin") #t) - (else #f))) + (else #f) + ) ;cond +) ;define (define (latex-recognizes? s) - (and (string? s) (latex-recognizes-at? s 0))) + (and (string? s) (latex-recognizes-at? s 0)) +) ;define (define-format latex (:name "LaTeX") (:suffix "tex") - (:recognize latex-recognizes?)) + (:recognize latex-recognizes?) +) ;define-format -(define-format latex-class - (:name "LaTeX class") - (:suffix "ltx" "sty" "cls")) +(define-format latex-class (:name "LaTeX class") (:suffix "ltx" "sty" "cls")) -(define-preferences - ("texmacs->latex:transparent-tracking" "on" noop)) +(define-preferences ("texmacs->latex:transparent-tracking" "on" noop)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TeXmacs->LaTeX @@ -51,7 +52,8 @@ (lazy-define (convert latex texout) serialize-latex) (lazy-define (convert latex tmtex) texmacs->latex) -(converter texmacs-stree latex-stree +(converter texmacs-stree + latex-stree (:function-with-options texmacs->latex) (:option "texmacs->latex:source-tracking" "off") (:option "texmacs->latex:conservative" "on") @@ -62,20 +64,21 @@ (:option "texmacs->latex:expand-user-macros" "off") (:option "texmacs->latex:indirect-bib" "off") (:option "texmacs->latex:use-macros" "on") - (:option "texmacs->latex:encoding" "ascii")) + (:option "texmacs->latex:encoding" "ascii") +) ;converter -(converter latex-stree latex-document - (:function serialize-latex)) +(converter latex-stree latex-document (:function serialize-latex)) -(converter latex-stree latex-snippet - (:function serialize-latex)) +(converter latex-stree latex-snippet (:function serialize-latex)) (tm-define (texmacs->latex-document x opts) - (serialize-latex (texmacs->latex (tm->stree x) opts))) + (serialize-latex (texmacs->latex (tm->stree x) opts)) +) ;tm-define -(converter texmacs-stree latex-document +(converter texmacs-stree + latex-document (:function-with-options conservative-texmacs->latex) - ;;(:function-with-options tracked-texmacs->latex) + ;; (:function-with-options tracked-texmacs->latex) (:option "texmacs->latex:source-tracking" "off") (:option "texmacs->latex:conservative" "on") (:option "texmacs->latex:transparent-source-tracking" "on") @@ -85,7 +88,8 @@ (:option "texmacs->latex:expand-user-macros" "off") (:option "texmacs->latex:indirect-bib" "off") (:option "texmacs->latex:use-macros" "on") - (:option "texmacs->latex:encoding" "ascii")) + (:option "texmacs->latex:encoding" "ascii") +) ;converter ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; LaTeX -> TeXmacs @@ -93,27 +97,31 @@ (tm-define (latex-document->texmacs x . opts) (if (list-1? opts) (set! opts (car opts))) - (with as-pic (== (get-preference "latex->texmacs:fallback-on-pictures") "on") - (conservative-latex->texmacs x as-pic))) + (with as-pic + (== (get-preference "latex->texmacs:fallback-on-pictures") "on") + (conservative-latex->texmacs x as-pic) + ) ;with +) ;tm-define -(converter latex-document latex-tree - (:function parse-latex-document)) +(converter latex-document latex-tree (:function parse-latex-document)) -(converter latex-snippet latex-tree - (:function parse-latex)) +(converter latex-snippet latex-tree (:function parse-latex)) -(converter latex-document texmacs-tree +(converter latex-document + texmacs-tree (:function-with-options latex-document->texmacs) (:option "latex->texmacs:fallback-on-pictures" "on") (:option "latex->texmacs:source-tracking" "off") (:option "latex->texmacs:conservative" "off") - (:option "latex->texmacs:transparent-source-tracking" "off")) + (:option "latex->texmacs:transparent-source-tracking" "off") +) ;converter -(converter latex-class-document texmacs-tree - (:function latex-class-document->texmacs)) +(converter latex-class-document + texmacs-tree + (:function latex-class-document->texmacs) +) ;converter -(converter latex-tree texmacs-tree - (:function latex->texmacs)) +(converter latex-tree texmacs-tree (:function latex->texmacs)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Post-processing imported LaTeX: insert space between d and differential @@ -122,123 +130,531 @@ (define (is-letter-char? c) (and (char? c) - (or (and (char>=? c #\a) (char<=? c #\z)) - (and (char>=? c #\A) (char<=? c #\Z))))) + (or (and (char>=? c #\a) (char<=? c #\z)) (and (char>=? c #\A) (char<=? c #\Z))) + ) ;and +) ;define (define (is-word-boundary-before? s i) - (or (= i 0) - (not (is-letter-char? (string-ref s (- i 1)))))) + (or (= i 0) (not (is-letter-char? (string-ref s (- i 1))))) +) ;define (define (is-word-boundary-after? s i) (or (= i (- (string-length s) 1)) - (not (is-letter-char? (string-ref s (+ i 1)))))) + (not (is-letter-char? (string-ref s (+ i 1)))) + ) ;or +) ;define (define (match-differential s i) (and (< i (- (string-length s) 1)) - (char=? (string-ref s i) #\d) - (char=? (string-ref s (+ i 1)) #\*) - (let ((rest (substring s (+ i 2) (string-length s)))) - (cond ((or (string-starts? rest "x") - (string-starts? rest "y") - (string-starts? rest "z") - (string-starts? rest "r")) - (cons 1 (substring rest 0 1))) - ((string-starts? rest "") - (cons 5 "")) - ((string-starts? rest "") - (cons 8 "")) - ((string-starts? rest "") - (cons 7 "")) - ((string-starts? rest "") - (cons 10 "")) - (else #f))))) + (char=? (string-ref s i) #\d) + (char=? (string-ref s (+ i 1)) #\*) + (let ((rest (substring s (+ i 2) (string-length s)))) + (cond ((or (string-starts? rest "x") + (string-starts? rest "y") + (string-starts? rest "z") + (string-starts? rest "r") + ) ;or + (cons 1 (substring rest 0 1)) + ) ; + ((string-starts? rest "") (cons 5 "")) + ((string-starts? rest "") (cons 8 "")) + ((string-starts? rest "") (cons 7 "")) + ((string-starts? rest "") (cons 10 "")) + (else #f) + ) ;cond + ) ;let + ) ;and +) ;define (define (transform-math-string s) - (let* ((n (string-length s)) - (res '())) - (let loop ((i 0) (last-idx 0)) + (let* ((n (string-length s)) (res '())) + (let loop + ((i 0) (last-idx 0)) (cond ((>= i n) - (if (null? res) s - (begin - (if (< last-idx n) - (set! res (append res (list (substring s last-idx n))))) - (cons 'concat res)))) - (else - (let ((match (match-differential s i))) - (if (and match - (is-word-boundary-before? s i) - (is-word-boundary-after? s (+ i 1 (car match)))) - (let* ((match-len (car match)) - (var (cdr match))) - (if (> i last-idx) - (set! res (append res (list (substring s last-idx i))))) - (set! res (append res (list "d" " " var))) - (loop (+ i 2 match-len) (+ i 2 match-len))) - (loop (+ i 1) last-idx)))))))) + (if (null? res) + s + (begin + (if (< last-idx n) (set! res (append res (list (substring s last-idx n))))) + (cons 'concat res) + ) ;begin + ) ;if + ) ; + (else (let ((match (match-differential s i))) + (if (and match + (is-word-boundary-before? s i) + (is-word-boundary-after? s (+ i 1 (car match))) + ) ;and + (let* ((match-len (car match)) (var (cdr match))) + (if (> i last-idx) (set! res (append res (list (substring s last-idx i))))) + (set! res (append res (list "d" " " var))) + (loop (+ i 2 match-len) (+ i 2 match-len)) + ) ;let* + (loop (+ i 1) last-idx) + ) ;if + ) ;let + ) ;else + ) ;cond + ) ;let + ) ;let* +) ;define (define (transform-concat-children children) (cond ((null? children) '()) ((and (pair? children) (pair? (cdr children))) - (let* ((c1 (car children)) - (c2 (cadr children))) - (if (and (string? c1) (string? c2) - (or (string=? c2 "") (string=? c2 "") - (string=? c2 "") (string=? c2 "")) - (let ((len (string-length c1))) - (and (> len 0) - (char=? (string-ref c1 (- len 1)) #\d) - (or (= len 1) - (not (is-letter-char? (string-ref c1 (- len 2)))))))) - (let* ((len (string-length c1)) - (prefix (if (> len 1) (substring c1 0 (- len 1)) #f)) - (spaced-part (if prefix (list prefix "d" " " c2) (list "d" " " c2)))) - (append spaced-part (transform-concat-children (cddr children)))) - (cons (car children) (transform-concat-children (cdr children)))))) - (else children))) + (let* ((c1 (car children)) (c2 (cadr children))) + (if (and (string? c1) + (string? c2) + (or (string=? c2 "") + (string=? c2 "") + (string=? c2 "") + (string=? c2 "") + ) ;or + (let ((len (string-length c1))) + (and (> len 0) + (char=? (string-ref c1 (- len 1)) #\d) + (or (= len 1) (not (is-letter-char? (string-ref c1 (- len 2))))) + ) ;and + ) ;let + ) ;and + (let* ((len (string-length c1)) + (prefix (if (> len 1) (substring c1 0 (- len 1)) #f)) + (spaced-part (if prefix (list prefix "d" " " c2) (list "d" " " c2))) + ) ; + (append spaced-part (transform-concat-children (cddr children))) + ) ;let* + (cons (car children) (transform-concat-children (cdr children))) + ) ;if + ) ;let* + ) ; + (else children) + ) ;cond +) ;define (define math-environments - '(math equation equation* eqnarray eqnarray* align align* multline multline*)) + '(math equation equation* eqnarray eqnarray* align align* multline multline*) +) ;define (define (upgrade-latex-differentials-stree t in-math) - (cond ((string? t) - (if in-math - (transform-math-string t) - t)) + (cond ((string? t) (if in-math (transform-math-string t) t)) ((pair? t) - (let* ((head (car t)) - (next-in-math (or in-math (memq head math-environments)))) + (let* ((head (car t)) (next-in-math (or in-math (memq head math-environments)))) (if (and next-in-math (eq? head 'concat)) - (let* ((new-children (map (lambda (x) (upgrade-latex-differentials-stree x #t)) (cdr t))) - (transformed-children (transform-concat-children new-children))) - (cons 'concat transformed-children)) - (cons head (map (lambda (x) (upgrade-latex-differentials-stree x next-in-math)) (cdr t)))))) - (else t))) + (let* ((new-children (map (lambda (x) (upgrade-latex-differentials-stree x #t)) (cdr t)) + ) ;new-children + (transformed-children (transform-concat-children new-children)) + ) ; + (cons 'concat transformed-children) + ) ;let* + (cons head + (map (lambda (x) (upgrade-latex-differentials-stree x next-in-math)) (cdr t)) + ) ;cons + ) ;if + ) ;let* + ) ; + (else t) + ) ;cond +) ;define + +(define (has-cwith-property? options + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-property? + (cond ((null? options) #f) + ((and (pair? (car options)) (eq? (caar options) 'cwith)) + (let* ((opt (car options)) + (r-start (and (> (length opt) 1) (list-ref opt 1))) + (r-end (and (> (length opt) 2) (list-ref opt 2))) + (c-start (and (> (length opt) 3) (list-ref opt 3))) + (c-end (and (> (length opt) 4) (list-ref opt 4))) + (prop (and (> (length opt) 5) (list-ref opt 5))) + (val (and (> (length opt) 6) (list-ref opt 6))) + ) ; + (if (and (or (not row-start) (equal? r-start row-start)) + (or (not row-end) (equal? r-end row-end)) + (or (not col-start) (equal? c-start col-start)) + (or (not col-end) (equal? c-end col-end)) + (or (not property) (equal? prop property)) + (and val (value-pred val)) + ) ;and + #t + (has-cwith-property? (cdr options) + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-property? + ) ;if + ) ;let* + ) ; + (else (has-cwith-property? (cdr options) + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-property? + ) ;else + ) ;cond +) ;define + +(define (find-table-num-rows children) + (cond ((null? children) 0) + ((and (pair? (car children)) (eq? (caar children) 'table)) + (length (cdar children)) + ) ; + (else (find-table-num-rows (cdr children))) + ) ;cond +) ;define + +(define (is-three-line-table-tformat? x) + (if (and (pair? x) (eq? (car x) 'tformat)) + (let* ((options (cdr x)) (num-rows (find-table-num-rows options))) + (if (> num-rows 0) + (let* ((has-top? (has-cwith-property? options + "1" + "1" + #f + #f + "cell-tborder" + (lambda (v) (not (equal? v "0ln"))) + ) ;has-cwith-property? + ) ;has-top? + (has-bottom? (has-cwith-property? options + (number->string num-rows) + (number->string num-rows) + #f + #f + "cell-bborder" + (lambda (v) (not (equal? v "0ln"))) + ) ;has-cwith-property? + ) ;has-bottom? + (has-vertical? (has-cwith-property? options + #f + #f + #f + #f + "cell-lborder" + (lambda (v) (not (equal? v "0ln"))) + ) ;has-cwith-property? + ) ;has-vertical? + (has-vertical-r? (has-cwith-property? options + #f + #f + #f + #f + "cell-rborder" + (lambda (v) (not (equal? v "0ln"))) + ) ;has-cwith-property? + ) ;has-vertical-r? + ) ; + (and has-top? has-bottom? (not has-vertical?) (not has-vertical-r?)) + ) ;let* + #f + ) ;if + ) ;let* + #f + ) ;if +) ;define + +(define (transform-three-line-tables x) + (cond ((null? x) '()) + ((and (pair? x) (eq? (car x) 'tformat)) + (let ((transformed-args (map transform-three-line-tables (cdr x)))) + (let ((new-tformat (cons 'tformat transformed-args))) + (if (is-three-line-table-tformat? new-tformat) + (list 'three-line-table new-tformat) + new-tformat + ) ;if + ) ;let + ) ;let + ) ; + ((pair? x) + (cons (transform-three-line-tables (car x)) + (transform-three-line-tables (cdr x)) + ) ;cons + ) ; + (else x) + ) ;cond +) ;define + +(define (clean-multirow t) + (cond ((null? t) (cons '() #f)) + ((and (pair? t) (eq? (car t) 'multirow)) + (let ((n (list-ref t 1)) + (w (list-ref t 2)) + (text (if (> (length t) 3) (list-ref t 3) "")) + ) ; + (cons text (cons n w)) + ) ;let + ) ; + ((pair? t) + (let* ((res-car (clean-multirow (car t))) (res-cdr (clean-multirow (cdr t)))) + (cond ((cdr res-car) (cons (cons (car res-car) (car res-cdr)) (cdr res-car))) + ((cdr res-cdr) (cons (cons (car res-car) (car res-cdr)) (cdr res-cdr))) + (else (cons (cons (car res-car) (car res-cdr)) #f)) + ) ;cond + ) ;let* + ) ; + (else (cons t #f)) + ) ;cond +) ;define + +(define (process-row-cells cells r c options-acc new-cells-acc) + (cond ((null? cells) (cons (reverse new-cells-acc) options-acc)) + (else (let* ((cell (car cells)) + (cleaned-res (clean-multirow cell)) + (new-cell (car cleaned-res)) + (info (cdr cleaned-res)) + ) ; + (if info + (let* ((n (car info)) + (row-str (number->string r)) + (col-str (number->string c)) + (new-opt1 (list 'cwith row-str row-str col-str col-str "cell-row-span" n)) + (new-opt2 (list 'cwith row-str row-str col-str col-str "cell-valign" "c")) + ) ; + (process-row-cells (cdr cells) + r + (+ c 1) + (cons new-opt1 (cons new-opt2 options-acc)) + (cons new-cell new-cells-acc) + ) ;process-row-cells + ) ;let* + (process-row-cells (cdr cells) r (+ c 1) options-acc (cons cell new-cells-acc)) + ) ;if + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (process-table-rows rows r options-acc new-rows-acc) + (cond ((null? rows) (cons (reverse new-rows-acc) options-acc)) + (else (let* ((row (car rows)) + (cells (cdr row)) + (res-cells (process-row-cells cells r 1 '() '())) + ) ; + (process-table-rows (cdr rows) + (+ r 1) + (append options-acc (cdr res-cells)) + (cons (cons 'row (car res-cells)) new-rows-acc) + ) ;process-table-rows + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (filter-table options) + (cond ((null? options) '()) + ((and (pair? (car options)) (eq? (caar options) 'table)) + (filter-table (cdr options)) + ) ; + (else (cons (car options) (filter-table (cdr options)))) + ) ;cond +) ;define + +(define (collect-all-regions options num-rows) + (let loop-r + ((r 1) (regions '())) + (if (> r num-rows) + regions + (let* ((r-str (number->string r)) + (row-regions (let loop-c + ((c 1) (c-acc '())) + (if (> c 50) + c-acc + (let* ((c-str (number->string c)) + (h-val (let loop-opt + ((lst options)) + (cond ((null? lst) #f) + ((and (pair? (car lst)) + (eq? (caar lst) 'cwith) + (equal? (list-ref (car lst) 1) r-str) + (equal? (list-ref (car lst) 3) c-str) + (equal? (list-ref (car lst) 5) "cell-row-span") + ) ;and + (list-ref (car lst) 6) + ) ; + (else (loop-opt (cdr lst))) + ) ;cond + ) ;let + ) ;h-val + (w-val (let loop-opt + ((lst options)) + (cond ((null? lst) #f) + ((and (pair? (car lst)) + (eq? (caar lst) 'cwith) + (equal? (list-ref (car lst) 1) r-str) + (equal? (list-ref (car lst) 3) c-str) + (equal? (list-ref (car lst) 5) "cell-col-span") + ) ;and + (list-ref (car lst) 6) + ) ; + (else (loop-opt (cdr lst))) + ) ;cond + ) ;let + ) ;w-val + (h (if h-val (string->number h-val) 1)) + (w (if w-val (string->number w-val) 1)) + ) ; + (if (or (> h 1) (> w 1)) + (loop-c (+ c 1) (cons (list r c h w) c-acc)) + (loop-c (+ c 1) c-acc) + ) ;if + ) ;let* + ) ;if + ) ;let + ) ;row-regions + ) ; + (loop-r (+ r 1) (append regions row-regions)) + ) ;let* + ) ;if + ) ;let +) ;define + +(define (is-cell-covered? ri ci regions) + (cond ((null? regions) #f) + (else (let* ((reg (car regions)) + (r (list-ref reg 0)) + (c (list-ref reg 1)) + (h (list-ref reg 2)) + (w (list-ref reg 3)) + ) ; + (if (and (>= ri r) + (< ri (+ r h)) + (>= ci c) + (< ci (+ c w)) + (not (and (= ri r) (= ci c))) + ) ;and + #t + (is-cell-covered? ri ci (cdr regions)) + ) ;if + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (clean-covered-cells-in-row cells r c regions new-cells-acc) + (cond ((null? cells) (reverse new-cells-acc)) + (else (let* ((cell (car cells)) + (new-cell (if (is-cell-covered? r c regions) '(cell "") cell)) + ) ; + (clean-covered-cells-in-row (cdr cells) + r + (+ c 1) + regions + (cons new-cell new-cells-acc) + ) ;clean-covered-cells-in-row + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (clean-covered-cells-in-rows rows r regions new-rows-acc) + (cond ((null? rows) (reverse new-rows-acc)) + (else (let* ((row (car rows)) + (cells (cdr row)) + (new-cells (clean-covered-cells-in-row cells r 1 regions '())) + (new-row (cons 'row new-cells)) + ) ; + (clean-covered-cells-in-rows (cdr rows) + (+ r 1) + regions + (cons new-row new-rows-acc) + ) ;clean-covered-cells-in-rows + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (transform-multirow-tformat x) + (if (and (pair? x) (eq? (car x) 'tformat)) + (let* ((options (cdr x)) + (table-cell-pair (let loop + ((lst options)) + (cond ((null? lst) #f) + ((and (pair? (car lst)) (eq? (caar lst) 'table)) (car lst)) + (else (loop (cdr lst))) + ) ;cond + ) ;let + ) ;table-cell-pair + ) ; + (if table-cell-pair + (let* ((table-rows (cdr table-cell-pair)) + (processed (process-table-rows table-rows 1 '() '())) + (new-rows-temp (car processed)) + (new-options (cdr processed)) + (all-options (append (filter-table options) new-options)) + (num-rows (length new-rows-temp)) + (regions (collect-all-regions all-options num-rows)) + (new-rows (clean-covered-cells-in-rows new-rows-temp 1 regions '())) + (rebuilt-options (append all-options (list (cons 'table new-rows)))) + ) ; + (cons 'tformat rebuilt-options) + ) ;let* + x + ) ;if + ) ;let* + x + ) ;if +) ;define + +(define (transform-multirow x) + (cond ((null? x) '()) + ((and (pair? x) (eq? (car x) 'tformat)) + (let* ((new-t (transform-multirow-tformat x)) + (transformed-args (map transform-multirow (cdr new-t))) + ) ; + (cons 'tformat transformed-args) + ) ;let* + ) ; + ((pair? x) (cons (transform-multirow (car x)) (transform-multirow (cdr x)))) + (else x) + ) ;cond +) ;define (define latex->texmacs-original latex->texmacs) (tm-define (latex->texmacs t) (let* ((res (latex->texmacs-original t)) (st (tree->stree res)) - (new-st (upgrade-latex-differentials-stree st #f))) - (stree->tree new-st))) + (new-st1 (upgrade-latex-differentials-stree st #f)) + (new-st2 (transform-three-line-tables new-st1)) + (new-st (transform-multirow new-st2)) + ) ; + (stree->tree new-st) + ) ;let* +) ;tm-define (define latex-document->texmacs-original latex-document->texmacs) (tm-define (latex-document->texmacs x . opts) (let* ((res (apply latex-document->texmacs-original (cons x opts))) (st (tree->stree res)) - (new-st (upgrade-latex-differentials-stree st #f))) - (stree->tree new-st))) + (new-st1 (upgrade-latex-differentials-stree st #f)) + (new-st2 (transform-three-line-tables new-st1)) + (new-st (transform-multirow new-st2)) + ) ; + (stree->tree new-st) + ) ;let* +) ;tm-define ;; Re-register converters so that `converter-function` table points ;; to our wrapper definitions. The `converter` macro resolves the ;; function symbol at registration time; simply redefining the symbol ;; afterwards leaves the old reference in the table. -(converter latex-tree texmacs-tree - (:function latex->texmacs)) -(converter latex-document texmacs-tree +(converter latex-tree texmacs-tree (:function latex->texmacs)) +(converter latex-document + texmacs-tree (:function-with-options latex-document->texmacs) (:option "latex->texmacs:fallback-on-pictures" "on") (:option "latex->texmacs:source-tracking" "off") (:option "latex->texmacs:conservative" "off") - (:option "latex->texmacs:transparent-source-tracking" "off")) + (:option "latex->texmacs:transparent-source-tracking" "off") +) ;converter diff --git a/TeXmacs/tests/0631.scm b/TeXmacs/tests/0631.scm new file mode 100644 index 0000000000..c7c7670691 --- /dev/null +++ b/TeXmacs/tests/0631.scm @@ -0,0 +1,527 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MODULE : 0631.scm +;; DESCRIPTION : Integration tests for PR 0631 LaTeX Table Import and Extreme Cases +;; COPYRIGHT : (C) 2026 Sisyphus +;; +;; This software falls under the GNU general public license version 3 or later. +;; It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE +;; in the root directory or . +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(import (liii check)) + +(check-set-mode! 'report-failed) + +(define (load-latex path) + (with path + (string-append "$TEXMACS_PATH/tests/tex/" path) + (string-replace (string-load path) "\r\n" "\n") + ) ;with +) ;define + +(define (has-cwith-property? options + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-property? + (cond ((null? options) #f) + ((and (pair? (car options)) (eq? (caar options) 'cwith)) + (let* ((opt (car options)) + (r-start (and (> (length opt) 1) (list-ref opt 1))) + (r-end (and (> (length opt) 2) (list-ref opt 2))) + (c-start (and (> (length opt) 3) (list-ref opt 3))) + (c-end (and (> (length opt) 4) (list-ref opt 4))) + (prop (and (> (length opt) 5) (list-ref opt 5))) + (val (and (> (length opt) 6) (list-ref opt 6))) + ) ; + (if (and (or (not row-start) (equal? r-start row-start)) + (or (not row-end) (equal? r-end row-end)) + (or (not col-start) (equal? c-start col-start)) + (or (not col-end) (equal? c-end col-end)) + (or (not property) (equal? prop property)) + (and val (value-pred val)) + ) ;and + #t + (has-cwith-property? (cdr options) + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-property? + ) ;if + ) ;let* + ) ; + (else (has-cwith-property? (cdr options) + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-property? + ) ;else + ) ;cond +) ;define + +(define (has-cwith-in-tree? x row-start row-end col-start col-end property value-pred) + (cond ((null? x) #f) + ((and (pair? x) (eq? (car x) 'tformat)) + (or (has-cwith-property? (cdr x) + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-property? + (let loop-children + ((children (cdr x))) + (cond ((null? children) #f) + ((has-cwith-in-tree? (car children) + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-in-tree? + #t + ) ; + (else (loop-children (cdr children))) + ) ;cond + ) ;let + ) ;or + ) ; + ((pair? x) + (or (has-cwith-in-tree? (car x) + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-in-tree? + (has-cwith-in-tree? (cdr x) + row-start + row-end + col-start + col-end + property + value-pred + ) ;has-cwith-in-tree? + ) ;or + ) ; + (else #f) + ) ;cond +) ;define + +(define (find-table-num-rows children) + (cond ((null? children) 0) + ((and (pair? (car children)) (eq? (caar children) 'table)) + (length (cdar children)) + ) ; + (else (find-table-num-rows (cdr children))) + ) ;cond +) ;define + +(define (is-three-line-table-tformat? x) + (if (and (pair? x) (eq? (car x) 'tformat)) + (let* ((options (cdr x)) (num-rows (find-table-num-rows options))) + (if (> num-rows 0) + (let* ((has-top? (has-cwith-property? options + "1" + "1" + #f + #f + "cell-tborder" + (lambda (v) (not (equal? v "0ln"))) + ) ;has-cwith-property? + ) ;has-top? + (has-bottom? (has-cwith-property? options + (number->string num-rows) + (number->string num-rows) + #f + #f + "cell-bborder" + (lambda (v) (not (equal? v "0ln"))) + ) ;has-cwith-property? + ) ;has-bottom? + (has-vertical? (has-cwith-property? options + #f + #f + #f + #f + "cell-lborder" + (lambda (v) (not (equal? v "0ln"))) + ) ;has-cwith-property? + ) ;has-vertical? + (has-vertical-r? (has-cwith-property? options + #f + #f + #f + #f + "cell-rborder" + (lambda (v) (not (equal? v "0ln"))) + ) ;has-cwith-property? + ) ;has-vertical-r? + ) ; + (and has-top? has-bottom? (not has-vertical?) (not has-vertical-r?)) + ) ;let* + #f + ) ;if + ) ;let* + #f + ) ;if +) ;define + +(define (transform-three-line-tables x) + (cond ((null? x) '()) + ((and (pair? x) (eq? (car x) 'tformat)) + (let ((transformed-args (map transform-three-line-tables (cdr x)))) + (let ((new-tformat (cons 'tformat transformed-args))) + (if (is-three-line-table-tformat? new-tformat) + (list 'three-line-table new-tformat) + new-tformat + ) ;if + ) ;let + ) ;let + ) ; + ((pair? x) + (cons (transform-three-line-tables (car x)) + (transform-three-line-tables (cdr x)) + ) ;cons + ) ; + (else x) + ) ;cond +) ;define + +(define (clean-multirow t) + (cond ((null? t) (cons '() #f)) + ((and (pair? t) (eq? (car t) 'multirow)) + (let ((n (list-ref t 1)) + (w (list-ref t 2)) + (text (if (> (length t) 3) (list-ref t 3) "")) + ) ; + (cons text (cons n w)) + ) ;let + ) ; + ((pair? t) + (let* ((res-car (clean-multirow (car t))) (res-cdr (clean-multirow (cdr t)))) + (cond ((cdr res-car) (cons (cons (car res-car) (car res-cdr)) (cdr res-car))) + ((cdr res-cdr) (cons (cons (car res-car) (car res-cdr)) (cdr res-cdr))) + (else (cons (cons (car res-car) (car res-cdr)) #f)) + ) ;cond + ) ;let* + ) ; + (else (cons t #f)) + ) ;cond +) ;define + +(define (process-row-cells cells r c options-acc new-cells-acc) + (cond ((null? cells) (cons (reverse new-cells-acc) options-acc)) + (else (let* ((cell (car cells)) + (cleaned-res (clean-multirow cell)) + (new-cell (car cleaned-res)) + (info (cdr cleaned-res)) + ) ; + (if info + (let* ((n (car info)) + (row-str (number->string r)) + (col-str (number->string c)) + (new-opt1 (list 'cwith row-str row-str col-str col-str "cell-row-span" n)) + (new-opt2 (list 'cwith row-str row-str col-str col-str "cell-valign" "c")) + ) ; + (process-row-cells (cdr cells) + r + (+ c 1) + (cons new-opt1 (cons new-opt2 options-acc)) + (cons new-cell new-cells-acc) + ) ;process-row-cells + ) ;let* + (process-row-cells (cdr cells) r (+ c 1) options-acc (cons cell new-cells-acc)) + ) ;if + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (process-table-rows rows r options-acc new-rows-acc) + (cond ((null? rows) (cons (reverse new-rows-acc) options-acc)) + (else (let* ((row (car rows)) + (cells (cdr row)) + (res-cells (process-row-cells cells r 1 '() '())) + ) ; + (process-table-rows (cdr rows) + (+ r 1) + (append options-acc (cdr res-cells)) + (cons (cons 'row (car res-cells)) new-rows-acc) + ) ;process-table-rows + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (filter-table options) + (cond ((null? options) '()) + ((and (pair? (car options)) (eq? (caar options) 'table)) + (filter-table (cdr options)) + ) ; + (else (cons (car options) (filter-table (cdr options)))) + ) ;cond +) ;define + +(define (collect-all-regions options num-rows) + (let loop-r + ((r 1) (regions '())) + (if (> r num-rows) + regions + (let* ((r-str (number->string r)) + (row-regions (let loop-c + ((c 1) (c-acc '())) + (if (> c 50) + c-acc + (let* ((c-str (number->string c)) + (h-val (let loop-opt + ((lst options)) + (cond ((null? lst) #f) + ((and (pair? (car lst)) + (eq? (caar lst) 'cwith) + (equal? (list-ref (car lst) 1) r-str) + (equal? (list-ref (car lst) 3) c-str) + (equal? (list-ref (car lst) 5) "cell-row-span") + ) ;and + (list-ref (car lst) 6) + ) ; + (else (loop-opt (cdr lst))) + ) ;cond + ) ;let + ) ;h-val + (w-val (let loop-opt + ((lst options)) + (cond ((null? lst) #f) + ((and (pair? (car lst)) + (eq? (caar lst) 'cwith) + (equal? (list-ref (car lst) 1) r-str) + (equal? (list-ref (car lst) 3) c-str) + (equal? (list-ref (car lst) 5) "cell-col-span") + ) ;and + (list-ref (car lst) 6) + ) ; + (else (loop-opt (cdr lst))) + ) ;cond + ) ;let + ) ;w-val + (h (if h-val (string->number h-val) 1)) + (w (if w-val (string->number w-val) 1)) + ) ; + (if (or (> h 1) (> w 1)) + (loop-c (+ c 1) (cons (list r c h w) c-acc)) + (loop-c (+ c 1) c-acc) + ) ;if + ) ;let* + ) ;if + ) ;let + ) ;row-regions + ) ; + (loop-r (+ r 1) (append regions row-regions)) + ) ;let* + ) ;if + ) ;let +) ;define + +(define (is-cell-covered? ri ci regions) + (cond ((null? regions) #f) + (else (let* ((reg (car regions)) + (r (list-ref reg 0)) + (c (list-ref reg 1)) + (h (list-ref reg 2)) + (w (list-ref reg 3)) + ) ; + (if (and (>= ri r) + (< ri (+ r h)) + (>= ci c) + (< ci (+ c w)) + (not (and (= ri r) (= ci c))) + ) ;and + #t + (is-cell-covered? ri ci (cdr regions)) + ) ;if + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (clean-covered-cells-in-row cells r c regions new-cells-acc) + (cond ((null? cells) (reverse new-cells-acc)) + (else (let* ((cell (car cells)) + (new-cell (if (is-cell-covered? r c regions) '(cell "") cell)) + ) ; + (clean-covered-cells-in-row (cdr cells) + r + (+ c 1) + regions + (cons new-cell new-cells-acc) + ) ;clean-covered-cells-in-row + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (clean-covered-cells-in-rows rows r regions new-rows-acc) + (cond ((null? rows) (reverse new-rows-acc)) + (else (let* ((row (car rows)) + (cells (cdr row)) + (new-cells (clean-covered-cells-in-row cells r 1 regions '())) + (new-row (cons 'row new-cells)) + ) ; + (clean-covered-cells-in-rows (cdr rows) + (+ r 1) + regions + (cons new-row new-rows-acc) + ) ;clean-covered-cells-in-rows + ) ;let* + ) ;else + ) ;cond +) ;define + +(define (transform-multirow-tformat x) + (if (and (pair? x) (eq? (car x) 'tformat)) + (let* ((options (cdr x)) + (table-cell-pair (let loop + ((lst options)) + (cond ((null? lst) #f) + ((and (pair? (car lst)) (eq? (caar lst) 'table)) (car lst)) + (else (loop (cdr lst))) + ) ;cond + ) ;let + ) ;table-cell-pair + ) ; + (if table-cell-pair + (let* ((table-rows (cdr table-cell-pair)) + (processed (process-table-rows table-rows 1 '() '())) + (new-rows-temp (car processed)) + (new-options (cdr processed)) + (all-options (append (filter-table options) new-options)) + (num-rows (length new-rows-temp)) + (regions (collect-all-regions all-options num-rows)) + (new-rows (clean-covered-cells-in-rows new-rows-temp 1 regions '())) + (rebuilt-options (append all-options (list (cons 'table new-rows)))) + ) ; + (cons 'tformat rebuilt-options) + ) ;let* + x + ) ;if + ) ;let* + x + ) ;if +) ;define + +(define (transform-multirow x) + (cond ((null? x) '()) + ((and (pair? x) (eq? (car x) 'tformat)) + (let* ((new-t (transform-multirow-tformat x)) + (transformed-args (map transform-multirow (cdr new-t))) + ) ; + (cons 'tformat transformed-args) + ) ;let* + ) ; + ((pair? x) (cons (transform-multirow (car x)) (transform-multirow (cdr x)))) + (else x) + ) ;cond +) ;define + +(define (stree-contains? x target) + (cond ((null? x) #f) + ((equal? x target) #t) + ((pair? x) + (or (stree-contains? (car x) target) (stree-contains? (cdr x) target)) + ) ; + (else #f) + ) ;cond +) ;define + +(define (test-latex-table-import) + (display "Testing 45 extreme cases of LaTeX table import...\n") + (let* ((latex-content (load-latex "0631_table_import.tex")) + (parsed (parse-latex-document latex-content)) + (texmacs-tree (latex->texmacs parsed)) + (st (tree->stree texmacs-tree)) + ) ; + + (display "Verifying specific table properties in converted tree...\n") + + ;; Verify that the document parsed successfully + (check (null? st) => #f) + + ;; 1. Check three-line-table support + ;; The booktabs toprule/midrule/bottomrule tables should be converted to 'three-line-table + (check (stree-contains? st 'three-line-table) => #t) + + ;; 2. Check basic tabular format (like 'tabular or 'tabular*) + (check (stree-contains? st 'tabular*) => #t) + + ;; 3. Check for specific cell contents to ensure no content was lost during parsing + (check (stree-contains? st "Span Three Columns") => #t) + (check (stree-contains? st "Span Two Right") => #t) + (check (stree-contains? st "Row Span") => #t) + (check (stree-contains? st "MultiRowCol") => #t) + (check (stree-contains? st "Fixed Width Row") => #t) + (check (stree-contains? st "Solo") => #t) + (check (stree-contains? st "Left text") => #t) + + ;; 4. Check for nested tabular environments + (check (stree-contains? st "Outer cell") => #t) + (check (stree-contains? st "Inner 1") => #t) + + ;; 5. Check for math mode cell preservation + (check (stree-contains? st "*") => #t) + + ;; 6. Check for float environments and captions + (check (stree-contains? st 'big-table) => #t) + (check (stree-contains? st "Test Caption") => #t) + (check (stree-contains? st "tab:test_label") => #t) + + ;; 7. Check multirow resolution and cleanliness + ;; There must be NO undefined multirow macro in the final tree + (check (stree-contains? st 'multirow) => #f) + ;; Check new extreme cases of multirow combined/nested + (check (stree-contains? st "DualHeader") => #t) + (check (stree-contains? st "Multi 1") => #t) + (check (stree-contains? st "Span Four Rows") => #t) + (check (stree-contains? st "Combined MultiRowCol Width") => #t) + (check (stree-contains? st "Extreme Nested Combined") => #t) + + ;; 8. Check column width extraction from p{width} specifications + (check (has-cwith-in-tree? st + "1" + "-1" + "1" + "1" + "cell-width" + (lambda (v) (equal? v "3cm")) + ) ;has-cwith-in-tree? + => + #t + ) ;check + (check (has-cwith-in-tree? st + "1" + "-1" + "2" + "2" + "cell-width" + (lambda (v) (equal? v "4.5cm")) + ) ;has-cwith-in-tree? + => + #t + ) ;check + ) ;let* +) ;define + +(tm-define (test_0631) (test-latex-table-import) (check-report)) diff --git a/TeXmacs/tests/tex/0631_table_import.tex b/TeXmacs/tests/tex/0631_table_import.tex new file mode 100644 index 0000000000..f9a281fe2f --- /dev/null +++ b/TeXmacs/tests/tex/0631_table_import.tex @@ -0,0 +1,331 @@ +\documentclass{article} +\usepackage[english]{babel} +\usepackage{booktabs} +\usepackage{multirow} +\usepackage{tabularx} + +\begin{document} + +% Case 1: Standard tabular with basic ccc columns and no borders +\begin{tabular}{ccc} + 1 & 2 & 3 \\ + 4 & 5 & 6 \\ +\end{tabular} + +% Case 2: Standard tabular with lrc columns and single borders +\begin{tabular}{|l|r|c|} + a & b & c \\ + d & e & f \\ +\end{tabular} + +% Case 3: Standard tabular with double vertical borders +\begin{tabular}{||c||c||} + 1 & 2 \\ + 3 & 4 \\ +\end{tabular} + +% Case 4: Tabular with p{width} columns +\begin{tabular}{p{3cm}p{4.5cm}} + Left text & Right text \\ + More left & More right \\ +\end{tabular} + +% Case 5: Tabular with mix of alignment and width +\begin{tabular}{|c|p{2cm}|l|} + A & Width block & Left \\ + B & Content & Text \\ +\end{tabular} + +% Case 6: Tabular with hline at top, bottom, and middle +\begin{tabular}{cc} + \hline + 1 & 2 \\ + \hline + 3 & 4 \\ + \hline +\end{tabular} + +% Case 7: Tabular with double hline +\begin{tabular}{cc} + \hline\hline + A & B \\ + \hline\hline + C & D \\ +\end{tabular} + +% Case 8: Tabular with cline{1-2} +\begin{tabular}{ccc} + 1 & 2 & 3 \\ + \cline{1-2} + 4 & 5 & 6 \\ +\end{tabular} + +% Case 9: Tabular with multiple clines on same row +\begin{tabular}{cccc} + a & b & c & d \\ + \cline{1-1}\cline{3-3} + e & f & g & h \\ +\end{tabular} + +% Case 10: Booktabs three-line table (toprule, midrule, bottomrule) +\begin{tabular}{cc} + \toprule + Header 1 & Header 2 \\ + \midrule + Value 1 & Value 2 \\ + Value 3 & Value 4 \\ + \bottomrule +\end{tabular} + +% Case 11: Booktabs three-line table with cmidrule{1-1} and cmidrule{2-2} +\begin{tabular}{cc} + \toprule + A & B \\ + \cmidrule{1-1}\cmidrule{2-2} + C & D \\ + \bottomrule +\end{tabular} + +% Case 12: Booktabs with thick/thin lines via custom trim +\begin{tabular}{cc} + \toprule + 1 & 2 \\ + \cmidrule(r){1-1} + 3 & 4 \\ + \bottomrule +\end{tabular} + +% Case 13: multicolumn spanning 3 columns with vertical borders +\begin{tabular}{|c|c|c|} + \hline + \multicolumn{3}{|c|}{Span Three Columns} \\ + \hline + A & B & C \\ +\end{tabular} + +% Case 14: multicolumn spanning 2 columns with right alignment +\begin{tabular}{ccc} + \multicolumn{2}{r}{Span Two Right} & C \\ + A & B & D \\ +\end{tabular} + +% Case 15: multirow spanning 2 rows +\begin{tabular}{cc} + \multirow{2}{*}{Row Span} & A \\ + & B \\ + C & D \\ +\end{tabular} + +% Case 16: multirow spanning 3 rows with custom width +\begin{tabular}{cc} + \multirow{3}{2cm}{Fixed Width Row} & X \\ + & Y \\ + & Z \\ +\end{tabular} + +% Case 17: Multirow and multicolumn combined +\begin{tabular}{ccc} + \multicolumn{2}{c}{\multirow{2}{*}{MultiRowCol}} & A \\ + \multicolumn{2}{c}{} & B \\ + C & D & E \\ +\end{tabular} + +% Case 18: Empty cell at the beginning of row +\begin{tabular}{ccc} + & 2 & 3 \\ + 4 & 5 & 6 \\ +\end{tabular} + +% Case 19: Empty cell at the end of row +\begin{tabular}{ccc} + 1 & 2 & \\ + 4 & 5 & 6 \\ +\end{tabular} + +% Case 20: Multiple consecutive empty cells +\begin{tabular}{cccc} + 1 & & & 4 \\ + 5 & 6 & 7 & 8 \\ +\end{tabular} + +% Case 21: Entirely empty row +\begin{tabular}{cc} + & \\ + 3 & 4 \\ +\end{tabular} + +% Case 22: Extra spaces and weird padding around & and \\ +\begin{tabular}{ c c } + 1 & 2 \\ + 3 & 4 \\ +\end{tabular} + +% Case 23: Tabularnewline instead of \\ +\begin{tabular}{cc} + 1 & 2 \tabularnewline + 3 & 4 \tabularnewline +\end{tabular} + +% Case 24: Nested tabular environments +\begin{tabular}{c} + Outer cell \\ + \begin{tabular}{cc} + Inner 1 & Inner 2 \\ + \end{tabular} \\ +\end{tabular} + +% Case 25: Table with math mode cells (inline $) +\begin{tabular}{cc} + $x^2 + y^2$ & $\alpha \beta$ \\ + $\int_0^1 f(x) dx$ & $\sqrt{2}$ \\ +\end{tabular} + +% Case 26: Table with displaystyle block math +\begin{tabular}{c} + $\displaystyle \sum_{i=1}^{n} i = \frac{n(n+1)}{2}$ \\ +\end{tabular} + +% Case 27: Table with text formatting inside cells +\begin{tabular}{cc} + \textbf{Bold Text} & \textit{Italic Text} \\ + \underline{Underlined} & Plain \\ +\end{tabular} + +% Case 28: Table with special LaTeX character symbols +\begin{tabular}{cc} + \% & \& \\ + \_ & \# \\ +\end{tabular} + +% Case 29: Extremely long text in cells +\begin{tabular}{p{5cm}} + This is an extremely long sentence to test how natural word wrapping is handled inside a table cell when exporting or importing documents. \\ +\end{tabular} + +% Case 30: Very many columns (12 columns) +\begin{tabular}{cccccccccccc} + 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & 11 & 12 \\ +\end{tabular} + +% Case 31: Very many rows (10 rows) +\begin{tabular}{c} + Row 1 \\ + Row 2 \\ + Row 3 \\ + Row 4 \\ + Row 5 \\ + Row 6 \\ + Row 7 \\ + Row 8 \\ + Row 9 & Row 10 \\ +\end{tabular} + +% Case 32: Table wrapped in standard table float environment +\begin{table}[h] + \begin{tabular}{cc} + 1 & 2 \\ + 3 & 4 \\ + \end{tabular} +\end{table} + +% Case 33: Table float with centering +\begin{table}[htbp] + \centering + \begin{tabular}{cc} + A & B \\ + \end{tabular} +\end{table} + +% Case 34: Table float with caption and label +\begin{table} + \centering + \begin{tabular}{cc} + 1 & 2 \\ + \end{tabular} + \caption{Test Caption} + \label{tab:test_label} +\end{table} + +% Case 35: Table with vertical alignments t and b +\begin{tabular}[t]{cc} + 1 & 2 \\ +\end{tabular} + +% Case 36: Table with [b] vertical alignment +\begin{tabular}[b]{cc} + 3 & 4 \\ +\end{tabular} + +% Case 37: Multiple tabular environments inside one table float +\begin{table} + \begin{tabular}{c} + Table A \\ + \end{tabular} + \begin{tabular}{c} + Table B \\ + \end{tabular} +\end{table} + +% Case 38: Comments inside tabular environment +\begin{tabular}{cc} + % This is a comment row + 1 & 2 \\ + % Another comment + 3 & 4 % inline comment +\end{tabular} + +% Case 39: Single cell table +\begin{tabular}{c} + Solo \\ +\end{tabular} + +% Case 40: Table with no data cells but hlines +\begin{tabular}{c} + \hline + \hline +\end{tabular} + +% Case 41: Multirow inside a tabular with top and bottom borders (test three-line support with multirow) +\begin{tabular}{cc} + \toprule + \multirow{2}{*}{DualHeader} & Header B \\ + & Header C \\ + \midrule + Value 1 & Value 2 \\ + \bottomrule +\end{tabular} + +% Case 42: Tabular with multiple independent multirows on the same row +\begin{tabular}{ccc} + \multirow{2}{*}{Multi 1} & \multirow{2}{*}{Multi 2} & Simple \\ + & & Simple 2 \\ +\end{tabular} + +% Case 43: Tabular with multirow spanning 4 rows +\begin{tabular}{cc} + \multirow{4}{*}{Span Four Rows} & R1 \\ + & R2 \\ + & R3 \\ + & R4 \\ +\end{tabular} + +% Case 44: Multirow inside multicolumn with custom width and border +\begin{tabular}{|ccc|} + \hline + \multicolumn{2}{|c|}{\multirow{2}{*}{Combined MultiRowCol Width}} & Col 3 \\ + \multicolumn{2}{|c|}{} & Col 3 Row 2 \\ + \hline +\end{tabular} + +% Case 45: Extreme multirow and multicolumn nested complex table +\begin{tabular}{|c|c|c|c|} + \hline + \multicolumn{3}{|c|}{\multirow{3}{*}{Extreme Nested Combined}} & Header 4 \\ + \multicolumn{3}{|c|}{} & Sub 4-1 \\ + \multicolumn{3}{|c|}{} & Sub 4-2 \\ + \hline + Val 1 & Val 2 & Val 3 & Val 4 \\ + \hline +\end{tabular} + +\end{document} diff --git a/devel/0631.md b/devel/0631.md new file mode 100644 index 0000000000..56de7e09fd --- /dev/null +++ b/devel/0631.md @@ -0,0 +1,30 @@ +# [0631] 修复 LaTeX 表格导入 (fromtex) + +## 相关文档 +- [0630.md](0630.md) - LaTeX 表格导出修复 + +## 任务相关的代码文件 +- `src/Plugins/Tex/fromtex.cpp` +- `TeXmacs/tests/0631.scm` +- `TeXmacs/tests/tex/0631_table_import.tex` + +## 如何测试 + +### 确定性测试(单元与集成测试) +```bash +xmake run 0631 +``` + +### 非确定性测试(文档验证) +1. 打开 Mogan STEM,打开 `TeXmacs/tests/tex/0631_table_import.tex` 后全文复制 +2. 选择性粘贴到 Mogan STEM (需清除缓存并打开新的文档)检查是否和 LaTeX 编译出来的效果一致 + +## How +1. 在 `fromtex.cpp` 中全面修复和优化表格解析。 +2. 支持各种复杂和极端表格情况: + - 三线表 `booktabs` (`\toprule`, `\midrule`, `\bottomrule`) + - 各种对齐列规则:`l`, `c`, `r`, `p{width}`,以及包含 `|` 边框、双 `||` 边框等 + - 包含多行 `\hline`, `\cline` + - `\multicolumn`, `\multirow` 复杂单元格跨越 + - 包含嵌套表格、数学模式、多行单元格等极端测试 +3. 建立完备的集成测试,包含至少 40 个各具特色且极端的单元测试用例。 diff --git a/src/Plugins/Tex/fromtex.cpp b/src/Plugins/Tex/fromtex.cpp index 79fd515ca2..5423d0a02c 100644 --- a/src/Plugins/Tex/fromtex.cpp +++ b/src/Plugins/Tex/fromtex.cpp @@ -782,9 +782,9 @@ latex_symbol_to_tree (string s) { if (s == "hfill") return tree (HTAB, "0pt"); if (s == "hfilll") return tree (HTAB, "0pt"); if (s == "hline") return tree (APPLY, "hline"); - if (s == "toprule") return tree (APPLY, "hline"); - if (s == "midrule") return tree (APPLY, "hline"); - if (s == "bottomrule") return tree (APPLY, "hline"); + if (s == "toprule") return tree (APPLY, "toprule"); + if (s == "midrule") return tree (APPLY, "midrule"); + if (s == "bottomrule") return tree (APPLY, "bottomrule"); if (s == "hrulefill") return tree (APPLY, "hrule"); if (s == "hdashline") return ""; if (s == "appendix") { diff --git a/src/Plugins/Tex/fromtex_post.cpp b/src/Plugins/Tex/fromtex_post.cpp index 9174ade161..151d9e0cef 100644 --- a/src/Plugins/Tex/fromtex_post.cpp +++ b/src/Plugins/Tex/fromtex_post.cpp @@ -86,11 +86,27 @@ finalize_returns (tree t) { else return u; } +static string +column_format_to_string (tree t) { + if (is_atomic (t)) return t->label; + else if (is_concat (t)) { + string r; + int i, n= N (t); + for (i= 0; i < n; i++) + r << column_format_to_string (t[i]); + return r; + } + else if (is_apply (t, "begingroup")) return "{"; + else if (is_apply (t, "endgroup")) return "}"; + else if (is_func (t, APPLY, 1) && t[0] == "nbsp") return " "; + else return ""; +} + static tree parse_matrix_params (tree t, string tr, string br, string hoff) { // cout << "parse_matrix_params: " << hoff << LF; tree tformat (TFORMAT); - string s = string_arg (t); + string s = column_format_to_string (t); bool col_flag= true; int i, n= N (s), col= as_int (hoff); for (i= 0; i < n; i++) { @@ -119,12 +135,29 @@ parse_matrix_params (tree t, string tr, string br, string hoff) { tformat << tree (CWITH, tr, br, col_s, col_s, halign, how); if (how_c != 'X') { int start= ++i; - while (i < n && (s[i] != ' ') && (s[i] != '|') && (s[i] != '<') && - (s[i] != '*')) - i++; - string width= s (start, i); - tformat << tree (CWITH, tr, br, col_s, col_s, CELL_HMODE, "exact"); - tformat << tree (CWITH, tr, br, col_s, col_s, CELL_WIDTH, width); + if (i < n && s[start] == '{') { + start++; + int braces= 1; + while (i < n && braces > 0) { + i++; + if (i < n) { + if (s[i] == '{') braces++; + else if (s[i] == '}') braces--; + } + } + string width= s (start, i); + tformat << tree (CWITH, tr, br, col_s, col_s, CELL_HMODE, "exact"); + tformat << tree (CWITH, tr, br, col_s, col_s, CELL_WIDTH, width); + } + else { + while (i < n && (s[i] != ' ') && (s[i] != '|') && (s[i] != '<') && + (s[i] != '*')) + i++; + string width= s (start, i); + tformat << tree (CWITH, tr, br, col_s, col_s, CELL_HMODE, "exact"); + tformat << tree (CWITH, tr, br, col_s, col_s, CELL_WIDTH, width); + i--; + } } else { tformat << tree (CWITH, tr, br, col_s, col_s, CELL_HALIGN, "l"); @@ -249,6 +282,7 @@ parse_pmatrix (tree& r, tree t, int& i, string lb, string rb, string fm) { if (lb != "") r << tree (LEFT, lb); int rows= 0, cols= 0; + bool is_three_line_table= false; tree V (CONCAT); tree L (CONCAT); tree E (CONCAT); @@ -337,27 +371,47 @@ parse_pmatrix (tree& r, tree t, int& i, string lb, string rb, string fm) { else if (v == tree (END, "vmatrix")) break; else if (v == tree (END, "smallmatrix")) break; else if (v == tree (END, "aligned")) break; - else if (v == tree (APPLY, "hline")) { - int howmany= 1; + else if (v == tree (APPLY, "hline") || is_apply (v, "toprule", 0) || + is_apply (v, "midrule", 0) || is_apply (v, "bottomrule", 0)) { + bool is_booktabs= is_apply (v, "toprule", 0) || + is_apply (v, "midrule", 0) || + is_apply (v, "bottomrule", 0); + if (is_booktabs) { + is_three_line_table= true; + } + int howmany = 1; + string line_type= is_apply (v, "midrule", 0) ? "0.5ln" : "1ln"; while (i + 1 < N (t) && - (t[i + 1] == tree (APPLY, "hline") || t[i + 1] == " ")) { - if (t[i + 1] == tree (APPLY, "hline")) howmany++; + (t[i + 1] == tree (APPLY, "hline") || + is_apply (t[i + 1], "toprule", 0) || + is_apply (t[i + 1], "midrule", 0) || + is_apply (t[i + 1], "bottomrule", 0) || t[i + 1] == " ")) { + if (t[i + 1] == tree (APPLY, "hline") || + is_apply (t[i + 1], "toprule", 0) || + is_apply (t[i + 1], "midrule", 0) || + is_apply (t[i + 1], "bottomrule", 0)) { + howmany++; + } i++; } while (i + 1 < N (t) && t[i + 1] == " ") i++; - string how = as_string (howmany) * "ln"; + string how = (howmany > 1) ? as_string (howmany) * "ln" : line_type; int row = N (V) + (N (L) == 0 ? 0 : 1); string row_s= row == 0 ? as_string (row + 1) : as_string (row); string vbor = row == 0 ? copy (CELL_TBORDER) : copy (CELL_BBORDER); tformat << tree (CWITH, row_s, row_s, "1", "-1", vbor, how); } - else if (is_apply (v, "cline", 1)) { + else if (is_apply (v, "cline", 1) || is_apply (v, "cmidrule")) { + bool is_cmid= is_apply (v, "cmidrule"); + if (is_cmid) { + is_three_line_table= true; + } int row = N (V) + (N (L) == 0 ? 0 : 1); - tree arg = parse_cline (v[1]); + tree arg = parse_cline (v[N (v) - 1]); string row_s= row == 0 ? as_string (row + 1) : as_string (row); string vbor = row == 0 ? copy (CELL_TBORDER) : copy (CELL_BBORDER); - string how = "1ln"; + string how = is_cmid ? "0.5ln" : "1ln"; tformat << tree (CWITH, row_s, row_s, arg[0], arg[1], vbor, how); while (i + 1 < N (t) && t[i + 1] == " ") i++; @@ -386,7 +440,16 @@ parse_pmatrix (tree& r, tree t, int& i, string lb, string rb, string fm) { tree tmp= parse_matrix_params (v[2], row_s, row_s, col_s); for (int j= 0; j < N (tmp); j++) tformat << tmp[j]; - E << v[3]; + if (is_apply (v[3], "multirow", 3)) { + string height= as_string (v[3][1]); + tformat << tree (CWITH, row_s, row_s, col_s, col_s, CELL_ROW_SPAN, + height); + tformat << tree (CWITH, row_s, row_s, col_s, col_s, CELL_VALIGN, "c"); + E << v[3][3]; + } + else { + E << v[3]; + } for (int j= 1; j < as_int (width); j++) F << concat (); } @@ -424,7 +487,12 @@ parse_pmatrix (tree& r, tree t, int& i, string lb, string rb, string fm) { M << R; } tformat << trim_cell_spaces (M); - r << compound (fm, tformat); + if (is_three_line_table) { + r << compound ("three-line-table", tformat); + } + else { + r << compound (fm, tformat); + } if (rb != "") r << tree (RIGHT, rb); }