0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JATSのXML Catalogを準備してvscode-xmlで使う

Last updated at Posted at 2025-06-29

JATS XMLのDTDなどはリモートにあるのですが、XML Catalogという仕掛けを使うとローカルに置いたモノを使えます。

XML Catalogは、外部実体の場所をいわば「リダイレクト」する仕組みで、リダイレクト先をローカルファイルにもできる。そうすると、JATS XML文書が指してるDTDなどが記述上はリモートにあっても、オフラインで作業できます。例えば、Visual Studio CodeのXML拡張(以下、vscode-xml)を使って、フェリーの船上などオフラインでも仕事できます。

そうするために、次の3つが必要です:

  • 1度は、JATSのDTDなどをローカルにダウンロードする
  • XML Catalogを用意する
  • vscode-xmlがXML Catalogを参照するように設定する

先にまとめると:

  • 次のページからJATS-Publishing-1-1-MathML2-DTD.zipをダウンロードすればよい…ようだ
    https://public.nlm.nih.gov/projects/jats/publishing/1.1/
  • このZIPにXML Catalogも含まれてる
    • xml:base付き: catalog-jats-v1-1-with-base.xml
    • xml:baseなし: catalog-jats-v1-1-no-base.xml
  • そのXML Catalogを使うように、ユーザー設定またはワークスペース設定settings.jsonに、例えば次のように設定する
"xml.catalogs": [
   "catalog-jats-v1-1-with-base.xml"
]

「…ようだ」というのは、特に後述のバラエティについて、J-STAGEに説明を見つけられなかったからです。

JATSのDTDなどをローカルに持ってくる

JATSにはいくつかバージョン/リビジョンがあって、また、同じバージョンでも、表(table)や数式(MathMl)の組み合わせでバリエーションがあるとのこと1

例えばバージョン1.1には次の4つのバリエーションがあるそうです:

  • Publishing Tag Set using XHTML tables and MathML 2.0
  • Publishing Tag Set using XHTML tables and MathML 3.0
  • Publishing Tag Set using both XHTML tables and OASIS Exchange CALS tables with MathML 2.0
  • Publishing Tag Set using both XHTML tables and OASIS Exchange CALS tables with MathML 3.0

どのバリエーションも、次のURLのページからZIPでダウンロードできます:

で、どれなんだろう?

これとは別に、後述のスクリプトでダウンロードしたものと比べてみると、J-STAGEのJATSは、Publishing Tag Set using XHTML tables and MathML 2.0が該当しているようです。

そこで、J-STAGEのJATSのDTD他は、そのページからJATS-Publishing-1-1-MathML2-DTD.zipをダウンロードすればよいようです。

XML Catalogを用意する

前記ダウンロードしたZIPファイルにはXML Catalogも2種類含まれています:

  • xml:base付き
  • xml:baseなし

xml:baseは、HTMLのbase要素みたいに、相対URL(というかURI)の基点となるURLを指定する属性です。JATSのDTDなどは1つのフォルダ/ディレクトリの下に階層的に配置できるようになってます。このフォルダ/ディレクトリを変えたら、xml:baseの値だけを変えればよい…という仕組みです。

vscode-xmlがXML Catalogを参照するように設定する

vscode-xmlはXML Catalogに対応しています。前記ダウンロードできたJATSのXML CatalogはDTDでバリデート(validate、妥当性検証)する方式なので、次のページのXML catalog with DTDの項に従ってVisual Studio Codeのsettings.jsonに設定します:

前記ダウンロードしたxml:base付きのXML Catalogファイルをそのまま使えばcatalog-jats-v1-1-with-base.xmlというファイル名ですから

"xml.catalogs": [
   "catalog-jats-v1-1-with-base.xml"
]

ですね。

setting.jsonにはユーザー設定とワークスペース設定があります23。後で変えられますし、お試しであればユーザー設定ワークスペース設定にしておくのがよいかと。

JATSのDTDなどを自分でダウンロードしてXML Catalogも作ってみる

次のPythonのコードは、指定されたJATS XML文書をバリデートしつつ、DTDなどをファイルとして所定のフォルダ構造の下に保存し、それに基づいてXML Catalogファイルを作ります。こうしてダウンロードしたモノと比較して、「Publishing Tag Set using XHTML tables and MathML 2.0が該当している」と言いました。

つまり、確認用の一時的なスクリプトです。スクリプトは、調べてすでに分かってることを前提に、サボったりフォルダ構造を作ったりしてます:

  • J-STAGEのJATSのDTDなどのパスはhttps://www.jstage.jst.go.jp/dtds/1.1/までは共通
  • 実体宣言は相対パスで書かれている

実体宣言はこんなふうに書かれてました。

<!ENTITY % JATS-ali-namespace.ent
                        PUBLIC
"-//NLM//DTD JATS ALI Namespace Module v1.1 20151215//EN"
"JATS-ali-namespace1.ent"                                            >

一方、JATS XML文書のDOCTYPE宣言は次のようにフルパスです。

<!DOCTYPE article
  PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Publishing DTD v1.1 20151215//EN"
  "https://www.jstage.jst.go.jp/dtds/1.1/JATS-journalpublishing1.dtd"
>

そこまで分かってると、次のようなスクリプトでよいかと:

from lxml import etree
import argparse
import os
import sys
import requests

arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("file", help="XML file to validate.")
arg_parser.add_argument("-e", "--entity-folder", help="Folder to store remote entities.", default='entity')
arg_parser.add_argument("-c", "--catalog-file", help="Catalog file to store entity references.", default='catalog.xml')
args = arg_parser.parse_args()
print(f'start parsing {args.file}')
# sys.exit(0)

count = 0
entity_folder = args.entity_folder
catalog_file = args.catalog_file
path = 'https://www.jstage.jst.go.jp/dtds/1.1/'
path_length = len(path)

with open(catalog_file, 'w') as catalog:
    catalog.write(f'<?xml version="1.0" encoding="UTF-8"?>\n')
    catalog.write(f'<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">\n')

class DTDResolver(etree.Resolver):
    def resolve(self, url, id, context):
        global entity_folder
        global count
        global catalog_file
        global path_length
        if not os.path.exists(entity_folder):
            os.makedirs(entity_folder)
        if url.startswith('http'):
            count += 1
            res = requests.get(url)
            # 読み込んだリモートのファイルを保存しておく
            file_name = url[path_length:] if url.startswith(path) else os.path.basename(url)
            file_name_split = file_name.split('/')
            if (len(file_name_split) > 1):
                entity_folder_x = os.path.join(entity_folder, *file_name_split[:-1])
                if not os.path.exists(entity_folder_x):
                    os.makedirs(entity_folder_x)
                file_name = file_name_split[-1]
            else:
                entity_folder_x = entity_folder
            with open(f"{entity_folder_x}/{file_name}", mode='w') as f:
                f.write(res.text)
            # print("'%s','%s,'%s'" % (url, id, file_name))
            if os.path.exists(catalog_file):
                with open(catalog_file, 'a') as catalog:
                    catalog.write(f'<system systemId="{url}" uri="./{entity_folder_x}/{file_name}" />\n')
            else:
                with open(catalog_file, 'w') as catalog:
                    catalog.write(f'<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">\n')
                    catalog.write(f'<system systemId="{url}" uri="./{entity_folder_x}/{file_name}" />\n')
            # リモートのDTDを読み込んで返す
            return self.resolve_string(res.text, context)
        else:
            f = open(url, 'r')
            return self.resolve_file(f, context)

parser = etree.XMLParser(dtd_validation=True, no_network=False)
parser.resolvers.add( DTDResolver() )

try:
    tree = etree.parse(args.file, parser=parser)
except etree.XMLSyntaxError as e:
    print(f'etree.XMLSyntaxError: {e}')
finally:
    print(f'Validation completed.')
    print(f'Loaded {count} remote entities and stored under the "{entity_folder}" folder.')
    with open(catalog_file, 'a') as catalog:
        catalog.write(f"</catalog>\n")
    print(f'Catalog file "{catalog_file}" has been created with the list of entities.')

前記ZIPに含まれているXML CatalogはPUBLIC IDとファイルを対応付けるものでした。このスクリプトはSYSTEM IDとファイルを対応付けるものです。

  1. General Introduction - Journal Publishing Tag Library NISO JATS Version 1.1 (ANSI/NISO Z39.96-2015) https://xml-sch.com/jats/tag-library/ver2/1.1-J/chapter/gen-intro.html

  2. User and workspace settings https://code.visualstudio.com/docs/configure/settings

  3. VSCodeにおけるsettings.jsonの優先度について / Cursorも対応 https://qiita.com/tatsuyayamakawa/items/df7e5b1b0d7c336af124

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?