作者 Anonymous [actionscript] 2012-01-30 22:23 (点击下载)

  1. ;; Copyright (C) 2011,2012 Chen Fengyuan (jeova.sanctus.unus+po2db (at) gmail.com)
  2.  
  3. ;; This program is free software; you can redistribute it and/or
  4. ;; modify it under the terms of the GNU General Public License
  5. ;; as published by the Free Software Foundation; either version 2
  6. ;; of the License, or (at your option) any later version.
  7.  
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12.  
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program; if not, write to the Free Software
  15. ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. (defpackage :cfy.po2db
  17. ;; (:use :common-lisp :sqlite :cl-ppcre)
  18. (:use :common-lisp :cl-ppcre)
  19. (:export :po-read :po-get-headinfo :po-parse :flatlist :po-clear :main))
  20.  
  21. (in-package :cfy.po2db)
  22. (defvar *default-db-file-path* "main.sqlite")
  23. (defvar *default-sql* "sql")
  24. (defvar *default-table-suffix* "default")
  25. (defvar *default-table-prefix* "t_")
  26. (defvar *default-headinfo-prefix* "h_")
  27.  
  28. ;;file coding(utf-8)
  29. #+ccl
  30. (setf ccl:*default-external-format* :utf-8)
  31.  
  32.  
  33. ;; regular expressions
  34. (defvar quote-text ""[^"\\]*(?:(?:\\.)+[^"\\]*)+"")
  35.  
  36. (defvar mydebug nil)
  37. (defun mydebug(&rest rest)
  38. (if mydebug
  39. (apply #'format rest)))
  40. (defun flatlist (l)
  41. (cond
  42. ((null l) nil)
  43. ((atom l) (list l))
  44. ((atom (car l)) (cons (car l) (flatlist (cdr l))))
  45. ((append (flatlist (car l)) (flatlist (cdr l))))))
  46.  
  47. (defun read-file-to-vector(filepath)
  48. (let ((content (make-array 0 :fill-pointer t :adjustable t)))
  49. (with-open-file (in filepath)
  50. (loop as i = (read-line in nil) while i do (vector-push-extend i content)))
  51. content))
  52.  
  53.  
  54. (defun read-file-to-list-and-count-msgid(filepath)
  55. (with-open-file (in filepath)
  56. (apply #'values
  57. (loop as i = (read-line in nil)
  58. while i
  59. collect i into s
  60. count (search "msgid" i) into id
  61. finally (return (list s id))))))
  62.  
  63. (defun concatenate-strings(&rest strings)
  64. (apply #'concatenate 'string strings))
  65.  
  66. (let ((po)(index)(total))
  67. (defun po-clear()
  68. (setf po nil
  69. index 0
  70. total 0))
  71. (defun po-reset-index()
  72. (setf index 0))
  73. (defun po-read(filename)
  74. (setf po (read-file-to-vector filename))
  75. (setf index 0)
  76. (setf total (length po)))
  77.  
  78. (defun po-read-line()
  79. (cond ((< index total)
  80. (aref po (1- (incf index))))
  81. (t nil)))
  82.  
  83. (defun po-goto-previous-line()
  84. (if (> index 0)
  85. (decf index)))
  86.  
  87. (defun po-if-eof()
  88. (= index total))
  89.  
  90. (defun po-index()
  91. index)
  92.  
  93. (defun po-total()
  94. total)
  95.  
  96. (defun po-set-index(i)
  97. (cond ((>= i total)
  98. (setf index (1- total)))
  99. ((< i 0)
  100. (po-reset-index))
  101. (t
  102. (setf index i)))))
  103.  
  104. (let ((pre))
  105. (defun po-index-save()
  106. (setf pre (po-index)))
  107. (defun po-index-restore()
  108. (and pre (po-set-index pre))))
  109.  
  110.  
  111. (defun escape (string)
  112. (let ((l (coerce string 'list)))
  113. (coerce
  114. (loop for i in l
  115. if (eql #' i)
  116. collect #' and collect #'
  117. else collect i) 'string)))
  118. (defun escape-args (&rest args)
  119. (loop for i in args collect (escape i)))
  120. (defmacro escape-and-setf (&rest args)
  121. `(progn ,@(loop for i in args collect `(setf ,i (escape ,i)))))
  122. (defun get-quoted-text(string)
  123. (let* ((first (search """ string))
  124. (last (search """ string :from-end t)))
  125. (if (and first last (not (= first last)))
  126. (subseq string (1+ first) last)
  127. "")))
  128. (defun po-read-whole-item()
  129. (let ((first (get-quoted-text (po-read-line))))
  130. (apply #'concatenate-strings
  131. first
  132. (loop for i = (po-read-line)
  133. while i
  134. if (eql 0 (search """ i))
  135. collect (get-quoted-text i) into s
  136. else
  137. do (po-goto-previous-line) and
  138. return s))))
  139. (defun po-get-headinfo-item(re string)
  140. (cadr
  141. (multiple-value-list
  142. (cl-ppcre:scan-to-strings
  143. re
  144. string))))
  145. (defun po-get-headinfo()
  146. (po-index-save)
  147. (po-reset-index)
  148. (values
  149. (mapcar #'po-get-headinfo-item
  150. ;; (""Last-Translator: YunQiang Su <wzssyqa@gmail.com>\n""
  151. ;; ""Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n""
  152. ;; ""Content-Type: text/plain; charset=UTF-8\n""
  153. ;; ""Plural-Forms: nplurals=1; plural=0;\n"")
  154. '("^"Last-Translator: *([^<]+[^ <]) *<([^>]+)>"
  155. "^"Language-Team: *([^<]+[^ <]) *<([^>]+)>"
  156. "^"Content-Type: text/plain; charset=([^ ]+) *\\n""
  157. "^"Plural-Forms: *(.+[^ ]) *\\n"")
  158. (flatlist
  159. (loop for i = (po-read-line)
  160. while i
  161. if (eql 0 (search ""Last-Translator:" i ))collect i into last
  162. if (eql 0 (search ""Language-Team:" i ))collect i into lang
  163. if (eql 0 (search ""Content-Type: text/plain; charset=" i)) collect i into char
  164. if (eql 0 (search ""Plural-Forms:" i)) collect i into plural
  165. until (and last lang char plural) finally (return (list last lang char plural)))))
  166. (po-index-restore)))
  167. (defun po-read-whole-item-for-loop()
  168. (po-goto-previous-line)
  169. (po-read-whole-item))
  170. (defun po-parse()
  171. (let* ((id)(str)(ctxt)(flag)(result (make-array 0 :fill-pointer t :adjustable t))
  172. (when-id (lambda (new-id)
  173. (if (eql nil id)
  174. (setf id new-id)
  175. (error (format nil "dumplicated id:~a~%" (po-index))))))
  176. (when-str (lambda (new-str)
  177. (if (and (not (eql nil id)) (eql nil str))
  178. (setf str new-str)
  179. (error (format nil "error str:~a~%" (po-index))))))
  180. (when-ctxt (lambda (new-ctxt)
  181. (if (eql nil ctxt)
  182. (setf ctxt new-ctxt)
  183. (error (format nil "dumplicated ctxt:~a~%" (po-index))))))
  184. (when-flag (lambda (new-flag)
  185. (if (eql nil flag)
  186. (setf flag new-flag)
  187. (error (format nil "dumplicated flag:~a~%" (po-index))))))
  188. (when-comment (lambda (string)
  189. string
  190. (cond ((and id str)
  191. (vector-push-extend (list id str ctxt flag) result)
  192. (setf id nil str nil ctxt nil flag nil))
  193. ((not (eql nil flag))
  194. (setf flag nil)))))
  195. (when-blank-or-eof (lambda (string)
  196. string
  197. (mydebug t "blank:~a " (po-index))
  198. (cond ((and id str)
  199. (vector-push-extend (list id str ctxt flag) result)
  200. (setf id nil str nil ctxt nil flag nil)))))
  201. (s0
  202. (lambda (s1 s2 fn &optional (ext-fun nil))
  203. (cond ((eql 0 (search s1 s2))
  204. (if (eql nil ext-fun)
  205. (funcall fn s2)
  206. (funcall fn (funcall ext-fun)))
  207. t)
  208. (t nil))))
  209. (determined-when
  210. (lambda (string)
  211. (cond ((funcall s0 "msgid " string when-id #'po-read-whole-item-for-loop)(mydebug t "id:~a~%" (po-index)))
  212. ((funcall s0 "msgstr " string when-str #'po-read-whole-item-for-loop)(mydebug t "str:~a~%" (po-index)))
  213. ((funcall s0 "msgstr[0]" string when-str #'po-read-whole-item-for-loop)(mydebug t "str:~a~%" (po-index)))
  214. ((funcall s0 "msgctxt" string when-ctxt #'po-read-whole-item-for-loop)(mydebug t "ctxt:~a~%" (po-index)))
  215. ((funcall s0 "#," string when-flag)(mydebug t "#,:~a~%" (po-index)))
  216. ((eql nil string) (funcall s0 "" string when-blank-or-eof)(mydebug t "nil:~a~%" (po-index)))
  217. ((funcall s0 "#" string when-comment)(mydebug t "comment:~a~%" (po-index)))
  218. ((funcall s0 "" string when-blank-or-eof)(mydebug t "empty:~a~%" (po-index)))
  219. (t (error (format nil "unexpect:~a~%" string)))))))
  220. (po-index-save)
  221. (po-reset-index)
  222. (do ()
  223. ((po-if-eof) (funcall determined-when (po-read-line))result)
  224. (funcall determined-when (po-read-line)))))
  225.  
  226. ;; $dbh->do("create table '$t2' (pof text,lname text,lmail text,tname text,tmail text,charset text,pforms text)");
  227. (defun headinfo-sql (table-name po-file-name headinfo)
  228. (let* ((last-translator (if (car headinfo) (car headinfo) #("" "")))
  229. (lang-team (if (cadr headinfo) (cadr headinfo) #("" "")))
  230. (charset (if (caddr headinfo) (aref (caddr headinfo )0) ""))
  231. (plural-forms (if (cadddr headinfo) (aref (cadddr headinfo) 0) ""))
  232. (last-translator-name (aref last-translator 0))
  233. (last-translator-email (aref last-translator 1))
  234. (lang-team-name (aref lang-team 0))
  235. (lang-team-email (aref lang-team 1)))
  236. (escape-and-setf table-name po-file-name lang-team-name lang-team-email last-translator-name last-translator-email charset plural-forms)
  237. ;; $dbh->do("insert into '$t2' values('$pof','$trans','$trans_e','$team','$team_e','$charset','$pf')");
  238. (format nil "insert into '~a' values('~a','~a','~a','~a','~a','~a','~a');" table-name po-file-name last-translator-name last-translator-email lang-team-name lang-team-email charset plural-forms)))
  239.  
  240. ;; $dbh->do("create table '$t1' (id integer,msgid text,msgstr text,msgctxt text,fuzzy bool,flag text,pof text)");
  241. (defun po-sql (table-name po-file-name po-parse-result)
  242. (escape-and-setf table-name po-file-name)
  243. (loop
  244. for i across po-parse-result
  245. for id from 0
  246. for msgid = (car i)
  247. for msgstr = (cadr i)
  248. for msgctxt = (caddr i)
  249. for fuzzy = (if (search "fuzzy" (cadddr i)) 1 0)
  250. for flag = (if (cadddr i)
  251. (cl-ppcre:regex-replace-all " *"
  252. (cl-ppcre:regex-replace "# *, *"
  253. (cl-ppcre:regex-replace ", *fuzzy" (cadddr i) "")
  254. "")
  255. "")
  256. "")
  257. do (escape-and-setf msgid msgstr msgctxt flag)
  258. if (not (string= "" msgid))
  259. collect (format nil
  260. ;; $dbh->do("insert into '$t1' values($id,'$msgid','$msgstr','$msgctxt',$fuzzy,'$flag','$pof');");
  261. "insert into '~a' values('~a','~a','~a','~a','~a','~a','~a');"
  262. table-name id msgid msgstr msgctxt fuzzy flag po-file-name)
  263. else do (decf id)))
  264.  
  265. (defun probe-list (string-or-list)
  266. (if (and string-or-list (not (listp string-or-list)))
  267. (list string-or-list)
  268. string-or-list))
  269. (defun com-with-sqlite3(db-filepath sql &key sqlite3-options)
  270. (let (;; (in (make-string-input-stream input))
  271. (output (make-string-output-stream )));; :element-type '(unsigned-byte 8)))
  272. (if (and sqlite3-options (not (listp sqlite3-options)))
  273. (setf sqlite3-options (list sqlite3-options)))
  274. (if sqlite3-options
  275. (progn
  276. #+sbcl
  277. (sb-ext:run-program "sqlite3" (list sqlite3-options db-filepath sql) :output output :search t)
  278. #+ccl
  279. (ccl:run-program "sqlite3" (append sqlite3-options `( ,db-filepath ,sql)) :output output))
  280. (progn
  281. #+sbcl
  282. (sb-ext:run-program "sqlite3" (list db-filepath sql) :output output :search t)
  283. #+ccl
  284. (ccl:run-program "sqlite3" `(,db-filepath ,sql) :output output)))
  285. (get-output-stream-string output)))
  286. (defun po2sql (po-files output-file headinfo-table-name po-table-name &key pre-sql suf-sql db-filepath)
  287. (with-open-file (out output-file :direction :output :if-exists :supersede :if-does-not-exist :create)
  288. (format out "begin transaction;~%")
  289. ;; pre sql output
  290. (if pre-sql
  291. (loop for i in (probe-list pre-sql)
  292. do (format out "~a~%" i)))
  293. (if db-filepath
  294. (flet ((if-table-exists-rename (db-filepath table-name)
  295. (let ((number-of-tables))
  296. (if (= 1 (parse-integer (com-with-sqlite3
  297. db-filepath
  298. (format nil "select count(name) from sqlite_master where name == '~a';" table-name))))
  299. (progn
  300. (setf number-of-tables
  301. (parse-integer
  302. (com-with-sqlite3
  303. db-filepath
  304. (format nil "select count(name) from sqlite_master where name like '~a%';" table-name))))
  305. ;; $dbh->do("alter table '${t1}_$j1' rename to '${t1}_$j2'");
  306. (format nil "alter table '~a' rename to '~:*~a_~a';" table-name (1- number-of-tables)))
  307. nil))))
  308. (loop for i in `(,po-table-name ,headinfo-table-name)
  309. for sql = (if-table-exists-rename db-filepath i)
  310. if sql
  311. do (format out "~a~%" sql))))
  312. ;; $dbh->do("create table '$t1' (id integer,msgid text,msgstr text,msgctxt text,fuzzy bool,flag text,pof text)");
  313. ;; $dbh->do("create table '$t2' (pof text,lname text,lmail text,tname text,tmail text,charset text,pforms text)");
  314. (format out "create table '~a' (id integer,msgid text,msgstr text,msgctxt text,fuzzy bool,flag text,pof text);~%" po-table-name)
  315. (format out "create table '~a' (pof text,lname text,lmail text,tname text,tmail text,charset text,pforms text);~%" headinfo-table-name)
  316. ;; (if (listp po-files)
  317. ;; t
  318. ;; (setf po-files (list po-files)))
  319. (setf po-files (probe-list po-files))
  320. (loop for po in po-files
  321. for po-file-name = (namestring po)
  322. do (po-read po)
  323. do (format out "~a~%" (headinfo-sql headinfo-table-name po-file-name (po-get-headinfo)))
  324. do (loop for i in (po-sql po-table-name po-file-name (po-parse))
  325. do (format out "~a~%" i)))
  326. ;; output index sql
  327. (if db-filepath
  328. (let ((index-of-headinfo (concatenate-strings "i_" headinfo-table-name))
  329. (index-of-po (concatenate-strings "i_" po-table-name))
  330. (number-of-headinfo
  331. (parse-integer
  332. (com-with-sqlite3
  333. db-filepath
  334. (format nil "select count(name) from sqlite_master where name like '~a%';" headinfo-table-name))))
  335. (number-of-po
  336. (parse-integer
  337. (com-with-sqlite3
  338. db-filepath
  339. (format nil "select count(name) from sqlite_master where name like '~a%';" po-table-name)))))
  340. ;; $dbh->do("create index '$i1' on '$t1' (id,msgid,msgstr,msgctxt,fuzzy,flag,pof)");
  341. ;; $dbh->do("create index '$i2' on '$t2' (pof,lname,lmail,tname,tmail,charset,pforms)");
  342. (format out "create index '~a_~a' on '~a' (id,msgid,msgstr,msgctxt,fuzzy,flag,pof);~%" index-of-po number-of-po po-table-name)
  343. (format out "create index '~a_~a' on '~a' (pof,lname,lmail,tname,tmail,charset,pforms);~%" index-of-headinfo number-of-headinfo headinfo-table-name))
  344. (let ((index-of-headinfo (concatenate-strings "i_" headinfo-table-name))
  345. (index-of-po (concatenate-strings "i_" po-table-name)))
  346. (format out "create index '~a' on '~a' (id,msgid,msgstr,msgctxt,fuzzy,flag,pof);~%" index-of-po po-table-name)
  347. (format out "create index '~a' on '~a' (pof,lname,lmail,tname,tmail,charset,pforms);~%" index-of-headinfo headinfo-table-name)))
  348. ;; suffix sql output
  349. (loop for i in (probe-list suf-sql)
  350. do (format out "~a~%" i))
  351. (format out "commit;~%")))
  352. (defun max-string(s1 s2)
  353. (loop
  354. with b1 = (length s1)
  355. with b2 = (length s2)
  356. for i from 0 upto b1
  357. if (or (>= i b2) (char/= (char s1 i)
  358. (char s2 i)))
  359. return (subseq s1 0 i)))
  360. (defun test ()
  361. (let* ((po-table "t_")
  362. (headinfo-table "h_")
  363. (table-suffix "default")
  364. (output-file "/dev/shm/lisp2sqlite")
  365. (po-files (loop for i in (directory "/dev/shm/pos/*.po") collect (namestring i)))
  366. (headinfo-table-name (concatenate-strings headinfo-table table-suffix))
  367. (po-table-name (concatenate-strings po-table table-suffix))
  368. (db-filepath "/dev/shm/main"))
  369. ;; (with-open-file (out output-file :direction :output :if-exists :supersede :if-does-not-exist :create)
  370. ;; (loop for i in sql
  371. ;; do (format out "~a~%" i)))
  372. (po2sql po-files output-file headinfo-table-name po-table-name :db-filepath db-filepath)
  373. (com-with-sqlite3 db-filepath (concatenate-strings ".read " output-file))))
  374. ;;; argument parser
  375. #+sbcl
  376. (defun argv (&optional argv-test)
  377. (let ((argv (or argv-test (cdr sb-ext:*posix-argv*)))
  378. po-files)
  379. (multiple-value-bind (db-file-path table-suffix output-file)
  380. (values-list
  381. (loop for i in argv
  382. if (all-matches "\.po$" i)
  383. do (push i po-files)
  384. else
  385. collect i into opt
  386. finally (return opt)))
  387. (list (or db-file-path *default-db-file-path*)
  388. (or table-suffix *default-table-suffix*)
  389. (or output-file *default-sql*)
  390. po-files))))
  391. (defun main ()
  392. (destructuring-bind
  393. (db-file-path table-suffix output-file po-files)
  394. (argv)
  395. (if (null po-files)
  396. (format
  397. *standard-output*
  398. "Usage: ~a [db-file-path] [table-suffix] [sql-file] po-files~%~aReport po2db.lisp bugs to jeova.sanctus.unus~agmail.org~%"
  399. #+sbcl
  400. (car sb-ext:*posix-argv*)
  401. #-sbcl
  402. "lisp"
  403. (with-output-to-string (out)
  404. (loop for (i j)in `(`,("db-file-path" ,*default-db-file-path*)
  405. `,("table-suffix" ,*default-table-suffix*)
  406. `,("sql-file" ,*default-sql*))
  407. do (format out "The default value of ~a is ~a~%" i j)))
  408. "@")
  409. (let ((headinfo-table-name (concatenate-strings *default-headinfo-prefix* table-suffix))
  410. (po-table-name (concatenate-strings *default-table-prefix* table-suffix)))
  411. (po2sql po-files output-file headinfo-table-name po-table-name :db-filepath db-file-path)
  412. (com-with-sqlite3 db-file-path (concatenate-strings ".read " output-file))))))
  413. ;; compile as elf
  414. ;; (declaim (optimize (speed 3)(debug 0)(space 3)))
  415. ;; (load "/home/cfy/gits/po2db/po2db.lisp")
  416. ;; (save-lisp-and-die "po2db" :toplevel #'cfy.po2db:main :executable t)

提交下面的校正或者修改. (点击这里开始一个新的帖子)
姓名: 在 cookie 中记住我的名字

屏幕抓图:(jpeg 或 png)