mofile.lisp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. ;; This software is Copyright (c) cage, 2012.
  2. ;; cage grants you the rights to distribute
  3. ;; and use this software as governed by the terms
  4. ;; of the Lisp Lesser GNU Public License
  5. ;; (http://opensource.franz.com/preamble.html),
  6. ;; known as the LLGPL
  7. (in-package :cl-i18n)
  8. (alexandria:define-constant +stream-element-type+ '(unsigned-byte 8) :test 'equalp)
  9. (alexandria:define-constant +string-num-byte-version-number+ 4 :test '=)
  10. (alexandria:define-constant +string-num-byte-size+ 4 :test '=)
  11. (alexandria:define-constant +offset-original-byte-size+ 4 :test '=)
  12. (alexandria:define-constant +offset-translation-byte-size+ 4 :test '=)
  13. (alexandria:define-constant +hashing-table-size-byte-size+ 4 :test '=)
  14. (alexandria:define-constant +hashing-table-offset-byte-size+ 4 :test '=)
  15. (alexandria:define-constant +original-strings-offset-size-chunk-size+ 8 :test '=)
  16. (alexandria:define-constant +original-strings-offset-chunk-size+ 4 :test '=)
  17. (alexandria:define-constant +original-strings-length-chunk-size+ 4 :test '=)
  18. (alexandria:define-constant +translated-strings-offset-size-chunk-size+ 8 :test '=)
  19. (alexandria:define-constant +translated-strings-offset-chunk-size+ 4 :test '=)
  20. (alexandria:define-constant +translated-strings-length-chunk-size+ 4 :test '=)
  21. (defun mo-magic-number-p (seq)
  22. (or (equalp seq +mo-file-magic-number+)
  23. (equalp seq (reverse +mo-file-magic-number+))))
  24. (defun 2byte->word (byte1 byte2)
  25. (let ((res #x00000000))
  26. (boole boole-ior
  27. (ash (boole boole-ior byte1 res) 8)
  28. byte2)))
  29. (defun 2word->int (word1 word2)
  30. (let ((res #x00000000))
  31. (boole boole-ior
  32. (ash (boole boole-ior word1 res) 16)
  33. word2)))
  34. (defun byte->int (bytes)
  35. (let ((res #x0000000000000000))
  36. (loop
  37. for i in bytes and
  38. ct = 0 then (+ ct 8) do
  39. (setf res
  40. (boole boole-ior
  41. (ash i ct)
  42. res)))
  43. res))
  44. (defclass mofile ()
  45. ((mofile
  46. :initform nil
  47. :accessor mofile)
  48. (magic-number
  49. :initform -1
  50. :accessor magic-number)
  51. (version-number
  52. :initform -1
  53. :accessor version-number)
  54. (string-number
  55. :initform -1
  56. :accessor string-number)
  57. (offset-original
  58. :initform -1
  59. :accessor offset-original)
  60. (offset-translations
  61. :initform -1
  62. :accessor offset-translations)
  63. (hashing-table-size
  64. :initform -1
  65. :accessor hashing-table-size)
  66. (hashing-table-offset
  67. :initform -1
  68. :accessor hashing-table-offset)
  69. (parsing-errors
  70. :initform nil
  71. :accessor parsing-errors)
  72. (original-strings
  73. :initform nil
  74. :accessor original-strings)
  75. (translated-strings
  76. :initform nil
  77. :accessor translated-strings)
  78. (pofile
  79. :initform nil
  80. :accessor pofile)))
  81. (defmacro with-mo-file ((stream moclass mofile) &rest body)
  82. `(let ((,moclass (make-instance 'mofile)))
  83. (with-open-file (,stream ,mofile :direction :input :element-type +stream-element-type+ :if-does-not-exist :error)
  84. ,@body)))
  85. (defgeneric parse-magic-number (object stream))
  86. (defgeneric parse-original-strings (object stream))
  87. (defgeneric parse-translated-strings (object stream))
  88. (defgeneric parse-mofile (object stream))
  89. (defgeneric mofile->pofile (object))
  90. (defgeneric mofile->translation (object &optional originals translated
  91. plural-function translations))
  92. (defmethod print-object :after ((object mofile) stream)
  93. (with-slots (magic-number version-number string-number offset-original offset-translations hashing-table-size hashing-table-offset original-strings translated-strings parsing-errors) object
  94. (print-unreadable-object (object stream :type nil :identity nil)
  95. (format stream "Magic number #x~x~%Version ~d~%string number ~d~%offset original #x~x~%offset translation #x~x~%hashing-table-size ~d~%hashing-table-offset #x~x~%original strings: ~s~%translated strings ~s~%errors ~s" magic-number version-number string-number offset-original offset-translations hashing-table-size hashing-table-offset original-strings translated-strings parsing-errors))))
  96. (defmethod parse-magic-number ((object mofile) stream)
  97. (let* ((bytes (loop for i from 0 below (length +mo-file-magic-number+)
  98. collect (read-byte stream)))
  99. (magic-number (2word->int (2byte->word (fourth bytes) (third bytes))
  100. (2byte->word (second bytes) (first bytes)))))
  101. (if (mo-magic-number-p bytes)
  102. (progn
  103. (setf (magic-number object) magic-number)
  104. (values magic-number object))
  105. (progn
  106. (push (format nil "Invalid magic-number ~x instead of ~a"
  107. magic-number
  108. +mo-file-magic-number+)
  109. (parsing-errors object))
  110. nil))))
  111. (defmacro define-parse-header-chunk ((name size &optional (slot name)))
  112. (alexandria:with-gensyms (bytes res)
  113. `(progn
  114. (defgeneric ,(alexandria:format-symbol t "PARSE-~:@(~a~)" name) (mofile stream))
  115. (defmethod ,(alexandria:format-symbol t "PARSE-~:@(~a~)" name) ((object mofile) stream)
  116. (let* ((,bytes (loop for i from 0 below ,size collect (read-byte stream)))
  117. (,res (byte->int ,bytes)))
  118. ,(when (not (null slot))
  119. `(setf (,slot object) ,res))
  120. (values ,res object))))))
  121. (define-parse-header-chunk (version-number +string-num-byte-version-number+))
  122. (define-parse-header-chunk (string-number +string-num-byte-size+))
  123. (define-parse-header-chunk (offset-original +offset-original-byte-size+))
  124. (define-parse-header-chunk (offset-translations +offset-translation-byte-size+))
  125. (define-parse-header-chunk (hashing-table-size +hashing-table-size-byte-size+))
  126. (define-parse-header-chunk (hashing-table-offset +hashing-table-offset-byte-size+))
  127. (define-parse-header-chunk (original-string-length +original-strings-length-chunk-size+ nil))
  128. (define-parse-header-chunk (original-string-offset +original-strings-offset-chunk-size+ nil))
  129. (define-parse-header-chunk (translated-string-length +translated-strings-length-chunk-size+ nil))
  130. (define-parse-header-chunk (translated-string-offset +translated-strings-offset-chunk-size+ nil))
  131. (defmacro with-parse-strings-chunks ((stream start-offset chunk-size whole-chunk-size parse-length-fun parse-offset-func) mofile)
  132. (alexandria:with-gensyms (pos strings str-len str-offset str-bytes orig-strings)
  133. `(let ((,strings '()))
  134. (if (file-position ,stream ,start-offset)
  135. (progn
  136. (do ((,pos ,start-offset (+ ,pos ,chunk-size)))
  137. ((not (< ,pos ,whole-chunk-size))
  138. (reverse ,strings))
  139. (file-position ,stream ,pos)
  140. (let* ((,str-len (,parse-length-fun ,mofile ,stream))
  141. (,str-offset (,parse-offset-func ,mofile ,stream))
  142. (,str-bytes (make-array ,str-len :element-type +stream-element-type+)))
  143. (when-debug
  144. (format t "string @ ~d length ~x offset ~x " ,pos ,str-len ,str-offset))
  145. (file-position ,stream ,str-offset)
  146. (read-sequence ,str-bytes ,stream)
  147. (let ((,orig-strings (cl-ppcre:split "\\x0" (babel:octets-to-string ,str-bytes))))
  148. (when-debug
  149. (format t "val: ~s~%" ,orig-strings))
  150. (push ,orig-strings ,strings)))))
  151. (push (format nil "Invalid offset (~a) for original strings offset" ,start-offset)
  152. (parsing-errors ,mofile))))))
  153. (defmethod parse-original-strings ((object mofile) stream)
  154. (with-accessors ((offset-original offset-original)
  155. (original-strings original-strings)) object
  156. (setf original-strings
  157. (with-parse-strings-chunks (stream offset-original
  158. +original-strings-offset-size-chunk-size+
  159. (+ offset-original (* (string-number object) +original-strings-offset-size-chunk-size+))
  160. parse-original-string-length
  161. parse-original-string-offset) object))
  162. (values original-strings object)))
  163. (defmethod parse-translated-strings ((object mofile) stream)
  164. (with-accessors ((offset-original offset-original)
  165. (offset-translations offset-translations)
  166. (translated-strings translated-strings)) object
  167. (let ((end-chunk (+ offset-translations
  168. (* (string-number object)
  169. +original-strings-offset-size-chunk-size+))))
  170. (setf translated-strings
  171. (with-parse-strings-chunks (stream offset-translations
  172. +translated-strings-offset-size-chunk-size+
  173. end-chunk
  174. parse-translated-string-length
  175. parse-translated-string-offset) object))
  176. (values translated-strings object))))
  177. (defmethod parse-mofile ((object mofile) stream)
  178. (parse-magic-number object stream)
  179. (parse-version-number object stream)
  180. (parse-string-number object stream)
  181. (parse-offset-original object stream)
  182. (parse-offset-translations object stream)
  183. (parse-hashing-table-size object stream)
  184. (parse-hashing-table-offset object stream)
  185. (parse-original-strings object stream)
  186. (parse-translated-strings object stream))
  187. (defun split-escape (msg)
  188. (if (null msg)
  189. (format nil "\"\"~%")
  190. (let ((splitted (cl-ppcre:split "\\n" msg)))
  191. (if (> (length splitted) 1)
  192. (format nil "~{\"~a\\n\"~%~}" splitted)
  193. (format nil "~{\"~a\"~%~}" splitted)))))
  194. (defmethod mofile->pofile ((object mofile))
  195. (with-accessors ((pofile pofile)
  196. (original-strings original-strings)
  197. (translated-strings translated-strings)) object
  198. (labels ((concat (str)
  199. (setf pofile
  200. (concatenate 'string pofile str))))
  201. (let ((ct 0))
  202. (mapcar #'(lambda (orig)
  203. (concat (format nil "~a ~a" +msgid+ (split-escape (first orig))))
  204. (when (> (length orig) 1)
  205. (mapcar #'(lambda (plur)
  206. (concat (format nil "~a ~a" +msgid-plural+ (split-escape plur))))
  207. (rest orig)))
  208. (let ((ct-pl 0))
  209. (if (> (length (nth ct translated-strings)) 1)
  210. (mapcar #'(lambda(plur)
  211. (concat (format nil "~a[~a] ~a" +msgstr+ ct-pl (split-escape plur)))
  212. (incf ct-pl))
  213. (nth ct translated-strings))
  214. (concat (format nil "~a ~a~%" +msgstr+ (split-escape (first (nth ct translated-strings)))))))
  215. (concat (format nil "~%"))
  216. (incf ct))
  217. original-strings)
  218. (values pofile object)))))
  219. (defmethod mofile->translation ((object mofile) &optional
  220. (original (original-strings object))
  221. (translated (translated-strings object))
  222. (plural-function (extract-plural-function (first (first translated))))
  223. (translations (make-hash-table :test 'equal)))
  224. (if (null original)
  225. (values translations plural-function)
  226. (let ((translation (make-instance 'translation))
  227. (orig (first original))
  228. (transl (first translated)))
  229. (setf (translated translation) (first transl))
  230. (if (> (length orig) 1)
  231. (setf (plural-form translation) (split-escape (second orig))))
  232. (do ((plural-form (rest transl) (rest plural-form)))
  233. ((null plural-form) (setf (plural-translated translation)
  234. (reverse (plural-translated translation))))
  235. (push (first plural-form) (plural-translated translation)))
  236. (setf (gethash (first orig) translations) translation)
  237. (mofile->translation object (rest original) (rest translated) plural-function translations))))