Amn378
@Amn378

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

xslでRedmineのデータをCSV化する際のエラー

解決したいこと

【参考】https://qiita.com/Amn378/questions/4f6fa6eaa5a53cb84269?utm_campaign=email&utm_content=link&utm_medium=email&utm_source=public_answer#answer-307d8bab0546f15eb7f3
こちらの続きになります。(RedmineのデータをXMLで取得、XSLでTAB区切りのcsvにしています)
おおむね読み込みはうまくいっていますが、VBAでQueryTableにてCSVを読み込む際、一部のXMLでエラーとなります。
原因は一部の文字コードにあることがわかりました。
今までわかっているエラーの出る文字は「~」(U+007E)と「أُ」(\u0623\u064F アラビア語だそうです )で、その文字を取り除いたxmlはxslで無事CSVに変換できました。
Redmineの性質上、どのような文字コードが想定されるかあらかじめわかりません。(日本語だけでもないようです)
このような場合の最適解を探しています。
例えば禁則文字的なものを指定し、そのコードが含まれているか事前に検査する?などということになるのでしょうか?(xml自体数も多いのでなるべくやりたくありませんが…)また、左記を行う場合、想定する禁則文字は?というところでまた壁がはだかります。

このようなご経験のある方がいたら参考までに教えてください。

発生している問題・エラー

プロシージャの呼び出し、または引数が不正です

該当するソースコード

Dim oXml,oXsl,oFso,f

Set oXml = CreateObject("MSXML2.DOMDocument")
oXml.async = False
oXml.Load("ファイル.xml")

Set oXsl = CreateObject("MSXML2.DOMDocument")
oXsl.async = False
oXsl.Load("Format_tab.xsl")

Set oFso = CreateObject("Scripting.FileSystemObject")
Set f = oFso.OpenTextFile("result.csv" , 8 , True)
f.WriteLine oXml.transformNode(oXsl) ’ *** ここでエラー

Set oXml = nothing
Set oXsl = nothing
Set oFso = nothing
Set f = nothing

上記の***でエラーとなります。

自分で試したこと

どの文字がエラーを引き起こしているのか、1つずつ項目を絞り、Subjectに入っている文字であること、その文字がどの文字かということまでは断定できました。

0

【参考】のリンク先の
<?xml version="1.0" encoding="Shift_JIS"?>

<?xml version="1.0" encoding="utf-8"?>
に変えるだけの話のような。(もちろんファイルエンコーディングもutf-8に)

0Like

@albireo さん ありがとうございます。
確かにエンコーディングにずれがありますが、修正してやってみた結果、同じでした。
CSVを作成する際にshift-jisの方が都合がいいかな、という思いで、xslはそのようにしてみたのですが、何故まだ始めて間がないので、その記述で出力するCSVに影響しているかは不明なところでもありました。
utf-8でも他のファイルは問題なく読めやはり上記文字コードを含むもののみ読めませんでした。

0Like

ちょっと整理します

  • ShiftJISでエンコーディングすれば、アラビア文字などのShiftJISで定義されてない文字は化ける【動かせない事実】
  • 文字化けしないためにはunicodeに対応したエンコーディング(実質もっとも多くのソフトウェアが対応しているUTF-8一択)でCSVファイルを作成する必要がある
  • 現在作成しているシステムに必要な修正は「UTF-8エンコードしたCSVファイルを作成する」「VBAのプログラムをUTF-8に対応させる」の2つ

検索してみましたが、OpenTextFileはUTF-8に対応していないようなので別の方法でテキストファイルを読み込む必要があります。
私はVBAはあまりわからないので「VBA OpenTextFile UTF-8」で検索してみてください。

(正直、ShiftJISでは扱えないようなデータが出てくるとVBAを使うのもきついんじゃないかな、と思います)

0Like

Redmineの性質上、どのような文字コードが想定されるかあらかじめわかりません。(日本語だけでもないようです)

アラビア語はともかく
・チルダ・オーバーライン問題
によりエンコードエラーになってるのだとしたらエンコードライブラリの問題かと。
RedmineのプラグインでCSVを吐き出す方向に行ったほうが幸せになれるかもしれません。
自分が運用していた時はMySQLがutf8mb4にせよと警告が出ていたので
RedmineのDBのエンコードはutf8mb4とかに設定していましたが・・・

直接DBから引っ張ってきたとしてもエンコード周りは結構面倒そうなのでVBAはおすすめ出来ないです。
https://qiita.com/t-yama-3/items/2dbd23b0b7e8dd741b41

0Like

今気づいたんですが、元のXMLファイルに<?xml version="1.0" encoding="UTF-8"?>と書かれているのだから

どのような文字コードが想定されるかあらかじめわかりません。

は変ですね。

0Like

エラーの原因

手元で簡単に確認してみたところ、問題の発生している原因は以下の箇所のようです。

エラーの原因
Set f = oFso.OpenTextFile("result.csv" , 8 , True)
'object.OpenTextFile (filename, [ iomode, [ create, [ format ]]])

OpenTextFileメソッドの4個目の引数Formatが指定されていないため、Shift_JISで出力するモードになっています。
そのため、書き込み時にShift_JIS範囲外の文字が含まれるとエラーとなります。

関係ありそうでない箇所

xsl:outputの指定はちょっと気になったのですが、今回の問題には影響がないようです。

xsl:outpu気になる箇所
<xsl:output method="text" encoding="Shift_JIS" indent="no" />

oXml.transformNode(oXsl)で変換する場合はVBA内部の文字列表現(UTF-16)として扱われるため、xsl:outputencodingは一切影響がないようです。

修正内容

実際どう修正するのがいいのか?については、結果としてどのエンコーディングでファイルが欲しいのか?によります。

UTF-16LEでも問題ないのであればOpenTextFileに引数を追加すれば、それで終わりです。

UTF-8で出力する場合はADODB.Streamオブジェクトを使う必要があります。
UTF-8(BOMあり)なら簡単ですが、より一般的なUTF-8(BOMなし)にしたい場合は少し手を加える必要があります。

0Like

皆様、いろいろありがとうございます。自分の文字コードに対する知識が足りないことを思い知らされました。
結論から申し上げますと@nukie_53さんご指摘のOpenTextFileの第4パラメーターに-1(TristateTrueはなぜかVBAでは定数として認識しませんでした)を付加し、すべてをUTF-8の形式で統一した形にすることで、今のところ解決です。(エラーが起きなくなりました)

Set f = oFso.OpenTextFile("result.csv", 8, True, -1)

当初よりこのパラメーターの存在を知らなかったのでExcelでCSV読み込みする場合にはあらかじめshift-jisのCSVを用意しなくてはいけないと思い込んでおりました。
@albireoのおっしゃる通り、世界で一番使用されている文字コードなので、その対応がないわけありませんでしたね。

そしてそもそもの大きな勘違いは、書き込み時の文字コードばかり意識していましたが、Openする際にも意識が必要だったのだな、というところです。

大変良い勉強させていただきました。
ありがとうございました。

@bunaImage さん
>RedmineのプラグインでCSVを吐き出す方向に行ったほうが・・・
全く同意です。そのようにお客様にもご案内したのですが、アクセス権など?の不都合があるのか・・・今回はXMLから出す仕様となってしまいました。
たしかRedmineでCSV出力するには管理者権限?が必要だったでしょうか?そのあたりかなぁと私も簡単にあきらめてしまいました・・・。

@albireo さん
>どのような文字コードが想定されるかあらかじめわかりません。
上記の文字コードとはUNICODE(\u0623\u064Fなど)の方を指したつもりでした。表現が紛らわしくて失礼いたしました。

0Like

蛇足かもしれないのですが、認識違いがありそうだったため、追加でコメントさせていただきます。

作成されるCSVはUTF-8ではない

Set f = oFso.OpenTextFile("result.csv", 8, True, -1)

に書き換えてCSVファイルを作成したのであれば、そのCSVファイルは UTF-16 (LE BOMつき) であり、UTF-8ではありません。

ExcelのQueryTableの動作を確認してみましたが、UTF-8相当のファイルを読み込む指定をしていても、BOMを検出した場合は ユーザーの指定は無視して そのBOMが示すエンコーディングでファイルを読み込むようです。

この動作によりExcel側でエンコーディングの読み替えを行ってくれたため、結果的にエラーが出なかったのではないかと推測します。

とはいえ、CSVファイルをExcelに読み込ませた後、即座に削除するのであれば、現状でも動作はしているそうなので、そのままでもいいと思います。

しかし、そのCSVファイルを別の用途に流用する可能性があれば、正しくUTF-8で出力した方が無難だと思われます。


その他こまごまとしたこと

そしてそもそもの大きな勘違いは、書き込み時の文字コードばかり意識していましたが、Openする際にも意識が必要だったのだな、というところです。

私の認識では書き込み時の文字コードの話に終始した内容だと感じました。

Set f = oFso.OpenTextFile("result.csv", 8, True, -1)

この処理はテキストファイルを開くようにみえますが、8 = ForAppending = 追記モードのため、テキストファイルの現在の末尾以降にどのように書き込むのかを指定していることになります。

TristateTrueはなぜかVBAでは定数として認識しませんでした

VBAのプロジェクトの参照設定が不足しているためと思われます。

メニューバー > ツール(T) > 参照設定(R)から設定を行うことで、他のライブラリに定義された定数を使用することができます。

参照設定の注意として、
環境によってdllのパスが違う場合に参照先が見つからず、VBAがコンパイルエラーとなってしまう、
などの問題が発生する可能性があります。

そのため、作成者と使用者が別の場合は設定しないこともあるようです。
しかし、開発時には設定しておいたほうが絶対便利なため、今後もVBAを触ることがあれば知っておくといいと思います。

参考までに、質問本文のコードを参照設定ありで書いたものを以下に記載します。
この状態でoXml.と記述すると各種メンバーが候補に表示されるのがわかるかと思います。

参照設定ありのコード
Public Sub Sample()
'以下の Qiita の記事に添付したコード。
'xslでRedmineのデータをCSV化する際のエラー
'https://qiita.com/Amn378/questions/c5acf7314eac7f4a24ef
    
'メニューバー > ツール(T) > 参照設定(R) から以下の参照設定を追加しています。
'参照設定のダイアログ上の表記   | コード中の表記
'Microsoft Scripting Runtime    | Scripting
'Microsoft XML, v3.0            | MSXML2

'変数宣言の`As MSXML2.DOMDocument`などの
'`MSXML2.`の部分は省略可能です。
    
    'Redmine からエクスポートした XML を読み込む。
    Dim oXml As MSXML2.DOMDocument
    Set oXml = CreateObject("MSXML2.DOMDocument")
    oXml.async = False
    Debug.Assert oXml.Load("ファイル.xml") 'Debug.Assert により、読み込みに失敗したときにこの行で止まる。
    
    '変換用の XSL を読み込む。
    Dim oXsl As MSXML2.DOMDocument
    Set oXsl = CreateObject("MSXML2.DOMDocument")
    oXsl.async = False
    Debug.Assert oXsl.Load("Format_tab.xsl")
    
    
    Dim oFso As Scripting.FileSystemObject
    Set oFso = CreateObject("Scripting.FileSystemObject")
    
    '出力先の CSV を UTF-16 (LE BOMつき) の追記モードで開く。
    Dim f As Scripting.TextStream
    Set f = oFso.OpenTextFile("result.csv", ForAppending, True, TristateTrue)
    
    'XML に XSL を適用し、結果の文字列を CSV に書き込み。
    f.WriteLine oXml.transformNode(oXsl)
    
    
    Set oXml = Nothing
    Set oXsl = Nothing
    Set oFso = Nothing
    Set f = Nothing
End Sub
0Like

@nukie_53 さん
気が付くのが遅くなり失礼しました。

ExcelのQueryTableの動作を確認してみましたが、UTF-8相当のファイルを読み込む指定をしていても、BOMを検出した場合は ユーザーの指定は無視して そのBOMが示すエンコーディングでファイルを読み込むようです。

これは驚きました。こちらのパラメーターでうまくいったのですっかりコーディングによってUTF-8になったものと思っておりましたが、BOMを読んだということだったのですね。大変勉強になりました。
現在のところここで出力するCSVはテンポラリのため、このままにしますが、確かに流用されることなどを考えると、正しく処理が必要ですね・・・。
この場合のパラメーターは何が適切なのか、また時間ができたときにやってみたいと思います。

この処理はテキストファイルを開くようにみえますが、8 = ForAppending = 追記モードのため、テキストファイルの現在の末尾以降にどのように書き込むのかを指定していることになります。

こちらもありがとうございます。私の最初の認識でよかった、ということで承知いたしました。文字コードの複雑さ(自分の理解の乏しさ)もあり、読んだり書いたりしているうちにOpenする際にも読み込む際の文字コードを指定して、例えば期待した文字コード以外のコードで書かれているものも変換しながら読めたりするのか?と一瞬勘違いしてしまいました。
やはり書き込み時の指定としてOpen時に指定しているのですね。

TristateTrueはなぜかVBAでは定数として認識しませんでした

おっしゃる通りで、参照設定不足でした。(久々のVBAですっかり忘れておりました)
そしてコーディングもありがとうございます。
MSXML2 は「Microsoft XML, v3.0」だったのですね・・・。
Microsoft XML, v6.0 を参照しておりましたが、とりあえず動いてはおりましたので、このままにしておいてよいものか迷いながら。(すでに第1稿をお渡し済み)

机上だけでなく、丁寧に検証してくださった結果を共有いただき感謝いたします。
ありがとうございました。

1Like

Your answer might help someone💌