Skip to content

Commit 01ae724

Browse files
authored
Merge pull request #46 from keith-hall/st4_fixes
ST4 fixes
2 parents f4916d1 + 9e72e1d commit 01ae724

File tree

9 files changed

+519
-296
lines changed

9 files changed

+519
-296
lines changed

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.8

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,37 @@ The recommended way to install the Sublime Text XPath plugin is via [Package Con
8787

8888
## Troubleshooting
8989

90+
## Context menu items disabled
91+
92+
This can happen if the `lxml` dependency didn't load properly. You'll see errors in the ST console.
93+
94+
### Mac
95+
On a Mac with Apple silicon, the version of lxml installed by Package Control 4 doesn't seem to work.
96+
In ST console, we can see that ST build 4180 is using Python 3.8.12:
97+
```python
98+
import sys; sys.version_info
99+
```
100+
So you can build lxml manually using this version of Python. You will need to download the source code release asset, as lxml's git repository doesn't contain some `.c` files which are required to build. lxml v5.1.1 for sure works:
101+
- Navigate to https://github.com/lxml/lxml/releases/tag/lxml-5.1.1
102+
- Download `lxml-5.1.1.tar.gz`
103+
- extract it
104+
105+
```sh
106+
brew install pyenv
107+
pyenv init # follow instructions
108+
109+
pyenv install 3.8.12
110+
pyenv shell 3.8.12
111+
112+
cd ~/Downloads/lxml-5.1.1
113+
python setup.py build
114+
```
115+
116+
- then copy `~/Downloads/lxml-5.1.1/build/lib.macosx-15.1-arm64-3.8/lxml` into `~/Library/Application Support/Sublime Text/Lib/python38/lxml`, overwriting anything already there.
117+
- Restart ST.
118+
119+
(If you were to try downloading `lxml-5.1.1-cp38-cp38-macosx_10_9_universal2.whl` for example, and extracting that into ST's lib folder mentioned above, when you restart ST, you would be told that an `.so` file in `Lib/python38/lxml/` folder isn't trusted, and there would be no option to "allow". You could go in Mac Settings -> Privacy and Security and it should show up there with an option to allow it. But you'd still see that the `lxml` dependency fails to load in ST, and the only solution seems to be building from source on the Mac.)
120+
90121
### CDATA Nodes
91122

92123
When working with XML documents, you are probably used to the Document Object Model (DOM), where CDATA nodes are separate to text nodes. XPath sees `text()` nodes as all adjacent CDATA and text node siblings together.

example_xml_ns.xml

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
<?xml version="1.0"?>
22
<test>
3-
<hello xmlns="hello_ns">
4-
<!-- from an element name standpoint, you could expect this element to be reachable by the path "/test/hello".
5-
However, XPath 1.0 does not have the concept of a default namespace. If the XML document being queried defines a default namespace, the XPath expression should map the namespace to a prefix for easier access.
6-
This is what this plugin does for you. Because this is the first prefix-less namespace declared in document order, it will become "default", assuming "default" is set as the default namespace prefix to use.
7-
But, because there is at least one other prefix-less namespace in the document - which resolves to a different uri - it will become "default1" instead. -->
8-
9-
<!-- The scope of a default namespace declaration extends from the beginning of the start-tag in which it appears to the end of the corresponding end-tag, excluding the scope of any inner default namespace declarations. -->
10-
<world xmlns="world_ns"><!-- second prefix-less namespace in doc order, becomes "default2" -->
11-
<example /><!-- path here is "/test/default1:hello/default2:world/default2:example" -->
12-
</world>
13-
</hello>
14-
<more xmlns="more_ns" xmlns:an="another_ns"><!-- third prefix-less namespace in doc order, becomes "default3". First "an" prefix declared in doc-order. Because there is more than one unique namespace uri declared with this prefix, it will become "an1" for query purposes. -->
15-
<an:another></an:another><!-- path here is "/test/default3:more[1]/an1:another" -->
16-
</more>
17-
<more xmlns="more_ns" xmlns:an="yet_another_ns" xmlns:unique="single"><!-- this prefixless namespace has the same uri as one already used, "default3", so this is also "default3". Because the an prefix is being declared again to a different uri than the previous one, it becomes "an2". As the "unique" prefix is unique, it remains as "unique", with no numeric suffix. -->
18-
<an:yet_another></an:yet_another><!-- path here is "/test/default3:more[2]/an2:yet_another" -->
19-
<unique:example></unique:example><!-- path here is "/test/default3:more[2]/unique:example" -->
20-
</more>
21-
<foo xmlns="world_ns" /><!-- same uri as default2 used, path is therefore "/test/default2:foo" -->
22-
<numeric xmlns:an="yans"><!-- this one becomes "an4", because "an" has been used twice already and "an3" is explicitly defined later in the document -->
23-
<an:test /><!-- /test/numeric/an4:test -->
24-
</numeric>
25-
<numeric xmlns:an3="numbered_ns"><!-- specific namespace prefix with a numeric suffix declared -->
26-
<an3:okay></an3:okay>
27-
</numeric>
28-
<text attr1="hello" attr2='world'>sample text<more some_value
29-
=
30-
"foobar" another_value = "super" xmlns:abc="abc" abc:another_value="value" /> lorem ipsum etc.</text>abc<![CDATA[def]]>ghi<hij><![CDATA[klm]]></hij><![CDATA[nop]]>
3+
<hello xmlns="hello_ns">
4+
<!-- from an element name standpoint, you could expect this element to be reachable by the path "/test/hello".
5+
However, XPath 1.0 does not have the concept of a default namespace. If the XML document being queried defines a default namespace, the XPath expression should map the namespace to a prefix for easier access.
6+
This is what this plugin does for you. Because this is the first prefix-less namespace declared in document order, it will become "default", assuming "default" is set as the default namespace prefix to use.
7+
But, because there is at least one other prefix-less namespace in the document - which resolves to a different uri - it will become "default1" instead. -->
8+
9+
<!-- The scope of a default namespace declaration extends from the beginning of the start-tag in which it appears to the end of the corresponding end-tag, excluding the scope of any inner default namespace declarations. -->
10+
<world xmlns="world_ns"><!-- second prefix-less namespace in doc order, becomes "default2" -->
11+
<example /><!-- path here is "/test/default1:hello/default2:world/default2:example" -->
12+
</world>
13+
</hello>
14+
<more xmlns="more_ns" xmlns:an="another_ns"><!-- third prefix-less namespace in doc order, becomes "default3". First "an" prefix declared in doc-order. Because there is more than one unique namespace uri declared with this prefix, it will become "an1" for query purposes. -->
15+
<an:another></an:another><!-- path here is "/test/default3:more[1]/an1:another" -->
16+
</more>
17+
<more xmlns="more_ns" xmlns:an="yet_another_ns" xmlns:unique="single"><!-- this prefixless namespace has the same uri as one already used, "default3", so this is also "default3". Because the an prefix is being declared again to a different uri than the previous one, it becomes "an2". As the "unique" prefix is unique, it remains as "unique", with no numeric suffix. -->
18+
<an:yet_another></an:yet_another><!-- path here is "/test/default3:more[2]/an2:yet_another" -->
19+
<unique:example></unique:example><!-- path here is "/test/default3:more[2]/unique:example" -->
20+
</more>
21+
<foo xmlns="world_ns" /><!-- same uri as default2 used, path is therefore "/test/default2:foo" -->
22+
<numeric xmlns:an="yans"><!-- this one becomes "an4", because "an" has been used twice already and "an3" is explicitly defined later in the document -->
23+
<an:test /><!-- /test/numeric/an4:test -->
24+
</numeric>
25+
<numeric xmlns:an3="numbered_ns"><!-- specific namespace prefix with a numeric suffix declared -->
26+
<an3:okay></an3:okay>
27+
</numeric>
28+
<text attr1="hello" attr2='world'>sample text<more some_value
29+
=
30+
"foobar" another_value = "super" xmlns:abc="abc" abc:another_value="value" /> lorem ipsum etc.</text>abc<![CDATA[def]]>ghi<hij><![CDATA[klm]]></hij><![CDATA[nop]]>
3131
</test>

lxml_parser.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ def element_start(self, tag, attrib=None, nsmap=None, location=None):
158158

159159
def create_element(self, tag, attrib=None, nsmap=None):
160160
LocationAwareElement.TAG = tag
161+
if nsmap: # a change made in lxml 3.8.0 / 3.5.0b1 requires us to pass None instead of an empty prefix string
162+
if '' in nsmap:
163+
nsmap[None] = nsmap['']
164+
del nsmap['']
165+
161166
return LocationAwareElement(attrib=attrib, nsmap=nsmap)
162167

163168
def element_end(self, tag, location=None):
@@ -299,11 +304,10 @@ def unique_namespace_prefixes(namespaces, replaceNoneWith = 'default', start = 1
299304

300305
def get_results_for_xpath_query(query, tree, context = None, namespaces = None, **variables):
301306
"""Given a query string and a document trees and optionally some context elements, compile the xpath query and execute it."""
302-
nsmap = {}
303-
if namespaces is not None:
307+
nsmap = dict()
308+
if namespaces:
304309
for prefix in namespaces.keys():
305-
if namespaces[prefix][0] != '':
306-
nsmap[prefix] = namespaces[prefix][0]
310+
nsmap[prefix] = namespaces[prefix][0]
307311

308312
xpath = etree.XPath(query, namespaces = nsmap)
309313

sublime_lxml.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from .sublime_helper import get_scopes
44
import re
55

6-
RE_TAG_NAME_END_POS = re.compile('[>\s/]')
7-
RE_TAG_ATTRIBUTES = re.compile('\s+((\w+(?::\w+)?)\s*=\s*(?:"([^"]*)"|\'([^\']*)\'))')
6+
RE_TAG_NAME_END_POS = re.compile(r'[>\s/]')
7+
RE_TAG_ATTRIBUTES = re.compile(r'\s+((\w+(?::\w+)?)\s*=\s*(?:"([^"]*)"|\'([^\']*)\'))')
88

99
# TODO: consider subclassing etree.ElementBase and adding as methods to that
1010
def getNodeTagRegion(view, node, position_type):

0 commit comments

Comments
 (0)