Oga
した
取組中
HTMLをXHTMLにしたい。
invalid XMLをvalidにする機能
</li>を補うとか
HTMLの必須要素や親子関係の間違いなどを正す機能
リリース済み
方針はYorick Peterseの言う方向で
コンパイラーのon_testで呼んでるmatch_name_and_namespaceの中で、追加の名前空間エイリアスを見ながらRuby ASTを変えるのがいいかな?
Elementにnamespaceメソッドがあるから、child3.namespace.uriとかと@ns_aliasesの中のURIを比較するのがよさそう
xpath()に名前付き引き数としてns_aliasesを追加
デフォルト値: nil ... これまでと同じ挙動
名前空間URIは気にせず、名前空間名だけを見て評価する
nilがいいかは自明ではない
I just realised this logic is not entirely correct. If a custom group of aliases is given, we only match if a used namespace is in that group.
これは意図したものだったけど、うまく説明できないので一旦日本語で考える。
「ドキュメントの名前空間と指定した名前空間エイリアスとのミックス」というのが分かりにくいのではないか、「引数なしの場合はドキュメントの名前空間を使う、引数を与えた時はエイリアスのみを使う」という方がいいのではないか。
後者も自明ではないので、結局選択の問題ではある。が、後者のメリットを示した上で選んでほしい。
Nokogiriはどうなってる?
引数がある時はそれのみを使う、ない時はドキュメントで宣言された名前空間名を使う
code:pry.rb
Nokogiri.XML('<root xmlns:foo="a"><foo:bar/></root>').xpath('//foo:bar')
Nokogiri.XML('<root xmlns:foo="a"><foo:bar/></root>').xpath('//foo:bar', {})
# Nokogiri::XML::XPath::SyntaxError: ERROR: Undefined namespace prefix: //foo:bar
# from /home/kitaitimakoto/.gem/ruby/2.6.0/gems/nokogiri-1.10.5/lib/nokogiri/xml/searchable.rb:198:in `evaluate'
Yorick仕様の場合、「自分が指定した名前空間のみを使う」ということができない。必ずドキュメントの名前空間名が使われる。Kitaiti仕様の場合、「自分が指定した名前空間名のみを使う」「ドキュメントの名前空間名のみを使う」「両方を合わせたものを使う」のいずれもできる。自由度が高い。
両方を合わせたものを使うのは結構むずいかも。Nokogiriでも。
自分の指定した名前空間URI以外の物が欲しくない時、という観点はどうか
EPUB Parserはこれかな。
"/ocf:container/ocf:rootfiles/ocf:rootfile"
2.16でリリースされた
READMEのその辺が書かれたコミット:8c8ecce
この時は動いていたとみなしていいだろう
code:toplevelxpath.rb
require "oga"
if document.xpath('xmlns:root/xmlns:bar').length == 0
abort
end
code:shell
% git busect start HEAD 8c8ecce
% git bisect run ./toplevelxpath.rb
running ./toplevelxpath.rb
e4919d7c312a1f6f057b3416ced1b0f708c2dfaa is the first bad commit
commit e4919d7c312a1f6f057b3416ced1b0f708c2dfaa
Author: Yorick Peterse <yorickpeterse@gmail.com>
Date: Sun Aug 30 01:22:33 2015 +0200
Use XPath::Compiler in XML::Querying
:040000 040000 816fa890689f9927f861aef1c17ee3dbf6d44871 91ced426c26f5555a4ee6bbfa2d7effb531ebe22 M lib
bisect run success
これで本当に壊れるだろうか?
前後のコミットをチェックアウトしてみたが、壊れてた。
ただ、Compilerの導入が破壊を招いていた。これは、導入自体やCompiler事態が問題なのではなくて、コンパイラーが生成するRuby ASTもしくはASTから生成されるRubyコードが間違っていることを仄めかしている。
Evaluator使ってる時のロジック流用したい
テストも
xmlnsを複数回宣言した時上書きするならavailable nss使える
code:pry
2 pry(main)> require "oga" => true
3 pry(main)> doc = Oga.parse_xml('<root xmlns="a"><x xmlns="b"><y/></x></root>') => Document(
)))
)
4 pry(main)> doc.children0.namespaces => {"xmlns"=>Namespace(name: "xmlns" uri: "a")}
5 pry(main)> doc.children0.children0.namespaces => {"xmlns"=>Namespace(name: "xmlns" uri: "b")}
6 pry(main)> doc.children0.children0.children0.namespaces => {}
7 pry(main)> doc.children0.children0.children0.available_namespaces => {"xmlns"=>Namespace(name: "xmlns" uri: "b")}
8 pry(main)> doc.children0.children0.available_namespaces => {"xmlns"=>Namespace(name: "xmlns" uri: "b")}
9 pry(main)> doc.children0.available_namespaces => {"xmlns"=>Namespace(name: "xmlns" uri: "a")}
available_namespacesにあるxmlnsをみればよさそう
XPathでxmlnsを使っている場合が今回対応したいケース
だが、より一般的にできるか?
「XPathには名前空間をnamespacesを使いながら渡している、しかしXMLドキュメントでは先祖要素でしか(デフォルト)名前空間を宣言していない」というケースに一般化できそう
[namespace-uri() = "..."]の時のASTやRubyコードを調べてみるのもあり
-> ちゃんと動いてそう
XML: <a xmlns="n" xmlns:ns1="x">Foo<b></b><b></b><ns1:c></ns1:c></a>
XPath: //b[namespace-uri() = "n"]
code:ast
s(:absolute_path,
s(:axis, "descendant-or-self",
s(:type_test, "node"),
s(:predicate,
s(:axis, "child",
s(:test, nil, "b")),
s(:eq,
s(:call, "namespace-uri"),
s(:string, "n")))))
code:nsuri.rb
lambda do |node, variables = nil|
original_input = node
matched = Oga::XML::NodeSet.new
if (node.root_node.is_a?(Oga::XML::Document) || node.root_node.is_a?(Oga::XML::Node))
if ((node.root_node.is_a?(Oga::XML::Document) || node.root_node.is_a?(Oga::XML::Node)) || node.root_node.is_a?(Oga::XML::Attribute))
index2 = 1
if (node.root_node.is_a?(Oga::XML::Document) || node.root_node.is_a?(Oga::XML::Node))
node.root_node.children.each do |child4|
if (child4.is_a?(Oga::XML::Element) || child4.is_a?(Oga::XML::Attribute)) && (child4.name == "b" || child4.name.casecmp("b") == 0)
pred_var3 = begin
op_left5 = begin
argument_or_first_node7 = child4
if argument_or_first_node7
if (argument_or_first_node7.is_a?(Oga::XML::Element) || argument_or_first_node7.is_a?(Oga::XML::Attribute)).!
raise(TypeError, "argument is not an Element or Attribute")
end
if argument_or_first_node7.namespace
argument_or_first_node7.namespace.uri
else
""
end
else
""
end
end
op_right6 = "n"
op_left5, op_right6 = Oga::XPath::Conversion.to_compatible_types(op_left5, op_right6)
op_left5 == op_right6
end
if pred_var3.is_a?(Numeric)
pred_var3 = pred_var3.to_i == index2
end
if Oga::XPath::Conversion.to_boolean(pred_var3)
matched.push(child4)
end
index2 = index2.+(1)
end
end
end
end
node.root_node.each_node do |descendant1|
if ((descendant1.is_a?(Oga::XML::Document) || descendant1.is_a?(Oga::XML::Node)) || descendant1.is_a?(Oga::XML::Attribute))
index8 = 1
if (descendant1.is_a?(Oga::XML::Document) || descendant1.is_a?(Oga::XML::Node))
descendant1.children.each do |child10|
if (child10.is_a?(Oga::XML::Element) || child10.is_a?(Oga::XML::Attribute)) && (child10.name == "b" || child10.name.casecmp("b") == 0)
pred_var9 = begin
op_left11 = begin
argument_or_first_node13 = child10
if argument_or_first_node13
if (argument_or_first_node13.is_a?(Oga::XML::Element) || argument_or_first_node13.is_a?(Oga::XML::Attribute)).!
raise(TypeError, "argument is not an Element or Attribute")
end
if argument_or_first_node13.namespace
argument_or_first_node13.namespace.uri
else
""
end
else
""
end
end
op_right12 = "n"
op_left11, op_right12 = Oga::XPath::Conversion.to_compatible_types(op_left11, op_right12)
op_left11 == op_right12
end
if pred_var9.is_a?(Numeric)
pred_var9 = pred_var9.to_i == index8
end
if Oga::XPath::Conversion.to_boolean(pred_var9)
matched.push(child10)
end
index8 = index8.+(1)
end
end
end
end
end
end
matched
end
op_left5 == op_right6とop_left11 == op_right12で二回URIを比較している
が、これが結果が二要素であることと対応しているとは限らない
on_call_namespace_uriを見てみる
code:compiler.rb
# File 'lib/oga/xpath/compiler.rb', line 935
def on_call_namespace_uri(input, arg = nil)
default = string('')
argument_or_first_node(input, arg) do |arg_var|
arg_var
.if_true do
ensure_element_or_attribute(arg_var).followed_by do
arg_var.namespace
.if_true { block_given? ? yield : arg_var.namespace.uri }
.else { default } # no yield so predicates aren't matched
end
end
.else { default }
end
end
うーんこれを真似するの辛そう
部分的に拝借しよう
TODO
README編集
xpathのnamespaces引数
ドキュメントではNS宣言されてないけどXPath式ではしてるやつ
/ocf:container/ocf:rootfiles/ocf:rootfileとか
てか、式に名前空間書いてないやつも名前空間見なくてはいけないのでは?
これはいけなくない可能性もあるな。ちゃんと考えること。
問題なかったぽい