Help us understand the problem. What is going on with this article?

xlsx-populateで取得した値がややこしいことになってた時に文字列だけを取り出す方法

RedmineとJavaScriptに身も心も捧げる8amjpです。

xlsx-populate

さて、私の職場でもRedmineを導入したんですけど、今まで使っていたエクセル形式の帳票も継続して使わなきゃならんという事で、node.jsとxlsx-populateというライブラリを使用して、エクセル帳票のインポート/エクスポートツールを作成して運用しているんです。
このxlsx-populate、JavaScriptからエクセルファイルを非常に高い精度で読み書きできる素晴らしいツール。しかもエクセルのインストールは不要です。ありがたいですねー。

ところが、運用しているうちに一つの問題にぶつかりました。

ややこしい値

workbook.sheet("Sheet1").cell("A1").value();

xlsx-populateでは、こんな感じでセルの値を取得します。わかりやすいですね。
で、通常は数値やら文字列やら、とにかく単純な値が返ってくるんですけど。たまに、セルの一部の文字だけ赤字にしたり大きくしたりフォント変えたり、と余計な事する人いるじゃないですか。そういうセルの値を取得すると、ものすごくややこしいデータが返ってきます。
どれくらいややこしいかというと、こんな感じ。

[
  {"name":"r","attributes":{},"children":[
    {"name":"t","attributes":{},"children":["テキスト1"]}
  ]},
  {"name":"r","attributes":{},"children":[
    {"name":"rPr","attributes":{},"children":[
      {"name":"sz","attributes":{"val":11},"children":[]},
      {"name":"rFont","attributes":{"val":"MSPゴシック"},"children":[]},
      {"name":"family","attributes":{"val":3},"children":[]},
      {"name":"charset","attributes":{"val":128},"children":[]}
    ]},
    {"name":"t","attributes":{},"children":["テキスト2"]}
  ]},
  {"name":"r","attributes":{},"children":[
    {"name":"rPr","attributes":{},"children":[
      {"name":"sz","attributes":{"val":11},"children":[]},
      {"name":"rFont","attributes":{"val":"MS Pゴシック"},"children":[]},
      {"name":"family","attributes":{"val":3},"children":[]},
      {"name":"charset","attributes":{"val":128},"children":[]}
    ]},
    {"name":"t","attributes":{},"children":["テキスト3"]}
  ]},

  /* 中略 */

  {"name":"phoneticPr","attributes":{"fontId":2},"children":[]}
]

なんすかこれ。
まあ、エクセルはエクセルで、指定されたとおり忠実に再現しないといけないわけで、こんな律儀なデータになるんですけど。エクセルには罪はないんですけど。
でも、必要なのは単純なテキストだけなので、その部分だけ取り出したいんです。さて、どうしましょう。

こたえ

必要な文字列は、「nameプロパティの値がtであるオブジェクトのchildrenプロパティの配列内」に入ってます。
で、そのオブジェクトは、「nameプロパティの値がrであるオブジェクトのchildrenプロパティの配列内」に入ってます。
なので、それを上手いこと取り出せばいいんですね。
さあ、JavaScriptが誇る配列操作用の関数、filtermap関数の出番ですよ。

var value = workbook.sheet("Sheet1").cell("A1").value();
if (Array.isArray(value)) value = value.filter(prop => prop.name == 'r').map(r => r.children.filter(prop => prop.name == 't')[0].children[0]).join();

書いてて気持ちいいですねー、このコード。
特に細かく解説しませんけど、今まで説明したことをやってるだけですよ。

というわけで、xlsx-populateで取得した値がややこしいことになってた時に文字列だけを取り出す方法でした。

2019/9/13 追記

v1.20.0から仕様が変わりまして、RichTextオブジェクトがサポートされ、リッチテキストを使用しているセルの値を取得するとRichTextオブジェクトが返るようになりました。

const RichText = require('xlsx-Populate').RichText;
let value = workbook.sheet(0).cell('A1').value();
let text = value instanceof RichText ? value.text() : value;

とすれば、リッチテキストだろうがプレーンテキストだろうが文字列のみを取得できます。

……のはずなのですが、instanceof RichTextとしてもなぜかfalseが返ってきてしまうので、さしあたり

let text = value instanceof Object ? value.text() : value;

として逃げてます。

8amjp
福井市に住むSE・プログラマ。Kindleストアで技術系異世界ファンタジー小説「Redmineで始める異世界人心掌握術」販売中。JavaScript学園コメディ「恋に落ちるコード.js」電子書籍化準備中。
https://8am.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした