@fiduswriter/document 0.1.0-alpha.3 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/README.md +22 -1
  2. package/dist/bibliography/common.d.ts +92 -0
  3. package/dist/bibliography/common.d.ts.map +1 -0
  4. package/dist/bibliography/common.js +85 -0
  5. package/dist/bibliography/common.js.map +1 -0
  6. package/dist/bibliography/csl_bib.d.ts +3 -0
  7. package/dist/bibliography/csl_bib.d.ts.map +1 -0
  8. package/dist/bibliography/csl_bib.js +131 -0
  9. package/dist/bibliography/csl_bib.js.map +1 -0
  10. package/dist/citations/citeproc_sys.d.ts +13 -0
  11. package/dist/citations/citeproc_sys.d.ts.map +1 -0
  12. package/dist/citations/citeproc_sys.js +43 -0
  13. package/dist/citations/citeproc_sys.js.map +1 -0
  14. package/dist/citations/format.d.ts +23 -0
  15. package/dist/citations/format.d.ts.map +1 -0
  16. package/dist/citations/format.js +161 -0
  17. package/dist/citations/format.js.map +1 -0
  18. package/dist/editor/e2ee/encryptor.d.ts +123 -0
  19. package/dist/editor/e2ee/encryptor.d.ts.map +1 -0
  20. package/dist/editor/e2ee/encryptor.js +201 -0
  21. package/dist/editor/e2ee/encryptor.js.map +1 -0
  22. package/dist/exporter/docx/citations.d.ts +21 -0
  23. package/dist/exporter/docx/citations.d.ts.map +1 -0
  24. package/dist/exporter/docx/citations.js +154 -0
  25. package/dist/exporter/docx/citations.js.map +1 -0
  26. package/dist/exporter/docx/comments.d.ts +18 -0
  27. package/dist/exporter/docx/comments.d.ts.map +1 -0
  28. package/dist/exporter/docx/comments.js +137 -0
  29. package/dist/exporter/docx/comments.js.map +1 -0
  30. package/dist/exporter/docx/footnotes.d.ts +42 -0
  31. package/dist/exporter/docx/footnotes.d.ts.map +1 -0
  32. package/dist/exporter/docx/footnotes.js +182 -0
  33. package/dist/exporter/docx/footnotes.js.map +1 -0
  34. package/dist/exporter/docx/images.d.ts +14 -0
  35. package/dist/exporter/docx/images.d.ts.map +1 -0
  36. package/dist/exporter/docx/images.js +86 -0
  37. package/dist/exporter/docx/images.js.map +1 -0
  38. package/dist/exporter/docx/index.d.ts +22 -0
  39. package/dist/exporter/docx/index.d.ts.map +1 -0
  40. package/dist/exporter/docx/index.js +107 -0
  41. package/dist/exporter/docx/index.js.map +1 -0
  42. package/dist/exporter/docx/lists.d.ts +33 -0
  43. package/dist/exporter/docx/lists.d.ts.map +1 -0
  44. package/dist/exporter/docx/lists.js +229 -0
  45. package/dist/exporter/docx/lists.js.map +1 -0
  46. package/dist/exporter/docx/math.d.ts +13 -0
  47. package/dist/exporter/docx/math.d.ts.map +1 -0
  48. package/dist/exporter/docx/math.js +42 -0
  49. package/dist/exporter/docx/math.js.map +1 -0
  50. package/dist/exporter/docx/metadata.d.ts +14 -0
  51. package/dist/exporter/docx/metadata.d.ts.map +1 -0
  52. package/dist/exporter/docx/metadata.js +239 -0
  53. package/dist/exporter/docx/metadata.js.map +1 -0
  54. package/dist/exporter/docx/rels.d.ts +25 -0
  55. package/dist/exporter/docx/rels.d.ts.map +1 -0
  56. package/dist/exporter/docx/rels.js +168 -0
  57. package/dist/exporter/docx/rels.js.map +1 -0
  58. package/dist/exporter/docx/render.d.ts +20 -0
  59. package/dist/exporter/docx/render.d.ts.map +1 -0
  60. package/dist/exporter/docx/render.js +774 -0
  61. package/dist/exporter/docx/render.js.map +1 -0
  62. package/dist/exporter/docx/richtext.d.ts +25 -0
  63. package/dist/exporter/docx/richtext.d.ts.map +1 -0
  64. package/dist/exporter/docx/richtext.js +1052 -0
  65. package/dist/exporter/docx/richtext.js.map +1 -0
  66. package/dist/exporter/docx/tables.d.ts +14 -0
  67. package/dist/exporter/docx/tables.d.ts.map +1 -0
  68. package/dist/exporter/docx/tables.js +109 -0
  69. package/dist/exporter/docx/tables.js.map +1 -0
  70. package/dist/exporter/docx/tools.d.ts +3 -0
  71. package/dist/exporter/docx/tools.d.ts.map +1 -0
  72. package/dist/exporter/docx/tools.js +48 -0
  73. package/dist/exporter/docx/tools.js.map +1 -0
  74. package/dist/exporter/epub/index.d.ts +11 -0
  75. package/dist/exporter/epub/index.d.ts.map +1 -0
  76. package/dist/exporter/epub/index.js +102 -0
  77. package/dist/exporter/epub/index.js.map +1 -0
  78. package/dist/exporter/epub/templates.d.ts +32 -0
  79. package/dist/exporter/epub/templates.d.ts.map +1 -0
  80. package/dist/exporter/epub/templates.js +95 -0
  81. package/dist/exporter/epub/templates.js.map +1 -0
  82. package/dist/exporter/epub/tools.d.ts +5 -0
  83. package/dist/exporter/epub/tools.d.ts.map +1 -0
  84. package/dist/exporter/epub/tools.js +81 -0
  85. package/dist/exporter/epub/tools.js.map +1 -0
  86. package/dist/exporter/html/citations.d.ts +27 -0
  87. package/dist/exporter/html/citations.d.ts.map +1 -0
  88. package/dist/exporter/html/citations.js +91 -0
  89. package/dist/exporter/html/citations.js.map +1 -0
  90. package/dist/exporter/html/convert.d.ts +100 -0
  91. package/dist/exporter/html/convert.d.ts.map +1 -0
  92. package/dist/exporter/html/convert.js +718 -0
  93. package/dist/exporter/html/convert.js.map +1 -0
  94. package/dist/exporter/html/index.d.ts +61 -0
  95. package/dist/exporter/html/index.d.ts.map +1 -0
  96. package/dist/exporter/html/index.js +134 -0
  97. package/dist/exporter/html/index.js.map +1 -0
  98. package/dist/exporter/html/templates.d.ts +10 -0
  99. package/dist/exporter/html/templates.d.ts.map +1 -0
  100. package/dist/exporter/html/templates.js +21 -0
  101. package/dist/exporter/html/templates.js.map +1 -0
  102. package/dist/exporter/html/tools.d.ts +2 -0
  103. package/dist/exporter/html/tools.d.ts.map +1 -0
  104. package/dist/exporter/html/tools.js +49 -0
  105. package/dist/exporter/html/tools.js.map +1 -0
  106. package/dist/exporter/jats/bibliography.d.ts +2 -0
  107. package/dist/exporter/jats/bibliography.d.ts.map +1 -0
  108. package/dist/exporter/jats/bibliography.js +153 -0
  109. package/dist/exporter/jats/bibliography.js.map +1 -0
  110. package/dist/exporter/jats/citations.d.ts +14 -0
  111. package/dist/exporter/jats/citations.d.ts.map +1 -0
  112. package/dist/exporter/jats/citations.js +83 -0
  113. package/dist/exporter/jats/citations.js.map +1 -0
  114. package/dist/exporter/jats/convert.d.ts +45 -0
  115. package/dist/exporter/jats/convert.d.ts.map +1 -0
  116. package/dist/exporter/jats/convert.js +799 -0
  117. package/dist/exporter/jats/convert.js.map +1 -0
  118. package/dist/exporter/jats/index.d.ts +19 -0
  119. package/dist/exporter/jats/index.d.ts.map +1 -0
  120. package/dist/exporter/jats/index.js +66 -0
  121. package/dist/exporter/jats/index.js.map +1 -0
  122. package/dist/exporter/jats/templates.d.ts +16 -0
  123. package/dist/exporter/jats/templates.d.ts.map +1 -0
  124. package/dist/exporter/jats/templates.js +25 -0
  125. package/dist/exporter/jats/templates.js.map +1 -0
  126. package/dist/exporter/jats/text.d.ts +3 -0
  127. package/dist/exporter/jats/text.d.ts.map +1 -0
  128. package/dist/exporter/jats/text.js +72 -0
  129. package/dist/exporter/jats/text.js.map +1 -0
  130. package/dist/exporter/latex/convert.d.ts +27 -0
  131. package/dist/exporter/latex/convert.d.ts.map +1 -0
  132. package/dist/exporter/latex/convert.js +865 -0
  133. package/dist/exporter/latex/convert.js.map +1 -0
  134. package/dist/exporter/latex/escape_latex.d.ts +2 -0
  135. package/dist/exporter/latex/escape_latex.d.ts.map +1 -0
  136. package/dist/exporter/latex/escape_latex.js +20 -0
  137. package/dist/exporter/latex/escape_latex.js.map +1 -0
  138. package/dist/exporter/latex/index.d.ts +23 -0
  139. package/dist/exporter/latex/index.d.ts.map +1 -0
  140. package/dist/exporter/latex/index.js +57 -0
  141. package/dist/exporter/latex/index.js.map +1 -0
  142. package/dist/exporter/latex/readme.d.ts +2 -0
  143. package/dist/exporter/latex/readme.d.ts.map +1 -0
  144. package/dist/exporter/latex/readme.js +23 -0
  145. package/dist/exporter/latex/readme.js.map +1 -0
  146. package/dist/exporter/native/shrink.d.ts +21 -0
  147. package/dist/exporter/native/shrink.d.ts.map +1 -0
  148. package/dist/exporter/native/shrink.js +115 -0
  149. package/dist/exporter/native/shrink.js.map +1 -0
  150. package/dist/exporter/odt/citations.d.ts +18 -0
  151. package/dist/exporter/odt/citations.d.ts.map +1 -0
  152. package/dist/exporter/odt/citations.js +87 -0
  153. package/dist/exporter/odt/citations.js.map +1 -0
  154. package/dist/exporter/odt/footnotes.d.ts +25 -0
  155. package/dist/exporter/odt/footnotes.d.ts.map +1 -0
  156. package/dist/exporter/odt/footnotes.js +112 -0
  157. package/dist/exporter/odt/footnotes.js.map +1 -0
  158. package/dist/exporter/odt/images.d.ts +13 -0
  159. package/dist/exporter/odt/images.d.ts.map +1 -0
  160. package/dist/exporter/odt/images.js +98 -0
  161. package/dist/exporter/odt/images.js.map +1 -0
  162. package/dist/exporter/odt/index.d.ts +23 -0
  163. package/dist/exporter/odt/index.d.ts.map +1 -0
  164. package/dist/exporter/odt/index.js +99 -0
  165. package/dist/exporter/odt/index.js.map +1 -0
  166. package/dist/exporter/odt/math.d.ts +13 -0
  167. package/dist/exporter/odt/math.d.ts.map +1 -0
  168. package/dist/exporter/odt/math.js +48 -0
  169. package/dist/exporter/odt/math.js.map +1 -0
  170. package/dist/exporter/odt/metadata.d.ts +14 -0
  171. package/dist/exporter/odt/metadata.d.ts.map +1 -0
  172. package/dist/exporter/odt/metadata.js +203 -0
  173. package/dist/exporter/odt/metadata.js.map +1 -0
  174. package/dist/exporter/odt/render.d.ts +24 -0
  175. package/dist/exporter/odt/render.d.ts.map +1 -0
  176. package/dist/exporter/odt/render.js +644 -0
  177. package/dist/exporter/odt/render.js.map +1 -0
  178. package/dist/exporter/odt/richtext.d.ts +22 -0
  179. package/dist/exporter/odt/richtext.d.ts.map +1 -0
  180. package/dist/exporter/odt/richtext.js +728 -0
  181. package/dist/exporter/odt/richtext.js.map +1 -0
  182. package/dist/exporter/odt/styles.d.ts +33 -0
  183. package/dist/exporter/odt/styles.d.ts.map +1 -0
  184. package/dist/exporter/odt/styles.js +348 -0
  185. package/dist/exporter/odt/styles.js.map +1 -0
  186. package/dist/exporter/odt/track.d.ts +11 -0
  187. package/dist/exporter/odt/track.d.ts.map +1 -0
  188. package/dist/exporter/odt/track.js +59 -0
  189. package/dist/exporter/odt/track.js.map +1 -0
  190. package/dist/exporter/pandoc/citations.d.ts +17 -0
  191. package/dist/exporter/pandoc/citations.d.ts.map +1 -0
  192. package/dist/exporter/pandoc/citations.js +85 -0
  193. package/dist/exporter/pandoc/citations.js.map +1 -0
  194. package/dist/exporter/pandoc/convert.d.ts +38 -0
  195. package/dist/exporter/pandoc/convert.d.ts.map +1 -0
  196. package/dist/exporter/pandoc/convert.js +881 -0
  197. package/dist/exporter/pandoc/convert.js.map +1 -0
  198. package/dist/exporter/pandoc/index.d.ts +38 -0
  199. package/dist/exporter/pandoc/index.d.ts.map +1 -0
  200. package/dist/exporter/pandoc/index.js +67 -0
  201. package/dist/exporter/pandoc/index.js.map +1 -0
  202. package/dist/exporter/pandoc/readme.d.ts +2 -0
  203. package/dist/exporter/pandoc/readme.d.ts.map +1 -0
  204. package/dist/exporter/pandoc/readme.js +9 -0
  205. package/dist/exporter/pandoc/readme.js.map +1 -0
  206. package/dist/exporter/pandoc/tools.d.ts +6 -0
  207. package/dist/exporter/pandoc/tools.d.ts.map +1 -0
  208. package/dist/exporter/pandoc/tools.js +52 -0
  209. package/dist/exporter/pandoc/tools.js.map +1 -0
  210. package/dist/exporter/print/index.d.ts +9 -0
  211. package/dist/exporter/print/index.d.ts.map +1 -0
  212. package/dist/exporter/print/index.js +140 -0
  213. package/dist/exporter/print/index.js.map +1 -0
  214. package/dist/exporter/tools/doc_content.d.ts +7 -0
  215. package/dist/exporter/tools/doc_content.d.ts.map +1 -0
  216. package/dist/exporter/tools/doc_content.js +129 -0
  217. package/dist/exporter/tools/doc_content.js.map +1 -0
  218. package/dist/exporter/tools/file.d.ts +2 -0
  219. package/dist/exporter/tools/file.d.ts.map +1 -0
  220. package/dist/exporter/tools/file.js +10 -0
  221. package/dist/exporter/tools/file.js.map +1 -0
  222. package/dist/exporter/tools/json.d.ts +7 -0
  223. package/dist/exporter/tools/json.d.ts.map +1 -0
  224. package/dist/exporter/tools/json.js +82 -0
  225. package/dist/exporter/tools/json.js.map +1 -0
  226. package/dist/exporter/tools/svg.d.ts +2 -0
  227. package/dist/exporter/tools/svg.d.ts.map +1 -0
  228. package/dist/exporter/tools/svg.js +26 -0
  229. package/dist/exporter/tools/svg.js.map +1 -0
  230. package/dist/exporter/tools/xml.d.ts +41 -0
  231. package/dist/exporter/tools/xml.d.ts.map +1 -0
  232. package/dist/exporter/tools/xml.js +440 -0
  233. package/dist/exporter/tools/xml.js.map +1 -0
  234. package/dist/exporter/tools/xml_zip.d.ts +20 -0
  235. package/dist/exporter/tools/xml_zip.d.ts.map +1 -0
  236. package/dist/exporter/tools/xml_zip.js +86 -0
  237. package/dist/exporter/tools/xml_zip.js.map +1 -0
  238. package/dist/exporter/tools/zip.d.ts +21 -0
  239. package/dist/exporter/tools/zip.d.ts.map +1 -0
  240. package/dist/exporter/tools/zip.js +71 -0
  241. package/dist/exporter/tools/zip.js.map +1 -0
  242. package/dist/exporter/tools/zotero_csl.d.ts +10 -0
  243. package/dist/exporter/tools/zotero_csl.d.ts.map +1 -0
  244. package/dist/exporter/tools/zotero_csl.js +78 -0
  245. package/dist/exporter/tools/zotero_csl.js.map +1 -0
  246. package/dist/importer/citations.d.ts +25 -0
  247. package/dist/importer/citations.d.ts.map +1 -0
  248. package/dist/importer/citations.js +116 -0
  249. package/dist/importer/citations.js.map +1 -0
  250. package/dist/importer/docx/citations.d.ts +65 -0
  251. package/dist/importer/docx/citations.d.ts.map +1 -0
  252. package/dist/importer/docx/citations.js +106 -0
  253. package/dist/importer/docx/citations.js.map +1 -0
  254. package/dist/importer/docx/convert.d.ts +228 -0
  255. package/dist/importer/docx/convert.d.ts.map +1 -0
  256. package/dist/importer/docx/convert.js +1226 -0
  257. package/dist/importer/docx/convert.js.map +1 -0
  258. package/dist/importer/docx/helpers.d.ts +2 -0
  259. package/dist/importer/docx/helpers.d.ts.map +1 -0
  260. package/dist/importer/docx/helpers.js +10 -0
  261. package/dist/importer/docx/helpers.js.map +1 -0
  262. package/dist/importer/docx/omml2mathml.d.ts +7 -0
  263. package/dist/importer/docx/omml2mathml.d.ts.map +1 -0
  264. package/dist/importer/docx/omml2mathml.js +1239 -0
  265. package/dist/importer/docx/omml2mathml.js.map +1 -0
  266. package/dist/importer/docx/parse.d.ts +205 -0
  267. package/dist/importer/docx/parse.d.ts.map +1 -0
  268. package/dist/importer/docx/parse.js +663 -0
  269. package/dist/importer/docx/parse.js.map +1 -0
  270. package/dist/importer/native/get_images.d.ts +11 -0
  271. package/dist/importer/native/get_images.d.ts.map +1 -0
  272. package/dist/importer/native/get_images.js +64 -0
  273. package/dist/importer/native/get_images.js.map +1 -0
  274. package/dist/importer/native/update.d.ts +6 -0
  275. package/dist/importer/native/update.d.ts.map +1 -0
  276. package/dist/importer/native/update.js +26 -0
  277. package/dist/importer/native/update.js.map +1 -0
  278. package/dist/importer/odt/citations.d.ts +47 -0
  279. package/dist/importer/odt/citations.d.ts.map +1 -0
  280. package/dist/importer/odt/citations.js +79 -0
  281. package/dist/importer/odt/citations.js.map +1 -0
  282. package/dist/importer/odt/convert.d.ts +330 -0
  283. package/dist/importer/odt/convert.d.ts.map +1 -0
  284. package/dist/importer/odt/convert.js +1506 -0
  285. package/dist/importer/odt/convert.js.map +1 -0
  286. package/dist/importer/pandoc/convert.d.ts +84 -0
  287. package/dist/importer/pandoc/convert.d.ts.map +1 -0
  288. package/dist/importer/pandoc/convert.js +777 -0
  289. package/dist/importer/pandoc/convert.js.map +1 -0
  290. package/dist/importer/pandoc/helpers.d.ts +4 -0
  291. package/dist/importer/pandoc/helpers.d.ts.map +1 -0
  292. package/dist/importer/pandoc/helpers.js +75 -0
  293. package/dist/importer/pandoc/helpers.js.map +1 -0
  294. package/dist/importer/zip_analyzer.d.ts +25 -0
  295. package/dist/importer/zip_analyzer.d.ts.map +1 -0
  296. package/dist/importer/zip_analyzer.js +84 -0
  297. package/dist/importer/zip_analyzer.js.map +1 -0
  298. package/dist/index.d.ts +2 -0
  299. package/dist/index.d.ts.map +1 -0
  300. package/dist/index.js +2 -0
  301. package/dist/index.js.map +1 -0
  302. package/dist/mathlive/opf_includes.d.ts +2 -0
  303. package/dist/mathlive/opf_includes.d.ts.map +1 -0
  304. package/dist/mathlive/opf_includes.js +25 -0
  305. package/dist/mathlive/opf_includes.js.map +1 -0
  306. package/dist/schema/common/annotate.d.ts +59 -0
  307. package/dist/schema/common/annotate.d.ts.map +1 -0
  308. package/dist/schema/common/annotate.js +75 -0
  309. package/dist/schema/common/annotate.js.map +1 -0
  310. package/dist/schema/common/base.d.ts +109 -0
  311. package/dist/schema/common/base.d.ts.map +1 -0
  312. package/dist/schema/common/base.js +109 -0
  313. package/dist/schema/common/base.js.map +1 -0
  314. package/dist/schema/common/citation.d.ts +29 -0
  315. package/dist/schema/common/citation.d.ts.map +1 -0
  316. package/dist/schema/common/citation.js +62 -0
  317. package/dist/schema/common/citation.js.map +1 -0
  318. package/dist/schema/common/equation.d.ts +18 -0
  319. package/dist/schema/common/equation.d.ts.map +1 -0
  320. package/dist/schema/common/equation.js +32 -0
  321. package/dist/schema/common/equation.js.map +1 -0
  322. package/dist/schema/common/figure.d.ts +108 -0
  323. package/dist/schema/common/figure.d.ts.map +1 -0
  324. package/dist/schema/common/figure.js +176 -0
  325. package/dist/schema/common/figure.js.map +1 -0
  326. package/dist/schema/common/heading.d.ts +33 -0
  327. package/dist/schema/common/heading.d.ts.map +1 -0
  328. package/dist/schema/common/heading.js +41 -0
  329. package/dist/schema/common/heading.js.map +1 -0
  330. package/dist/schema/common/index.d.ts +11 -0
  331. package/dist/schema/common/index.d.ts.map +1 -0
  332. package/dist/schema/common/index.js +11 -0
  333. package/dist/schema/common/index.js.map +1 -0
  334. package/dist/schema/common/list.d.ts +83 -0
  335. package/dist/schema/common/list.d.ts.map +1 -0
  336. package/dist/schema/common/list.js +92 -0
  337. package/dist/schema/common/list.js.map +1 -0
  338. package/dist/schema/common/reference.d.ts +78 -0
  339. package/dist/schema/common/reference.d.ts.map +1 -0
  340. package/dist/schema/common/reference.js +97 -0
  341. package/dist/schema/common/reference.js.map +1 -0
  342. package/dist/schema/common/table.d.ts +92 -0
  343. package/dist/schema/common/table.d.ts.map +1 -0
  344. package/dist/schema/common/table.js +85 -0
  345. package/dist/schema/common/table.js.map +1 -0
  346. package/dist/schema/common/track.d.ts +131 -0
  347. package/dist/schema/common/track.d.ts.map +1 -0
  348. package/dist/schema/common/track.js +184 -0
  349. package/dist/schema/common/track.js.map +1 -0
  350. package/dist/schema/const.d.ts +3 -0
  351. package/dist/schema/const.d.ts.map +1 -0
  352. package/{src → dist}/schema/const.js +3 -3
  353. package/dist/schema/const.js.map +1 -0
  354. package/dist/schema/convert.d.ts +3 -0
  355. package/dist/schema/convert.d.ts.map +1 -0
  356. package/dist/schema/convert.js +1215 -0
  357. package/dist/schema/convert.js.map +1 -0
  358. package/dist/schema/document/content.d.ts +137 -0
  359. package/dist/schema/document/content.d.ts.map +1 -0
  360. package/dist/schema/document/content.js +177 -0
  361. package/dist/schema/document/content.js.map +1 -0
  362. package/dist/schema/document/index.d.ts +879 -0
  363. package/dist/schema/document/index.d.ts.map +1 -0
  364. package/dist/schema/document/index.js +68 -0
  365. package/dist/schema/document/index.js.map +1 -0
  366. package/dist/schema/document/structure.d.ts +273 -0
  367. package/dist/schema/document/structure.d.ts.map +1 -0
  368. package/dist/schema/document/structure.js +445 -0
  369. package/dist/schema/document/structure.js.map +1 -0
  370. package/dist/schema/export.d.ts +880 -0
  371. package/dist/schema/export.d.ts.map +1 -0
  372. package/dist/schema/export.js +16 -0
  373. package/dist/schema/export.js.map +1 -0
  374. package/dist/schema/footnotes.d.ts +499 -0
  375. package/dist/schema/footnotes.d.ts.map +1 -0
  376. package/dist/schema/footnotes.js +88 -0
  377. package/dist/schema/footnotes.js.map +1 -0
  378. package/dist/schema/footnotes_convert.d.ts +5 -0
  379. package/dist/schema/footnotes_convert.d.ts.map +1 -0
  380. package/dist/schema/footnotes_convert.js +26 -0
  381. package/dist/schema/footnotes_convert.js.map +1 -0
  382. package/dist/schema/i18n.d.ts +586 -0
  383. package/dist/schema/i18n.d.ts.map +1 -0
  384. package/dist/schema/i18n.js +585 -0
  385. package/dist/schema/i18n.js.map +1 -0
  386. package/dist/schema/index.d.ts +6 -0
  387. package/dist/schema/index.d.ts.map +1 -0
  388. package/dist/schema/index.js +6 -0
  389. package/dist/schema/index.js.map +1 -0
  390. package/dist/schema/mini_json.d.ts +5 -0
  391. package/dist/schema/mini_json.d.ts.map +1 -0
  392. package/dist/schema/mini_json.js +50 -0
  393. package/dist/schema/mini_json.js.map +1 -0
  394. package/dist/schema/text.d.ts +2 -0
  395. package/dist/schema/text.d.ts.map +1 -0
  396. package/dist/schema/text.js +23 -0
  397. package/dist/schema/text.js.map +1 -0
  398. package/dist/types.d.ts +122 -0
  399. package/dist/types.d.ts.map +1 -0
  400. package/dist/types.js +9 -0
  401. package/dist/types.js.map +1 -0
  402. package/package.json +48 -16
  403. package/scripts/export-schema.js +1 -1
  404. package/src/exporter/tools/{file.js → file.ts} +1 -1
  405. package/src/exporter/tools/{json.js → json.ts} +30 -19
  406. package/src/global.d.ts +11 -0
  407. package/src/schema/const.ts +58 -0
  408. package/src/types.ts +136 -0
  409. package/jest.config.js +0 -24
  410. /package/src/{index.js → index.ts} +0 -0
@@ -0,0 +1,1506 @@
1
+ import { MathMLToLaTeX } from "mathml-to-latex";
2
+ import { xmlDOM } from "../../exporter/tools/xml.js";
3
+ import { randomCommentId, randomFigureId, randomHeadingId, randomListId, randomTableId } from "../../schema/common/index.js";
4
+ import { parseTracks } from "../../schema/common/track.js";
5
+ import { isOdtBibliographyReferenceMark, isOdtBibliographySection, isOdtCitationMark, parseOdtBibliographyMark, parseOdtReferenceMark } from "./citations.js";
6
+ export class OdtConvert {
7
+ constructor(contentXml, stylesXml, metaXml, manifestXml, importId, template, bibliography, bibDb) {
8
+ this.importId = importId;
9
+ this.template = template;
10
+ this.bibliography = bibliography;
11
+ this.bibDB = bibDb;
12
+ this.images = {};
13
+ this.styles = {};
14
+ this.contentDoc = contentXml ? xmlDOM(contentXml) : null;
15
+ this.stylesDoc = stylesXml ? xmlDOM(stylesXml) : null;
16
+ this.metaDoc = metaXml ? xmlDOM(metaXml) : null;
17
+ this.manifestDoc = manifestXml ? xmlDOM(manifestXml) : null;
18
+ this.tracks = {};
19
+ this.comments = {};
20
+ this.currentCommentIds = [];
21
+ this.currentTracks = [];
22
+ this.referenceableObjects = {}; // All objects that can be referenced
23
+ }
24
+ init() {
25
+ this.parseTrackedChanges();
26
+ this.parseStyles();
27
+ this.parseComments();
28
+ this.collectReferenceableObjects(this.contentDoc);
29
+ const content = this.convert();
30
+ return {
31
+ content,
32
+ settings: {
33
+ import_id: this.importId,
34
+ tracked: Object.keys(this.tracks).length > 0,
35
+ language: this.detectLanguage()
36
+ },
37
+ comments: this.comments
38
+ };
39
+ }
40
+ parseTrackedChanges() {
41
+ const trackedChangesEl = this.contentDoc.query("text:tracked-changes");
42
+ if (!trackedChangesEl) {
43
+ return;
44
+ }
45
+ // Tracked deletions are stored in two different ways in FW and ODT.
46
+ // FW: The deleted content stays in place where it was before the deletion,
47
+ // and is marked with a tracked change mark. Megre only occurs after change
48
+ // has been accepted.
49
+ // ODT: The deleted content is removed from the content flow and is replaced by a marker.
50
+ // The removed content is stored in a special section of the document.
51
+ // This method takes all the deleted content and puts it back into the place where
52
+ // it was previously. That way the structure is more similar to the output FW document
53
+ // and is more easily converted.
54
+ const deletions = {};
55
+ const changedRegions = trackedChangesEl.queryAll("text:changed-region");
56
+ changedRegions.forEach(region => {
57
+ const id = region.getAttribute("text:id");
58
+ const insertion = region.query("text:insertion");
59
+ const deletion = region.query("text:deletion");
60
+ if (!insertion && !deletion) {
61
+ // Neither insertion or deletion. Must be type unknown to us
62
+ return;
63
+ }
64
+ const changeInfo = region.query("office:change-info");
65
+ if (changeInfo) {
66
+ const track = {
67
+ type: insertion ? "insertion" : "deletion",
68
+ user: 1,
69
+ username: changeInfo.query("dc:creator")?.textContent || "",
70
+ date: parseInt(new Date(changeInfo.query("dc:date")?.textContent || "").getTime() / 60000)
71
+ };
72
+ if (insertion) {
73
+ track.approved = false;
74
+ }
75
+ this.tracks[id] = track;
76
+ if (deletion) {
77
+ // Store deletion content for later use
78
+ deletions[id] = deletion.children.filter(child => child.tagName !== "office:change-info");
79
+ }
80
+ }
81
+ });
82
+ // Then find and replace all deletion change markers
83
+ const changeMarkers = this.contentDoc.queryAll("text:change");
84
+ changeMarkers.forEach(marker => {
85
+ const changeId = marker.getAttribute("text:change-id");
86
+ const deletion = deletions[changeId];
87
+ if (deletion) {
88
+ if (deletion.length > 0) {
89
+ // Create change-start and change-end elements
90
+ const markerIndex = marker.parentElement.children.indexOf(marker);
91
+ marker.parentElement.insertXMLAt(`<text:change-start text:change-id="${changeId}"/>`, markerIndex);
92
+ marker.parentElement.insertXMLAt(`<text:change-end text:change-id="${changeId}"/>`, markerIndex + 2);
93
+ if (deletion.length === 1) {
94
+ // Single block - just insert the content
95
+ deletion[0].children.forEach(content => {
96
+ marker.parentElement.insertBefore(content, marker);
97
+ });
98
+ }
99
+ else {
100
+ // Multiple blocks - need to split the paragraph/headline
101
+ const parentElement = marker.parentElement;
102
+ parentElement.splitAtChildElement(marker, deletion[0].children
103
+ ?.map(node => node.toString())
104
+ .join("") || "", // First block content to be added to current node
105
+ deletion
106
+ .slice(1, -1)
107
+ .map(node => node.toString())
108
+ .join(""), // Middle blocks
109
+ deletion[deletion.length - 1].toString() // Last block
110
+ );
111
+ }
112
+ }
113
+ // Remove the original change marker
114
+ marker.parentElement.removeChild(marker);
115
+ }
116
+ });
117
+ }
118
+ parseStyles() {
119
+ if (!this.stylesDoc) {
120
+ return;
121
+ }
122
+ const styleNodes = this.stylesDoc.queryAll("style:style");
123
+ styleNodes.forEach(node => {
124
+ const styleName = node.getAttribute("style:name");
125
+ this.styles[styleName] = this.parseStyle(node);
126
+ });
127
+ const contentStyleNodes = this.contentDoc.queryAll("style:style");
128
+ contentStyleNodes.forEach(node => {
129
+ const styleName = node.getAttribute("style:name");
130
+ this.styles[styleName] = this.parseStyle(node);
131
+ });
132
+ }
133
+ parseStyle(styleNode) {
134
+ const properties = {
135
+ // Basic style information
136
+ parentStyleName: styleNode.getAttribute("style:parent-style-name"),
137
+ isSection: styleNode.getAttribute("style:family") === "section" ||
138
+ Boolean(styleNode.query("style:section-properties")),
139
+ title: styleNode.getAttribute("style:display-name"),
140
+ // Family and name info
141
+ family: styleNode.getAttribute("style:family"),
142
+ name: styleNode.getAttribute("style:name"),
143
+ // Heading related
144
+ isHeading: styleNode.getAttribute("style:family") === "paragraph" &&
145
+ (styleNode
146
+ .getAttribute("style:name")
147
+ .toLowerCase()
148
+ .includes("heading") ||
149
+ styleNode
150
+ .getAttribute("style:parent-style-name")
151
+ ?.toLowerCase()
152
+ .includes("heading")),
153
+ outlineLevel: styleNode.getAttribute("text:outline-level"),
154
+ // Text properties
155
+ textProperties: {},
156
+ // Paragraph properties
157
+ paragraphProperties: {},
158
+ // Section properties
159
+ sectionProperties: {}
160
+ };
161
+ // Parse text properties
162
+ const textProperties = styleNode.query("style:text-properties");
163
+ if (textProperties) {
164
+ properties.textProperties = {
165
+ bold: textProperties.getAttribute("fo:font-weight") === "bold",
166
+ italic: textProperties.getAttribute("fo:font-style") === "italic",
167
+ fontSize: this.convertLength(textProperties.getAttribute("fo:font-size")),
168
+ fontFamily: textProperties.getAttribute("fo:font-family"),
169
+ color: textProperties.getAttribute("fo:color"),
170
+ backgroundColor: textProperties.getAttribute("fo:background-color"),
171
+ textDecoration: textProperties.getAttribute("style:text-underline-style") ||
172
+ textProperties.getAttribute("style:text-line-through-style"),
173
+ textPosition: textProperties.getAttribute("style:text-position")
174
+ };
175
+ }
176
+ // Parse paragraph properties
177
+ const paragraphProperties = styleNode.query("style:paragraph-properties");
178
+ if (paragraphProperties) {
179
+ properties.paragraphProperties = {
180
+ marginTop: this.convertLength(paragraphProperties.getAttribute("fo:margin-top")),
181
+ marginBottom: this.convertLength(paragraphProperties.getAttribute("fo:margin-bottom")),
182
+ marginLeft: this.convertLength(paragraphProperties.getAttribute("fo:margin-left")),
183
+ marginRight: this.convertLength(paragraphProperties.getAttribute("fo:margin-right")),
184
+ textAlign: paragraphProperties.getAttribute("fo:text-align"),
185
+ lineHeight: paragraphProperties.getAttribute("fo:line-height"),
186
+ backgroundColor: paragraphProperties.getAttribute("fo:background-color"),
187
+ padding: this.convertLength(paragraphProperties.getAttribute("fo:padding")),
188
+ borderStyle: paragraphProperties.getAttribute("fo:border-style")
189
+ };
190
+ }
191
+ // Parse section properties
192
+ const sectionProperties = styleNode.query("style:section-properties");
193
+ if (sectionProperties) {
194
+ properties.sectionProperties = {
195
+ columnCount: sectionProperties.getAttribute("fo:column-count"),
196
+ columnGap: this.convertLength(sectionProperties.getAttribute("fo:column-gap")),
197
+ backgroundColor: sectionProperties.getAttribute("fo:background-color"),
198
+ margins: {
199
+ top: this.convertLength(sectionProperties.getAttribute("fo:margin-top")),
200
+ bottom: this.convertLength(sectionProperties.getAttribute("fo:margin-bottom")),
201
+ left: this.convertLength(sectionProperties.getAttribute("fo:margin-left")),
202
+ right: this.convertLength(sectionProperties.getAttribute("fo:margin-right"))
203
+ }
204
+ };
205
+ }
206
+ // Additional table-specific properties
207
+ if (styleNode.getAttribute("style:family") === "table") {
208
+ properties.tableProperties = {
209
+ align: styleNode.getAttribute("table:align"),
210
+ width: this.convertLength(styleNode.getAttribute("style:width")),
211
+ relWidth: styleNode.getAttribute("style:rel-width")
212
+ };
213
+ }
214
+ return properties;
215
+ }
216
+ convertObject(node, attrs) {
217
+ const mathEl = node.query("math");
218
+ if (mathEl) {
219
+ attrs = Object.assign({
220
+ equation: MathMLToLaTeX.convert(mathEl.innerXML)
221
+ }, attrs);
222
+ return {
223
+ type: "equation",
224
+ attrs
225
+ };
226
+ }
227
+ return null;
228
+ }
229
+ parseComments() {
230
+ const annotations = this.contentDoc.queryAll("office:annotation");
231
+ annotations.forEach(annotation => {
232
+ const username = annotation.query("dc:creator")?.textContent || "";
233
+ const date = new Date(annotation.query("dc:date")?.textContent || "").getTime();
234
+ const id = (annotation.getAttribute("office:name") || "")
235
+ .replace(/\D/g, "")
236
+ .slice(0, 9);
237
+ if (id) {
238
+ // main comment
239
+ this.comments[id] = {
240
+ user: 0,
241
+ username,
242
+ date,
243
+ comment: annotation
244
+ .queryAll("text:p")
245
+ .map(par => this.convertBlockNode(par))
246
+ .filter(par => par)
247
+ .flat(),
248
+ answers: [],
249
+ resolved: annotation.getAttribute("loext:resolved") === "true"
250
+ };
251
+ }
252
+ else {
253
+ const parentId = (annotation.getAttribute("loext:parent-name") || "")
254
+ .replace(/\D/g, "")
255
+ .slice(0, 9);
256
+ if (parentId && this.comments[parentId]) {
257
+ this.comments[parentId].answers.push({
258
+ id: randomCommentId(),
259
+ user: 0,
260
+ username,
261
+ date,
262
+ // drop the frist paragraph. It only contains "Reply to...."
263
+ answer: annotation
264
+ .queryAll("text:p")
265
+ .slice(1)
266
+ .map(par => this.convertBlockNode(par))
267
+ .filter(par => par)
268
+ .flat()
269
+ });
270
+ }
271
+ }
272
+ });
273
+ }
274
+ collectReferenceableObjects(node) {
275
+ // Handle heading bookmarks
276
+ const bookmarkStarts = node.queryAll("text:bookmark-start");
277
+ bookmarkStarts.forEach(mark => {
278
+ const refName = mark.getAttribute("text:name");
279
+ if (!refName) {
280
+ return;
281
+ }
282
+ // Find the closest heading
283
+ let targetParent = mark.parentElement;
284
+ while (targetParent) {
285
+ if (targetParent.tagName === "text:h") {
286
+ const id = randomHeadingId();
287
+ this.referenceableObjects[refName] = {
288
+ type: "heading",
289
+ id,
290
+ node: targetParent
291
+ };
292
+ break;
293
+ }
294
+ targetParent = targetParent.parentElement;
295
+ }
296
+ });
297
+ // Handle figure sequences
298
+ const sequences = node.queryAll("text:sequence");
299
+ sequences.forEach(sequence => {
300
+ const refName = sequence.getAttribute("text:ref-name");
301
+ if (!refName) {
302
+ return;
303
+ }
304
+ // Find the figure container
305
+ let targetParent = sequence.parentElement;
306
+ while (targetParent) {
307
+ if (targetParent.tagName === "draw:frame") {
308
+ const id = randomFigureId();
309
+ this.referenceableObjects[refName] = {
310
+ type: "figure",
311
+ id,
312
+ node: targetParent
313
+ };
314
+ break;
315
+ }
316
+ targetParent = targetParent.parentElement;
317
+ }
318
+ });
319
+ }
320
+ convert() {
321
+ const templateParts = this.template.content.content.slice();
322
+ templateParts.shift();
323
+ const document = {
324
+ type: "doc",
325
+ attrs: {
326
+ import_id: this.importId
327
+ },
328
+ content: []
329
+ };
330
+ // Add title (required first element)
331
+ const title = this.extractTitle();
332
+ if (title.content.length) {
333
+ document.content.push({
334
+ type: "title",
335
+ content: title.content
336
+ });
337
+ }
338
+ else {
339
+ // If no title found, use default title
340
+ document.content.push({
341
+ type: "title",
342
+ content: [
343
+ {
344
+ type: "text",
345
+ text: gettext("Untitled")
346
+ }
347
+ ]
348
+ });
349
+ }
350
+ title.containerNodes.forEach(node => {
351
+ node.parentElement.removeChild(node);
352
+ });
353
+ document.attrs.title =
354
+ title.content.map(node => node.textContent).join("") ||
355
+ gettext("Untitled");
356
+ // Get all content sections from the ODT
357
+ const body = this.contentDoc.query("office:text");
358
+ if (!body) {
359
+ return document;
360
+ }
361
+ // Look for metadata sections first (author, abstract, etc.)
362
+ const metadataContent = this.extractMetadata();
363
+ metadataContent.forEach(({ type, attrs, content }) => {
364
+ const templatePart = templateParts.find(part => part.attrs.metadata === type);
365
+ if (templatePart) {
366
+ document.content.push({
367
+ type: templatePart.type,
368
+ attrs: {
369
+ ...templatePart.attrs,
370
+ ...attrs
371
+ },
372
+ content: content.content
373
+ });
374
+ // Remove paragraphs from content so they are not added to body
375
+ content.containerNodes.forEach(node => {
376
+ node.parentElement.removeChild(node);
377
+ });
378
+ }
379
+ });
380
+ // Group remaining content by sections based on style names/titles
381
+ const sections = this.groupContentIntoSections(body);
382
+ // Map ODT sections to template parts
383
+ sections.forEach(section => {
384
+ // Find matching template part
385
+ const templatePart = this.findMatchingTemplatePart(section.title, templateParts);
386
+ if (templatePart) {
387
+ // If template part found, use its configuration
388
+ document.content.push({
389
+ type: "richtext_part",
390
+ attrs: {
391
+ title: templatePart.attrs.title,
392
+ id: templatePart.attrs.id,
393
+ metadata: templatePart.attrs.metadata || undefined,
394
+ marks: templatePart.attrs.marks || [
395
+ "strong",
396
+ "em",
397
+ "link"
398
+ ]
399
+ },
400
+ content: section.content
401
+ });
402
+ }
403
+ });
404
+ // Add remaining content to body section
405
+ const unassignedContent = sections
406
+ .filter(section => !this.findMatchingTemplatePart(section.title, templateParts))
407
+ .flatMap(section => section.content);
408
+ if (unassignedContent.length) {
409
+ // Find default body template part
410
+ const bodyTemplatePart = templateParts.find(part => !part.attrs.metadata && part.type === "richtext_part");
411
+ document.content.push({
412
+ type: "richtext_part",
413
+ attrs: {
414
+ title: bodyTemplatePart
415
+ ? bodyTemplatePart.attrs.title
416
+ : "Body",
417
+ id: bodyTemplatePart ? bodyTemplatePart.attrs.id : "body",
418
+ marks: ["strong", "em", "link"]
419
+ },
420
+ content: unassignedContent
421
+ });
422
+ }
423
+ return document;
424
+ }
425
+ extractMetadata() {
426
+ const metadata = [];
427
+ // Try structured contributor data from meta.xml first
428
+ const contributorsByRole = this.extractContributorsFromMeta();
429
+ if (Object.keys(contributorsByRole).length) {
430
+ Object.entries(contributorsByRole).forEach(([role, contributors]) => {
431
+ metadata.push({
432
+ type: role,
433
+ content: { content: contributors, containerNodes: [] }
434
+ });
435
+ });
436
+ }
437
+ else {
438
+ // Fall back to legacy author extraction
439
+ const authors = this.extractAuthors();
440
+ if (authors.content.length) {
441
+ metadata.push({
442
+ type: "authors",
443
+ content: authors
444
+ });
445
+ }
446
+ }
447
+ // Extract abstract if present
448
+ const abstract = this.extractAbstract();
449
+ if (abstract.content.length) {
450
+ metadata.push({
451
+ type: "abstract",
452
+ content: abstract
453
+ });
454
+ }
455
+ // Extract keywords if present
456
+ const keywords = this.extractKeywords();
457
+ if (keywords.content.length) {
458
+ metadata.push({
459
+ type: "keywords",
460
+ content: keywords
461
+ });
462
+ }
463
+ return metadata;
464
+ }
465
+ extractContributorsFromMeta() {
466
+ if (!this.metaDoc) {
467
+ return {};
468
+ }
469
+ const userDefined = this.metaDoc.queryAll("meta:user-defined");
470
+ const contributors = [];
471
+ userDefined.forEach(prop => {
472
+ const name = prop.getAttribute("meta:name");
473
+ if (!name || !name.startsWith("fidus_contributor_")) {
474
+ return;
475
+ }
476
+ const match = name.match(/^fidus_contributor_(\d+)_(\w+)$/);
477
+ if (!match) {
478
+ return;
479
+ }
480
+ const num = parseInt(match[1]);
481
+ const field = match[2];
482
+ const value = prop.textContent || "";
483
+ if (!contributors[num - 1]) {
484
+ contributors[num - 1] = {
485
+ type: "contributor",
486
+ attrs: {
487
+ firstname: "",
488
+ lastname: "",
489
+ email: "",
490
+ institution: "",
491
+ id_type: "",
492
+ id_value: "",
493
+ role: ""
494
+ }
495
+ };
496
+ }
497
+ if (field === "role") {
498
+ contributors[num - 1].attrs.role = value;
499
+ }
500
+ else if ([
501
+ "firstname",
502
+ "lastname",
503
+ "email",
504
+ "institution",
505
+ "id_type",
506
+ "id_value"
507
+ ].includes(field)) {
508
+ contributors[num - 1].attrs[field] = value;
509
+ }
510
+ });
511
+ const byRole = {};
512
+ contributors.forEach(contributor => {
513
+ if (!contributor) {
514
+ return;
515
+ }
516
+ const role = contributor.attrs.role || "authors";
517
+ if (!byRole[role]) {
518
+ byRole[role] = [];
519
+ }
520
+ byRole[role].push(contributor);
521
+ });
522
+ return byRole;
523
+ }
524
+ extractAuthors() {
525
+ const authors = [];
526
+ // Try to find author information in metadata
527
+ const metaAuthors = this.contentDoc.queryAll("meta:user-defined", {
528
+ "meta:name": "author"
529
+ });
530
+ metaAuthors.forEach(authorMeta => {
531
+ const authorText = authorMeta.textContent;
532
+ const [firstname = "", lastname = ""] = authorText.split(" ", 2);
533
+ authors.push({
534
+ type: "contributor",
535
+ attrs: {
536
+ firstname,
537
+ lastname,
538
+ email: "",
539
+ institution: ""
540
+ }
541
+ });
542
+ });
543
+ if (authors.length) {
544
+ return {
545
+ content: authors,
546
+ containerNodes: metaAuthors
547
+ };
548
+ }
549
+ // Also check for creator in document metadata
550
+ const creator = this.contentDoc.query("meta:creator");
551
+ if (creator) {
552
+ const [firstname = "", lastname = ""] = creator.textContent.split(" ", 2);
553
+ return {
554
+ content: [
555
+ {
556
+ type: "contributor",
557
+ attrs: {
558
+ firstname,
559
+ lastname,
560
+ email: "",
561
+ institution: ""
562
+ }
563
+ }
564
+ ],
565
+ containerNodes: []
566
+ };
567
+ }
568
+ return { content: [], containerNodes: [] };
569
+ }
570
+ extractAbstract() {
571
+ // Look for section titled "Abstract" or with abstract style
572
+ const abstractSection = this.contentDoc.query("text:section", {
573
+ "text:style-name": "Abstract"
574
+ }) ||
575
+ this.contentDoc.query("text:h", {
576
+ "text:outline-level": "1"
577
+ }); // Then check content for "Abstract"
578
+ if (abstractSection &&
579
+ (abstractSection.getAttribute("text:style-name") === "Abstract" ||
580
+ abstractSection.textContent.includes("Abstract"))) {
581
+ return {
582
+ content: this.convertContainer(abstractSection),
583
+ containerNodes: [abstractSection]
584
+ };
585
+ }
586
+ return {
587
+ content: [],
588
+ containerNodes: []
589
+ };
590
+ }
591
+ extractKeywords() {
592
+ // Look for keywords section or metadata
593
+ const keywordsSection = this.contentDoc.query("text:p", { "text:style-name": "Keywords" }) ||
594
+ this.contentDoc.query("meta:user-defined", {
595
+ "meta:name": "keywords"
596
+ });
597
+ if (keywordsSection) {
598
+ return {
599
+ content: this.convertContainer(keywordsSection),
600
+ containerNodes: [keywordsSection]
601
+ };
602
+ }
603
+ return { content: [], containerNodes: [] };
604
+ }
605
+ findMatchingTemplatePart(sectionTitle, templateParts) {
606
+ if (!sectionTitle) {
607
+ return null;
608
+ }
609
+ // Try exact match first
610
+ let matchingPart = templateParts.find(part => part.type === "richtext_part" &&
611
+ !part.attrs.metadata &&
612
+ part.attrs.title.toLowerCase() === sectionTitle.toLowerCase());
613
+ if (!matchingPart) {
614
+ // Try fuzzy matching if exact match fails
615
+ matchingPart = templateParts.find(part => part.type === "richtext_part" &&
616
+ !part.attrs.metadata &&
617
+ this.isSimilarTitle(part.attrs.title, sectionTitle));
618
+ }
619
+ return matchingPart;
620
+ }
621
+ isSimilarTitle(title1, title2) {
622
+ // Remove special characters and extra spaces
623
+ const normalize = str => str
624
+ .toLowerCase()
625
+ .replace(/[^a-z0-9]/g, "")
626
+ .trim();
627
+ const normalized1 = normalize(title1);
628
+ const normalized2 = normalize(title2);
629
+ // Check if one string contains the other
630
+ return (normalized1.includes(normalized2) ||
631
+ normalized2.includes(normalized1));
632
+ }
633
+ extractTitle() {
634
+ // First try to find paragraph with Title style
635
+ const titleParagraph = this.contentDoc.query("text:p", {
636
+ "text:style-name": "Title"
637
+ });
638
+ if (titleParagraph) {
639
+ return {
640
+ content: this.convertBlockNode(titleParagraph)?.content || [],
641
+ containerNodes: [titleParagraph]
642
+ };
643
+ }
644
+ // Fall back to first heading
645
+ const titleHeading = this.contentDoc.query("text:h", {
646
+ "text:outline-level": "1"
647
+ });
648
+ if (titleHeading) {
649
+ return {
650
+ content: this.convertBlockNode(titleHeading)?.content || [],
651
+ containerNodes: [titleHeading]
652
+ };
653
+ }
654
+ // Check for other common title style names
655
+ const commonTitleStyles = [
656
+ "title",
657
+ "doctitle",
658
+ "document-title",
659
+ "heading-title"
660
+ ];
661
+ for (const styleName of commonTitleStyles) {
662
+ const titleElement = this.contentDoc.query("text:p", {
663
+ "text:style-name": styleName
664
+ });
665
+ if (titleElement) {
666
+ return {
667
+ content: this.convertBlockNode(titleElement)?.content || [],
668
+ containerNodes: [titleElement]
669
+ };
670
+ }
671
+ }
672
+ // Check style properties for title-like formatting
673
+ const firstParagraph = this.contentDoc.query("text:p");
674
+ if (firstParagraph) {
675
+ const styleName = firstParagraph.getAttribute("text:style-name");
676
+ const style = this.styles[styleName];
677
+ if (style && this.isTitleStyle(style)) {
678
+ // Remove this node from the document so it's not processed again
679
+ return {
680
+ content: this.convertBlockNode(firstParagraph)?.content || [],
681
+ containerNodes: [firstParagraph]
682
+ };
683
+ }
684
+ }
685
+ return {
686
+ content: [],
687
+ containerNodes: []
688
+ };
689
+ }
690
+ isTitleStyle(style) {
691
+ // Check if style or its parent has characteristics of a title style
692
+ if (!style) {
693
+ return false;
694
+ }
695
+ // Check style name
696
+ if (style.title?.toLowerCase().includes("title")) {
697
+ return true;
698
+ }
699
+ // Check text properties for title-like formatting
700
+ const textProps = style.textProperties;
701
+ if (textProps) {
702
+ // Title usually has larger font size and/or bold weight
703
+ if (textProps.fontSize > 14 || textProps.bold) {
704
+ return true;
705
+ }
706
+ }
707
+ // Check paragraph properties
708
+ const paraProps = style.paragraphProperties;
709
+ if (paraProps) {
710
+ // Titles are often centered and have larger margins
711
+ if (paraProps.textAlign === "center" ||
712
+ (paraProps.marginTop > 0.5 && paraProps.marginBottom > 0.5)) {
713
+ return true;
714
+ }
715
+ }
716
+ // Check parent style if exists
717
+ if (style.parentStyleName) {
718
+ const parentStyle = this.styles[style.parentStyleName];
719
+ return this.isTitleStyle(parentStyle);
720
+ }
721
+ return false;
722
+ }
723
+ getSectionTitle(node, styleName) {
724
+ if (!node || !styleName) {
725
+ return null;
726
+ }
727
+ // For headings, use the text content as section title
728
+ if (node.tagName === "text:h") {
729
+ // Get the heading level
730
+ const level = parseInt(node.getAttribute("text:outline-level")) || 1;
731
+ // Only use level 1 and 2 headings as section titles
732
+ if (level <= 2) {
733
+ return node.textContent.trim();
734
+ }
735
+ }
736
+ // Check if the style indicates a section title
737
+ const style = this.styles[styleName];
738
+ if (style) {
739
+ // Check for explicit section title style
740
+ if (style.title ||
741
+ styleName.toLowerCase().includes("section") ||
742
+ styleName.toLowerCase().includes("title")) {
743
+ // If it's a styled paragraph, use its content as title
744
+ if (node.tagName === "text:p") {
745
+ return node.textContent.trim();
746
+ }
747
+ }
748
+ // Check if it's a custom section style
749
+ const parentStyle = style.parentStyleName
750
+ ? this.styles[style.parentStyleName]
751
+ : null;
752
+ if (parentStyle?.isSection) {
753
+ return node.textContent.trim();
754
+ }
755
+ }
756
+ // For text:section elements, check for section-name attribute
757
+ if (node.tagName === "text:section") {
758
+ const sectionName = node.getAttribute("text:name");
759
+ if (sectionName) {
760
+ return this.formatSectionName(sectionName);
761
+ }
762
+ }
763
+ return null;
764
+ }
765
+ formatSectionName(name) {
766
+ // Remove common suffixes
767
+ name = name.replace(/_?(section|part|chapter)$/i, "");
768
+ // Split by underscores or hyphens
769
+ const words = name.split(/[_-]/);
770
+ // Capitalize first letter of each word and join
771
+ return words
772
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
773
+ .join(" ")
774
+ .trim();
775
+ }
776
+ groupContentIntoSections(body) {
777
+ const sections = [];
778
+ let currentSection = {
779
+ title: null,
780
+ content: []
781
+ };
782
+ body.children.forEach(node => {
783
+ const styleName = node.getAttribute("text:style-name");
784
+ const title = this.getSectionTitle(node, styleName);
785
+ if (title && this.isHeadingStyle(styleName)) {
786
+ // Start new section
787
+ if (currentSection.content.length) {
788
+ sections.push(currentSection);
789
+ }
790
+ currentSection = {
791
+ title: title,
792
+ content: []
793
+ };
794
+ }
795
+ const converted = [this.convertBlockNode(node)]
796
+ .filter(node => node)
797
+ .flat();
798
+ converted.forEach(node => currentSection.content.push(node));
799
+ });
800
+ // Add final section
801
+ if (currentSection.content.length) {
802
+ sections.push(currentSection);
803
+ }
804
+ return sections;
805
+ }
806
+ isCodeBlockStyle(styleName, style) {
807
+ if (!styleName) {
808
+ return false;
809
+ }
810
+ // Check if style name contains preformatted or code indicators
811
+ const lowerStyleName = styleName.toLowerCase();
812
+ if (lowerStyleName.includes("preformatted") ||
813
+ lowerStyleName.includes("code") ||
814
+ styleName === "Preformatted_20_Text") {
815
+ return true;
816
+ }
817
+ // Check if parent style is a code block style
818
+ if (style?.parentStyleName) {
819
+ const parentStyle = this.styles[style.parentStyleName];
820
+ return this.isCodeBlockStyle(style.parentStyleName, parentStyle);
821
+ }
822
+ // Check text properties for monospace fonts
823
+ if (style?.textProperties?.fontFamily) {
824
+ const fontFamily = style.textProperties.fontFamily.toLowerCase();
825
+ const monospacePatterns = [
826
+ "courier",
827
+ "consolas",
828
+ "monaco",
829
+ "menlo",
830
+ "lucida console",
831
+ "liberation mono",
832
+ "dejavu sans mono",
833
+ "bitstream vera sans mono",
834
+ "source code pro",
835
+ "fira code"
836
+ ];
837
+ return monospacePatterns.some(pattern => fontFamily.includes(pattern));
838
+ }
839
+ return false;
840
+ }
841
+ isHeadingStyle(styleName) {
842
+ if (!styleName) {
843
+ return false;
844
+ }
845
+ const style = this.styles[styleName];
846
+ if (!style) {
847
+ return false;
848
+ }
849
+ // Check multiple indicators that this might be a heading style
850
+ return (
851
+ // Direct heading indicators
852
+ style.isHeading ||
853
+ styleName.toLowerCase().includes("heading") ||
854
+ styleName.toLowerCase().includes("title") ||
855
+ // Check outline level property
856
+ Boolean(style.outlineLevel) ||
857
+ // Check if it's derived from a heading style
858
+ (style.parentStyleName &&
859
+ this.isHeadingStyle(style.parentStyleName)) ||
860
+ // Check specific formatting that's typical for headings
861
+ (style.paragraphProperties &&
862
+ // Larger margins than normal paragraphs
863
+ (style.paragraphProperties.marginTop > 0.3 ||
864
+ style.paragraphProperties.marginBottom > 0.3 ||
865
+ // Different alignment
866
+ style.paragraphProperties.textAlign === "center")) ||
867
+ // Check text properties typical for headings
868
+ (style.textProperties &&
869
+ // Larger font size
870
+ (style.textProperties.fontSize > 12 ||
871
+ // Bold text
872
+ style.textProperties.bold ||
873
+ // Different font family
874
+ style.textProperties.fontFamily)));
875
+ }
876
+ convertContainer(container) {
877
+ return container.children
878
+ .map(node => this.convertBlockNode(node))
879
+ .filter(node => node)
880
+ .flat();
881
+ }
882
+ convertBlockNode(node) {
883
+ const track = this.currentTracks.map(track => ({
884
+ type: track.type,
885
+ user: track.attrs.user,
886
+ username: track.attrs.username,
887
+ date: track.attrs.date
888
+ }));
889
+ const attrs = track.length ? { track } : {};
890
+ switch (node.tagName) {
891
+ case "text:p":
892
+ if (node.children.length === 1 &&
893
+ node.children[0].tagName === "draw:frame") {
894
+ // Paragraph consists of only one figure/image.
895
+ return this.convertImage(node.children[0], attrs);
896
+ }
897
+ return this.convertParagraph(node, attrs);
898
+ case "text:h":
899
+ return this.convertHeading(node, attrs);
900
+ case "text:list":
901
+ return this.convertList(node, attrs);
902
+ case "draw:frame":
903
+ return this.convertImage(node, attrs);
904
+ case "draw:object":
905
+ return this.convertObject(node, attrs);
906
+ case "table:table":
907
+ return this.convertTable(node, attrs);
908
+ case "text:sequence-decls":
909
+ case "office:forms":
910
+ case "text:tracked-changes":
911
+ return null;
912
+ case "text:bibliography":
913
+ // LibreOffice native bibliography — rendered output only,
914
+ // skip entirely in favour of Fidus Writer's own system.
915
+ return null;
916
+ case "text:section": {
917
+ // Skip bibliography sections inserted by citation managers
918
+ // (Zotero: name contains "ZOTERO_BIBL"/"CSL_BIBLIOGRAPHY",
919
+ // JabRef: name is "JR_bib" / "JR_BIB").
920
+ const sectionName = node.getAttribute("text:name") || "";
921
+ if (isOdtBibliographySection(sectionName)) {
922
+ return null;
923
+ }
924
+ // Other named sections are not bibliographies — fall through
925
+ // to default handling (treat children as block content).
926
+ return this.convertContainer(node);
927
+ }
928
+ default:
929
+ console.warn(`Unsupported block node: ${node.tagName}`);
930
+ return null;
931
+ }
932
+ }
933
+ convertParagraph(node, attrs = {}) {
934
+ const styleName = node.getAttribute("text:style-name");
935
+ const style = this.styles[styleName];
936
+ // Check if this is a code block (preformatted text)
937
+ if (this.isCodeBlockStyle(styleName, style)) {
938
+ attrs = Object.assign({
939
+ track: [],
940
+ language: "",
941
+ category: "",
942
+ title: "",
943
+ id: ""
944
+ }, attrs);
945
+ return {
946
+ type: "code_block",
947
+ attrs,
948
+ content: this.convertNodeChildren(node)
949
+ };
950
+ }
951
+ // Check if this paragraph is title-like
952
+ if (this.isTitleStyle(style)) {
953
+ attrs = Object.assign({
954
+ id: randomHeadingId()
955
+ }, attrs);
956
+ return {
957
+ type: "heading1",
958
+ attrs,
959
+ content: this.convertNodeChildren(node)
960
+ };
961
+ }
962
+ if (this.isHeadingStyle(styleName)) {
963
+ return this.convertHeading(node, attrs);
964
+ }
965
+ return {
966
+ type: "paragraph",
967
+ attrs,
968
+ content: this.convertNodeChildren(node)
969
+ };
970
+ }
971
+ convertHeading(node, attrs = {}) {
972
+ const level = parseInt(node.getAttribute("text:outline-level") || 1) || 1;
973
+ // Check for bookmark
974
+ let id = null;
975
+ const bookmarkStart = node.query("text:bookmark-start");
976
+ if (bookmarkStart) {
977
+ const refName = bookmarkStart.getAttribute("text:name");
978
+ if (refName && this.referenceableObjects[refName]) {
979
+ id = this.referenceableObjects[refName].id;
980
+ }
981
+ }
982
+ attrs = Object.assign({
983
+ id: id || randomHeadingId()
984
+ }, attrs);
985
+ return {
986
+ type: `heading${level}`,
987
+ attrs,
988
+ content: this.convertNodeChildren(node)
989
+ };
990
+ }
991
+ convertNodeChildren(node, currentStyleMarks = []) {
992
+ let insideCitationReferenceMark = false;
993
+ let insideBibliographyReferenceMark = false;
994
+ return node.children
995
+ .map(child => {
996
+ if (insideBibliographyReferenceMark) {
997
+ // Swallow all rendered bibliography content until the
998
+ // closing mark — we have our own bibliography system.
999
+ if (child.tagName === "text:reference-mark-end") {
1000
+ const name = child.getAttribute("text:name");
1001
+ if (name && isOdtBibliographyReferenceMark(name)) {
1002
+ insideBibliographyReferenceMark = false;
1003
+ }
1004
+ }
1005
+ return null;
1006
+ }
1007
+ if (insideCitationReferenceMark) {
1008
+ if (child.tagName === "text:reference-mark-end") {
1009
+ // Process citation when we hit the end mark
1010
+ const name = child.getAttribute("text:name");
1011
+ if (name && isOdtCitationMark(name)) {
1012
+ insideCitationReferenceMark = false;
1013
+ return this.convertCitation(name, currentStyleMarks);
1014
+ }
1015
+ }
1016
+ return null;
1017
+ }
1018
+ switch (child.tagName) {
1019
+ case "text:change-start": {
1020
+ const changeId = child.getAttribute("text:change-id");
1021
+ const track = this.tracks[changeId];
1022
+ if (track) {
1023
+ const trackMark = {
1024
+ type: track.type,
1025
+ attrs: {
1026
+ user: track.user,
1027
+ username: track.username,
1028
+ date: track.date
1029
+ }
1030
+ };
1031
+ if (track.type === "insertion") {
1032
+ trackMark.attrs.approved = track.approved;
1033
+ }
1034
+ this.currentTracks.push(trackMark);
1035
+ }
1036
+ return null;
1037
+ }
1038
+ case "text:change-end": {
1039
+ const changeId = child.getAttribute("text:change-id");
1040
+ const track = this.tracks[changeId];
1041
+ if (track) {
1042
+ this.currentTracks = this.currentTracks.filter(mark => mark.type !== track.type);
1043
+ }
1044
+ return null;
1045
+ }
1046
+ case "#text":
1047
+ return this.convertText(String(child.textContent), currentStyleMarks);
1048
+ case "text:s": // space
1049
+ return this.convertText(" ", currentStyleMarks);
1050
+ case "text:span": {
1051
+ return this.convertSpan(child, currentStyleMarks);
1052
+ }
1053
+ case "text:a":
1054
+ return this.convertLink(child, currentStyleMarks);
1055
+ case "text:note":
1056
+ return this.convertFootnote(child, currentStyleMarks);
1057
+ case "office:annotation":
1058
+ return this.convertAnnotationStart(child);
1059
+ case "office:annotation-end":
1060
+ return this.convertAnnotationEnd(child);
1061
+ case "text:reference-mark-start": {
1062
+ const name = child.getAttribute("text:name");
1063
+ if (name && isOdtCitationMark(name)) {
1064
+ insideCitationReferenceMark = true;
1065
+ }
1066
+ else if (name &&
1067
+ isOdtBibliographyReferenceMark(name)) {
1068
+ insideBibliographyReferenceMark = true;
1069
+ }
1070
+ return null;
1071
+ }
1072
+ case "text:bibliography-mark":
1073
+ return this.convertBibliographyMark(child, currentStyleMarks);
1074
+ case "text:bookmark-ref":
1075
+ return this.convertHeadingReference(child);
1076
+ case "text:sequence-ref":
1077
+ return this.convertFigureReference(child);
1078
+ case "text:soft-page-break":
1079
+ return null;
1080
+ default:
1081
+ console.warn(`Unsupported inline node: ${child.tagName}`);
1082
+ }
1083
+ })
1084
+ .filter(node => node)
1085
+ .flat();
1086
+ }
1087
+ getCurrentMarks(currentStyleMarks = []) {
1088
+ const commentMarks = [];
1089
+ // Add comment marks for any active comment IDs
1090
+ this.currentCommentIds.forEach(commentId => {
1091
+ commentMarks.push({
1092
+ type: "comment",
1093
+ attrs: {
1094
+ id: commentId
1095
+ }
1096
+ });
1097
+ });
1098
+ return [...currentStyleMarks, ...this.currentTracks, ...commentMarks];
1099
+ }
1100
+ convertText(text, currentStyleMarks) {
1101
+ const textNode = {
1102
+ type: "text",
1103
+ text
1104
+ };
1105
+ const marks = this.getCurrentMarks(currentStyleMarks);
1106
+ if (marks.length) {
1107
+ textNode.marks = marks;
1108
+ }
1109
+ return textNode;
1110
+ }
1111
+ convertSpan(node, currentStyleMarks) {
1112
+ const styleName = node.getAttribute("text:style-name");
1113
+ const style = this.styles[styleName];
1114
+ if (style?.textProperties?.bold) {
1115
+ currentStyleMarks = [...currentStyleMarks, { type: "strong" }];
1116
+ }
1117
+ if (style?.textProperties?.italic) {
1118
+ currentStyleMarks = [...currentStyleMarks, { type: "em" }];
1119
+ }
1120
+ // Handle superscript and subscript
1121
+ if (style?.textProperties?.textPosition) {
1122
+ const position = style.textProperties.textPosition;
1123
+ if (position.includes("super")) {
1124
+ currentStyleMarks = [...currentStyleMarks, { type: "sup" }];
1125
+ }
1126
+ else if (position.includes("sub")) {
1127
+ currentStyleMarks = [...currentStyleMarks, { type: "sub" }];
1128
+ }
1129
+ }
1130
+ // Handle inline code (monospace fonts)
1131
+ if (style?.textProperties?.fontFamily) {
1132
+ const fontFamily = style.textProperties.fontFamily.toLowerCase();
1133
+ const monospacePatterns = [
1134
+ "courier",
1135
+ "consolas",
1136
+ "monaco",
1137
+ "menlo",
1138
+ "lucida console",
1139
+ "liberation mono",
1140
+ "dejavu sans mono",
1141
+ "bitstream vera sans mono",
1142
+ "source code pro",
1143
+ "fira code",
1144
+ "ubuntu mono",
1145
+ "droid sans mono",
1146
+ "monospace"
1147
+ ];
1148
+ const isMonospace = monospacePatterns.some(pattern => fontFamily.includes(pattern));
1149
+ if (isMonospace) {
1150
+ currentStyleMarks = [...currentStyleMarks, { type: "code" }];
1151
+ }
1152
+ }
1153
+ return this.convertNodeChildren(node, currentStyleMarks);
1154
+ }
1155
+ convertFootnote(node, currentStyleMarks) {
1156
+ const noteBody = node.query("text:note-body");
1157
+ if (!noteBody) {
1158
+ return null;
1159
+ }
1160
+ // Get the first paragraph in the footnote
1161
+ const firstParagraph = noteBody.query("text:p");
1162
+ if (!firstParagraph) {
1163
+ return null;
1164
+ }
1165
+ // Check if this is a citation-only footnote
1166
+ const referenceMarkStart = firstParagraph.query("text:reference-mark-start");
1167
+ const referenceMarkEnd = firstParagraph.query("text:reference-mark-end");
1168
+ const markName = referenceMarkStart?.getAttribute("text:name");
1169
+ if (referenceMarkStart &&
1170
+ referenceMarkEnd &&
1171
+ markName &&
1172
+ isOdtCitationMark(markName) &&
1173
+ // Check that there's no content outside the reference marks
1174
+ firstParagraph.children.every(child => child.tagName === "text:reference-mark-start" ||
1175
+ child.tagName === "text:reference-mark-end" ||
1176
+ (child.tagName === "text:span" &&
1177
+ child.previousSibling?.tagName ===
1178
+ "text:reference-mark-start" &&
1179
+ child.nextSibling?.tagName ===
1180
+ "text:reference-mark-end"))) {
1181
+ // If it's a citation-only footnote, convert it directly to a citation
1182
+ return this.convertCitation(markName, currentStyleMarks);
1183
+ }
1184
+ // Otherwise, convert as regular footnote
1185
+ return {
1186
+ type: "footnote",
1187
+ attrs: {
1188
+ footnote: this.convertContainer(noteBody)
1189
+ },
1190
+ marks: this.getCurrentMarks(currentStyleMarks)
1191
+ };
1192
+ }
1193
+ convertCitation(markName, currentStyleMarks) {
1194
+ const citationNode = parseOdtReferenceMark(markName, this.bibliography, this.bibDB);
1195
+ if (citationNode) {
1196
+ citationNode.marks = this.getCurrentMarks(currentStyleMarks);
1197
+ return citationNode;
1198
+ }
1199
+ return null;
1200
+ }
1201
+ convertBibliographyMark(bibMarkNode, currentStyleMarks) {
1202
+ const citationNode = parseOdtBibliographyMark(bibMarkNode, this.bibliography);
1203
+ if (citationNode) {
1204
+ citationNode.marks = this.getCurrentMarks(currentStyleMarks);
1205
+ return citationNode;
1206
+ }
1207
+ return null;
1208
+ }
1209
+ convertList(node, attrs) {
1210
+ const listStyle = node.getAttribute("text:style-name");
1211
+ const isOrdered = this.isOrderedList(listStyle);
1212
+ attrs = Object.assign({
1213
+ id: randomListId()
1214
+ }, attrs);
1215
+ if (isOrdered) {
1216
+ attrs.order = 1;
1217
+ }
1218
+ return {
1219
+ type: isOrdered ? "ordered_list" : "bullet_list",
1220
+ attrs,
1221
+ content: node.queryAll("text:list-item").map(item => ({
1222
+ type: "list_item",
1223
+ content: this.convertContainer(item)
1224
+ }))
1225
+ };
1226
+ }
1227
+ convertAnnotationStart(node) {
1228
+ const commentId = (node.getAttribute("office:name") || "")
1229
+ .replace(/\D/g, "")
1230
+ .slice(0, 9);
1231
+ if (commentId && this.comments[commentId]) {
1232
+ this.currentCommentIds.push(commentId);
1233
+ }
1234
+ return null;
1235
+ }
1236
+ convertAnnotationEnd(node) {
1237
+ const commentId = (node.getAttribute("office:name") || "")
1238
+ .replace(/\D/g, "")
1239
+ .slice(0, 9);
1240
+ if (commentId) {
1241
+ const index = this.currentCommentIds.indexOf(commentId);
1242
+ if (index !== -1) {
1243
+ this.currentCommentIds.splice(index, 1);
1244
+ }
1245
+ }
1246
+ return null;
1247
+ }
1248
+ convertHeadingReference(node) {
1249
+ const refName = node.getAttribute("text:ref-name");
1250
+ if (!refName || !this.referenceableObjects[refName]) {
1251
+ return null;
1252
+ }
1253
+ const targetObject = this.referenceableObjects[refName];
1254
+ if (targetObject.type !== "heading") {
1255
+ return null;
1256
+ }
1257
+ return {
1258
+ type: "cross_reference",
1259
+ attrs: {
1260
+ id: targetObject.id,
1261
+ title: targetObject.node.textContent
1262
+ }
1263
+ };
1264
+ }
1265
+ convertFigureReference(node) {
1266
+ const refName = node.getAttribute("text:ref-name");
1267
+ if (!refName || !this.referenceableObjects[refName]) {
1268
+ return null;
1269
+ }
1270
+ const targetObject = this.referenceableObjects[refName];
1271
+ if (targetObject.type !== "figure") {
1272
+ return null;
1273
+ }
1274
+ // Find the caption text within the figure
1275
+ const caption = targetObject.node.query("text:p")?.textContent || "";
1276
+ return {
1277
+ type: "cross_reference",
1278
+ attrs: {
1279
+ id: targetObject.id,
1280
+ title: caption
1281
+ }
1282
+ };
1283
+ }
1284
+ isOrderedList(styleName) {
1285
+ if (!this.stylesDoc) {
1286
+ return false;
1287
+ }
1288
+ const listStyle = this.stylesDoc.query("text:list-style", {
1289
+ "style:name": styleName
1290
+ });
1291
+ return listStyle?.query("text:list-level-style-number") !== null;
1292
+ }
1293
+ convertImage(node, attrs = {}) {
1294
+ const imageElement = node.query("draw:image");
1295
+ if (!imageElement) {
1296
+ return null;
1297
+ }
1298
+ const frame = node.closest("draw:frame");
1299
+ if (!frame) {
1300
+ return null;
1301
+ }
1302
+ const href = imageElement.getAttribute("xlink:href");
1303
+ if (!href || !href.startsWith("Pictures/")) {
1304
+ return null;
1305
+ }
1306
+ const imageId = Math.floor(Math.random() * 1000000);
1307
+ const width = this.convertLength(node.getAttribute("svg:width"));
1308
+ const height = this.convertLength(node.getAttribute("svg:height"));
1309
+ const title = href.split("/").pop();
1310
+ this.images[imageId] = {
1311
+ id: imageId,
1312
+ title,
1313
+ copyright: {
1314
+ holder: false,
1315
+ year: false,
1316
+ freeToRead: true,
1317
+ licenses: []
1318
+ },
1319
+ image: href,
1320
+ file_type: this.getImageFileType(title),
1321
+ file: null,
1322
+ width,
1323
+ height,
1324
+ checksum: 0
1325
+ };
1326
+ // Find sequence element for figure reference
1327
+ const sequence = frame.query("text:sequence");
1328
+ let figureId = null;
1329
+ if (sequence) {
1330
+ const refName = sequence.getAttribute("text:ref-name");
1331
+ if (refName && this.referenceableObjects[refName]) {
1332
+ figureId = this.referenceableObjects[refName].id;
1333
+ }
1334
+ }
1335
+ const caption = node.query("text:p");
1336
+ const captionContent = caption ? this.convertNodeChildren(caption) : [];
1337
+ attrs = Object.assign({
1338
+ id: figureId || randomFigureId(),
1339
+ aligned: "center",
1340
+ width: Math.min(Math.round((width / 8.5) * 100), 100),
1341
+ caption: Boolean(captionContent.length)
1342
+ }, attrs);
1343
+ const figureCaption = { type: "figure_caption" };
1344
+ if (captionContent.length) {
1345
+ figureCaption.content = captionContent;
1346
+ }
1347
+ return {
1348
+ type: "figure",
1349
+ attrs,
1350
+ content: [
1351
+ {
1352
+ type: "image",
1353
+ attrs: {
1354
+ image: imageId
1355
+ }
1356
+ },
1357
+ figureCaption
1358
+ ]
1359
+ };
1360
+ }
1361
+ getImageFileType(filename) {
1362
+ const ext = filename.split(".").pop().toLowerCase();
1363
+ switch (ext) {
1364
+ case "avif":
1365
+ case "avifs":
1366
+ return "image/avif";
1367
+ case "png":
1368
+ return "image/png";
1369
+ case "jpg":
1370
+ case "jpeg":
1371
+ return "image/jpeg";
1372
+ case "gif":
1373
+ return "image/gif";
1374
+ case "svg":
1375
+ return "image/svg+xml";
1376
+ case "webp":
1377
+ return "image/webp";
1378
+ default:
1379
+ return "image/png"; // Default fallback
1380
+ }
1381
+ }
1382
+ convertLength(length) {
1383
+ if (!length) {
1384
+ return 0;
1385
+ }
1386
+ // Match number and unit
1387
+ const match = length.match(/^(-?\d*\.?\d+)(pt|cm|mm|in|pc|px|%)?$/);
1388
+ if (!match) {
1389
+ return 0;
1390
+ }
1391
+ const [_, value, unit = "pt"] = match;
1392
+ const numValue = parseFloat(value);
1393
+ // Convert to inches first (as base unit)
1394
+ switch (unit) {
1395
+ case "pt": // points
1396
+ return numValue / 72;
1397
+ case "pc": // picas (1 pica = 12 points)
1398
+ return (numValue * 12) / 72;
1399
+ case "cm": // centimeters
1400
+ return numValue / 2.54;
1401
+ case "mm": // millimeters
1402
+ return numValue / 25.4;
1403
+ case "in": // inches
1404
+ return numValue;
1405
+ case "px": // pixels (assuming 96 DPI)
1406
+ return numValue / 96;
1407
+ case "%": // percentage (return as is)
1408
+ return numValue;
1409
+ default:
1410
+ return 0;
1411
+ }
1412
+ }
1413
+ convertTable(node, attrs) {
1414
+ const width = node.getAttribute("style:rel-width")?.replace("%", "") || "100";
1415
+ const styleName = node.getAttribute("table:style-name");
1416
+ const style = this.styles[styleName];
1417
+ const aligned = style?.tableProperties.align || "center";
1418
+ attrs = Object.assign({
1419
+ id: randomTableId(),
1420
+ track: parseTracks(node.getAttribute("text:change-id")),
1421
+ width,
1422
+ aligned,
1423
+ layout: "fixed",
1424
+ category: "none",
1425
+ caption: false
1426
+ }, attrs);
1427
+ return {
1428
+ type: "table",
1429
+ attrs,
1430
+ content: [
1431
+ { type: "table_caption" },
1432
+ {
1433
+ type: "table_body",
1434
+ content: node
1435
+ .queryAll("table:table-row")
1436
+ .map(row => this.convertTableRow(row))
1437
+ }
1438
+ ]
1439
+ };
1440
+ }
1441
+ convertTableRow(row) {
1442
+ return {
1443
+ type: "table_row",
1444
+ content: row
1445
+ .queryAll(["table:table-cell", "table:covered-table-cell"])
1446
+ .map(cell => this.convertTableCell(cell))
1447
+ };
1448
+ }
1449
+ convertTableCell(node) {
1450
+ if (node.tagName === "table:covered-table-cell") {
1451
+ return null;
1452
+ }
1453
+ return {
1454
+ type: "table_cell",
1455
+ attrs: {
1456
+ colspan: parseInt(node.getAttribute("table:number-columns-spanned")) || 1,
1457
+ rowspan: parseInt(node.getAttribute("table:number-rows-spanned")) ||
1458
+ 1,
1459
+ track: parseTracks(node.getAttribute("text:change-id"))
1460
+ },
1461
+ content: this.convertContainer(node)
1462
+ };
1463
+ }
1464
+ convertLink(node, currentStyleMarks) {
1465
+ const href = node.getAttribute("xlink:href");
1466
+ currentStyleMarks = currentStyleMarks.concat([
1467
+ { type: "link", attrs: { href } }
1468
+ ]);
1469
+ return this.convertNodeChildren(node, currentStyleMarks);
1470
+ }
1471
+ detectLanguage() {
1472
+ // Try to detect document language in following order:
1473
+ // 1. From document content
1474
+ // 2. From document styles
1475
+ // 3. Default to "en-US"
1476
+ // Check content language
1477
+ if (this.contentDoc) {
1478
+ const langAttr = this.contentDoc.getAttribute("office:default-language") ||
1479
+ this.contentDoc.getAttribute("dc:language");
1480
+ if (langAttr) {
1481
+ return langAttr;
1482
+ }
1483
+ const firstParagraph = this.contentDoc.query("text:p");
1484
+ if (firstParagraph) {
1485
+ const paraLang = firstParagraph.getAttribute("xml:lang");
1486
+ if (paraLang) {
1487
+ return paraLang;
1488
+ }
1489
+ }
1490
+ }
1491
+ // Check styles language
1492
+ if (this.stylesDoc) {
1493
+ const defaultStyle = this.stylesDoc.query("style:default-style");
1494
+ if (defaultStyle) {
1495
+ const styleLang = defaultStyle.getAttribute("fo:language") ||
1496
+ defaultStyle.getAttribute("style:language-complex");
1497
+ if (styleLang) {
1498
+ return styleLang;
1499
+ }
1500
+ }
1501
+ }
1502
+ // Default to "en-US"
1503
+ return "en-US";
1504
+ }
1505
+ }
1506
+ //# sourceMappingURL=convert.js.map