UI AutomationでExcelのセルを操作してみたかった(未完)

メモ程度。

このツイートの内容の確認に使用したコード。

<#
.Synopsis
# UI Automation でExcelのセルの値を取得するサンプル
## 前提条件
- Excelを起動し、何かブックを開いていること
- Windows 10 の Windows PowerShell ISE で実行すること
#>

# 実行に必要なアセンブリ類のロード
using assembly  UIAutomationClient
using assembly  UIAutomationTypes
using assembly  UIAutomationClientSideProviders
using namespace System.Windows.Automation

# Excelのウィンドウを取得。
# 今のデスクトップのルートの子どもから「XLMAIN」というクラス名の要素を探索。
[AutomationElement]$uiaXl =
    [AutomationElement]::RootElement.FindFirst(
        [TreeScope]::Children,
        [PropertyCondition]::new([AutomationElement]::ClassNameProperty, 'XLMAIN')
    )

# 取得したExcelのウィンドウ配下からテーブルとしての機能を持つ要素を探索。
[AutomationElement]$uiaCellTable = 
    $uiaXl.FindFirst(
        [TreeScope]::Descendants, 
        [PropertyCondition]::new([AutomationElement]::IsTablePatternAvailableProperty, $true)
    )

# テーブルとしての機能を使えるようにする。
[TablePattern]$ptnTable = $uiaCellTable.GetCurrentPattern([TablePattern]::Pattern)

# 今見えている範囲の左上から3,3の位置の要素を取得する(行・列見出しも含めて数える/左上がA1でない場合はB2ではない)。
[AutomationElement]$uiaB2 = $ptnTable.GetItem(2, 2)
<# 持っている機能の確認
PS > $uiaB2.GetSupportedPatterns()
   Id ProgrammaticName                       
   -- ----------------                       
10002 ValuePatternIdentifiers.Pattern        
10007 GridItemPatternIdentifiers.Pattern     
10010 SelectionItemPatternIdentifiers.Pattern
10013 TableItemPatternIdentifiers.Pattern    
10014 TextPatternIdentifiers.Pattern
#>

# 選択する機能
[SelectionItemPattern]$ptnSel = $uiaB2.GetCurrentPattern([SelectionItemPattern]::Pattern)
$ptnSel.Select() # セルの選択は可能

# 値の取得・設定をする機能
[ValuePattern]$ptnVal = $uiaB2.GetCurrentPattern([ValuePattern]::Pattern)
$ptnVal.Current.Value # セルに表示されている値を出力

<# SetValueでエラーは出ないけど表示に反映されない
$ptnVal.SetValue('Hoge')
$ptnVal.Current.Value # ここの値では反映されている
#>

型を検索する PowerShell 関数(親クラス→子クラス)

はじめに

ネット環境無しで PowerShell を弄っているとたまに起こるのが、「引数に何を渡せば良いのかわからない」問題です。

Get-Member コマンドレットなどで各種メンバーの定義は確認できますが、引数の型が抽象的な型になっていて、具体的な型がわからない、という問題です。

例:System.DateTimeToString メソッド

PS >[datetime]::Now.ToString.OverloadDefinitions

string ToString()
string ToString(string format)
string ToString(System.IFormatProvider provider)
string ToString(string format, System.IFormatProvider provider)
string IFormattable.ToString(string format, System.IFormatProvider formatProvider)
string IConvertible.ToString(System.IFormatProvider provider)

System.IFormatProvider って具体的に何……?」という問題です。

型名の先頭にIが付いていることからインターフェイスということはわかりますが、これだけではその先に繋がりません。

Microsoft Docs を見られれば、派生が書いてあるのでそれでOKなのですが……。 IFormatProvider Interface (System) | Microsoft Docs

対策の方針

今使えるすべての型の中から、該当する型の子クラス(やや不正確な表現)を探索する。

System.AppDomain を使えばロード済みアセンブリを取得できるので、さらにそのアセンブリ内の型を列挙すれば、使えるすべての型を取得できる。

Type.IsAssignableFrom(Type) Method (System) | Microsoft Docs を使えば、該当する型の子クラスかどうかも判定できる。

作成した関数

github.com

<#
.SYNOPSIS
Search type from loaded assemblies.
ロード済みアセンブリー内から型を検索します。
.DESCRIPTION
Search type by root type from Loaded assemblies.
ロード済みアセンブリー内から、指定した型及びサブクラスを検索します。
.EXAMPLE
[System.IFormatProvider] | Search-Type
IsPublic IsSerial Name               BaseType     
-------- -------- ----               --------     
True     False    IFormatProvider                 
True     True     CultureInfo        System.Object
True     True     DateTimeFormatInfo System.Object
True     True     NumberFormatInfo   System.Object
.INPUTS
System.Type
.OUTPUTS
System.Type
By default, return all public type in loaded assemblies.
#>
function Search-Type {
    [CmdletBinding()]
    [OutputType([type])]
    param (
        [Parameter(ValueFromPipeline=$true)]
        [type]$RootType = [System.Object]
        ,
        [SupportsWildcards()]
        [string]$Name
        ,
        [SupportsWildcards()]
        [string]$Namespace
        ,
        [SupportsWildcards()]
        [string]$FullName
    )
    begin {
        # Declare foreach variables for IntelliSense.
        [System.Reflection.Assembly]$asm = [type]$t = $null
    }
    process {
        foreach ($asm in [System.AppDomain]::CurrentDomain.GetAssemblies()) {
            foreach ($t in $asm.GetTypes()) {
                # Public only.
                if (-not $t.IsPublic) { continue }

                if (-not $RootType.IsAssignableFrom($t)) { continue }

                # Name check.
                if (-not [string]::IsNullOrEmpty($Name)      -and ($t.Name      -notlike $Name)      ) { continue }
                if (-not [string]::IsNullOrEmpty($Namespace) -and ($t.Namespace -notlike $Namespace) ) { continue }
                if (-not [string]::IsNullOrEmpty($FullName)  -and ($t.FullName  -notlike $FullName)  ) { continue }

                Write-Output -InputObject $t
            }
        }
    }
}

使用例1

前述のSystem.IFormatProviderを探したい場合は以下のようにする。

PS> [System.IFormatProvider] | Search-Type

IsPublic IsSerial Name               BaseType     
-------- -------- ----               --------     
True     False    IFormatProvider                 
True     True     CultureInfo        System.Object
True     True     DateTimeFormatInfo System.Object
True     True     NumberFormatInfo   System.Object

さらに以下のようにすれば、FullNameも分かるのであとはある程度何とかなる。

PS> [System.IFormatProvider] | Search-Type | Select-Object -ExpandProperty FullName

System.IFormatProvider
System.Globalization.CultureInfo
System.Globalization.DateTimeFormatInfo
System.Globalization.NumberFormatInfo

使用例2

どんなコレクションがあるんだっけ…?と発作的に調べてくなったら以下のようにする(ワイルドカード指定のサンプル)。

PS> [System.Collections.IEnumerable] | Search-Type -Namespace System.Collections*

IsPublic IsSerial Name                             BaseType                                                     
-------- -------- ----                             --------                                                     
True     True     CollectionBase                   System.Object                                                
True     True     DictionaryBase                   System.Object                                                
True     True     ReadOnlyCollectionBase           System.Object                                                
True     True     Queue                            System.Object                                                
True     True     ArrayList                        System.Object                                                
True     True     BitArray                         System.Object                                                
True     True     Stack                            System.Object                                                
True     True     Hashtable                        System.Object                                                
True     False    ICollection                                                                                   
True     False    IDictionary                                                                                   
True     False    IEnumerable                                                                                   
True     False    IList                                         
...

めもがき

曲線の始点終点どっちが近いのかな?判断

曲線の始点終点どっちが近いのかな?判断 - C#ATIA

関連のなにか。

実環境が無いのでスペルミス上等ということで。

Option Explicit

Private Sub Sample(crv As INFITF.Reference, pln As INFITF.Reference)
    
    Dim measureCrv As SPATypeLib.Measurable
    Set measureCrv = GetMeasurable(crv)
    Select Case measureCrv.GeometryName
        Case CatMeasurableCurve, CatMeasurableCircle, CatMeasurableLine 'OK
        Case Else: Err.Raise 13
    End Select
    
    Dim A As Double, B As Double, C As Double, D As Double
    ComputePlaneEquationABCD pln, A, B, C, D
    
    Dim onCrvPointsCoordinates(0 To 8) As Variant
    Call asDisp(measureCrv).GetPointsOnCurve(onCrvPointsCoordinates)
    
    'Start point coordinates
    Dim ptX As Double, ptY As Double, ptZ As Double
    ptX = onCrvPointsCoordinates(0)
    ptY = onCrvPointsCoordinates(1)
    ptZ = onCrvPointsCoordinates(2)
    
    '面の方程式と、始点から伸びる直線上の点の座標の方程式を解く
    
    'Origin point coordinates as ptX, ptY, ptZ
    'Plane projection point coordinates as prjX, prjY , prjZ
    'Distance of origin to projection as L
    
    'A * prjX + B * prjY + C * prjZ = D
    'prjX = ptX + A * L
    'prjY = ptY + B * L
    'prjZ = ptZ + C * L
    
    Dim L As Double
    L = (D + A * ptX + B * ptY + C * ptZ) / _
        (A ^ 2 + B ^ 2 + C ^ 2)
    Dim prjX As Double, prjY As Double, prjZ As Double
    prjX = ptX + A * L
    prjY = ptY + B * L
    prjZ = ptZ + C * L
    
    Debug.Print ComputeScalar(ptX - prjX, ptY - prjY, ptZ - prjZ)
    
End Sub

'Plane Equation
'Ax + By + Cz = D
Private Sub ComputePlaneEquationABCD( _
              iPlane As INFITF.Reference, _
        ByRef oA As Double, _
        ByRef oB As Double, _
        ByRef oC As Double, _
        ByRef oD As Double _
    )
    
    Dim measurePln As SPATypeLib.Measurable
    Set measurePln = GetMeasurable(iPlane)
    Select Case measureCrv.GeometryName
        Case CatMeasurablePlane 'OK
        Case Else: Err.Raise 13
    End Select
    
    Dim planeComponents(0 To 8) As Variant
    Call asDisp(measurePln).GetPlane(planeComponents)
    
    
    Dim x1st As Double, y1st As Double, z1st As Double
    x1st = planeComponents(3)
    y1st = planeComponents(4)
    z1st = planeComponents(5)
    
    Dim x2nd As Double, y2nd As Double, z2nd As Double
    x2nd = planeComponents(6)
    y2nd = planeComponents(7)
    z2nd = planeComponents(8)
    
    Dim planeNomalDirection() As Double
    planeNomalDirection = CrossProduct( _
        x1st, y1st, z1st, _
        x2nd, y2nd, z2nd _
    )
    
    Let oA = planeNomalDirection(0)
    Let oB = planeNomalDirection(1)
    Let oC = planeNomalDirection(2)
    
    Dim plnOriginX As Double, plnOriginY As Double, plnOriginZ As Double
    plnOriginX = planeComponents(0)
    plnOriginY = planeComponents(1)
    plnOriginZ = planeComponents(2)
    
    Let oD = ComputeScalar(plnOriginX, plnOriginY, plnOriginZ)
End Sub

'ベクトルの外積
Public Function CrossProduct( _
        iX1 As Double, iY1 As Double, iZ1 As Double, _
        iX2 As Double, iY2 As Double, iZ2 As Double _
    ) As Double() 'Double(0 To 2)
    
    Const X = 0, Y = 1, Z = 2
    Dim resultVector(0 To 2) As Double
    resultVector(X) = iY1 * iZ2 - iZ1 * iY2
    resultVector(Y) = iZ1 * iX2 - iX1 * iZ2
    resultVector(Z) = iX1 * iY2 - iY1 * iX2
    
    Let CrossProduct = resultVector
End Function

'ベクトルから大きさを求める
Public Function ComputeScalar( _
                 iX As Double, _
                 iY As Double, _
        Optional iZ As Double = 0# _
    ) As Double
    
    Let ComputeScalar = VBA.Math.Sqr(iX ^ 2 + iY ^ 2 + iZ ^ 2)
    
End Function

'てきとう
Public Function GetMeasurable(iRef As INFITF.Reference) As SPATypeLib.Measurable
    Dim doc As INFITF.Document
    Set doc = GetModelElement(iRef).Document
    Dim spaWb As SPATypeLib.SPAWorkbench
    Set spaWb = doc.GetWorkbench("SPAWorkbench")
    Set GetMeasurable = spaWb.GetMeasurable(iRef)
End Function

'[選択要素からドキュメントを取得する - C#ATIA](http://kantoku.hatenablog.com/entry/2016/04/07/183709 "選択要素からドキュメントを取得する - C#ATIA")
Public Function GetModelElement(iAnyObject As INFITF.AnyObject) As INFITF.ModelElement
    Set GetModelElement = iAnyObject.GetItem("ModelElement")
End Function

'disable VBE static syntax check.
Private Function asDisp(o As INFITF.CATBaseDispatch) As INFITF.CATBaseDispatch
    Set asDisp = o
End Function

参考

選択要素からドキュメントを取得する - C#ATIA
GetDirectionが上手く行かない2 - C#ATIA

何度でもよみがえるメモ帳(ネタ)

とあるソフトを間違えて閉じてしまうことが頻発したため、終了してもゾンビのごとく蘇るようにしてみた。

もっと良い方法がありそう……。

# メモ帳を起動してイベントを購読する処理
[scriptblock]$startNotepad = {
    # メモ帳を起動
    [Diagnostics.Process]$notepadProc = 
        Start-Process -FilePath notepad -PassThru
    # 起動を待機
    $notepadProc.WaitForInputIdle() > $null
    # イベントを通知させる
    $notepadProc.EnableRaisingEvents = $true

    # Exited(終了時)のイベントを購読開始
    Register-ObjectEvent -InputObject $notepadProc -EventName Exited
}

# 無限ループ
while ($true) {
    # メモ帳を起動
    $startNotepad.Invoke()
    
    # 何かしらイベントが起きるまで待つ
    Wait-Event

    # 発生したイベント情報を取得して破棄(破棄しないと`Wait-Event`で待機しない)
    Get-Event | Remove-Event
}

PowerShellなり、Windows PowerShell ISEなりに貼り付けて実行すると、何回閉じても復活するメモ帳が起動する。

終了したい場合は、PowerShellのウィンドウでCtrl+Cを押すか、PowerShellそのものを終了する。


190111追記

そもそもイベントにする必要が無かった。

# 無限ループ
while ($true) {
    # メモ帳を起動
    [Diagnostics.Process]$notepadProc = Start-Process -FilePath notepad -PassThru
    # 終了を待機
    $notepadProc.WaitForExit()
}

【VBA実験】何回NotしてもTrueになるTrueを作る

あけましておめでとうございます。 今年もよろしくお願いいたします。

前書き

私が職場で使っているVBAのライブラリの中には、「何回NotしてもTrueになるTrue」を返すAPIを持つものがあります(一般には使われていないライブラリ)。

この「何回NotしてもTrueになるTrue」をVBAだけで作成する方法の記事となります。
そのため、実際のコードに役立つことはほぼ無いでしょう。

VBAのBoolean周りの動作

本来、VBAのTrueは、16bitの符号付き整数で表すと-1になります(VBAの16進数表現で&HFFFF)。
これを、Not演算子でビット単位で否定すると0&H0000)、すなわちFalseになります。

対して、問題のTrueは16bitの符号付き整数で表すと1&H0001)になっており、これに対してNot演算子を使用すると-2&HFFFE)、0ではないためTrueになります。

コード

以下のコードにあるGetTrueNotTrue()関数の返り値が「何回NotしてもTrueになるTrue」になります。

Private Type intType
    Value As Integer
End Type

Private Type boolType
    Value As Boolean
End Type

Function GetTrueNotTrue() As Boolean
    Dim i As intType
    i.Value = 1
    
    Dim b As boolType
    LSet b = i
    
    Let GetTrueNotTrue = b.Value
    
End Function

コードの解説

普通に数値をBoolean型の変数に代入しても、自動型変換が行われ、VBA本来のTrue・Falseとして代入されてしまいます。
今回は数値のバイナリ表現を保ったまま、Boolean型の変数に代入したいため、別の方法をとる必要がありました。

上記のコードでは、ユーザー定義型のバイナリコピーができるLSetステートメントを使用し、Integer型の1のバイナリ表現をBoolean型にコピーしています。

Private Type intType
    Value As Integer
End Type

Private Type boolType
    Value As Boolean
End Type

で入れ物となるユーザー定義型を定義します (VBAではIntegerもBooleanも2バイトの領域を必要とします)。

    Dim i As intType
    i.Value = 1

でInteger型の1を設定、

    Dim b As boolType
    LSet b = i

で別のユーザー定義型にバイナリコピーしています。

動作の確認

Private Sub Sample()
    Debug.Print GetTrueNotTrue()        '-> True
    Debug.Print Not GetTrueNotTrue()    '-> True
    Debug.Print CInt(GetTrueNotTrue())  '-> 1
End Sub

無事、「何回NotしてもTrueになるTrue」を作成できました。

タスクバーに通知を表示するPowerShellスクリプト

小ネタ

以下のようなメッセージを簡単に表示できるPowerShellスクリプト
表示はそれぞれWIn 8.1 Win10

f:id:imihito:20180822223221p:plain

f:id:imihito:20180822223007p:plain

PowerShellスクリプト

本体。適当な場所に「○○.ps1」として保存する。

param(
    [string]$Prompt = 'メッセージ',
    [string]$Title  = '通知',
    $CallBack = ''
)

Add-Type -AssemblyName System.Windows.Forms, System.Drawing

function Show-NotifyIcon {
    param(
        [string]$Prompt = 'メッセージ',
        [string]$Title  = '通知',
        [scriptblock]$CallBack = {}
    )
    
    [Windows.Forms.NotifyIcon]$notifyIcon =
        New-Object -TypeName Windows.Forms.NotifyIcon -Property @{
            BalloonTipIcon  = [Windows.Forms.ToolTipIcon]::Info
            BalloonTipText  = $Prompt
            BalloonTipTitle = $Title
            Icon    = [Drawing.SystemIcons]::Information
            Text    = $Title
            Visible = $true
        }
    
    # イベント定義
    $notifyIcon.add_BalloonTipClicked( $CallBack )
    
    [int]$timeout = 3 # sec

    [DateTimeOffset]$finishTime = 
        [DateTimeOffset]::UtcNow.AddSeconds( $timeout )

    $notifyIcon.ShowBalloonTip( $timeout )
    
    # そのままだとイベントが走らない&すぐに消えてしまうので適当wait
    while ( [DateTimeOffset]::UtcNow -lt $finishTime ) {
        Start-Sleep -Milliseconds 1
    }
    $notifyIcon.Dispose()
}

$parameters = $MyInvocation.BoundParameters
$parameters.CallBack = [scriptblock]::Create( $CallBack )
Show-NotifyIcon @parameters

使い方

引数1:表示するメッセージ
引数2:タイトル
引数3:クリックされたときのコールバック処理
を文字列で渡す。

バッチ

powershell.exe -Sta -NoProfile -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File 上記のps1ファイル メッセージ タイトル コールバック処理

VBA

VBAならWindows API使えば?という話はさておく。

Sub ShowNotifySample()
    Const NotifyScriptPath = "保存したスクリプト(ps1ファイル)の保存場所"
    Const PsCommandLineBase = "powershell.exe -Sta -NoProfile -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File """
    Const WQuoteSpaceWQuote = """ """
    
    '表示するメッセージ及びタイトル
    Dim notifyMessage As String
    notifyMessage = "めっせーじ"
    Dim notifyTitle As String
    notifyTitle = "たいとる"
    
    'クリックされた時の処理(PowerShellスクリプト)
    'エクスプローラーでエクセルの場所を開く
    Dim callBackPsScript As String
    callBackPsScript = "explorer.exe " & Excel.Application.Path
    
    Dim execCmd As String
    execCmd = PsCommandLineBase & NotifyScriptPath & WQuoteSpaceWQuote & _
              notifyMessage & WQuoteSpaceWQuote & _
              notifyTitle & WQuoteSpaceWQuote & _
              callBackPsScript & """"
    
    Call VBA.Shell(execCmd, vbHide)
    
End Sub

im@sparqlを触ってみる その3 - クエリの内容を理解する

imihito.hatenablog.jp

の続き。

実際にちょこちょこ触って、クエリの雰囲気を掴めてきたので、自分なりの理解の仕方を書いておく(一部間違えているかも)。

用語

Resource Description Framework - Wikipedia

略称RDF。 データベース(im@sparql)内のデータの構成を示すもの。

主語(subject)・述語(predicate)・目的語(object)の3要素(トリプル)で関係情報を示す。 (リレーショナルデータベースでは無い=NoSQLと考えて良いのだろうか?)

無理矢理オブジェクトに当てはめると、「主語」はオブジェクト本体、「述語」はプロパティ名及び型、「目的語」はプロパティの値、のようなものと解釈した。

主語・述語にはURI、目的語には値やURIなどが指定される(目的語がURIの場合はオブジェクト型のプロパティのような形で階層構造になる)。

SPARQL - Wikipedia

RDFに対するクエリ言語。 SQL風の文法らしい(私自身はSQLを碌に触ったことないので不明)。

今回の記事で扱うクエリはこの言語で書かれたものになる。

どことなくXPathに近い雰囲気も感じる。

クエリ

今回は以下のクエリの内容を理解する。

PREFIX

PREFIX schema: <http://schema.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/URIs/imas-schema.ttl#>

C#で言う名前空間エイリアス

主語・述語・目的語の一部はURIで示されるが、いちいち書くのは大変なのでエイリアスを設定するのが一般的。

例えば

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

http://www.w3.org/1999/02/22-rdf-syntax-ns#rdf:で省略できるようになり、 <http://www.w3.org/1999/02/22-rdf-syntax-ns#type>rdf:type と書けるようになる。

SELECT

SELECT *

SELECT ?変数名とすることで、結果として出力する値(変数)を指定できる。 今回は*が指定されているため、次のWHERE内で使用された変数全てが結果として出力される。

WHERE-1

WHERE内は文が多いので、前後に分けて考えてみる。

WHERE {
  ?s rdf:type imas:ScriptText;
     imas:Source ?source;
     schema:text ?text.

WHERE内には主語 述語 目的語と書き、書かれた条件に当てはまるものが探索される。

まず?s rdf:type imas:ScriptTextとなっているため、主語(?s)は任意で、述語がrdf:type、目的語がimas:ScriptTextとなるものが探索され、結果が?s変数に入る。

3行目には2個しか要素が書かれていないが、前の行が;で終わっているため、主語は前の行と同じもの指定する、となる(4行目も同じ)。

3行目、4行目では2行目で見つかった主語(?s)についてimas:Sourceschema:textの情報を?source変数、?text変数に格納している。

WHERE-2

  ?source schema:name ?name;
     filter(regex(str(?name),"千早"))
}

1行目は先ほどと同じで、?sourceについてschema:nameの情報を?name変数に格納している。

2行目ではフィルター、特定の要素のみの抽出が行われている。

filterはかっこ内の評価結果がTrueとなるものだけを結果として出力する。

中の式は、?name変数をstr()関数で文字列化し、regex正規表現マッチ)で"千早"と一致するものだけがTrueとなる。

WHEREまとめ

出力される変数(結果)

  • rdf:typeimas:ScriptTextとなる要素?s
  • ?simas:Sourceを示す?source
  • ?sschema:textを示す?text
  • ?sourceschema:nameを示す?name

ただし、?name"千早"に一致するもののみ。

Order By

order by ?text

?text変数の値でソート。

自分なりの解釈まとめ

はてなブログだとシンタックスハイライトが効かないので読みにくいけれど……。

# 使用する名前空間の宣言
PREFIX schema: <http://schema.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX imas: <https://sparql.crssnky.xyz/imasrdf/URIs/imas-schema.ttl#>

# WHERE内の全変数出力
SELECT *
WHERE {
  # rdf:type が imas:ScriptText のものを ?s に入れて
  ?s rdf:type imas:ScriptText;
     # $s の imas:Source を ?source に
     imas:Source ?source;
     # $s の schema:text を ?text に入れる
     schema:text ?text.
  # ?source の schema:name を ?name に入れて
  ?source schema:name ?name;
     # ?name が "千早" と一致するものだけを抽出
     filter(regex(str(?name),"千早"))
}order by ?text # ?text の値でソート

参考サイト

sparql.crssnky.xyz

im@sparql.doc

SPARQL 1.1クエリ言語