37 lines
1.5 KiB
JavaScript
37 lines
1.5 KiB
JavaScript
import { decodeXmlEntities } from '#import-recipes/odt/decodeXmlEntities.js'
|
|
|
|
// Either a whole tag, or the run of text sitting between two tags.
|
|
const TOKEN_PATTERN = /<[^>]*>|[^<]+/g
|
|
const TAG_NAME_PATTERN = /^<\/?([\w:.-]+)/
|
|
const SPACE_COUNT_PATTERN = /text:c="(\d+)"/
|
|
|
|
// Turns the `content.xml` of an ODT document into the lines it displays.
|
|
//
|
|
// Every `<text:p>` and `<text:h>` boundary starts a new line, and so does a
|
|
// manual line break: someone who typed an ingredient list with Shift+Enter gets
|
|
// one entry per ingredient, which is exactly what the extraction step needs.
|
|
// Styling, tracked changes, bookmarks and other markup carry no text of their
|
|
// own and are skipped. Empty lines are dropped.
|
|
export function extractOdtText(contentXml) {
|
|
if (typeof contentXml !== 'string') return []
|
|
const lines = []
|
|
let current = ''
|
|
for (const token of contentXml.match(TOKEN_PATTERN) ?? []) {
|
|
if (token[0] !== '<') {
|
|
current += decodeXmlEntities(token)
|
|
continue
|
|
}
|
|
const tagName = (TAG_NAME_PATTERN.exec(token) ?? [])[1] ?? ''
|
|
if (tagName === 'text:p' || tagName === 'text:h' || tagName === 'text:line-break') {
|
|
lines.push(current)
|
|
current = ''
|
|
} else if (tagName === 'text:tab') {
|
|
current += '\t'
|
|
} else if (tagName === 'text:s') {
|
|
const count = Number.parseInt((SPACE_COUNT_PATTERN.exec(token) ?? [])[1] ?? '1', 10)
|
|
current += ' '.repeat(Number.isInteger(count) && count > 0 ? count : 1)
|
|
}
|
|
}
|
|
lines.push(current)
|
|
return lines.map((line) => line.trim()).filter((line) => line.length > 0)
|
|
}
|