Home Reference Source Test Repository

src/lib/wpxml2md.js

const Fs = require( 'fs' );
const Path = require( 'path' );
const XmlParser = require( 'xml2js' );
const Util = require( './util.js' );
const Converter = require( './converter.js' );

/**
 * Convert WordPress XML to Markdown files.
 */
class WordPressXmlToMarkdown {
  /**
   * Conver WordPress XML file to Markdown files.
   *
   * @param {String} src     Path of the WordPress XML file.
   * @param {String} dest    Path of Markdown files output directory.
   * @param {Logger} logger  Logger.
   * @param {Option} options Options.
   *
   * @return {Promise} Promise object.
   */
  static convert( src, dest, logger, options ) {
    return new Promise( ( resolve, reject ) => {
      const data = Fs.readFileSync( Path.resolve( src ) );
      if( !( data ) ) {
        return reject( new Error( '"' + src + '" is not found.' ) );
      }

      const dir = WordPressXmlToMarkdown.createUniqueDestDir( dest );
      if( !( dir ) ) {
        return reject( new Error( 'Failed to create the root directory.' ) );
      }

      const postsDir = Path.join( dir, 'posts' );
      if( !( Util.mkdirSync( postsDir ) ) ) {
        return reject( new Error( 'Failed to create the posts directory.' ) );
      }

      const pagesDir = Path.join( dir, 'pages' );
      if( !( Util.mkdirSync( pagesDir ) ) ) {
        return reject( new Error( 'Failed to create the pages directory.' ) );
      }

      return Promise
      .resolve()
      .then( () => {
        return WordPressXmlToMarkdown.postsFromXML( data.toString() );
      } )
      .then( ( posts ) => {
        posts.forEach( ( post ) => {
          const type = post[ 'wp:post_type' ][ 0 ];
          switch( type ) {
            case 'post':
              WordPressXmlToMarkdown.convertPost( type, postsDir, post, logger, options );
              break;

            case 'page':
              WordPressXmlToMarkdown.convertPost( type, pagesDir, post, logger, options );
              break;

            default:
              break;
          }
        } );
      } );
    } );
  }

  /**
   * Convert the post data to markdown file.
   *
   * @param {String} type   Type of post ( post or page ).
   * @param {String} dir    Path of Markdown file output directory.
   * @param {Object} post   Post data.
   * @param {Logger} logger Logger.
   * @param {Option} options Options.
   *
   * @return {Promise} Promise task.
   */
  static convertPost( type, dir, post, logger, options ) {
    let date  = Util.formatDate( new Date( post.pubDate ), 'YYYY/MM/DD' );
    if( !( date ) ) {
      date = Util.formatDate( new Date( post[ 'wp:post_date' ] ), 'YYYY/MM/DD' );
      if( !( date ) ) {
        date = Util.formatDate( new Date(), 'YYYY/MM/DD' );
      }
    }

    const units = date.split( '/' );
    const year  = units[ 0 ];
    const month = units[ 1 ];
    const day   = units[ 2 ];

    logger.log( year + '/' + month + '/' + day + ' [' + type + ']: ' + post.title );

    const stream = WordPressXmlToMarkdown.createStream( dir, year, month + '-' + day + '.md' );
    if( !( stream ) ) {
      throw new Error( 'Failed to create the stream.' );
    }

    const markdown = Converter.convert( post[ 'content:encoded' ][ 0 ], options );
    stream.write( '# ' + post.title + '\n\n', 'utf8' );
    stream.write( markdown, 'utf8' );
  }

  /**
   * Create a stream of Markdown files to be written.
   *
   * @param {String} root     Path of the roo directory.
   * @param {String} year     Year.
   * @param {String} fileName File name.
   *
   * @return {WriteStream} If successful stream, otherwise "null".
   */
  static createStream( root, year, fileName ) {
    // root/year
    const dir  = Path.join( root, year );
    if( !( Util.mkdirSync( dir ) ) ) {
      return null;
    }

    // root/year/month/day.md or day-X.md
    const filePath = Util.uniquePathWithSequentialNumber( Path.join( dir, fileName ) );
    if( !( filePath ) ) {
      return null;
    }

    return Fs.createWriteStream( filePath );
  }

  /**
   * Create a directory with a unique name.
   *
   * @param {String} dir Base directory path.
   *
   * @return {String} The path of the created directory. Failure is null.
   */
  static createUniqueDestDir( dir ) {
    const base = Path.resolve( dir );
    const name = Util.formatDate( new Date(), 'YYYYMMDD-hhmmss' );

    let path = Path.resolve( base, name );
    if( !( Util.existsSync( path ) ) ) {
      if( Util.mkdirSync( path ) ) {
        return path;
      }
    }

    // Add sequential number
    for( let i = 1; i <= 256; ++i ) {
      path = Path.resolve( base, name + '-' + i );
      if( !( Util.existsSync( path ) ) ) {
        if( Util.mkdirSync( path ) ) {
          return path;
        }
      }
    }

    return null;
  }

  /**
   * Gets the posts data from XML.
   *
   * @param {String} text XML text.
   *
   * @return {Promise} Promise task.
   */
  static postsFromXML( text ) {
    return new Promise( ( resolve, reject ) => {
      XmlParser.parseString( text, ( err, xml ) => {
        if( err ) {
          return reject( err );
        }

        if( !( xml.rss && xml.rss.channel && 0 < xml.rss.channel.length && xml.rss.channel[ 0 ].item && 0 < xml.rss.channel[ 0 ].item.length ) ) {
          return reject( new Error( 'Invalid WordPress Post XML.' ) );
        }

        return resolve( xml.rss.channel[ 0 ].item );
      } );
    } );
  }
}

module.exports = WordPressXmlToMarkdown;