Compare commits
25 Commits
Author | SHA1 | Date |
---|---|---|
|
dbcf687fdd | |
|
47238a8c7b | |
|
c4efbcec1b | |
|
0a9d6302c3 | |
|
36c11b2712 | |
|
cf27b764e8 | |
|
2142243227 | |
|
5bdd0207a0 | |
|
b7daee79de | |
|
6736bcaeba | |
|
7f5c0dc8b0 | |
|
b9bcd21b2b | |
|
804f755101 | |
|
ac3fe8f343 | |
|
f00fa40e3a | |
|
10e9101c42 | |
|
f4832339b2 | |
|
bf44e357e4 | |
|
82373d164f | |
|
e413165afd | |
|
95da93949b | |
|
d492a775bf | |
|
977bd594c8 | |
|
da9721cb34 | |
|
d9e7346b60 |
|
@ -3,7 +3,7 @@
|
||||||
before_script:
|
before_script:
|
||||||
- apk add --update ragel build-base
|
- apk add --update ragel build-base
|
||||||
- if [ "$INSTALL_OPENJDK" == "true" ]; then apk add openjdk8; fi
|
- if [ "$INSTALL_OPENJDK" == "true" ]; then apk add openjdk8; fi
|
||||||
- gem install bundler --no-ri --no-rdoc
|
- gem install bundler --no-document
|
||||||
- ruby --version
|
- ruby --version
|
||||||
- gem --version
|
- gem --version
|
||||||
- bundle --version
|
- bundle --version
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
paths:
|
paths:
|
||||||
- vendor/ruby
|
- vendor/ruby
|
||||||
|
|
||||||
Ruby 2.2:
|
|
||||||
image: "ruby:2.2-alpine"
|
|
||||||
<<: *defaults
|
|
||||||
|
|
||||||
Ruby 2.3:
|
Ruby 2.3:
|
||||||
image: "ruby:2.3-alpine"
|
image: "ruby:2.3-alpine"
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
|
@ -26,6 +22,18 @@ Ruby 2.4:
|
||||||
image: "ruby:2.4-alpine"
|
image: "ruby:2.4-alpine"
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
|
|
||||||
|
Ruby 2.5:
|
||||||
|
image: "ruby:2.5-alpine"
|
||||||
|
<<: *defaults
|
||||||
|
|
||||||
|
Ruby 2.6:
|
||||||
|
image: "ruby:2.6-alpine"
|
||||||
|
<<: *defaults
|
||||||
|
|
||||||
|
Ruby 2.7:
|
||||||
|
image: "ruby:2.7-alpine"
|
||||||
|
<<: *defaults
|
||||||
|
|
||||||
JRuby 9.1:
|
JRuby 9.1:
|
||||||
image: "jruby:9.1-alpine"
|
image: "jruby:9.1-alpine"
|
||||||
variables:
|
variables:
|
||||||
|
|
50
CHANGELOG.md
50
CHANGELOG.md
|
@ -3,6 +3,56 @@
|
||||||
This document contains details of the various releases and their release dates.
|
This document contains details of the various releases and their release dates.
|
||||||
Dates are in the format `yyyy-mm-dd`.
|
Dates are in the format `yyyy-mm-dd`.
|
||||||
|
|
||||||
|
## 3.4 - 2022-08-02
|
||||||
|
|
||||||
|
This release includes a change that when setting the child nodes of a node A,
|
||||||
|
node A takes ownership over the entire new child tree. See merge request
|
||||||
|
https://gitlab.com/yorickpeterse/oga/-/merge_requests/194 for more details.
|
||||||
|
|
||||||
|
## 3.3 - 2020-07-27
|
||||||
|
|
||||||
|
This release adds `to_s` as an alias for `to_xml`, thanks to Roy Zwambag. See
|
||||||
|
merge request https://gitlab.com/yorickpeterse/oga/-/merge_requests/192 for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
## 3.2 - 2020-01-10
|
||||||
|
|
||||||
|
This release fixes a few warnings that would show up when using Oga on Ruby
|
||||||
|
2.7.0. See https://gitlab.com/yorickpeterse/oga/merge_requests/190 for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
## 3.1 - 2020-01-08
|
||||||
|
|
||||||
|
This release fixes a bug in the XML lexer that prevented the parsing of doctypes
|
||||||
|
using "public" or "system" instead of "PUBLIC"/"SYSTEM". See issue
|
||||||
|
<https://gitlab.com/yorickpeterse/oga/issues/199> for more information.
|
||||||
|
|
||||||
|
## 3.0 - 2019-12-03
|
||||||
|
|
||||||
|
This release bumps the Ruby version requirement to Ruby 2.3.0, as we haven't
|
||||||
|
supported older versions for several years now. We also no longer officially
|
||||||
|
support Rubinius.
|
||||||
|
|
||||||
|
## 2.17 - 2019-12-02
|
||||||
|
|
||||||
|
Elements using the default XML namespace can now be queried using XPath queries,
|
||||||
|
which was broken for quite a while.
|
||||||
|
|
||||||
|
See commit <https://gitlab.com/yorickpeterse/oga/commit/95da93949bf613612981f5cd7decc0d2c2a60e15>
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
## 2.16 - 2019-11-29
|
||||||
|
|
||||||
|
* XPath namespace aliases can now be used when querying elements using XPath
|
||||||
|
expressions.
|
||||||
|
* Several RDOc and RubyGems deprecation warnings have been resolved.
|
||||||
|
|
||||||
|
See the following commits for more information:
|
||||||
|
|
||||||
|
* <https://gitlab.com/yorickpeterse/oga/commit/d9e7346b60c3afa2b3e83a240f9807c6bb819d48>
|
||||||
|
* <https://gitlab.com/yorickpeterse/oga/commit/da9721cb34f91527e72b096c6bd6a128e37b1992>
|
||||||
|
* <https://gitlab.com/yorickpeterse/oga/commit/977bd594c8bfd1a29aeba9d3a4ab7d0ebbc7d11a>
|
||||||
|
|
||||||
## 2.15 - 2018-04-11
|
## 2.15 - 2018-04-11
|
||||||
|
|
||||||
The HTML parser now allows `th` elements to occur in `thead`, `tbody`, and
|
The HTML parser now allows `th` elements to occur in `thead`, `tbody`, and
|
||||||
|
|
|
@ -28,7 +28,7 @@ When making changes please stick to the existing style and patterns as this
|
||||||
keeps the codebase consistent. If a certain pattern or style is getting in your
|
keeps the codebase consistent. If a certain pattern or style is getting in your
|
||||||
way please open a separate issue about this so it can be discussed.
|
way please open a separate issue about this so it can be discussed.
|
||||||
|
|
||||||
Every commit and every pull request made is carefully reviewed. Chances are I'll
|
Every commit and every merge request made is carefully reviewed. Chances are I'll
|
||||||
spend more time reviewing it than the time an author spent on their changes.
|
spend more time reviewing it than the time an author spent on their changes.
|
||||||
This should ensure that Oga's codebase is stable, of high quality and easy to
|
This should ensure that Oga's codebase is stable, of high quality and easy to
|
||||||
maintain. As such _please_ take my feedback into consideration (or discuss it in
|
maintain. As such _please_ take my feedback into consideration (or discuss it in
|
||||||
|
@ -36,18 +36,18 @@ a civilized manner) instead of just dismissing it with comments such as "But I
|
||||||
fixed the problem so your feedback is irrelevant" or "This is my way of doing
|
fixed the problem so your feedback is irrelevant" or "This is my way of doing
|
||||||
things".
|
things".
|
||||||
|
|
||||||
Finally, and this will sound harsh: I will _not_ merge pull requests if the
|
Finally, and this will sound harsh: I will _not_ merge merge requests if the
|
||||||
author(s) simply disregard the feedback I've given them or if there are other
|
author(s) simply disregard the feedback I've given them or if there are other
|
||||||
problems with the pull request. Do not expect me to just blindly accept whatever
|
problems with the merge request. Do not expect me to just blindly accept whatever
|
||||||
changes are submitted.
|
changes are submitted.
|
||||||
|
|
||||||
Some examples of good pull request:
|
Some examples of good merge requests:
|
||||||
|
|
||||||
* https://gitlab.com/yorickpeterse/oga/pull/96
|
* https://gitlab.com/yorickpeterse/oga/-/merge_requests/96
|
||||||
* https://gitlab.com/yorickpeterse/oga/pull/67
|
* https://gitlab.com/yorickpeterse/oga/-/merge_requests/67
|
||||||
* https://gitlab.com/yorickpeterse/ffi-aspell/pull/21
|
* https://gitlab.com/yorickpeterse/ffi-aspell/-/merge_requests/21
|
||||||
* https://gitlab.com/yorickpeterse/ffi-aspell/pull/20
|
* https://gitlab.com/yorickpeterse/ffi-aspell/-/merge_requests/20
|
||||||
* https://gitlab.com/yorickpeterse/ruby-ll/pull/16
|
* https://gitlab.com/yorickpeterse/ruby-ll/-/merge_requests/16
|
||||||
|
|
||||||
## Git
|
## Git
|
||||||
|
|
||||||
|
@ -140,19 +140,14 @@ such a case use `describe 'foo'` for class methods and `describe '#foo'` for
|
||||||
instance methods.
|
instance methods.
|
||||||
|
|
||||||
Whenever adding new specifications please keep them in the existing style. If
|
Whenever adding new specifications please keep them in the existing style. If
|
||||||
the style is problematic you can open a separate pull request to address it. If
|
the style is problematic you can open a separate merge request to address it. If
|
||||||
you expect this to be a lot of work you should open an issue first to discuss
|
you expect this to be a lot of work you should open an issue first to discuss
|
||||||
things.
|
things.
|
||||||
|
|
||||||
## Continuous Integration
|
## Continuous Integration
|
||||||
|
|
||||||
Two continuous integration services are used to ensure the tests of Oga pass
|
Oga is tested using GitLab CI. Merge requests require that all tests pass before
|
||||||
at all times:
|
they can be merged.
|
||||||
|
|
||||||
* Travis CI: <https://gitlab.com/yorickpeterse/oga/pipelines>
|
|
||||||
* AppVeyor (Windows): <https://ci.appveyor.com/project/yorickpeterse/oga>
|
|
||||||
|
|
||||||
Pull requests won't be merged if any of the builds fail unless stated otherwise.
|
|
||||||
|
|
||||||
## Extension Setup
|
## Extension Setup
|
||||||
|
|
||||||
|
@ -219,7 +214,7 @@ modify `$LOAD_PATH`, instead run any scripts using `ruby -I lib`.
|
||||||
In case you have any further questions or would like to receive feedback before
|
In case you have any further questions or would like to receive feedback before
|
||||||
submitting a change, feel free to contact me. You can either open an issue,
|
submitting a change, feel free to contact me. You can either open an issue,
|
||||||
send a tweet to [@yorickpeterse][twitter] or send an Email to
|
send a tweet to [@yorickpeterse][twitter] or send an Email to
|
||||||
<yorickpeterse@gmail.com>.
|
<yorick@yorickpeterse.com>.
|
||||||
|
|
||||||
[editorconfig]:http://editorconfig.org/
|
[editorconfig]:http://editorconfig.org/
|
||||||
[twitter]: https://twitter.com/yorickpeterse
|
[twitter]: https://twitter.com/yorickpeterse
|
||||||
|
|
16
README.md
16
README.md
|
@ -173,8 +173,8 @@ Querying a document using a namespace:
|
||||||
| Ruby | Required | Recommended |
|
| Ruby | Required | Recommended |
|
||||||
|:---------|:--------------|:------------|
|
|:---------|:--------------|:------------|
|
||||||
| MRI | >= 1.9.3 | >= 2.1.2 |
|
| MRI | >= 1.9.3 | >= 2.1.2 |
|
||||||
| Rubinius | >= 2.2 | >= 2.2.10 |
|
|
||||||
| JRuby | >= 1.7 | >= 1.7.12 |
|
| JRuby | >= 1.7 | >= 1.7.12 |
|
||||||
|
| Rubinius | Not supported | |
|
||||||
| Maglev | Not supported | |
|
| Maglev | Not supported | |
|
||||||
| Topaz | Not supported | |
|
| Topaz | Not supported | |
|
||||||
| mruby | Not supported | |
|
| mruby | Not supported | |
|
||||||
|
@ -227,15 +227,14 @@ And if you want to specify an explicit namespace URI, you can use this:
|
||||||
|
|
||||||
descendant::*[local-name() = "bar" and namespace-uri() = "http://example.com"]
|
descendant::*[local-name() = "bar" and namespace-uri() = "http://example.com"]
|
||||||
|
|
||||||
Unlike Nokogiri, Oga does _not_ provide a way to create "dynamic" namespaces.
|
Like Nokogiri, Oga provides a way to create "dynamic" namespaces.
|
||||||
That is, Nokogiri allows one to query the above document as following:
|
That is, Oga allows one to query the above document as following:
|
||||||
|
|
||||||
document = Nokogiri::XML('<root xmlns="http://example.com"><bar>bar</bar></root>')
|
document = Oga.parse_xml('<root xmlns="http://example.com"><bar>bar</bar></root>')
|
||||||
|
|
||||||
document.xpath('x:root/x:bar', :x => 'http://example.com')
|
document.xpath('x:root/x:bar', namespaces: {'x' => 'http://example.com'})
|
||||||
|
|
||||||
Oga does have a small trick you can use to cut down the size of your XPath
|
Moreover, because Oga assigns the name "xmlns" to default namespaces you can use
|
||||||
queries. Because Oga assigns the name "xmlns" to default namespaces you can use
|
|
||||||
this in your XPath queries:
|
this in your XPath queries:
|
||||||
|
|
||||||
document = Oga.parse_xml('<root xmlns="http://example.com"><bar>bar</bar></root>')
|
document = Oga.parse_xml('<root xmlns="http://example.com"><bar>bar</bar></root>')
|
||||||
|
@ -246,9 +245,6 @@ When using this you can still restrict the query to the correct namespace URI:
|
||||||
|
|
||||||
document.xpath('xmlns:root[namespace-uri() = "http://example.com"]/xmlns:bar')
|
document.xpath('xmlns:root[namespace-uri() = "http://example.com"]/xmlns:bar')
|
||||||
|
|
||||||
In the future I might add an API to ease this process, although at this time I
|
|
||||||
have little interest in providing an API similar to Nokogiri.
|
|
||||||
|
|
||||||
## HTML5 Support
|
## HTML5 Support
|
||||||
|
|
||||||
Oga fully supports HTML5 including the omission of certain tags. For example,
|
Oga fully supports HTML5 including the omission of certain tags. For example,
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
241e4861fb8cdb8576b72672a2ad1d59e0f72333eb203d19b8922e15091a3470d0150d417f78d2394e2c9140fa7c9d87508acc51907537f57813b8c23272922e
|
|
@ -0,0 +1 @@
|
||||||
|
5a2abc35e0696adf408f1d517865e49d511b26e39c0fe6a1f299baf77563327661498f3e1d70e20feb118810eb6457649706dc4fc3e8c45868d4b3d0ef56bfc8
|
|
@ -0,0 +1 @@
|
||||||
|
4e35c653ef64ebfbbee7a933923e9cf53e988028e53cb1535127962d249501881ce35e5c7375b98e43e220b7561961a9d35fe15caf20b263b20367660a59b3eb
|
|
@ -0,0 +1 @@
|
||||||
|
9cbb14e1abea3ebec3b7e9051bff5cae466cc4e608df6aa7826add38bcdb5b406cc8090405e63128a6902b24a64082ef5b9d1a36970c399ef4c941f63f2ee305
|
|
@ -0,0 +1 @@
|
||||||
|
b64906a38edefb346c2ba9770336cb69f424e0776690932fea524f014dda00d8fe1b13b69fff1f01ca75c8f5107b92056c25f8ae7ee4aeb83770dc03b5d482c0
|
|
@ -0,0 +1 @@
|
||||||
|
56eea6f76968afb2916e73d729a9c94dcacfb1cccc6fa0ef27888e6e8006a80cda9279db4b040be81b33ee354916e49e437ce0189ce79bdbab7c0f54203b9f2e
|
|
@ -0,0 +1 @@
|
||||||
|
a2246547f87d1901e280d9df915bf41a6b78ac14805c5c0f471f5dd1cf617f1e5b3e4aa05e58ae5aa816e188456ccd3638e44a7aba41ed2dfb942f509b2093af
|
|
@ -0,0 +1 @@
|
||||||
|
deb03862d5263b2cb47169267aa37c41be82fcb01d048d840506d2c8924fb0bba6a4407401052e8a5f42d053c2bb3410701316fe21570b35a2981850dc05a481
|
|
@ -0,0 +1 @@
|
||||||
|
b0c3740f08d33f5b9a76c6532de749b8004fb591e3bd3b745e8c57a3bbc5b3d6be4c9a02432fc32f4ae3ca53b488dc409243af91e43646ba3f486fec4738911e
|
|
@ -0,0 +1 @@
|
||||||
|
27f941862134b9e5fc46b33d8dd642a7c816f8b0bb6448789c5f23e9da42abdc72b19713985cf81257074931a4f51e014aa551e09bcb7858cde9987ea17aca75
|
|
@ -0,0 +1 @@
|
||||||
|
c5521d5bc9e025fadfb4e0719c8ff0fa103dd7c184cdf4c60154a1bb5f7d71c9d807a84e76a21b13665de1bbe54f9ba23f6e479650b1bd497302f86ff2af8bbf
|
|
@ -0,0 +1 @@
|
||||||
|
be44f4fb2f5f821306556b965a928e42753a57e489516654bdda74662058510cdc9885b50f6170f23762309f3a7c94791d8db71180272961341c917ffc3560e4
|
|
@ -0,0 +1 @@
|
||||||
|
eef134163a86451be4a5ec72b262fec6a1dad10613e0d4002142b09e02cb444cc25ce018cdd62a870b266fc8dd390ba4fe110e07a2c41f50a3d8abdcc69b5dec
|
|
@ -0,0 +1 @@
|
||||||
|
2ba0fdbfa3fa15b8d1ce5df4df4cfb3813f34399c93517f98ec8da1e82ff3cdc6e3543bf017b8246daa8b2521a64af92a9438a404b69da0f319272c510961314
|
|
@ -289,7 +289,7 @@
|
||||||
# Machine for processing doctypes. Doctype values such as the public
|
# Machine for processing doctypes. Doctype values such as the public
|
||||||
# and system IDs are treated as T_STRING tokens.
|
# and system IDs are treated as T_STRING tokens.
|
||||||
doctype := |*
|
doctype := |*
|
||||||
'PUBLIC' | 'SYSTEM' => {
|
'PUBLIC'i | 'SYSTEM'i => {
|
||||||
callback(id_on_doctype_type, data, encoding, ts, te);
|
callback(id_on_doctype_type, data, encoding, ts, te);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module Oga
|
module Oga
|
||||||
VERSION = '2.15'
|
VERSION = '3.4'
|
||||||
end # Oga
|
end # Oga
|
||||||
|
|
|
@ -46,6 +46,8 @@ module Oga
|
||||||
# @param [Oga::XML::NodeSet|Array] nodes
|
# @param [Oga::XML::NodeSet|Array] nodes
|
||||||
def children=(nodes)
|
def children=(nodes)
|
||||||
if nodes.is_a?(NodeSet)
|
if nodes.is_a?(NodeSet)
|
||||||
|
nodes.owner = self
|
||||||
|
nodes.take_ownership_on_nodes
|
||||||
@children = nodes
|
@children = nodes
|
||||||
else
|
else
|
||||||
@children = NodeSet.new(nodes, self)
|
@children = NodeSet.new(nodes, self)
|
||||||
|
|
|
@ -49,6 +49,8 @@ module Oga
|
||||||
# @param [Oga::XML::NodeSet|Array] nodes
|
# @param [Oga::XML::NodeSet|Array] nodes
|
||||||
def children=(nodes)
|
def children=(nodes)
|
||||||
if nodes.is_a?(NodeSet)
|
if nodes.is_a?(NodeSet)
|
||||||
|
nodes.owner = self
|
||||||
|
nodes.take_ownership_on_nodes
|
||||||
@children = nodes
|
@children = nodes
|
||||||
else
|
else
|
||||||
@children = NodeSet.new(nodes, self)
|
@children = NodeSet.new(nodes, self)
|
||||||
|
|
|
@ -42,11 +42,7 @@ module Oga
|
||||||
@owner = owner
|
@owner = owner
|
||||||
@existing = {}
|
@existing = {}
|
||||||
|
|
||||||
@nodes.each_with_index do |node, index|
|
take_ownership_on_nodes
|
||||||
mark_existing(node)
|
|
||||||
|
|
||||||
take_ownership(node, index) if @owner
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Yields the supplied block for every node.
|
# Yields the supplied block for every node.
|
||||||
|
@ -289,6 +285,14 @@ module Oga
|
||||||
"NodeSet(#{values})"
|
"NodeSet(#{values})"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def take_ownership_on_nodes
|
||||||
|
@nodes.each_with_index do |node, index|
|
||||||
|
mark_existing(node)
|
||||||
|
|
||||||
|
take_ownership(node, index) if @owner
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Takes ownership of the given node. This only occurs when the current
|
# Takes ownership of the given node. This only occurs when the current
|
||||||
|
|
|
@ -10,6 +10,7 @@ module Oga
|
||||||
# document = Oga.parse_xml <<-EOF
|
# document = Oga.parse_xml <<-EOF
|
||||||
# <people>
|
# <people>
|
||||||
# <person age="25">Alice</person>
|
# <person age="25">Alice</person>
|
||||||
|
# <ns:person xmlns:ns="http://example.net">Bob</ns:person>
|
||||||
# </people>
|
# </people>
|
||||||
# EOF
|
# EOF
|
||||||
#
|
#
|
||||||
|
@ -25,15 +26,23 @@ module Oga
|
||||||
#
|
#
|
||||||
# document.xpath('people/person[@age = $age]', 'age' => 25)
|
# document.xpath('people/person[@age = $age]', 'age' => 25)
|
||||||
#
|
#
|
||||||
|
# Using namespace aliases:
|
||||||
|
#
|
||||||
|
# namespaces = {'example' => 'http://example.net'}
|
||||||
|
# document.xpath('people/example:person', namespaces: namespaces)
|
||||||
|
#
|
||||||
# @param [String] expression The XPath expression to run.
|
# @param [String] expression The XPath expression to run.
|
||||||
#
|
#
|
||||||
# @param [Hash] variables Variables to bind. The keys of this Hash should
|
# @param [Hash] variables Variables to bind. The keys of this Hash should
|
||||||
# be String values.
|
# be String values.
|
||||||
#
|
#
|
||||||
|
# @param [Hash] namespaces Namespace aliases. The keys of this Hash should
|
||||||
|
# be String values.
|
||||||
|
#
|
||||||
# @return [Oga::XML::NodeSet]
|
# @return [Oga::XML::NodeSet]
|
||||||
def xpath(expression, variables = {})
|
def xpath(expression, variables = {}, namespaces: nil)
|
||||||
ast = XPath::Parser.parse_with_cache(expression)
|
ast = XPath::Parser.parse_with_cache(expression)
|
||||||
block = XPath::Compiler.compile_with_cache(ast)
|
block = XPath::Compiler.compile_with_cache(ast, namespaces: namespaces)
|
||||||
|
|
||||||
block.call(self, variables)
|
block.call(self, variables)
|
||||||
end
|
end
|
||||||
|
@ -54,8 +63,8 @@ module Oga
|
||||||
#
|
#
|
||||||
# @see [#xpath]
|
# @see [#xpath]
|
||||||
# @return [Oga::XML::Node|Oga::XML::Attribute]
|
# @return [Oga::XML::Node|Oga::XML::Attribute]
|
||||||
def at_xpath(*args)
|
def at_xpath(*args, namespaces: nil)
|
||||||
result = xpath(*args)
|
result = xpath(*args, namespaces: namespaces)
|
||||||
|
|
||||||
result.is_a?(XML::NodeSet) ? result.first : result
|
result.is_a?(XML::NodeSet) ? result.first : result
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,8 @@ module Oga
|
||||||
def to_xml
|
def to_xml
|
||||||
Generator.new(self).to_xml
|
Generator.new(self).to_xml
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias_method :to_s, :to_xml
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,12 +42,16 @@ module Oga
|
||||||
# Compiles and caches an AST.
|
# Compiles and caches an AST.
|
||||||
#
|
#
|
||||||
# @see [#compile]
|
# @see [#compile]
|
||||||
def self.compile_with_cache(ast)
|
def self.compile_with_cache(ast, namespaces: nil)
|
||||||
CACHE.get_or_set(ast) { new.compile(ast) }
|
cache_key = namespaces ? [ast, namespaces] : ast
|
||||||
|
CACHE.get_or_set(cache_key) { new(namespaces: namespaces).compile(ast) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
# @param [Hash] namespaces
|
||||||
|
def initialize(namespaces: nil)
|
||||||
reset
|
reset
|
||||||
|
|
||||||
|
@namespaces = namespaces
|
||||||
end
|
end
|
||||||
|
|
||||||
# Resets the internal state.
|
# Resets the internal state.
|
||||||
|
@ -1385,7 +1389,23 @@ module Oga
|
||||||
end
|
end
|
||||||
|
|
||||||
if ns and ns != STAR
|
if ns and ns != STAR
|
||||||
ns_match = input.namespace_name.eq(string(ns))
|
if @namespaces
|
||||||
|
ns_uri = @namespaces[ns]
|
||||||
|
ns_match =
|
||||||
|
if ns_uri
|
||||||
|
input.namespace.and(input.namespace.uri.eq(string(ns_uri)))
|
||||||
|
else
|
||||||
|
self.false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ns_match =
|
||||||
|
if ns == XML::Element::XMLNS_PREFIX
|
||||||
|
input
|
||||||
|
else
|
||||||
|
input.namespace_name.eq(string(ns))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
condition = condition ? condition.and(ns_match) : ns_match
|
condition = condition ? condition.and(ns_match) : ns_match
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -29,7 +29,6 @@ Gem::Specification.new do |s|
|
||||||
s.extensions = ['ext/c/extconf.rb']
|
s.extensions = ['ext/c/extconf.rb']
|
||||||
end
|
end
|
||||||
|
|
||||||
s.has_rdoc = 'yard'
|
|
||||||
s.required_ruby_version = '>= 1.9.3'
|
s.required_ruby_version = '>= 1.9.3'
|
||||||
|
|
||||||
s.add_dependency 'ast'
|
s.add_dependency 'ast'
|
||||||
|
|
|
@ -22,6 +22,7 @@ describe Oga::XML::Document do
|
||||||
document.children = [child]
|
document.children = [child]
|
||||||
|
|
||||||
expect(document.children[0]).to eq(child)
|
expect(document.children[0]).to eq(child)
|
||||||
|
expect(document.children[0].parent).to eq(document)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets the child nodes using a NodeSet' do
|
it 'sets the child nodes using a NodeSet' do
|
||||||
|
@ -31,6 +32,7 @@ describe Oga::XML::Document do
|
||||||
document.children = Oga::XML::NodeSet.new([child])
|
document.children = Oga::XML::NodeSet.new([child])
|
||||||
|
|
||||||
expect(document.children[0]).to eq(child)
|
expect(document.children[0]).to eq(child)
|
||||||
|
expect(document.children[0].parent).to eq(document)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -168,5 +168,16 @@ describe Oga::XML::Generator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'using an Element with replaced children' do
|
||||||
|
it 'returns a string' do
|
||||||
|
element = Oga::XML::Element.new(name: 'foo')
|
||||||
|
element.children = Oga::XML::Parser.new('<bar></bar>').parse.children
|
||||||
|
|
||||||
|
output = described_class.new(element).to_xml
|
||||||
|
|
||||||
|
expect(output).to eq('<foo><bar /></foo>')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -108,7 +108,7 @@ describe Oga::XML::Lexer do
|
||||||
|
|
||||||
# Technically not valid, put in place to make sure that the Ragel rules are
|
# Technically not valid, put in place to make sure that the Ragel rules are
|
||||||
# not too greedy.
|
# not too greedy.
|
||||||
it 'lexes an inline doftype followed by a system ID' do
|
it 'lexes an inline doctype followed by a system ID' do
|
||||||
expect(lex('<!DOCTYPE html [<!ELEMENT foo>] "foo">')).to eq([
|
expect(lex('<!DOCTYPE html [<!ELEMENT foo>] "foo">')).to eq([
|
||||||
[:T_DOCTYPE_START, nil, 1],
|
[:T_DOCTYPE_START, nil, 1],
|
||||||
[:T_DOCTYPE_NAME, 'html', 1],
|
[:T_DOCTYPE_NAME, 'html', 1],
|
||||||
|
@ -119,5 +119,31 @@ describe Oga::XML::Lexer do
|
||||||
[:T_DOCTYPE_END, nil, 1]
|
[:T_DOCTYPE_END, nil, 1]
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not care about the casing when using a public doctype' do
|
||||||
|
expect(lex('<!DoCtYpE HtMl PuBlIc [<!ELEMENT foo>] "foo">')).to eq([
|
||||||
|
[:T_DOCTYPE_START, nil, 1],
|
||||||
|
[:T_DOCTYPE_NAME, 'HtMl', 1],
|
||||||
|
[:T_DOCTYPE_TYPE, 'PuBlIc', 1],
|
||||||
|
[:T_DOCTYPE_INLINE, '<!ELEMENT foo>', 1],
|
||||||
|
[:T_STRING_DQUOTE, nil, 1],
|
||||||
|
[:T_STRING_BODY, 'foo', 1],
|
||||||
|
[:T_STRING_DQUOTE, nil, 1],
|
||||||
|
[:T_DOCTYPE_END, nil, 1]
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not care about the casing when using a system doctype' do
|
||||||
|
expect(lex('<!DoCtYpE HtMl SyStEm [<!ELEMENT foo>] "foo">')).to eq([
|
||||||
|
[:T_DOCTYPE_START, nil, 1],
|
||||||
|
[:T_DOCTYPE_NAME, 'HtMl', 1],
|
||||||
|
[:T_DOCTYPE_TYPE, 'SyStEm', 1],
|
||||||
|
[:T_DOCTYPE_INLINE, '<!ELEMENT foo>', 1],
|
||||||
|
[:T_STRING_DQUOTE, nil, 1],
|
||||||
|
[:T_STRING_BODY, 'foo', 1],
|
||||||
|
[:T_STRING_DQUOTE, nil, 1],
|
||||||
|
[:T_DOCTYPE_END, nil, 1]
|
||||||
|
])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,10 @@ require 'spec_helper'
|
||||||
describe Oga::XML::Querying do
|
describe Oga::XML::Querying do
|
||||||
before do
|
before do
|
||||||
@document = parse('<a>foo</a>')
|
@document = parse('<a>foo</a>')
|
||||||
|
@document2 = parse('<a xmlns:x="y"><x:b>bar</x:b></a>')
|
||||||
|
@namespaces = {
|
||||||
|
"n" => "y"
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#xpath' do
|
describe '#xpath' do
|
||||||
|
@ -15,7 +19,11 @@ describe Oga::XML::Querying do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'evaluates an expression using a variable' do
|
it 'evaluates an expression using a variable' do
|
||||||
expect(@document.xpath('$number', 'number' => 10)).to eq(10)
|
expect(@document.xpath('$number', {'number' => 10})).to eq(10)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'respects custom namespace aliases' do
|
||||||
|
expect(@document2.xpath('a/n:b', namespaces: @namespaces)[0].text).to eq('bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -29,7 +37,11 @@ describe Oga::XML::Querying do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'evaluates an expression using a variable' do
|
it 'evaluates an expression using a variable' do
|
||||||
expect(@document.at_xpath('$number', 'number' => 10)).to eq(10)
|
expect(@document.at_xpath('$number', {'number' => 10})).to eq(10)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'respects custom namespace aliases' do
|
||||||
|
expect(@document2.at_xpath('a/n:b', namespaces: @namespaces).text).to eq('bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Oga::XML::ToXML do
|
||||||
|
describe '#to_s' do
|
||||||
|
it 'is an alias of to_xml' do
|
||||||
|
node = Oga::XML::Element.new(name: 'foo')
|
||||||
|
|
||||||
|
expect(node.method(:to_s)).to eq(node.method(:to_xml))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,40 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Oga::XPath::Compiler do
|
||||||
|
before do
|
||||||
|
@document = parse('<root xmlns:x="y"><x:a></x:a><b x:num="10"></b></root>')
|
||||||
|
@root = @document.children[0]
|
||||||
|
@a = @root.children[0]
|
||||||
|
@b = @root.children[1]
|
||||||
|
@attr = @b.attributes[0]
|
||||||
|
@namespaces = {
|
||||||
|
"n" => "y"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'with custom namespace aliases' do
|
||||||
|
it 'uses aliases when querying an element' do
|
||||||
|
expect(evaluate_xpath(@document, 'root/n:a', namespaces: @namespaces)).to eq(node_set(@a))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't use namespaces in XPath expression when querying an element" do
|
||||||
|
expect(evaluate_xpath(@document, 'root/x:a', namespaces: @namespaces)).to eq(node_set)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses aliases when querying an attribute' do
|
||||||
|
expect(evaluate_xpath(@document, 'root/b/@n:num', namespaces: @namespaces)).to eq(node_set(@attr))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't use namespaces in XPath expression when querying an attribute" do
|
||||||
|
expect(evaluate_xpath(@document, 'root/b/@x:num', namespaces: @namespaces)).to eq(node_set)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses aliases when querying an element with a namespaced attribute' do
|
||||||
|
expect(evaluate_xpath(@document, 'root/b[@n:num]', namespaces: @namespaces)).to eq(node_set(@b))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't use namespaces in XPath expression when querying an element with a namespaced attribute" do
|
||||||
|
expect(evaluate_xpath(@document, 'root/b[@x:num]', namespaces: @namespaces)).to eq(node_set)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -68,4 +68,26 @@ describe Oga::XPath::Compiler do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'querying elements with a default namespace' do
|
||||||
|
before do
|
||||||
|
@document = parse('<a xmlns="n" xmlns:ns1="x">Foo<b></b><b></b><ns1:c></ns1:c></a>')
|
||||||
|
|
||||||
|
@a1 = @document.children[0]
|
||||||
|
@b1 = @a1.children[1]
|
||||||
|
@b2 = @a1.children[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '/xmlns:a' do
|
||||||
|
it 'returns a NodeSet' do
|
||||||
|
expect(evaluate_xpath(@document)).to eq(node_set(@a1))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '//xmlns:b' do
|
||||||
|
it 'returns a NodeSet' do
|
||||||
|
expect(evaluate_xpath(@document)).to eq(node_set(@b1, @b2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,9 +5,9 @@ module Oga
|
||||||
# @param [String] xpath
|
# @param [String] xpath
|
||||||
# @return [Oga::XML::NodeSet]
|
# @return [Oga::XML::NodeSet]
|
||||||
#
|
#
|
||||||
def evaluate_xpath(document, xpath = self.class.description)
|
def evaluate_xpath(document, xpath = self.class.description, namespaces: nil)
|
||||||
ast = parse_xpath(xpath)
|
ast = parse_xpath(xpath)
|
||||||
compiler = Oga::XPath::Compiler.new
|
compiler = Oga::XPath::Compiler.new(namespaces: namespaces)
|
||||||
block = compiler.compile(ast)
|
block = compiler.compile(ast)
|
||||||
|
|
||||||
block.call(document)
|
block.call(document)
|
||||||
|
|
Loading…
Reference in New Issue