XML を pydantic で読み取る

xmltodict dict(辞書)として読み取るのも簡単で良いけど、
FastAPI を使う案件なら、データ検証ライブラリ pydantic で構成するクラスとして
読込みたいものである。

pydantic-xml を使う。

pydantic-xml → https://pypi.org/project/pydantic-xml/

サンプル
以下のXML

<?xml version="1.0" encoding="UTF-8" ?>
<Customer>
    <name type="A">山田</name>
    <age>32</age>
    <items type="list">
        <tad>Red</tad>
        <tad>Blue</tad>
    </items>
</Customer>


Customerタグ、nameタグ、ageタグ、itemsタグ、tadタグ
各タグに合わせたクラスを用意して、

from pydantic_xml import BaseXmlModel, attr, element
from typing import List

class Name(BaseXmlModel):
    type: str = attr(name="type")
    value: str
class Age(BaseXmlModel):
    value: int
class Tad(BaseXmlModel):
    value: str
class Items(BaseXmlModel):
    type: str = attr("type")
    tadlist: List[Tad] = element(tag="tad", default=[])
class Customer(BaseXmlModel):
    name: Name = element(tag="name")
    age: Age = element(tag="age")
    items: Items = element(tag='items')
xmlstr = """<?xml version="1.0" encoding="UTF-8" ?>
<Customer>
    <name type="A">山田</name>
    <age>32</age>
    <items type="list">
        <tad>Red</tad>
        <tad>Blue</tad>
    </items>
</Customer>
"""

customer = Customer.from_xml(xmlstr)
print(customer)

print の結果

name=Name(type='A', value='山田') age=Age(value=32) 
 items=Items(type='list', tadlist=[Tad(value='Red'), Tad(value='Blue')])

XMLからJSONへ変換、属性の問題(Pythonの場合)

xmltodict を使い、XML→辞書→JSONダンプという処理という方法において、
XMLタグ属性が付いたXMLは、
属性名に prefix "@" が付いたキーで辞書に変換される。
タグBODY は、テキストであるならば、"#text" がキーの辞書に変換される。

例)

# -*- coding: utf-8 -*-
import xmltodict
import json

xml = """<?xml version="1.0" encoding="UTF-8" ?>
<customer>
    <name type="A">中田</name>
    <age>32</age>
    <items type="list">
        <tad>Red</tad>
        <tad>Blue</tad>
    </items>
</customer>
"""
dict_xml = xmltodict.parse(xml)
res = json.dumps(dict_xml, indent=2, ensure_ascii=False)
print(res)

結果

{
  "customer": {
    "name": {
      "@type": "A",
      "#text": "中田"
    },
    "age": "32",
    "items": {
      "@type": "list",
      "tad": [
        "Red",
        "Blue"
      ]
    }
  }
}

XMLからJSONへ変換、属性の問題(Javaの場合)

XMLのルートタグで属性を付ける場合はJava の場合、
属性であることを示さないけどJSONでkey-value に変換は
先日の
XMLからJSONへの変換 - Oboe吹きプログラマの黙示録
の方法で可能である。

しかし、ルートタグ以外で属性を付けるとこの方法では期待結果にならない。

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <name type="A">中田</name>
    <age>32</age>
    <items>
        <tad>Red</tad>
        <tad>Blue</tad>
    </items>
</customer>

は、

{
  "name" : {
    "type" : "A",
    "" : "中田"
  },
  "age" : "32",
  "items" : {
    "tad" : [ "Red", "Blue" ]
  }
}

type="A" を付けたところが、

  "name" : {
    "type" : "A",
    "" : "中田"
  },

となってしまう。
XmlMapper が読込み変換先のクラスを Object にしている場合は
このようになってしまう。
XMLルートから構造を合わせた 任意のBeanクラスを用意して。Beanクラスへの変換に
しないとならない。

lombok を使って以下のように変換先クラスを準備する。

import java.io.Serializable;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@JacksonXmlRootElement(localName="customer")
public class Customer implements Serializable{

    private CustomerName name;
    
    @JacksonXmlProperty(localName = "age")
    private int age;
    
    @JacksonXmlElementWrapper(localName = "items")
    @JacksonXmlProperty(localName = "tad")
    private List<String> tad;
    
    public Customer(){}
}

属性 type を持つ nameタグに該当するネストのクラスは以下のようにする

import java.io.Serializable;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
import lombok.Data;
@Data
@JacksonXmlRootElement(localName="name")
public class CustomerName implements Serializable{
    @JacksonXmlProperty(localName = "type", isAttribute=true)
    private String type;
    
    @JacksonXmlText
    private String value;
    
    public CustomerName(){}
}

@JacksonXmlProperty で属性であることを指定している。
@JacksonXmlText を付与したフィールド名が、JSONに変換した時のキー名になる。
これで、

XmlMapper xmlMapper = new XmlMapper();
Customer obj = xmlMapper.readValue(xmlString, Customer.class);

DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter("  ", DefaultIndenter.SYS_LF);
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
printer.indentObjectsWith(indenter);

ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writer(printer).writeValueAsString(obj);

System.out.println(json);

の結果は、

{
  "name" : {
    "type" : "A",
    "value" : "中田"
  },
  "age" : 32,
  "tad" : [ "Red", "Blue" ]
}

となる。

XMLからJSONへの変換

Javaで変換する場合とPythonを使って変換する場合を比較してみる。
Javaで変換する場合
xml サンプル

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <name>中田</name>
    <age>32</age>
    <items>
        <tad>Red</tad>
        <tad>Blue</tad>
    </items>
</customer>

Java では、JSONの処理、Jacksonライブラリと同じ Jacksonライブラリの jackson-dataformat-xml を使う。

	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>2.22.1</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-annotations</artifactId>
		<version>2.22</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-core</artifactId>
		<version>2.22.1</version>
	</dependency>
	<dependency>
	    <groupId>com.fasterxml.jackson.datatype</groupId>
	    <artifactId>jackson-datatype-jsr310</artifactId>
	    <version>2.22.1</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.dataformat</groupId>
		<artifactId>jackson-dataformat-xml</artifactId>
		<version>2.22.1</version>
	</dependency>

Java コード例

String xmlString = toStringFromInputStream(inst); // XML⇒ String

XmlMapper xmlMapper = new XmlMapper();
Object obj = xmlMapper.readValue(xmlString, Object.class);

DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter("  ", DefaultIndenter.SYS_LF);
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
printer.indentObjectsWith(indenter);

ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writer(printer).writeValueAsString(obj);

System.out.println(json);

結果、

{
  "name" : "中田",
  "age" : "32",
  "items" : {
    "tad" : [ "Red", "Blue" ]
  }
}

もしも、XMLタグに属性がある場合は、

   <customer type="A">

となっていると結果は以下のようになる。

{
  "type" : "A",
  "name" : "中田",
  "age" : "32",
  "items" : {
    "tad" : [ "Red", "Blue" ]
  }
}

Pythonで変換する場合
Python では、xmltodict を使い、XML→辞書→JSONダンプという処理で簡単にした場合
 (Python でXMLをパースするのによく使われるライブラリは、ElementTree lxml だが、単にJSONに変換したいだけだから xmltodictを選択)

import xmltodict
import json

xml = """<?xml version="1.0" encoding="UTF-8" ?>
<customer>
    <name>中田</name>
    <age>32</age>
    <items>
        <tad>Red</tad>
        <tad>Blue</tad>
    </items>
</customer>
"""

dict_xml = xmltodict.parse(xml)
res = json.dumps(dict_xml, indent=2, ensure_ascii=False)

print(res)

結果、

{
  "customer": {
    "name": "中田",
    "age": "32",
    "items": {
      "tad": [
        "Red",
        "Blue"
      ]
    }
  }
}

XMLタグに属性がある場合は、

   <customer type="A">

属性 type="A" が付いていた場合の結果は、
属性名に prefix "@" が付いたキーで辞書に変換される。

{
  "customer": {
    "@type": "A",
    "name": "中田",
    "age": "32",
    "items": {
      "tad": [
        "Red",
        "Blue"
      ]
    }
  }
}

Excelセルの値に対して行の背景色をつける

セルが特定の値の時だけ自動で行の色をつける設定、
(1)対象の表エリアを選択状態にします。

(2)Excelリボン「ホーム」から「条件付き書式」をクリックして「新しいルール(N)...」を選択

(3)「数式を使用して、書式設定するセルを決定」を選択して
   行の背景色を適用開始させる行の目的のセルを選択する


ここで注意が必要なのは、数式で指定するセルは選択範囲の中の最上部の行のセルであることである。
その選択セルの列の行から選択範囲最終行までを適用範囲にしたいので、
このサンプルでは、$B$2 を選択している。

(4)数式として ”=”で「~と値が等しい」を設定
  このサンプルでは、"D" という値と等しい場合にしたいので、(”)ダブルクォーテーションで囲んで
  続けて ="D"を入力するが、

このままでは、絶対指定の $2= 2行目だけ、になってしまうので、
=$B2="D" にしなければならない。
ここでカーソル矢印キーで位置を移動しようとすると次のように、思わぬものが入ってしまい
非常にイライラする。

F2キーを押してからカーソルキーを押すと思い通りに操作できて、以下の通りにする。

(5)「書式..」ボタンを押して、セルの塗りつぶしを設定する。


(6)最後にOKで設定が完成

設定したルールを確認する、

ここで、「このワークシート」を選択すれば見れる

表の設定結果は、、

PostgreSQL 配列型、配列から除去した値を求める。

WITH UPDATE ~ RETURNING を書いて、UPDATE実行前のテーブルと
INNER JOIN 結合するテクニックを使用する。

例) foo テーブルの id=3 の配列型カラム tname の先頭を除去した時の
除去された先頭の値を求める。

WITH afters AS (
    UPDATE foo SET
    tname = tname[2:array_length(tname, 1)]
    WHERE id = 3
    RETURNING id, tname
)
SELECT 
  b.id
, b.tname[1] AS pick_value   -- 除去された先頭の値
, a.tname    AS after_tname    -- 先頭除去後の配列
FROM foo b 
INNER JOIN afters a ON a.id = b.id

UPDATE 実行前の値と実行後の値の抽出

PostgreSQL の場合です。
WITH 句に、UPDATE を書いて RETURNING 句で実行後の値を返すようにして
以下のサンプルのようにする。

サンプル)user_name = 'C' のポイントを +10 する。
INNSER JOIN で結合して、変更前と変更後の値をクエリで求める。

WITH afters AS (
    UPDATE items SET
    point = point + 10
    WHERE user_name = 'C'
    RETURNING id, point
)
SELECT 
  before.id
, before.point  AS before_point    -- 変更前の値
, aft.point     AS after_point     -- 変更後の値
FROM items before
INNER JOIN afters aft ON before.id = aft.id