Repository: biopython/biopython

Issue:
XML Schema 'None' found not consistent with reported seqXML version 0.2
### Setup

I am reporting a problem with Biopython version, Python version, and operating
system as follows:

```python
import sys; print(sys.version)
import platform; print(platform.python_implementation()); print(platform.platform())
import Bio; print(Bio.__version__)
```

3.11.7 (main, Dec 15 2023, 18:12:31) [GCC 11.2.0]
CPython
Linux-6.2.0-39-generic-x86_64-with-glibc2.35
1.83

### Expected behaviour

Running the below code should load in the attached seqXML file. Instead, it errors. Note that I had to change the file extension to ".txt" to get GitHub to accept the file. It is originally a seq.xml file, however, downloaded from [OMA](https://omabrowser.org/oma/home/).

```
for seq in SeqIO.parse("ACAM1.seq.xml", "seqxml"):
    pass
```
[ACAM1.seq.txt](https://github.com/biopython/biopython/files/13891282/ACAM1.seq.txt)


### Actual behaviour

The following traceback results:

```
ValueError                                Traceback (most recent call last)
Cell In[13], [line 10](vscode-notebook-cell:?execution_count=13&line=10)
      [7](vscode-notebook-cell:?execution_count=13&line=7)     continue
      [9](vscode-notebook-cell:?execution_count=13&line=9) # Process sequences
---> [10](vscode-notebook-cell:?execution_count=13&line=10) for seq in SeqIO.parse(filename, "seqxml"):
     [11](vscode-notebook-cell:?execution_count=13&line=11)     pass
     [13](vscode-notebook-cell:?execution_count=13&line=13) break

File [~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/__init__.py:613](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/__init__.py:613), in parse(handle, format, alphabet)
    [611](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/__init__.py:611) iterator_generator = _FormatToIterator.get(format)
    [612](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/__init__.py:612) if iterator_generator:
--> [613](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/__init__.py:613)     return iterator_generator(handle)
    [614](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/__init__.py:614) if format in AlignIO._FormatToIterator:
    [615](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/__init__.py:615)     # Use Bio.AlignIO to read in the alignments
    [616](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/__init__.py:616)     return (r for alignment in AlignIO.parse(handle, format) for r in alignment)

File [~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:400](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:400), in SeqXmlIterator.__init__(self, stream_or_path, namespace)
    [398](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:398) self.parser.setContentHandler(content_handler)
    [399](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:399) self.parser.setFeature(handler.feature_namespaces, True)
--> [400](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:400) super().__init__(stream_or_path, mode="b", fmt="SeqXML")

File [~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/Interfaces.py:76](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/Interfaces.py:76), in SequenceIterator.__init__(self, source, alphabet, mode, fmt)
     [74](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/Interfaces.py:74)     self.should_close_stream = False
     [75](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/Interfaces.py:75) try:
---> [76](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/Interfaces.py:76)     self.records = self.parse(self.stream)
     [77](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/Interfaces.py:77) except Exception:
     [78](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/Interfaces.py:78)     if self.should_close_stream:

File [~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:415](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:415), in SeqXmlIterator.parse(self, handle)
    [413](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:413)     else:
    [414](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:414)         raise ValueError("XML file contains no data.")
--> [415](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:415) parser.feed(text)
    [416](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:416) seqXMLversion = content_handler.seqXMLversion
    [417](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:417) if seqXMLversion is not None:

File [~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:217](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:217), in ExpatParser.feed(self, data, isFinal)
    [210](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:210)     self._cont_handler.startDocument()
    [212](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:212) try:
    [213](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:213)     # The isFinal parameter is internal to the expat reader.
    [214](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:214)     # If it is set to true, expat will check validity of the entire
    [215](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:215)     # document. When feeding chunks, they are not normally final -
    [216](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:216)     # except when invoked from close.
--> [217](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:217)     self._parser.Parse(data, isFinal)
    [218](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:218) except expat.error as e:
    [219](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:219)     exc = SAXParseException(expat.ErrorString(e.code), e, self)

File /croot/python-split_1702663341049/work/Modules/pyexpat.c:416, in StartElement()

File [~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:369](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:369), in ExpatParser.start_element_ns(self, name, attrs)
    [366](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:366)     newattrs[apair] = value
    [367](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:367)     qnames[apair] = qname
--> [369](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:369) self._cont_handler.startElementNS(pair, None,
    [370](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/xml/sax/expatreader.py:370)                                   AttributesNSImpl(newattrs, qnames))

File [~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:84](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:84), in ContentHandler.startSeqXMLElement(self, name, qname, attrs)
     [82](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:82) url = f"http://www.seqxml.org/{self.seqXMLversion}/seqxml.xsd"
     [83](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:83) if schema != url:
---> [84](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:84)     raise ValueError(
     [85](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:85)         "XML Schema '%s' found not consistent with reported seqXML version %s"
     [86](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:86)         % (schema, self.seqXMLversion)
     [87](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:87)     )
     [88](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:88) self.endElementNS = self.endSeqXMLElement
     [89](https://vscode-remote+ssh-002dremote-002bbruce-002dubuntu.vscode-resource.vscode-cdn.net/home/bwittmann/GitRepos/pst/data_organization/~/anaconda3/envs/pst/lib/python3.11/site-packages/Bio/SeqIO/SeqXmlIO.py:89) self.startElementNS = self.startEntryElement

ValueError: XML Schema 'None' found not consistent with reported seqXML version 0.2
```

### Steps to reproduce

Just run the provided code with the provided seq.xml file.



Discussion:
Confirming behaviour:

```
$ python -c 'from Bio import SeqIO; print(len(list(SeqIO.parse("ACAM1.seq.xml", "seqxml"))))'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/xxx/mambaforge/lib/python3.10/site-packages/Bio/SeqIO/__init__.py", line 613, in parse
    return iterator_generator(handle)
  File "/Users/xxx/mambaforge/lib/python3.10/site-packages/Bio/SeqIO/SeqXmlIO.py", line 400, in __init__
    super().__init__(stream_or_path, mode="b", fmt="SeqXML")
  File "/Users/xxx/mambaforge/lib/python3.10/site-packages/Bio/SeqIO/Interfaces.py", line 76, in __init__
    self.records = self.parse(self.stream)
  File "/Users/xxx/mambaforge/lib/python3.10/site-packages/Bio/SeqIO/SeqXmlIO.py", line 415, in parse
    parser.feed(text)
  File "/Users/xxx/mambaforge/lib/python3.10/xml/sax/expatreader.py", line 217, in feed
    self._parser.Parse(data, isFinal)
  File "/Users/runner/miniforge3/conda-bld/python-split_1687559261959/work/Modules/pyexpat.c", line 416, in StartElement
  File "/Users/xxx/mambaforge/lib/python3.10/xml/sax/expatreader.py", line 369, in start_element_ns
    self._cont_handler.startElementNS(pair, None,
  File "/Users/xxx/mambaforge/lib/python3.10/site-packages/Bio/SeqIO/SeqXmlIO.py", line 84, in startSeqXMLElement
    raise ValueError(
ValueError: XML Schema 'None' found not consistent with reported seqXML version 0.2
```

However,

```
$ head -n 4 ACAM1.seq.xml 
<?xml version="1.0"?>
<seqXML source="Genome Reviews" sourceVersion="18-MAR-2008 (Rel. 88, Last updated, Version 2)" seqXMLversion="0.2">
 <entry id="ACAM100001">
  <species name="Acaryochloris marina (strain MBIC 11017)" ncbiTaxID="-644103924"/>
(base) Peters-iMac:Downloads pc40583$ head -n 3 ACAM1.seq.xml 
<?xml version="1.0"?>
<seqXML source="Genome Reviews" sourceVersion="18-MAR-2008 (Rel. 88, Last updated, Version 2)" seqXMLversion="0.2">
 <entry id="ACAM100001">
```

Note the minimal ``<seqXML ...>`` tag, lacking ``xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.seqxml.org/0.4/seqxml.xsd"`` seen in our test cases.

I am surprised to see v0.2 still in use, v0.4 was released June 28, 2011 -- see https://seqxml.org/0.4/seqxml_doc_v0.4.html#changes

I tried adding that snippet (adjusted to v0.2), and then the parser fails with ``ValueError: Unexpected attribute '(None, 'type')' found for DBRef element`` which seems somewhat related to the changes from v0.2 to v0.4. I suspect the parser simply doesn't work with v0.2 (which was current for less than a year in the early days of the file format).

Can you ask OMA to update their export code, or is this a static file they are hosting?
Unfortunately it's a static file. It wasn't much work for me to just use Python's inbuilt XML library instead. I get the sense that I'm probably the only person to have issues with this in a long time, seeing as v0.2 is so old. So maybe not worth fixing given how easy other options are to use. If it is worth fixing, perhaps adding an additional version flag for seqxml would work? For instance, the second argument to `SeqIO.parse` could be `seqxmlv0.2`.
I agree it is probably not worth fixing, but the XML says which version it
is, so the user ought not to need to say. So no API change or new format
name required.

On Sat, 13 Jan 2024 at 19:29, Bruce Wittmann ***@***.***>
wrote:

> Unfortunately it's a static file. It wasn't much work for me to just use
> Python's inbuilt XML library instead. I get the sense that I'm probably the
> only person to have issues with this in a long time, seeing as v0.2 is so
> old. So maybe not worth fixing given how easy other options are to use. If
> it is worth fixing, perhaps adding an additional version flag for seqxml
> would work? For instance, the second argument to SeqIO.parse could be
> seqxmlv0.2.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/biopython/biopython/issues/4578#issuecomment-1890747213>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAAPTV3KV3IS3T5WZ4TH5B3YOLOATAVCNFSM6AAAAABBVG4VG6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQOJQG42DOMRRGM>
> .
> You are receiving this because you commented.Message ID:
> ***@***.***>
>

> I agree it is probably not worth fixing, but the XML says which version it is, so the user ought not to need to say. So no API change or new format name required.

The parser could look at the value of seqXMLversion and adjust its behavior accordingly. It may be a simple change, depending on how much the two versions differ from each other. If it is a more extensive change, I agree that it is not worth fixing.
> I tried adding that snippet (adjusted to v0.2), and then the parser fails with ValueError: Unexpected attribute '(None, 'type')' found for DBRef element which seems somewhat related to the changes from v0.2 to v0.4. I suspect the parser simply doesn't work with v0.2 (which was current for less than a year in the early days of the file format).

@peterjc According to the [documentation](https://seqxml.org/0.4/seqxml_doc_v0.4.html#DBRef), the `type` attribute on DBRef elements is still supported and encouraged after 0.4, but only optional. Even if the SeqXML parser doesn't support versions before 0.4, the `type` attribute should still be supported.

But it looks like there are only a few differences between the versions 0.1, 0.2, 0.3, and 0.4 anyway, so IMO we might as well add support. I can work on a PR for this.

Resolving patch:
diff --git a/Bio/SeqIO/SeqXmlIO.py b/Bio/SeqIO/SeqXmlIO.py
--- a/Bio/SeqIO/SeqXmlIO.py
+++ b/Bio/SeqIO/SeqXmlIO.py
@@ -79,12 +79,24 @@ def startSeqXMLElement(self, name, qname, attrs):
                 )
         if self.seqXMLversion is None:
             raise ValueError("Failed to find seqXMLversion")
+        elif self.seqXMLversion not in ("0.1", "0.2", "0.3", "0.4"):
+            raise ValueError("Unsupported seqXMLversion")
         url = f"http://www.seqxml.org/{self.seqXMLversion}/seqxml.xsd"
-        if schema != url:
+        if schema is not None and schema != url:
             raise ValueError(
                 "XML Schema '%s' found not consistent with reported seqXML version %s"
                 % (schema, self.seqXMLversion)
             )
+        # speciesName and ncbiTaxID attributes on the root are only supported
+        # in 0.4
+        if self.speciesName and self.seqXMLversion != "0.4":
+            raise ValueError(
+                "Attribute 'speciesName' on root is only supported in version 0.4"
+            )
+        if self.ncbiTaxID and self.seqXMLversion != "0.4":
+            raise ValueError(
+                "Attribute 'ncbiTaxID' on root is only supported in version 0.4"
+            )
         self.endElementNS = self.endSeqXMLElement
         self.startElementNS = self.startEntryElement
 
@@ -117,7 +129,9 @@ def startEntryElement(self, name, qname, attrs):
             if namespace is None:
                 if localname == "id":
                     record.id = value
-                elif localname == "source":
+                elif localname == "source" and (
+                    self.seqXMLversion == "0.3" or self.seqXMLversion == "0.4"
+                ):
                     record.annotations["source"] = value
                 else:
                     raise ValueError(
@@ -130,7 +144,10 @@ def startEntryElement(self, name, qname, attrs):
         if record.id is None:
             raise ValueError("Failed to find entry ID")
         self.records.append(record)
-        self.startElementNS = self.startEntryFieldElement
+        if self.seqXMLversion == "0.1":
+            self.startElementNS = self.startEntryFieldElementVersion01
+        else:
+            self.startElementNS = self.startEntryFieldElement
         self.endElementNS = self.endEntryElement
 
     def endEntryElement(self, name, qname):
@@ -139,11 +156,34 @@ def endEntryElement(self, name, qname):
             raise ValueError("Expected to find the end of an entry element")
         if qname is not None:
             raise RuntimeError("Unexpected qname for entry element")
+        if self.records[-1].seq is None:
+            raise ValueError("Failed to find a sequence for entry element")
         self.startElementNS = self.startEntryElement
         self.endElementNS = self.endSeqXMLElement
 
+    def startEntryFieldElementVersion01(self, name, qname, attrs):
+        """Receive a field of an entry element and forward it for version 0.1."""
+        namespace, localname = name
+        if namespace is not None:
+            raise ValueError(
+                f"Unexpected namespace '{namespace}' for {localname} element"
+            )
+        if qname is not None:
+            raise RuntimeError(f"Unexpected qname '{qname}' for {localname} element")
+        if localname == "species":
+            return self.startSpeciesElement(attrs)
+        if localname == "description":
+            return self.startDescriptionElement(attrs)
+        if localname in ("dnaSeq", "rnaSeq", "aaSeq"):
+            return self.startSequenceElement(attrs)
+        if localname == "alternativeID":
+            return self.startDBRefElement(attrs)
+        if localname == "property":
+            return self.startPropertyElement(attrs)
+        raise ValueError(f"Unexpected field {localname} in entry")
+
     def startEntryFieldElement(self, name, qname, attrs):
-        """Receive a field of an entry element and forward it."""
+        """Receive a field of an entry element and forward it for versions >=0.2."""
         namespace, localname = name
         if namespace is not None:
             raise ValueError(
@@ -251,11 +291,17 @@ def endSequenceElement(self, name, qname):
         if qname is not None:
             raise RuntimeError(f"Unexpected qname '{qname}' for sequence end")
         record = self.records[-1]
-        if localname == "DNAseq":
+        if (localname == "DNAseq" and self.seqXMLversion != "0.1") or (
+            localname == "dnaSeq" and self.seqXMLversion == "0.1"
+        ):
             record.annotations["molecule_type"] = "DNA"
-        elif localname == "RNAseq":
+        elif (localname == "RNAseq" and self.seqXMLversion != "0.1") or (
+            localname == "rnaSeq" and self.seqXMLversion == "0.1"
+        ):
             record.annotations["molecule_type"] = "RNA"
-        elif localname == "AAseq":
+        elif (localname == "AAseq" and self.seqXMLversion >= "0.1") or (
+            localname == "aaSeq" and self.seqXMLversion == "0.1"
+        ):
             record.annotations["molecule_type"] = "protein"
         else:
             raise RuntimeError(
@@ -267,12 +313,15 @@ def endSequenceElement(self, name, qname):
 
     def startDBRefElement(self, attrs):
         """Parse a database cross reference."""
+        TYPE = None
         source = None
         ID = None
         for key, value in attrs.items():
             namespace, localname = key
             if namespace is None:
-                if localname == "source":
+                if localname == "type":
+                    TYPE = value
+                elif localname == "source":
                     source = value
                 elif localname == "id":
                     ID = value
@@ -284,11 +333,16 @@ def startDBRefElement(self, attrs):
                 raise ValueError(
                     f"Unexpected namespace '{namespace}' for DBRef attribute"
                 )
-        # The attributes "source" and "id" are required:
+        # The attributes "source" and "id" are required, and "type" in versions
+        # 0.2-0.3:
         if source is None:
             raise ValueError("Failed to find source for DBRef element")
         if ID is None:
             raise ValueError("Failed to find id for DBRef element")
+        if TYPE is None and (
+            self.seqXMLversion == "0.2" or self.seqXMLversion == "0.3"
+        ):
+            raise ValueError("Failed to find type for DBRef element")
         if self.data is not None:
             raise RuntimeError(f"Unexpected data found: '{self.data}'")
         self.data = ""
@@ -305,7 +359,9 @@ def endDBRefElement(self, name, qname):
             raise RuntimeError(f"Unexpected namespace '{namespace}' for DBRef element")
         if qname is not None:
             raise RuntimeError(f"Unexpected qname '{qname}' for DBRef element")
-        if localname != "DBRef":
+        if (localname != "DBRef" and self.seqXMLversion != "0.1") or (
+            localname != "alternativeID" and self.seqXMLversion == "0.1"
+        ):
             raise RuntimeError(f"Unexpected localname '{localname}' for DBRef element")
         if self.data:
             raise RuntimeError(


Test patch:
diff --git a/Tests/SeqXML/corrupt_example3.xml b/Tests/SeqXML/corrupt_example3.xml
new file mode 100644
--- /dev/null
+++ b/Tests/SeqXML/corrupt_example3.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<seqXML seqXMLversion="0.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.seqxml.org/0.3/seqxml.xsd" speciesName="fake">
+    <entry id="fake">
+        <description>entry has no sequence data</description>
+    </entry>
+</seqXML>
diff --git a/Tests/SeqXML/version_01_example.xml b/Tests/SeqXML/version_01_example.xml
new file mode 100644
--- /dev/null
+++ b/Tests/SeqXML/version_01_example.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<seqXML seqXMLversion="0.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.seqxml.org/0.1/seqxml.xsd">
+    <entry id="fakeDNA">
+        <dnaSeq>AAAAAACGTAAA</dnaSeq>
+        <alternativeID source="db" id="1"/>
+    </entry>
+    <entry id="fakeRNA">
+        <rnaSeq>AAAUUUUCTGAA</rnaSeq>
+    </entry>
+    <entry id="fakeAA">
+        <aaSeq>GAKKVFIEDVSKEFVEEFIWPAVQSSALYE</aaSeq>
+    </entry>
+</seqXML>
diff --git a/Tests/SeqXML/wrong_version1.xml b/Tests/SeqXML/wrong_version1.xml
new file mode 100644
--- /dev/null
+++ b/Tests/SeqXML/wrong_version1.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<seqXML seqXMLversion="0.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.seqxml.org/0.2/seqxml.xsd">
+    <entry id="fakeDNA">
+        <DNAseq>AAAAAACGTAAA</DNAseq>
+        <!-- Should fail because v0.2 DBRef tags must have a type attribute -->
+        <DBRef source="test" id="test"/>
+    </entry>
+    <entry id="fakeRNA">
+        <RNAseq>AAAUUUUCTGAA</RNAseq>
+    </entry>
+    <entry id="fakeAA">
+        <AAseq>GAKKVFIEDVSKEFVEEFIWPAVQSSALYE</AAseq>
+    </entry>
+</seqXML>
diff --git a/Tests/SeqXML/wrong_version2.xml b/Tests/SeqXML/wrong_version2.xml
new file mode 100644
--- /dev/null
+++ b/Tests/SeqXML/wrong_version2.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Should fail because speciesName in root is unsupported in v0.3 -->
+<seqXML seqXMLversion="0.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.seqxml.org/0.3/seqxml.xsd" speciesName="fake">
+    <entry id="fakeDNA">
+        <DNAseq>AAAAAACGTAAA</DNAseq>
+        <DBRef source="test" id="test"/>
+    </entry>
+</seqXML>
diff --git a/Tests/SeqXML/wrong_version3.xml b/Tests/SeqXML/wrong_version3.xml
new file mode 100644
--- /dev/null
+++ b/Tests/SeqXML/wrong_version3.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<seqXML seqXMLversion="0.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.seqxml.org/0.2/seqxml.xsd" speciesName="fake">
+    <!-- Should fail because "source" attribute on entries is only version >=0.3 -->
+    <entry id="fakeDNA" source="Fake">
+        <DNAseq>AAAAAACGTAAA</DNAseq>
+    </entry>
+</seqXML>
diff --git a/Tests/test_SeqIO_SeqXML.py b/Tests/test_SeqIO_SeqXML.py
--- a/Tests/test_SeqIO_SeqXML.py
+++ b/Tests/test_SeqIO_SeqXML.py
@@ -226,6 +226,29 @@ def f(path):
 
         self.assertRaises(ValueError, f, "SeqXML/corrupt_example1.xml")
         self.assertRaises(ValueError, f, "SeqXML/corrupt_example2.xml")
+        self.assertRaises(ValueError, f, "SeqXML/corrupt_example3.xml")
+
+
+class TestOldVersions(unittest.TestCase):
+    def test_version01(self):
+        """Test for version 0.1 specific features."""
+        records = list(SeqIO.parse("SeqXML/version_01_example.xml", "seqxml"))
+        self.assertEqual(records[0].seq, "AAAAAACGTAAA")
+        self.assertEqual(records[0].dbxrefs[0], "db:1")
+        self.assertEqual(records[1].seq, "AAAUUUUCTGAA")
+        self.assertEqual(records[2].seq, "GAKKVFIEDVSKEFVEEFIWPAVQSSALYE")
+
+    def test_wrong_version(self):
+        """Handling of wrong versions."""
+
+        def f(path):
+            records = SeqIO.parse(path, "seqxml")
+            for record in records:
+                pass
+
+        self.assertRaises(ValueError, f, "SeqXML/wrong_version1.xml")
+        self.assertRaises(ValueError, f, "SeqXML/wrong_version2.xml")
+        self.assertRaises(ValueError, f, "SeqXML/wrong_version3.xml")
 
 
 if __name__ == "__main__":