Files
wordpress-export-to-markdown/src/parser.js
T

189 lines
4.8 KiB
JavaScript
Raw Normal View History

const fs = require('fs');
const luxon = require('luxon');
const xml2js = require('xml2js');
const shared = require('./shared');
const settings = require('./settings');
2019-12-17 13:52:09 -05:00
const translator = require('./translator');
2019-12-17 14:03:58 -05:00
async function parseFilePromise(config) {
2020-01-14 10:26:50 -05:00
console.log('\nParsing...');
2019-12-21 15:57:25 -05:00
const content = await fs.promises.readFile(config.input, 'utf8');
2019-12-18 16:36:43 -05:00
const data = await xml2js.parseStringPromise(content, {
trim: true,
tagNameProcessors: [xml2js.processors.stripPrefix]
});
const posts = collectPosts(data, config);
2019-12-19 13:17:43 -05:00
const images = [];
2020-01-12 09:03:32 -05:00
if (config.saveAttachedImages) {
2019-12-19 12:35:33 -05:00
images.push(...collectAttachedImages(data));
}
2020-01-12 09:03:32 -05:00
if (config.saveScrapedImages) {
2019-12-19 12:35:33 -05:00
images.push(...collectScrapedImages(data));
}
mergeImagesIntoPosts(images, posts);
2019-12-17 14:03:58 -05:00
2019-12-21 15:57:25 -05:00
return posts;
}
2019-12-19 13:17:43 -05:00
function getItemsOfType(data, type) {
return data.rss.channel[0].item.filter(item => item.post_type[0] === type);
}
2020-01-12 09:03:32 -05:00
function collectPosts(data, config) {
// this is passed into getPostContent() for the markdown conversion
const turndownService = translator.initTurndownService();
2020-12-26 13:18:49 -05:00
let allPosts = [];
settings.post_types.forEach(postType => {
const postsForType = getItemsOfType(data, postType)
.filter(post => post.status[0] !== 'trash' && post.status[0] !== 'draft')
.map(post => ({
// meta data isn't written to file, but is used to help with other things
meta: {
id: getPostId(post),
slug: getPostSlug(post),
coverImageId: getPostCoverImageId(post),
type: postType,
imageUrls: []
},
frontmatter: {
title: getPostTitle(post),
date: getPostDate(post),
categories: getCategories(post),
tags: getTags(post)
},
content: translator.getPostContent(post, turndownService, config)
}));
if (settings.post_types.length > 1) {
console.log(`${postsForType.length} "${postType}" posts found.`);
}
allPosts.push(...postsForType);
});
if (settings.post_types.length === 1) {
console.log(allPosts.length + ' posts found.');
}
return allPosts;
}
function getPostId(post) {
return post.post_id[0];
}
2020-01-12 13:50:50 -05:00
function getPostSlug(post) {
2020-12-23 13:36:58 -05:00
return decodeURI(post.post_name[0]);
2020-01-12 13:50:50 -05:00
}
function getPostCoverImageId(post) {
2020-01-12 13:50:50 -05:00
if (post.postmeta === undefined) {
return undefined;
}
const postmeta = post.postmeta.find(postmeta => postmeta.meta_key[0] === '_thumbnail_id');
const id = postmeta ? postmeta.meta_value[0] : undefined;
return id;
}
function getPostTitle(post) {
2019-12-21 15:57:25 -05:00
return post.title[0];
}
function getPostDate(post) {
const dateTime = luxon.DateTime.fromRFC2822(post.pubDate[0], { zone: 'utc' });
if (settings.custom_date_formatting) {
return dateTime.toFormat(settings.custom_date_formatting);
} else if (settings.include_time_with_date) {
return dateTime.toISO();
} else {
return dateTime.toISODate();
}
}
function getCategories(post) {
const categories = processCategoryTags(post, 'category');
return categories.filter(category => !settings.filter_categories.includes(category));
}
function getTags(post) {
return processCategoryTags(post, 'post_tag');
}
function processCategoryTags(post, domain) {
2020-06-28 11:21:04 -04:00
if (!post.category) {
return [];
}
return post.category
.filter(category => category.$.domain === domain)
2020-12-23 13:36:58 -05:00
.map(({ $: attributes }) => decodeURI(attributes.nicename));
}
2019-12-19 13:17:43 -05:00
function collectAttachedImages(data) {
const images = getItemsOfType(data, 'attachment')
2019-12-19 13:17:43 -05:00
// filter to certain image file types
.filter(attachment => (/\.(gif|jpe?g|png)$/i).test(attachment.attachment_url[0]))
.map(attachment => ({
id: attachment.post_id[0],
postId: attachment.post_parent[0],
url: attachment.attachment_url[0]
}));
console.log(images.length + ' attached images found.');
return images;
}
function collectScrapedImages(data) {
const images = [];
2019-12-19 13:17:43 -05:00
getItemsOfType(data, 'post').forEach(post => {
const postId = post.post_id[0];
const postContent = post.encoded[0];
const postLink = post.link[0];
2019-12-19 13:17:43 -05:00
const matches = [...postContent.matchAll(/<img[^>]*src="(.+?\.(?:gif|jpe?g|png))"[^>]*>/gi)];
2019-12-19 13:17:43 -05:00
matches.forEach(match => {
// base the matched image URL relative to the post URL
const url = new URL(match[1], postLink).href;
2019-12-19 13:17:43 -05:00
images.push({
id: -1,
postId: postId,
2020-12-23 13:36:58 -05:00
url: decodeURI(url)
2019-12-19 13:17:43 -05:00
});
});
});
console.log(images.length + ' images scraped from post body content.');
return images;
}
function mergeImagesIntoPosts(images, posts) {
images.forEach(image => {
posts.forEach(post => {
let shouldAttach = false;
// this image was uploaded as an attachment to this post
if (image.postId === post.meta.id) {
shouldAttach = true;
}
// this image was set as the featured image for this post
if (image.id === post.meta.coverImageId) {
shouldAttach = true;
post.frontmatter.coverImage = shared.getFilenameFromUrl(image.url);
}
if (shouldAttach && !post.meta.imageUrls.includes(image.url)) {
2019-12-19 12:35:33 -05:00
post.meta.imageUrls.push(image.url);
}
});
});
}
2019-12-17 13:52:09 -05:00
exports.parseFilePromise = parseFilePromise;