tag:crieit.net,2005:https://crieit.net/tags/XML/feed 「XML」の記事 - Crieit Crieitでタグ「XML」に投稿された最近の記事 2021-01-10T00:49:33+09:00 https://crieit.net/tags/XML/feed tag:crieit.net,2005:PublicArticle/16573 2021-01-10T00:49:33+09:00 2021-01-10T00:49:33+09:00 https://crieit.net/posts/react-static-plugin-sitemap-xml-loc-rewrite-20210110 React Static のサイトマップ( sitemap.xml )の loc をサイトURL始まりで出力させる <p>React Static でサンプルページをビルドした際に、 <code>sitemap.xml</code> に記述されている <code>loc</code> の値がルートディレクトリからの絶対パスで記述されていたのに気付いたので、対処療法的に対処しました。</p> <h2 id="現象確認"><a href="#%E7%8F%BE%E8%B1%A1%E7%A2%BA%E8%AA%8D">現象確認</a></h2> <p>現象を確認するために React Static をセットアップします。</p> <pre><code class="bash">> npm init -y > yarn add react-static </code></pre> <p>ローカルインストール。</p> <pre><code class="bash">> react-static create ? What should we name this project? rsps_l ? Select a template below... basic Creating new react-static project... ## 略 [✓] Project "rsps_l" created (192s) To get started: cd "rsps_l" yarn start - Start the development server yarn build - Build for production yarn serve - Test a production build locally </code></pre> <p><code>react-static create</code> でサイトを生成します。</p> <pre><code class="javascript">import path from 'path' import axios from 'axios' export default { getRoutes: async () => { const { data: posts } = await axios.get( 'https://jsonplaceholder.typicode.com/posts' ) return [ { path: '/blog', getData: () => ({ posts, }), children: posts.map(post => ({ path: `/post/${post.id}`, template: 'src/containers/Post', getData: () => ({ post, }), })), }, ] }, plugins: [ [ require.resolve('react-static-plugin-source-filesystem'), { location: path.resolve('./src/pages'), }, ], require.resolve('react-static-plugin-reach-router'), require.resolve('react-static-plugin-sitemap'), ], } </code></pre> <p>ちなみに、デフォルトの状態で <code>static.config.js</code> は以上のような状態です。</p> <pre><code class="bash">> yarn build </code></pre> <p>まずはデフォルトの状態でビルドします。</p> <p><a href="https://crieit.now.sh/upload_images/b95116b3c303325226b47d8790b699185ff87a11ad445.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b95116b3c303325226b47d8790b699185ff87a11ad445.jpg?mw=700" alt="デフォルトの状態でビルドした sitemap.xml" /></a></p> <p>図のように、 <code>loc</code>タグ の値がルートディレクトリからの絶対パスになっています。</p> <h2 id="サイトマップの構造について"><a href="#%E3%82%B5%E3%82%A4%E3%83%88%E3%83%9E%E3%83%83%E3%83%97%E3%81%AE%E6%A7%8B%E9%80%A0%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">サイトマップの構造について</a></h2> <p>ここでサイトマップの構造について確認します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.sitemaps.org/protocol.html">sitemaps.org - Protocol</a></li> </ul> <blockquote> <p><code><loc></code> required URL of the page. This URL must begin with the protocol (such as http) and end with a trailing slash, if your web server requires it. This value must be less than 2,048 characters.</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.sitemaps.org/protocol.html">sitemaps.org - Protocol</a></p> </blockquote> <p>「ページのURL。このURLは<strong>プロトコル( <code>http</code> のような)から始まるURLでなければならない</strong>、また、Webサーバが必要とするならばスラッシュ <code>/</code> 終わりにしなければならない。この値は2,048文字未満にすること。」というところでしょうか。</p> <p>……ということは、デフォルトの状態ではよろしくなさそうですね。</p> <h3 id="余談"><a href="#%E4%BD%99%E8%AB%87">余談</a></h3> <p>ちなみに <a target="_blank" rel="nofollow noopener" href="https://www.sitemaps.org/ja/protocol.html">sitemap.org の日本語訳ページ</a>では</p> <blockquote> <p><code><loc></code> 必須 ページの URL です。 ウェブ サーバーによっては、http などのプロトコルから始め、末尾にスラッシュを含める必要があります。 この値は 2,048 文字以下で指定する必要があります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.sitemaps.org/ja/protocol.html">sitemaps.org - プロトコル</a></p> </blockquote> <p>となっていて、 <code>if your web server requires it</code> の部分が先頭に来てしまっているため、個人的には分かりづらいと感じました……。</p> <h2 id="対処"><a href="#%E5%AF%BE%E5%87%A6">対処</a></h2> <p>まずはどうやってカスタマイズできるか確認。</p> <pre><code class="javascript">import path from 'path' import axios from 'axios' export default { getRoutes: async () => { const { data: posts } = await axios.get( 'https://jsonplaceholder.typicode.com/posts' ) return [ { path: '/blog', getData: () => ({ posts, }), children: posts.map(post => ({ path: `/post/${post.id}`, template: 'src/containers/Post', getData: () => ({ post, }), })), }, ] }, plugins: [ [ require.resolve('react-static-plugin-source-filesystem'), { location: path.resolve('./src/pages'), }, ], require.resolve('react-static-plugin-reach-router'), [ // react-static-plugin-sitemap にオプションを追加 require.resolve('react-static-plugin-sitemap'), { getAttributes: route => { // とりあえず route に何が渡ってきているか確認 console.log(route) } } ] ], } </code></pre> <p><code>react-static-plugin-sitemap</code> の部分にコールバックで <code>console.log</code> を仕込んで、 <code>route</code> の中身を確認します。</p> <pre><code class="bash">> yarn build ## 略 { path: 'blog/post/98', template: '__react_static_root__/src/containers/Post', getData: [Function: getData], sitemap: { noindex: false }, data: { post: { userId: 10, id: 98, title: 'laboriosam dolor voluptates', body: 'doloremque ex facilis sit sint culpa\n' + 'soluta assumenda eligendi non ut eius\n' + 'sequi ducimus vel quasi\n' + 'veritatis est dolores' } }, sharedHashesByProp: {}, sharedData: {} } { path: 'blog/post/99', template: '__react_static_root__/src/containers/Post', getData: [Function: getData], sitemap: { noindex: false }, data: { post: { userId: 10, id: 99, title: 'temporibus sit alias delectus eligendi possimus magni', body: 'quo deleniti praesentium dicta non quod\n' + 'aut est molestias\n' + 'molestias et officia quis nihil\n' + 'itaque dolorem quia' } }, sharedHashesByProp: {}, sharedData: {} } { path: 'blog/post/100', template: '__react_static_root__/src/containers/Post', getData: [Function: getData], sitemap: { noindex: false }, data: { post: { userId: 10, id: 100, title: 'at nam consequatur ea labore ea harum', body: 'cupiditate quo est a modi nesciunt soluta\n' + 'ipsa voluptas error itaque dicta in\n' + 'autem qui minus magnam et distinctio eum\n' + 'accusamus ratione error aut' } }, sharedHashesByProp: {}, sharedData: {} } </code></pre> <p>出力結果から、 <code>path</code> が <code>loc</code> に出力されていそうなことが分かりました。</p> <p>今回は諸事情により時間をあまり割くことができないので、手っ取り早く定数でサイトURLを定義してそれを渡してURLを組み立てることにしました。</p> <pre><code class="javascript">import path from 'path' import axios from 'axios' // オリジン情報を定義 const originSiteURL = 'https://example.com/' export default { getRoutes: async () => { const { data: posts } = await axios.get( 'https://jsonplaceholder.typicode.com/posts' ) return [ { path: '/blog', getData: () => ({ posts, }), children: posts.map(post => ({ path: `/post/${post.id}`, template: 'src/containers/Post', getData: () => ({ post, }), })), }, ] }, plugins: [ [ require.resolve('react-static-plugin-source-filesystem'), { location: path.resolve('./src/pages'), }, ], require.resolve('react-static-plugin-reach-router'), [ // react-static-plugin-sitemap にオプションを追加 require.resolve('react-static-plugin-sitemap'), { getAttributes: route => ({ loc: new URL(route.path, originSiteURL).href }) } ] ], } </code></pre> <p>コールバックの戻り値に <code>loc</code> を指定してデフォルトを上書きします。</p> <p>これで <code>yarn build</code> 。</p> <p><a href="https://crieit.now.sh/upload_images/2e9a3e972403cec043fee7d6099c177b5ff87a21b3a53.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2e9a3e972403cec043fee7d6099c177b5ff87a21b3a53.jpg?mw=700" alt="カスタマイズした状態でビルドした sitemap.xml" /></a></p> <p>結果、意図通りプロトコル始まりのサイトURLに書き換えることができました。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="react-static-plugin-sitemap"><a href="#react-static-plugin-sitemap">react-static-plugin-sitemap</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/react-static-plugin-sitemap/v/7.2.0">react-static-plugin-sitemap - npm</a></li> </ul> <h3 id="sitemap.xml"><a href="#sitemap.xml">sitemap.xml</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.sitemaps.org/protocol.html">sitemaps.org - Protocol</a></li> </ul> <h4 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h4> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://blog.app.melvins-nest.com/posts/613831417/">Hugoのsitemap.xmlがlocを相対パスで出力してた - 常にいまいち</a></li> </ul> <p>この記事で「プロトコル始まりではない絶対パスではまずいのでは?」と気付き。</p> <h3 id="URL組み立て"><a href="#URL%E7%B5%84%E3%81%BF%E7%AB%8B%E3%81%A6">URL組み立て</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/16301503/can-i-use-requirepath-join-to-safely-concatenate-urls">node.js - Can I use require("path").join to safely concatenate urls? - Stack Overflow</a></li> </ul> <p>ここで <code>path</code> モジュールではなく ECMAScript の <code>new URL()</code> を使うと良い、というレスを見て採用。</p> arm-band tag:crieit.net,2005:PublicArticle/15156 2019-06-23T23:09:10+09:00 2019-06-23T23:09:43+09:00 https://crieit.net/posts/NodeJS-XML NodeJSでXMLをパースする <p>相変わらず、あちこち手を広げ過ぎてる気がしますが。</p> <h2 id="背景"><a href="#%E8%83%8C%E6%99%AF">背景</a></h2> <p>カラオケ「DAM」のサーバから採点履歴のデータを取得する際、<br /> RESTで返ってくるのがXMLなのでパース方法を調査した。</p> <h2 id="使うライブラリ"><a href="#%E4%BD%BF%E3%81%86%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA">使うライブラリ</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/fast-xml-parser">fast-xml-parser</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/he">he(HTML entities)</a></li> </ul> <h1 id="fast-xml-parser"><a href="#fast-xml-parser">fast-xml-parser</a></h1> <h2 id="パース対象とするデータ形式"><a href="#%E3%83%91%E3%83%BC%E3%82%B9%E5%AF%BE%E8%B1%A1%E3%81%A8%E3%81%99%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E5%BD%A2%E5%BC%8F">パース対象とするデータ形式</a></h2> <pre><code class="xml"><document xmlns="https://www.clubdam.com/app/damtomo/membership/MarkingDxListXML" type="2.2"> <result> <status>OK</status> <statusCode>0000</statusCode> <message/> </result> <data> <totalPage>40</totalPage> <page>1</page> <cdmCardNo>******</cdmCardNo> </data> <list count="5"> <data> <marking requestNo="3809-63" artist="fripSide" contents="black bullet" play="0" reportCommentNo="2252" chartInterval="86" chartStability="75" chartExpressiveness="85" chartVibrateLongtone="85" chartRhythm="86" highPitch="79" lowPitch="56" highTessitura="67" lowTessitura="56" modulation="8" measure="15" sob="1" fall="2" timing="5" longTone="7" vibrato="5" vibratoType="1" vibratoSumSeconds="7.9" averageTotalPoint="80187" averagePitch="74" averageStability="67" averageExpressiveness="74" averageVibrateLongtone="53" averageRhythm="95" lastPoint="92804" date="2019/06/15 **:**:**">90.979</marking> </data> <data> ..... </data> </list> </document> </code></pre> <h2 id="パースオプション"><a href="#%E3%83%91%E3%83%BC%E3%82%B9%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3">パースオプション</a></h2> <h3 id="attributeNamePrefix"><a href="#attributeNamePrefix">attributeNamePrefix</a></h3> <p>パースした際にattributeのprefixにつける文字を指定する。</p> <h3 id="ignoreAttributes"><a href="#ignoreAttributes">ignoreAttributes</a></h3> <p>これをtrueにしているとxmlのattributeを無視してしまう。<br /> 今回はmarkingの属性を取りたいので<strong>false</strong></p> <pre><code class="javascript">var options = { attributeNamePrefix : "@_", attrNodeName: "attr", //default is 'false' textNodeName : "#text", ignoreAttributes : false, ignoreNameSpace : false, allowBooleanAttributes : false, parseNodeValue : true, parseAttributeValue : false, trimValues: true, cdataTagName: "__cdata", //default is 'false' cdataPositionChar: "\\c", localeRange: "", //To support non english character in tag/attribute values. parseTrueNumberOnly: false, attrValueProcessor: a => he.decode(a, {isAttributeValue: true}),//default is a=>a tagValueProcessor : a => he.decode(a) //default is a=>a }; </code></pre> <h2 id="200件(上限)取ってくるコード"><a href="#200%E4%BB%B6%28%E4%B8%8A%E9%99%90%29%E5%8F%96%E3%81%A3%E3%81%A6%E3%81%8F%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89">200件(上限)取ってくるコード</a></h2> <p>1ページに5件ずつあるのでカウンタjで40まで取りに行く。<br /> resultArrayに結果を詰めていって、成功したらレスポンスとして返す。</p> <pre><code class="typescript"> public loadData(req:express.Request,res:express.Response) :void{ (async()=>{ let cdmCardNo = '******'//ユーザ固有のtokenを入れる let url = 'https://www.clubdam.com/app/damtomo/membership/MarkingDxListXML.do?cdmCardNo='+ cdmCardNo let resultArray = [] for(let j = 0 ; j < 40 ; j++){ let response = await fetch(url + '&pageNo=' + (j + 1)) let data = await response.text() //let data = response.json() let xmlData = parser.parse(data ,options); console.log(xmlData) if(xmlData.document.list.data === undefined){ break; } for(let i = 0; i < xmlData.document.list.data.length; i++){ resultArray.push(xmlData.document.list.data[i]) } } res.json(resultArray) })().catch(err=>{ console.error(err) res.sendStatus(500) }) } </code></pre> ckoshien