Skip to content

Commit

Permalink
Merge cf05a1c into c0cce66
Browse files Browse the repository at this point in the history
  • Loading branch information
TEParsons committed Mar 31, 2021
2 parents c0cce66 + cf05a1c commit 354fa3c
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 45 deletions.
66 changes: 21 additions & 45 deletions psychopy/experiment/_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,54 +276,30 @@ def writeScript(self, expPath=None, target="PsychoPy", modular=True):

return script

@property
def xml(self):
# Create experiment root element
experimentNode = xml.Element("PsychoPy2experiment")
experimentNode.set('encoding', 'utf-8')
experimentNode.set('version', __version__)
# Add settings node
settingsNode = self.settings.xml
experimentNode.append(settingsNode)
# Add routines node
routineNode = xml.Element("Routines")
for key, routine in self.routines.items():
routineNode.append(routine.xml)
experimentNode.append(routineNode)
# Add flow node
flowNode = self.flow.xml
experimentNode.append(flowNode)

return experimentNode

def saveToXML(self, filename):
self.psychopyVersion = psychopy.__version__ # make sure is current
# create the dom object
self.xmlRoot = xml.Element("PsychoPy2experiment")
self.xmlRoot.set('version', __version__)
self.xmlRoot.set('encoding', 'utf-8')
# store settings
settingsNode = xml.SubElement(self.xmlRoot, 'Settings')
for settingName in sorted(self.settings.params):
setting = self.settings.params[settingName]
self._setXMLparam(
parent=settingsNode, param=setting, name=settingName)
# store routines
routinesNode = xml.SubElement(self.xmlRoot, 'Routines')
# routines is a dict of routines
for routineName, routine in self.routines.items():
routineNode = self._setXMLparam(
parent=routinesNode, param=routine, name=routineName)
# a routine is based on a list of components
for component in routine:
componentNode = self._setXMLparam(
parent=routineNode, param=component,
name=component.params['name'].val)
for paramName in sorted(component.params):
param = component.params[paramName]
self._setXMLparam(
parent=componentNode, param=param, name=paramName)
# implement flow
flowNode = xml.SubElement(self.xmlRoot, 'Flow')
# a list of elements(routines and loopInit/Terms)
for element in self.flow:
elementNode = xml.SubElement(flowNode, element.getType())
if element.getType() == 'LoopInitiator':
loop = element.loop
name = loop.params['name'].val
elementNode.set('loopType', loop.getType())
elementNode.set('name', name)
for paramName in sorted(loop.params):
param = loop.params[paramName]
paramNode = self._setXMLparam(
parent=elementNode, param=param, name=paramName)
# override val with repr(val)
if paramName == 'conditions':
paramNode.set('val', repr(param.val))
elif element.getType() == 'LoopTerminator':
elementNode.set('name', element.loop.params['name'].val)
elif element.getType() == 'Routine':
elementNode.set('name', '%s' % element.params['name'])
self.xmlRoot = self.xml
# convert to a pretty string
# update our document to use the new root
self._doc._setroot(self.xmlRoot)
Expand Down
22 changes: 22 additions & 0 deletions psychopy/experiment/components/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from builtins import str, object, super
from past.builtins import basestring
from pathlib import Path
from xml.etree.ElementTree import Element

from psychopy import prefs
from psychopy.constants import FOREVER
Expand Down Expand Up @@ -124,6 +125,27 @@ def __init__(self, exp, parentName, name='',

self.order = ['name'] # name first, then timing, then others

@property
def xml(self):
# Make root element
element = Element(self.__class__.__name__)
element.set("name", self.params['name'].val)
# Add an element for each parameter
for key, param in sorted(self.params.items()):
# Create node
paramNode = Element("Param")
paramNode.set("name", key)
# Assign values
if hasattr(param, 'updates'):
paramNode.set('updates', "{}".format(param.updates))
if hasattr(param, 'val'):
paramNode.set('val', u"{}".format(param.val).replace("\n", "
"))
if hasattr(param, 'valType'):
paramNode.set('valType', param.valType)
element.append(paramNode)

return element

def integrityCheck(self):
"""
Run component integrity checks for non-visual components
Expand Down
22 changes: 22 additions & 0 deletions psychopy/experiment/components/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from builtins import object
import os
from pathlib import Path
from xml.etree.ElementTree import Element
import re
import wx.__version__
import psychopy
Expand Down Expand Up @@ -315,6 +316,27 @@ def __init__(self, parentName, exp, expName='', fullScr=True,
hint=_translate("When to export experiment to the HTML folder."),
label=_localized["Export HTML"], categ='Online')

@property
def xml(self):
# Make root element
element = Element("Settings")
# Add an element for each parameter
for key, param in sorted(self.params.items()):
if key == 'name':
continue
# Create node
paramNode = Element("Param")
paramNode.set("name", key)
# Assign values
if hasattr(param, 'updates'):
paramNode.set('updates', "{}".format(param.updates))
if hasattr(param, 'val'):
paramNode.set('val', u"{}".format(param.val).replace("\n", "
"))
if hasattr(param, 'valType'):
paramNode.set('valType', param.valType)
element.append(paramNode)
return element

def getInfo(self):
"""Rather than converting the value of params['Experiment Info']
into a dict from a string (which can lead to errors) use this function
Expand Down
17 changes: 17 additions & 0 deletions psychopy/experiment/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from __future__ import absolute_import, print_function
from past.builtins import basestring
from xml.etree.ElementTree import Element

from psychopy.experiment.routine import Routine
from psychopy.experiment.loops import LoopTerminator, LoopInitiator
Expand Down Expand Up @@ -54,6 +55,22 @@ def loopDict(self):
def __repr__(self):
return "psychopy.experiment.Flow(%s)" % (str(list(self)))

@property
def xml(self):
# Make root element
element = Element("Flow")
# Add an element for every Routine, Loop Initiator, Loop Terminator
for item in self:
sub = item.xml
if isinstance(item, Routine):
# Remove all sub elements (we only need its name)
comps = [comp for comp in sub]
for comp in comps:
sub.remove(comp)
element.append(sub)

return element

def addLoop(self, loop, startPos, endPos):
"""Adds initiator and terminator objects for the loop
into the Flow list"""
Expand Down
31 changes: 31 additions & 0 deletions psychopy/experiment/loops.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

from __future__ import absolute_import, print_function
from builtins import object
from xml.etree.ElementTree import Element

# from future import standard_library

from psychopy.experiment import getInitVals
Expand Down Expand Up @@ -592,6 +594,27 @@ def __init__(self, loop):
self.exp = loop.exp
loop.initiator = self

@property
def xml(self):
# Make root element
element = Element("LoopInitiator")
element.set("loopType", self.loop.__class__.__name__)
element.set("name", self.loop.params['name'].val)
# Add an element for each parameter
for key, param in sorted(self.loop.params.items()):
# Create node
paramNode = Element("Param")
paramNode.set("name", key)
# Assign values
if hasattr(param, 'updates'):
paramNode.set('updates', "{}".format(param.updates))
if hasattr(param, 'val'):
paramNode.set('val', u"{}".format(param.val).replace("\n", "
"))
if hasattr(param, 'valType'):
paramNode.set('valType', param.valType)
element.append(paramNode)
return element

def getType(self):
return 'LoopInitiator'

Expand Down Expand Up @@ -625,6 +648,14 @@ def __init__(self, loop):
self.exp = loop.exp
loop.terminator = self

@property
def xml(self):
# Make root element
element = Element("LoopTerminator")
element.set("name", self.loop.params['name'].val)

return element

def getType(self):
return 'LoopTerminator'

Expand Down
12 changes: 12 additions & 0 deletions psychopy/experiment/routine.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from __future__ import absolute_import, print_function

from psychopy.constants import FOREVER
from xml.etree.ElementTree import Element


class Routine(list):
Expand All @@ -37,6 +38,17 @@ def __repr__(self):
_rep = "psychopy.experiment.Routine(name='%s', exp=%s, components=%s)"
return _rep % (self.name, self.exp, str(list(self)))

@property
def xml(self):
# Make root element
element = Element("Routine")
element.set("name", self.name)
# Add each component's element
for comp in self:
element.append(comp.xml)

return element

@property
def name(self):
return self.params['name']
Expand Down
27 changes: 27 additions & 0 deletions psychopy/tests/test_app/test_builder/test_Experiment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import print_function
from past.builtins import execfile
from builtins import object
from pathlib import Path
import xml.etree.ElementTree as xml

import psychopy.experiment
from psychopy.experiment.components.text import TextComponent
Expand All @@ -18,6 +20,7 @@
from lxml import etree
import numpy
import sys
import re

# Jeremy Gray March 2011

Expand All @@ -30,6 +33,7 @@
# load should change things

allComponents = psychopy.experiment.getComponents(fetchIcons=False)
isTime = re.compile(r"\d+:\d+(:\d+)?( [AP]M)?")


def _filterout_legal(lines):
Expand Down Expand Up @@ -73,6 +77,29 @@ def setup(self):
def teardown_class(cls):
shutil.rmtree(cls.tmp_dir, ignore_errors=True)

def test_xml(self):
# Get all psyexp files in demos folder
demosFolder = Path(self.exp.prefsPaths['demos']) / 'builder'
for file in demosFolder.glob("**/*.psyexp"):
# Create experiment and load from psyexp
exp = psychopy.experiment.Experiment()
exp.loadFromXML(file)
# Compile to get what script should look like
target = exp.writeScript()
# Save as XML
temp = str(Path(self.tmp_dir) / "testXML.psyexp")
exp.saveToXML(temp)
# Load again
exp.loadFromXML(temp)
# Compile again
test = exp.writeScript()
# Remove any timestamps from script (these can cause false errors if compile takes longer than a second)
test = re.sub(isTime, "", test)
target = re.sub(isTime, "", target)
# Compare two scripts to make sure saving and loading hasn't changed anything
diff = difflib.unified_diff(target.splitlines(), test.splitlines())
assert list(diff) == []

def test_xsd(self):
# get files

Expand Down

0 comments on commit 354fa3c

Please sign in to comment.