diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java index e4465d5d4a25617af92bdc1f26ba716e57125cdd..13fdf1bf1649547dd35458991e37ccf81aa4e822 100644 --- a/src/main/java/org/olat/ims/qti21/QTI21Service.java +++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java @@ -96,7 +96,8 @@ public interface QTI21Service { /** * Load the assessmentTest based on the imsmanifest.xml found in the resource - * directory. Return null if the imsmanifest.xml is not found. + * directory. Return null if the imsmanifest.xml is not found. The assessmentTest + * is cached. * * @param resourceDirectory The directory where is the package * @param replace If true updates the cache @@ -105,8 +106,25 @@ public interface QTI21Service { */ public ResolvedAssessmentTest loadAndResolveAssessmentTest(File resourceDirectory, boolean replace, boolean debugInfo); + /** + * The assessment item is load and cached. + * + * @param assessmentObjectSystemId + * @param resourceDirectory + * @return + */ public ResolvedAssessmentItem loadAndResolveAssessmentItem(URI assessmentObjectSystemId, File resourceDirectory); + /** + * This method load a fresh instance from the disk and don't cache it. The instance can be changed and saved + * safely. + * + * @param assessmentObjectSystemId + * @param resourceDirectory + * @return + */ + public ResolvedAssessmentItem loadAndResolveAssessmentItemForCopy(URI assessmentObjectSystemId, File resourceDirectory); + public boolean updateAssesmentObject(File resourceFile, ResolvedAssessmentObject<?> resolvedAssessmentObject); public boolean persistAssessmentObject(File resourceFile, AssessmentObject assessmentObject); diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java index de4a1dd201112b72037bc84b19368fdbb3fa7716..3f4d3ebf6cb5ae14352c2ba91e8e77f849e69170 100644 --- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java @@ -320,16 +320,21 @@ public class QTI21ServiceImpl implements QTI21Service, UserDataDeletable, Initia public ResolvedAssessmentItem loadAndResolveAssessmentItem(URI assessmentObjectSystemId, File resourceDirectory) { File resourceFile = new File(assessmentObjectSystemId); return assessmentItemsCache.computeIfAbsent(resourceFile, (file) -> { - QtiXmlReader qtiXmlReader = new QtiXmlReader(jqtiExtensionManager()); - ResourceLocator fileResourceLocator = new PathResourceLocator(resourceDirectory.toPath()); - ResourceLocator inputResourceLocator = - ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator); - - AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, inputResourceLocator); - return assessmentObjectXmlLoader.loadAndResolveAssessmentItem(assessmentObjectSystemId); + return loadAndResolveAssessmentItemForCopy(assessmentObjectSystemId, resourceDirectory); }); } + @Override + public ResolvedAssessmentItem loadAndResolveAssessmentItemForCopy(URI assessmentObjectSystemId, File resourceDirectory) { + QtiXmlReader qtiXmlReader = new QtiXmlReader(jqtiExtensionManager()); + ResourceLocator fileResourceLocator = new PathResourceLocator(resourceDirectory.toPath()); + ResourceLocator inputResourceLocator = + ImsQTI21Resource.createResolvingResourceLocator(fileResourceLocator); + + AssessmentObjectXmlLoader assessmentObjectXmlLoader = new AssessmentObjectXmlLoader(qtiXmlReader, inputResourceLocator); + return assessmentObjectXmlLoader.loadAndResolveAssessmentItem(assessmentObjectSystemId); + } + @Override public boolean updateAssesmentObject(File resourceFile, ResolvedAssessmentObject<?> resolvedAssessmentObject) { AssessmentObject assessmentObject; diff --git a/src/main/java/org/olat/ims/qti21/pool/ImportExportHelper.java b/src/main/java/org/olat/ims/qti21/pool/ImportExportHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..7aea8beb416b11797431fa468aea63e992c53ef2 --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/pool/ImportExportHelper.java @@ -0,0 +1,141 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.ims.qti21.pool; + +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.olat.core.util.StringHelper; + +import uk.ac.ed.ph.jqtiplus.node.content.xhtml.hypertext.A; +import uk.ac.ed.ph.jqtiplus.node.content.xhtml.image.Img; +import uk.ac.ed.ph.jqtiplus.node.content.xhtml.object.Object; +import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; +import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; +import uk.ac.ed.ph.jqtiplus.utils.QueryUtils; + +/** + * + * Initial date: 16 mars 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ImportExportHelper { + + protected static List<String> getMaterials(AssessmentItem item) { + List<String> materials = new ArrayList<>(); + QueryUtils.search(Img.class, item).forEach((img) -> { + if(img.getSrc() != null) { + materials.add(img.getSrc().toString()); + } + }); + + QueryUtils.search(A.class, item).forEach((a) -> { + URI href = a.getHref(); + if(href != null && href.getHost() == null && href.getPath() != null) { + materials.add(href.getPath()); + } + }); + + QueryUtils.search(Object.class, item).forEach((object) -> { + if(StringHelper.containsNonWhitespace(object.getData())) { + materials.add(object.getData()); + } + }); + return materials; + } + + protected static void getMaterials(AssessmentItem item, File itemFile, AssessmentItemsAndResources materials) { + File directory = itemFile.getParentFile(); + + QueryUtils.search(Img.class, item).forEach((img) -> { + if(img.getSrc() != null) { + String imgPath = img.getSrc().toString(); + File imgFile = new File(directory, imgPath); + if(imgFile.exists()) { + materials.addMaterial(new ItemMaterial(imgFile, imgPath)); + } + } + }); + + QueryUtils.search(A.class, item).forEach((a) -> { + URI href = a.getHref(); + if(href != null && href.getHost() == null && href.getPath() != null) { + String hrefPath = href.getPath(); + File aFile = new File(directory, hrefPath); + if(aFile.exists()) { + materials.addMaterial(new ItemMaterial(aFile, hrefPath)); + } + } + }); + + QueryUtils.search(Object.class, item).forEach((object) -> { + if(StringHelper.containsNonWhitespace(object.getData())) { + String path = object.getData(); + File objectFile = new File(directory, path); + if(objectFile.exists()) { + materials.addMaterial(new ItemMaterial(objectFile, path)); + } + } + }); + } + + public static final class AssessmentItemsAndResources { + private final List<ResolvedAssessmentItem> itemEls = new ArrayList<>(); + private final List<ItemMaterial> materials = new ArrayList<>(); + + public List<ResolvedAssessmentItem> getAssessmentItems() { + return itemEls; + } + + public void addItemEl(ResolvedAssessmentItem el) { + itemEls.add(el); + } + + public List<ItemMaterial> getMaterials() { + return materials; + } + + public void addMaterial(ItemMaterial material) { + materials.add(material); + } + } + + public static final class ItemMaterial { + private final File file; + private final String exportUri; + + public ItemMaterial(File file, String exportUri) { + this.file = file; + this.exportUri = exportUri; + } + + public File getFile() { + return file; + } + + public String getExportUri() { + return exportUri; + } + } + +} diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java b/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java index f6c86be73af5a0aa1ea0484ce9f8d46badc2f2d3..1ade128960c01bf9cb7464d34a0f1717fe26cfe8 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java @@ -32,6 +32,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.concurrent.atomic.DoubleAdder; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -42,22 +43,24 @@ import org.olat.core.util.StringHelper; import org.olat.core.util.ZipUtil; import org.olat.core.util.io.ShieldOutputStream; import org.olat.ims.qti21.QTI21Service; +import org.olat.ims.qti21.model.QTI21QuestionType; +import org.olat.ims.qti21.model.xml.AssessmentTestBuilder; import org.olat.ims.qti21.model.xml.AssessmentTestFactory; import org.olat.ims.qti21.model.xml.ManifestBuilder; import org.olat.ims.qti21.model.xml.ManifestMetadataBuilder; +import org.olat.ims.qti21.model.xml.QtiNodesExtractor; +import org.olat.ims.qti21.pool.ImportExportHelper.AssessmentItemsAndResources; +import org.olat.ims.qti21.pool.ImportExportHelper.ItemMaterial; import org.olat.imscp.xml.manifest.ResourceType; import org.olat.modules.qpool.QuestionItemFull; import org.olat.modules.qpool.manager.QPoolFileStorage; -import uk.ac.ed.ph.jqtiplus.node.content.xhtml.image.Img; -import uk.ac.ed.ph.jqtiplus.node.content.xhtml.object.Object; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.item.interaction.Interaction; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer; -import uk.ac.ed.ph.jqtiplus.utils.QueryUtils; /** * @@ -97,7 +100,7 @@ public class QTI21ExportProcessor { URI assessmentItemUri = resourceFile.toURI(); ResolvedAssessmentItem resolvedAssessmentItem = qtiService - .loadAndResolveAssessmentItem(assessmentItemUri, rootDirectory); + .loadAndResolveAssessmentItemForCopy(assessmentItemUri, rootDirectory); enrichWithMetadata(qitem, resolvedAssessmentItem, manifestBuilder); try { @@ -154,37 +157,16 @@ public class QTI21ExportProcessor { File itemFile = new File(resourceDirectory, rootFilename); if(itemFile.exists()) { - ResolvedAssessmentItem assessmentItem = qtiService.loadAndResolveAssessmentItem(itemFile.toURI(), resourceDirectory); + ResolvedAssessmentItem resolvedAssessmentItem = qtiService.loadAndResolveAssessmentItemForCopy(itemFile.toURI(), resourceDirectory); + AssessmentItem assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); //enrichScore(itemEl); //enrichWithMetadata(fullItem, itemEl); - collectResources(assessmentItem.getRootNodeLookup().extractIfSuccessful(), itemFile, materials); - materials.addItemEl(assessmentItem); + ImportExportHelper.getMaterials(assessmentItem, itemFile, materials); + materials.addItemEl(resolvedAssessmentItem); } } - protected void collectResources(AssessmentItem item, File itemFile, AssessmentItemsAndResources materials) { - File directory = itemFile.getParentFile(); - QueryUtils.search(Img.class, item).forEach((img) -> { - if(img.getSrc() != null) { - String imgPath = img.getSrc().toString(); - File imgFile = new File(directory, imgPath); - if(imgFile.exists()) { - materials.addMaterial(new ItemMaterial(imgFile, imgPath)); - } - } - }); - - QueryUtils.search(Object.class, item).forEach((object) -> { - if(StringHelper.containsNonWhitespace(object.getData())) { - String path = object.getData(); - File objectFile = new File(directory, path); - if(objectFile.exists()) { - materials.addMaterial(new ItemMaterial(objectFile, path)); - } - } - }); - } public void enrichWithMetadata(QuestionItemFull qitem, ResolvedAssessmentItem resolvedAssessmentItem, ManifestBuilder manifestBuilder) { ResourceType resource = manifestBuilder.getResourceTypeByHref(qitem.getRootFilename()); @@ -202,6 +184,7 @@ public class QTI21ExportProcessor { ManifestBuilder manifest = ManifestBuilder.createAssessmentTestBuilder(); //assessment test + DoubleAdder atomicMaxScore = new DoubleAdder(); AssessmentTest assessmentTest = AssessmentTestFactory.createAssessmentTest("Assessment test from pool", "Section"); String assessmentTestFilename = assessmentTest.getIdentifier() + ".xml"; manifest.appendAssessmentTest(assessmentTestFilename); @@ -211,20 +194,49 @@ public class QTI21ExportProcessor { //assessment items for(QuestionItemFull qitem:fullItems) { - String rootFilename = qitem.getRootFilename(); File resourceDirectory = qpoolFileStorage.getDirectory(qitem.getDirectory()); File itemFile = new File(resourceDirectory, qitem.getRootFilename()); - String itemFilename = itemFile.getName(); - ResolvedAssessmentItem resolvedAssessmentItem = qtiService.loadAndResolveAssessmentItem(itemFile.toURI(), resourceDirectory); + ResolvedAssessmentItem resolvedAssessmentItem = qtiService.loadAndResolveAssessmentItemForCopy(itemFile.toURI(), resourceDirectory); + AssessmentItem assessmentItem = resolvedAssessmentItem.getRootNodeLookup().extractIfSuccessful(); + assessmentItem.setIdentifier(QTI21QuestionType.generateNewIdentifier(assessmentItem.getIdentifier())); + + //save the item in its own container + File container = new File(directory, qitem.getKey().toString()); + container.mkdirs(); + File newItemFile = new File(container, assessmentItem.getIdentifier() + ".xml"); + String newItemFilename = container + "/" + newItemFile.getName(); + qtiService.persistAssessmentObject(newItemFile, assessmentItem); - //enrichScore(itemEl); - //collectResources(itemEl, container, materials); - FileUtils.bcopy(itemFile, new File(directory, rootFilename), ""); - AssessmentTestFactory.appendAssessmentItem(section, itemFilename); - manifest.appendAssessmentItem(itemFilename); - ManifestMetadataBuilder metadata = manifest.getResourceBuilderByHref(itemFilename); + AssessmentTestFactory.appendAssessmentItem(section, newItemFilename); + manifest.appendAssessmentItem(newItemFilename); + ManifestMetadataBuilder metadata = manifest.getResourceBuilderByHref(newItemFilename); enrichWithMetadata(qitem, resolvedAssessmentItem, metadata); + + Double maxScore = QtiNodesExtractor.extractMaxScore(assessmentItem); + if(maxScore != null) { + atomicMaxScore.add(maxScore.doubleValue()); + } + + //write materials + AssessmentItemsAndResources materials = new AssessmentItemsAndResources(); + ImportExportHelper.getMaterials(assessmentItem, itemFile, materials); + for(ItemMaterial material:materials.getMaterials()) { + String exportPath = material.getExportUri(); + File originalFile = material.getFile(); + File exportFile = new File(container, exportPath); + if(!exportFile.getParentFile().exists()) { + exportFile.getParentFile().mkdirs(); + } + FileUtils.bcopy(originalFile, exportFile, "Copy material QTI 2.1"); + } + } + + AssessmentTestBuilder assessmentTestBuilder = new AssessmentTestBuilder(assessmentTest); + double sumMaxScore = atomicMaxScore.sum(); + if(sumMaxScore > 0.0d) { + assessmentTestBuilder.setMaxScore(sumMaxScore); } + assessmentTest = assessmentTestBuilder.build(); try(FileOutputStream out = new FileOutputStream(new File(directory, assessmentTestFilename))) { qtiSerializer.serializeJqtiObject(assessmentTest, out); @@ -233,7 +245,7 @@ public class QTI21ExportProcessor { } manifest.write(new File(directory, "imsmanifest.xml")); - } catch (IOException | URISyntaxException e) { + } catch (Exception e) { log.error("", e); } } @@ -257,17 +269,33 @@ public class QTI21ExportProcessor { File resourceDirectory = qpoolFileStorage.getDirectory(qitem.getDirectory()); File itemFile = new File(resourceDirectory, qitem.getRootFilename()); String itemFilename = itemFile.getName(); + String container = qitem.getKey().toString(); + String containedFilename = container + "/" + itemFilename; - ResolvedAssessmentItem resolvedAssessmentItem = qtiService.loadAndResolveAssessmentItem(itemFile.toURI(), resourceDirectory); - - //enrichScore(itemEl); - //collectResources(itemEl, container, materials); - - ZipUtil.addFileToZip(itemFilename, itemFile, zout); - AssessmentTestFactory.appendAssessmentItem(section, itemFilename); - manifest.appendAssessmentItem(itemFilename); - ManifestMetadataBuilder metadata = manifest.getResourceBuilderByHref(itemFilename); + ResolvedAssessmentItem resolvedAssessmentItem = qtiService.loadAndResolveAssessmentItemForCopy(itemFile.toURI(), resourceDirectory); + + ZipUtil.addFileToZip(containedFilename, itemFile, zout); + AssessmentTestFactory.appendAssessmentItem(section, containedFilename); + manifest.appendAssessmentItem(containedFilename); + ManifestMetadataBuilder metadata = manifest.getResourceBuilderByHref(containedFilename); enrichWithMetadata(qitem, resolvedAssessmentItem, metadata); + + //write materials + try { + Files.walkFileTree(resourceDirectory.toPath(), new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String filename = file.getFileName().toString(); + if(!"imsmanifest.xml".equals(filename) && !filename.startsWith(".") && !itemFilename.equals(filename)) { + String relPath = resourceDirectory.toPath().relativize(file).toString(); + ZipUtil.addFileToZip(container + "/" + relPath, file, zout); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + log.error("", e); + } } zout.putNextEntry(new ZipEntry(assessmentTestFilename)); @@ -371,43 +399,4 @@ public class QTI21ExportProcessor { metadata.setOpenOLATMetadataMasterIdentifier(qitem.getMasterIdentifier()); } } - - private static final class AssessmentItemsAndResources { - private final List<ResolvedAssessmentItem> itemEls = new ArrayList<>(); - private final List<ItemMaterial> materials = new ArrayList<>(); - - public List<ResolvedAssessmentItem> getAssessmentItems() { - return itemEls; - } - - public void addItemEl(ResolvedAssessmentItem el) { - itemEls.add(el); - } - - public List<ItemMaterial> getMaterials() { - return materials; - } - - public void addMaterial(ItemMaterial material) { - materials.add(material); - } - } - - private static final class ItemMaterial { - private final File file; - private final String exportUri; - - public ItemMaterial(File file, String exportUri) { - this.file = file; - this.exportUri = exportUri; - } - - public File getFile() { - return file; - } - - public String getExportUri() { - return exportUri; - } - } } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java b/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java index fe5db51af56803e1d32d7ea9a104e2124f905449..7e9099b45571e930de0b31ce8b9b2f7e4b71b218 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21ImportProcessor.java @@ -71,13 +71,10 @@ import org.olat.modules.qpool.model.QItemType; import org.olat.modules.qpool.model.QLicense; import org.olat.modules.qpool.model.QuestionItemImpl; -import uk.ac.ed.ph.jqtiplus.node.content.xhtml.image.Img; -import uk.ac.ed.ph.jqtiplus.node.content.xhtml.object.Object; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.reading.AssessmentObjectXmlLoader; import uk.ac.ed.ph.jqtiplus.reading.QtiXmlReader; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; -import uk.ac.ed.ph.jqtiplus.utils.QueryUtils; import uk.ac.ed.ph.jqtiplus.xmlutils.locators.FileResourceLocator; import uk.ac.ed.ph.jqtiplus.xmlutils.locators.ResourceLocator; @@ -193,7 +190,7 @@ public class QTI21ImportProcessor { manifest.write(new File(itemStorage, "imsmanifest.xml")); //process material - List<String> materials = getMaterials(assessmentItem); + List<String> materials = ImportExportHelper.getMaterials(assessmentItem); for(String material:materials) { if(material.indexOf("://") < 0) {// material can be an external URL Path materialFile = assessmentItemPath.getParent().resolve(material); @@ -221,22 +218,6 @@ public class QTI21ImportProcessor { log.error("", e); } } - - protected List<String> getMaterials(AssessmentItem item) { - List<String> materials = new ArrayList<>(); - QueryUtils.search(Img.class, item).forEach((img) -> { - if(img.getSrc() != null) { - materials.add(img.getSrc().toString()); - } - }); - - QueryUtils.search(Object.class, item).forEach((object) -> { - if(StringHelper.containsNonWhitespace(object.getData())) { - materials.add(object.getData()); - } - }); - return materials; - } protected QuestionItemImpl processItem(AssessmentItem assessmentItem, String comment, String originalItemFilename, String editor, String editorVersion, AssessmentItemMetadata metadata) { diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java index 0293c32288abc5ef38e8113f6cdc33735b93df57..992d62beb678688ea6d14463f099c13a5f53095c 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java @@ -28,8 +28,10 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.zip.ZipOutputStream; @@ -444,7 +446,7 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { //process material File materialDirRoot = itemFile.getParentFile(); - List<String> materials = processor.getMaterials(assessmentItem); + List<String> materials = ImportExportHelper.getMaterials(assessmentItem); for(String material:materials) { if(material.indexOf("://") < 0) {// material can be an external URL try { @@ -483,24 +485,36 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { } public void assembleTest(List<QuestionItemShort> items, Locale locale, ZipOutputStream zout) { - List<Long> itemKeys = new ArrayList<Long>(); - for(QuestionItemShort item:items) { - itemKeys.add(item.getKey()); - } - - List<QuestionItemFull> fullItems = questionItemDao.loadByIds(itemKeys); + List<QuestionItemFull> fullItems = loadQuestionFullItems(items); QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage, locale); processor.assembleTest(fullItems, zout); } public void exportToEditorPackage(File exportDir, List<QuestionItemShort> items, Locale locale) { - List<Long> itemKeys = toKeys(items); - List<QuestionItemFull> fullItems = questionItemDao.loadByIds(itemKeys); - + List<QuestionItemFull> fullItems = loadQuestionFullItems(items); QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage, locale); processor.assembleTest(fullItems, exportDir); } + private List<QuestionItemFull> loadQuestionFullItems(List<QuestionItemShort> items) { + List<Long> itemKeys = toKeys(items); + List<QuestionItemFull> fullItems = questionItemDao.loadByIds(itemKeys); + Map<Long, QuestionItemFull> fullItemMap = new HashMap<>(); + for(QuestionItemFull fullItem:fullItems) { + fullItemMap.put(fullItem.getKey(), fullItem); + } + + //reorder the fullItems; + List<QuestionItemFull> reorderedFullItems = new ArrayList<>(fullItems.size()); + for(QuestionItemShort item:items) { + QuestionItemFull itemFull = fullItemMap.get(item.getKey()); + if(itemFull != null) { + reorderedFullItems.add(itemFull); + } + } + return reorderedFullItems; + } + /** * Convert from QTI 1.2 to 2.1 * diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java index 8a2e3224f2d12456b4f8c44ba1c5531737d2af04..78ca7ccec75981bbf27f482f389e1696715bf2f1 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/AssessmentTestComposerController.java @@ -1104,7 +1104,7 @@ public class AssessmentTestComposerController extends MainLayoutBasicController qtiService.qtiSerializer().serializeJqtiObject(originalAssessmentItem, out); //change identifier and title - ResolvedAssessmentItem resolvedCopyItem = qtiService.loadAndResolveAssessmentItem(itemFile.toURI(), unzippedDirRoot); + ResolvedAssessmentItem resolvedCopyItem = qtiService.loadAndResolveAssessmentItemForCopy(itemFile.toURI(), unzippedDirRoot); AssessmentItem copiedAssessmentItem = resolvedCopyItem.getRootNodeLookup().extractIfSuccessful(); copiedAssessmentItem.setIdentifier(IdentifierGenerator.newAsString(type.getPrefix())); copiedAssessmentItem.setTitle(originalAssessmentItem.getTitle() + " (Copy)");