-
Notifications
You must be signed in to change notification settings - Fork 22
Implement custom format template interpolation for refbox citation display #1256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: clarin-v7
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -349,14 +349,61 @@ public ResponseEntity<RefBoxDTO> getRefboxInfo( | |||||||||||||||||||||||||||||||||||||||||||
| * Build the display text for the RefBox based on the Item Metadata. | ||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||
| private String buildDisplayText(Context context, Item item) { | ||||||||||||||||||||||||||||||||||||||||||||
| // 1. Authors | ||||||||||||||||||||||||||||||||||||||||||||
| // Check for custom format template | ||||||||||||||||||||||||||||||||||||||||||||
| String formatTemplate = itemService.getMetadataFirstValue(item, "local", "refbox", "format", DEFAULT_LANGUAGE); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if (formatTemplate != null && !formatTemplate.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||
| return buildDisplayTextFromTemplate(context, item, formatTemplate); | ||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||
| return buildDisplayTextDefault(context, item); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||
| * Build display text using a custom format template with variable interpolation. | ||||||||||||||||||||||||||||||||||||||||||||
| * Supported variables: {title}, {authors}, {pid}, {repository}, {year}, {publisher} | ||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||
| private String buildDisplayTextFromTemplate(Context context, Item item, String template) { | ||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The context parameter doesn't seem to be used in the method body. Similarly - in all buildDisplayText* methods: |
||||||||||||||||||||||||||||||||||||||||||||
| // Extract all metadata values | ||||||||||||||||||||||||||||||||||||||||||||
| List<String> authors = itemService.getMetadata(item, "dc", "contributor", "author", DEFAULT_LANGUAGE) | ||||||||||||||||||||||||||||||||||||||||||||
| .stream().map(MetadataValue::getValue).collect(Collectors.toList()); | ||||||||||||||||||||||||||||||||||||||||||||
| // If there are no authors, try to get the publisher metadata | ||||||||||||||||||||||||||||||||||||||||||||
| if (authors.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||
| authors = itemService.getMetadata(item, "dc", "publisher", null, DEFAULT_LANGUAGE) | ||||||||||||||||||||||||||||||||||||||||||||
| .stream().map(MetadataValue::getValue).collect(Collectors.toList()); | ||||||||||||||||||||||||||||||||||||||||||||
| String authorText = formatAuthors(authors); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| String year = ""; | ||||||||||||||||||||||||||||||||||||||||||||
| String issued = itemService.getMetadataFirstValue(item, "dc", "date", "issued", DEFAULT_LANGUAGE); | ||||||||||||||||||||||||||||||||||||||||||||
| if (issued != null && !issued.isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||
| year = issued.split("-")[0]; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| String title = itemService.getMetadataFirstValue(item, "dc", "title", null, DEFAULT_LANGUAGE); | ||||||||||||||||||||||||||||||||||||||||||||
| String publisher = itemService.getMetadataFirstValue(item, "dc", "publisher", null, DEFAULT_LANGUAGE); | ||||||||||||||||||||||||||||||||||||||||||||
| String repository = configurationService.getProperty("dspace.name"); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // Get identifier (prefer DOI, fallback to URI) | ||||||||||||||||||||||||||||||||||||||||||||
| String pid = itemService.getMetadataFirstValue(item, "dc", "identifier", "doi", DEFAULT_LANGUAGE); | ||||||||||||||||||||||||||||||||||||||||||||
| if (pid == null) { | ||||||||||||||||||||||||||||||||||||||||||||
| pid = itemService.getMetadataFirstValue(item, "dc", "identifier", "uri", DEFAULT_LANGUAGE); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // Perform template interpolation | ||||||||||||||||||||||||||||||||||||||||||||
| String result = template; | ||||||||||||||||||||||||||||||||||||||||||||
| result = result.replace("{title}", title != null ? title : ""); | ||||||||||||||||||||||||||||||||||||||||||||
| result = result.replace("{authors}", authorText != null ? authorText : ""); | ||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think, some of the fields like authorText, year, cannot be null (so check for null isn't needed) |
||||||||||||||||||||||||||||||||||||||||||||
| result = result.replace("{pid}", pid != null ? pid : ""); | ||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The {pid} should be (likely) replaced with :
(in order to render handle as html link) |
||||||||||||||||||||||||||||||||||||||||||||
| result = result.replace("{repository}", repository != null ? repository : ""); | ||||||||||||||||||||||||||||||||||||||||||||
| result = result.replace("{year}", year != null ? year : ""); | ||||||||||||||||||||||||||||||||||||||||||||
| result = result.replace("{publisher}", publisher != null ? publisher : ""); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| return result; | |
| // Perform template interpolation efficiently | |
| Map<String, String> variables = new HashMap<>(); | |
| variables.put("title", title != null ? title : ""); | |
| variables.put("authors", authorText != null ? authorText : ""); | |
| variables.put("pid", pid != null ? pid : ""); | |
| variables.put("repository", repository != null ? repository : ""); | |
| variables.put("year", year != null ? year : ""); | |
| variables.put("publisher", publisher != null ? publisher : ""); | |
| StringBuilder result = new StringBuilder(template); | |
| for (Map.Entry<String, String> entry : variables.entrySet()) { | |
| String placeholder = "{" + entry.getKey() + "}"; | |
| int idx; | |
| // Replace all occurrences of the placeholder | |
| while ((idx = result.indexOf(placeholder)) != -1) { | |
| result.replace(idx, idx + placeholder.length(), entry.getValue()); | |
| } | |
| } | |
| return result.toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be more consistent if buildDisplayTextDefault(Item item) delegates to
buildDisplayTextFromTemplate(Item item, String template), based on the properties, in the way that
StringBuilder template = new StringBuilder();
if (authors.isEmpty()) {
template.append("{author}, ");
}
if (!year.isEmpty()) {
template.append("{year}, ");
}
if (!title.isEmpty()) {
template.append("<i>{title</i>");
}
....
// and finally call
buildDisplayTextFromTemplate(Item item, template.toString());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useless check for null for authorText - can be ommited
Copilot
AI
Aug 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fallback logic is duplicated from the original implementation but creates confusion since publisher is now treated as author. Consider extracting this logic into a separate method with a clear name like getAuthorTextWithPublisherFallback() to make the intention explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same as above: check for null is not needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check for null not needed for authorText
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -257,6 +257,84 @@ public void testFeaturedServiceWithMalformedLink() throws Exception { | |||||
| .andExpect(jsonPath("$.featuredServices.featuredService").isEmpty()); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| public void testRefboxInfoWithBothAuthorAndPublisher() throws Exception { | ||||||
| context.turnOffAuthorisationSystem(); | ||||||
| Item itemWithBoth = ItemBuilder.createItem(context, collection) | ||||||
| .withTitle("Test Item") | ||||||
| .withAuthor("Test Author") | ||||||
| .withMetadata("dc", "publisher", null, "Test Publisher") | ||||||
| .withIssueDate("2023-01-01") | ||||||
| .build(); | ||||||
| context.restoreAuthSystemState(); | ||||||
|
|
||||||
| String token = getAuthToken(admin.getEmail(), password); | ||||||
| getClient(token).perform(get("/api/core/refbox?handle=" + itemWithBoth.getHandle())) | ||||||
| .andExpect(status().isOk()) | ||||||
| .andExpect(jsonPath("$.displayText").value(org.hamcrest.Matchers.containsString("Test Author"))) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The org.hamcrest.Matchers.containsString can be added to static imports The containsString method is also used in the test methods below. |
||||||
| .andExpect(jsonPath("$.displayText").value(org.hamcrest.Matchers.containsString("Test Publisher"))); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| public void testRefboxInfoWithCustomFormatTemplate() throws Exception { | ||||||
| context.turnOffAuthorisationSystem(); | ||||||
| Item itemWithTemplate = ItemBuilder.createItem(context, collection) | ||||||
| .withTitle("Custom Title") | ||||||
| .withAuthor("Custom Author") | ||||||
| .withMetadata("dc", "publisher", null, "Custom Publisher") | ||||||
| .withMetadata("local", "refbox", "format", | ||||||
| "{authors} ({year}). {title}. {publisher}. {repository}. {pid}") | ||||||
|
||||||
| "{authors} ({year}). {title}. {publisher}. {repository}. {pid}") | |
| CUSTOM_FORMAT_TEMPLATE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also use MockHttpServletRequestBuilder#param method here
getClient(token).perform(
get("/api/core/refbox")
.param("handle", itemWithTemplate.getHandle())
)
.andExpect(....
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The metadata field name should follow DSpace metadata naming conventions. Consider using a more standard namespace like
dc.format.templateor documenting whylocal.refbox.formatis the preferred choice in the code comments.