Page 1 of 1
JS plugin to automatically rewrite XML (was: Conref URI parsing issue)
Posted: Wed Sep 18, 2024 8:51 pm
by craigcharlie
Hi,
We recently moved from XMetaL to Oxygen Author 23.1. When we insert conrefs to our Tridion CMS topics, the links look like this:
Code: Select all
<row conref="GUID-FA47B387-E74E-4284-99EB-6C1831AECB5D#GUID-FA47B387-E74E-4284-99EB-6C1831AECB5D/OUTPUT_FORMAT" id="GUID-DB9E8AE7-42E7-4171-A620-659AE785C088">
However, XMetaL used a conref format like this:
Code: Select all
<row conref="GUID-FA47B387-E74E-4284-99EB-6C1831AECB5D#OUTPUT_FORMAT" id="GUID-DB9E8AE7-42E7-4171-A620-659AE785C086">
Both conref URI formats successfully display the content in our CMS client tools, and both are successfully rendered in our outputs.
However, when we open files created by XMetaL, Oxygen reports invalid conrefs and doesn't display the conref'ed content, which makes the files impossible to work with. Can I change our Oxygen configuration so that it:
a) successfully displays the conref'ed content in our legacy format, and
b) if possible, continue to add URIs in the legacy format?
thanks,
Charlie
Re: Conref URI parsing issue
Posted: Thu Sep 19, 2024 9:59 am
by Radu
Hi Charlie,
The general syntax for a DITA conref is something like this:
https://www.oxygenxml.com/dita/1.3/spec ... ibute.html
path/to/topic.dita#topicId/elementId
From your details it seems that XMetal did not add the
topicId/ part in the conref path reference. If this is how it behaves, this is incorrect according to the DITA 1.3 specification.
Oxygen does not have settings to support a syntax which is not correct according to the specification.
Regards,
Radu
Re: Conref URI parsing issue
Posted: Thu Sep 19, 2024 7:39 pm
by craigcharlie
Thanks for the quick reply Radu!
I've created a custom Author action that uses jsoperation to fix affected XML files, and exposed it as a menu action (code included at the end of this post).
However, I'd prefer to have this fix run automatically every time a topic is opened, at least for a few months until the bulk of files with this problem are fixed.
Is there a way to automatically run an action or XSLT transformation when an editable topic is opened? I could also handle this through XSLT if necessary.
Fyi, I tried leveraging Tridion Docs WriteXML plugin, which the connector runs when docs are opened. Turns out it's not a good candidate for this, there's not enough control/granularity over changes.
**************
Code: Select all
function doOperation() {
var results = authorAccess.getDocumentController().findNodesByXPath("//*[contains(@conref, '#') and not(contains(@conref, '/'))]", true, true, true);
for (var i = 0; i < results.length; i++) {
var node = results[i];
var conrefAttr = node.getAttribute("conref");
var origText = conrefAttr.getValue();
var correctPattern = /^([A-Z0-9\-]+)\#\1\/.*/;
if (!correctPattern.test(origText)) {
var regex = /^([^#]*)#/;
var replacedText = String.prototype.replace.call(origText, regex, function(match, p1) {
return p1 + '#' + p1 + '/';
});
if (replacedText !== origText) {
authorAccess.getDocumentController().setAttribute("conref", new Packages.ro.sync.ecss.extensions.api.node.AttrValue(replacedText), results[i]);
}
}
}
}
Re: Conref URI parsing issue
Posted: Fri Sep 20, 2024 6:25 am
by Radu
Hi,
There is this Javascript-based Oxygen plugin which applies an XSLT when the document is opened and when it is saved:
https://github.com/oxygenxml/wsaccess-j ... XSLTFilter
Maybe you can use it as an example. You can also modify the plugin to handle things from the Javascript code without using XSLT.
Regards,
Radu
Re: Conref URI parsing issue
Posted: Fri Sep 20, 2024 4:38 pm
by craigcharlie
That's perfect, ty Radu!
Re: Conref URI parsing issue
Posted: Tue Sep 24, 2024 8:15 pm
by craigcharlie
Hi Radu,
I've created my own native js plugin, and it's working mostly fine. However I'm having a couple of issues.
Firstly, I discovered that I need to use editorSelected in the editorOpenListener, because that's the only state that appears to catch the Tridion checkout operation - editorOpened is not fired when checkout happens.
This works well in one sense, because the contents of the rewritten conrefs show up correctly in opened read-only documents. However, it also sets the modified asterisk on the editor tab (*). I'm able to remove this with editorAccess.setModified(false). However, this isn't ideal for checked out documents, because if someone reloads without saving, then the changes go away. I tried to use isEditable() on both the page and editor to detect if the doc is read-only at open time, neither seem to return false for read-only docs. Any ideas on how to successfully detect whether a doc was originally read-only?
Finally, even though my operations correct the XML, the validation that happens when the editor is first opened still display in the author tab, see attached image.
I'm trying to use the following code to clear the tab's validation results, but I don't know how to identify the correct tabKey value for getAllResults - represented as question marks in the code below. I've Googled to try to find any implementation of getAllResults, but nothing is coming up.
Code: Select all
var PluginWorkspaceProvider = Packages.ro.sync.exml.workspace.api.PluginWorkspaceProvider;
var workspace = PluginWorkspaceProvider.getPluginWorkspace();
if (workspace !== null) {
var resultsManager = workspace.getResultsManager();
var allResults = resultsManager.getAllResults('?????');
if (allResults != null) {
for (var i = allResults.size() - 1; i >= 0; i--) {
var result = allResults.get(i);
resultsManager.removeResult(result);
}
}
}
Re: Conref URI parsing issue
Posted: Wed Sep 25, 2024 7:57 am
by Radu
Hi,
Firstly, I discovered that I need to use editorSelected in the editorOpenListener, because that's the only state that appears to catch the Tridion checkout operation - editorOpened is not fired when checkout happens.
What does the SDL "checkout" operation do?
1) Open the file and check it out.
or
2) Checkout an already opened file, a file which has already opened in the editor.
If it does (1), the "editorOpened" should be triggered, I do not see how SDL could open a new file in Oxygen without this "editorOpened" callback being triggered.
If it does (2), where is the checkout action mounted in Oxygen? Is it in the main menu?
I tried to use isEditable() on both the page and editor to detect if the doc is read-only at open time, neither seem to return false for read-only docs.
In general if the opened document does not allow editing inside it, the API "ro.sync.exml.workspace.api.editor.page.WSEditorPage.isEditable()" should return "false". Is the document not editable in this case also in the Text editing mode?
I'm trying to use the following code to clear the tab's validation results, but I don't know how to identify the correct tabKey value for getAllResults - represented as question marks in the code below.
The "tabKey" parameter should be exactly the name of the result tab, in this case "Author".
How about if you call:
Code: Select all
resultsManager.setResults("Author", new ArrayList(), ResultType.GENERIC)
?
Regards,
Radu
[Removed]
Posted: Wed Sep 25, 2024 12:34 pm
by craigcharlie
[Removed]
Re: Conref URI parsing issue
Posted: Wed Sep 25, 2024 12:34 pm
by craigcharlie
Hi Radu, thanks for the quick reply.
"Check out" is an option on the Tridion Docs menu. They don't expose their API, so unfortunately I can't hook into it directly. What I'm trying to achieve is to detect any open or checkout operation, and then perform xml updates on the backend. Regarding the editor modified state, I'm testing for this at the top of my listener:
Code: Select all
var isEditable = authorAccess.isEditable() && editorAccess.isEditable();
This should be false if either returns false. Then after I perform the xml replacement, I try this:
Code: Select all
if(!isEditable){editorAccess.setModified(false);}
However, isEditable is never false. If I take out the if statement and just call editorAccess.setModified(false), it does work. This is an issue for docs that are editable though, as a reload from the disk wipes the changes. I also can't save the editor without knowing if the doc was originally modifiable, as this causes issues with the repo paradigm - non-checked-out docs that become modifiable are then assumed to be checked out.
You asked if the editor was in text mode, it's not, it's in author mode. Here's the full code I'm using to call isEditable, in case you might see some incorrect structure:
Code: Select all
function applicationStarted(pluginWorkspaceAccess) {
Packages.java.lang.System.err.println("Plugin has been initialized and running.");
var editorOpenListener = {
editorSelected: function(editorLocation) {
var editorAccess = pluginWorkspaceAccess.getEditorAccess(editorLocation, Packages.ro.sync.exml.workspace.api.standalone.StandalonePluginWorkspace.MAIN_EDITING_AREA);
if (editorAccess == null) {
return;
}
var authorAccess = editorAccess.getCurrentPage();
var isEditable = authorAccess.isEditable() && editorAccess.isEditable();
Finally, using setResults throws an error "org.mozilla.javascript.EvaluatorException: Can't find method ro.sync.exml.workspace.b.g.b.setResults(string,java.util.ArrayList,object). " Using resultsManager.getAllResults("Author") doesn't throw any error but also doesn't return any results.
Thanks,
Charlie
Re: Conref URI parsing issue
Posted: Wed Sep 25, 2024 2:20 pm
by Radu
Hi Charlie,
Finally, using setResults throws an error "org.mozilla.javascript.EvaluatorException: Can't find method ro.sync.exml.workspace.b.g.b.setResults(string,java.util.ArrayList,object). "
Probably that third argument is somehow not properly passed. I think the javascript code should look like this:
Code: Select all
resultsManager.setResults("Author", new Packages.java.util.ArrayList(), Packages.ro.sync.exml.workspace.api.results.ResultsManager.ResultType.GENERIC)
In general the Javscript code may get quite hard to write and maintain as its complexity grows. If so maybe you can try switching to a Java-based plugin instead:
https://github.com/oxygenxml/sample-plu ... ace-access
Using resultsManager.getAllResults("Author") doesn't throw any error but also doesn't return any results.
Not sure why this does not work, maybe possibly because the "Author" tab with errors appears a bit after your code is executed?
Regards,
Radu
Re: Conref URI parsing issue
Posted: Wed Sep 25, 2024 2:56 pm
by craigcharlie
Not sure why this does not work, maybe possibly because the "Author" tab with errors appears a bit after your code is executed
Good point. Might need to set up a listener for results tab named Author, and then only run the logic if the earlier code was triggered.
Any comment on the isEditable part of my post? This is the main problem I need to solve...
Re: Conref URI parsing issue
Posted: Wed Sep 25, 2024 3:44 pm
by Radu
Hi,
About the "isEditable" bit, maybe the SDL code (and you could also ask on their side) sets a non-editable style on the root element.
So this API "ro.sync.exml.workspace.api.editor.page.author.WSAuthorEditorPageBase.getStyles(AuthorNode)"
https://archives.oxygenxml.com/Oxygen/E ... uthorNode-
could be called on the root element "authorEditorPage.getDocumentController().getAuthorDocumentNode().getRootElement()".
And then the obtained Styles object has an isEditable method:
https://archives.oxygenxml.com/Oxygen/E ... Editable--
and possibly this method returns "false" but I'm not sure.
Maybe we are digging too much into this, maybe we could have another approach.
Maybe just add an extra button on the toolbar or in the main menu and instruct people that when such errors occur they should click it...
Regards,
Radu
Re: Conref URI parsing issue
Posted: Wed Sep 25, 2024 9:16 pm
by craigcharlie
Hi Radu,
Thanks so much for all the help! It turns out that only the editorActivated state was a reliable source for isEdited, I guess the other states triggered before the doc was loaded.
Everything seems to be working perfectly now, including clearing the relevant results from the Author error result pane. Here's the full code of the plugin in case it's any use to anyone:
Code: Select all
function applicationStarted(pluginWorkspaceAccess) {
Packages.java.lang.System.err.println("Plugin has been initialized and running.");
//For troubleshooting
var Dialog = Packages.ro.sync.exml.workspace.api.standalone.ui.OKCancelDialog;
var dialog = new Dialog(
pluginWorkspaceAccess.getParentFrame(),
"Troubleshooting Information",
true
);
var JTextArea = Packages.javax.swing.JTextArea;
var JScrollPane = Packages.javax.swing.JScrollPane;
var textArea = new JTextArea();
textArea.setEditable(false);
var scrollPane = new JScrollPane(textArea);
scrollPane.setPreferredSize(new Packages.java.awt.Dimension(300, 200));
//Uncomment for troubleshooting
//textArea.append(isEditable + "\n");
//dialog.getContentPane().add(scrollPane);
//dialog.pack();
//dialog.setVisible(true);
var editorOpenListener = {
editorActivated: function(editorLocation) {
var editorAccess = pluginWorkspaceAccess.getEditorAccess(editorLocation, Packages.ro.sync.exml.workspace.api.standalone.StandalonePluginWorkspace.MAIN_EDITING_AREA);
if (editorAccess == null) {
return;
}
var authorAccess = editorAccess.getCurrentPage();
if (!(authorAccess instanceof Packages.ro.sync.exml.workspace.api.editor.page.author.WSAuthorEditorPage)) {
return;
}
var isEditable = false;
isEditable = authorAccess.isEditable();
authorAccess.setEditable(true);
var documentEdited = false;
var PluginWorkspaceProvider = Packages.ro.sync.exml.workspace.api.PluginWorkspaceProvider;
var workspace = PluginWorkspaceProvider.getPluginWorkspace();
var targetStrings = [];
// Fix XMetaL conref format
var results = authorAccess.getDocumentController().findNodesByXPath("//*[contains(@conref, '#') and not(contains(@conref, '/'))]", true, true, true);
for (var i = 0; i < results.length; i++) {
var node = results[i];
var conrefAttr = node.getAttribute("conref");
var origText = conrefAttr.getValue();
var correctPattern = /^([A-Z0-9\-]+)\#\1\/.*/;
var extractedText = conrefAttr.getValue().substring(conrefAttr.getValue().indexOf('#') + 1);
if (!correctPattern.test(origText)) {
var regex = /^([^#]*)#/;
var replacedText = String.prototype.replace.call(origText, regex, function(match, p1) {
return p1 + '#' + p1 + '/';
});
if (replacedText !== origText) {
targetStrings.push(extractedText);
authorAccess.getDocumentController().setAttribute("conref", new Packages.ro.sync.ecss.extensions.api.node.AttrValue(replacedText), results[i]);
documentEdited = true;
}
}
}
// Fix Duplicate IDs
var nodesWithId = authorAccess.getDocumentController().findNodesByXPath("//*[starts-with(@id, 'GUID-')]", true, true, true);
var ids = {};
var uuid = Packages.java.util.UUID;
for (var i = 0; i < nodesWithId.length; i++) {
var idAttr = nodesWithId[i].getAttribute("id");
if (idAttr) {
var idVal = idAttr.getValue();
if (ids[idVal]) {
ids[idVal].push(nodesWithId[i]);
} else {
ids[idVal] = [nodesWithId[i]];
}
}
}
for (var id in ids) {
if (ids[id].length > 1) { // More than one occurrence means duplicates exist
// We skip the first element because it's not a duplicate
targetStrings.push(ids[id][0].getAttribute("id").getValue());
for (var j = 1; j < ids[id].length; j++) {
var newGUID = "GUID-" + uuid.randomUUID().toString().toUpperCase();
authorAccess.getDocumentController().setAttribute("id", new Packages.ro.sync.ecss.extensions.api.node.AttrValue(newGUID), ids[id][j]);
documentEdited = true;
}
}
}
if(!isEditable){
editorAccess.setModified(false);
authorAccess.setEditable(false);
} else if (documentEdited) {
editorAccess.save();
}
// Clear results manager
if (workspace !== null) {
var resultsManager = workspace.getResultsManager();
var allResults = resultsManager.getAllResults("Author");
if (allResults != null) {
for (var i = allResults.size() - 1; i >= 0; i--) {
var result = allResults.get(i);
var resultText = result.toString();
if (containsAnyTarget(resultText, targetStrings)) {
resultsManager.removeResult("Author", result);
}
}
}
}
}
};
editorOpenListener = new JavaAdapter(Packages.ro.sync.exml.workspace.api.listeners.WSEditorChangeListener, editorOpenListener);
pluginWorkspaceAccess.addEditorChangeListener(editorOpenListener, Packages.ro.sync.exml.workspace.api.PluginWorkspace.MAIN_EDITING_AREA);
}
function applicationClosing(pluginWorkspaceAccess) {
}
function containsAnyTarget(resultText, targets) {
return targets.some(function(target) {
return resultText.includes(target);
});
}
Re: JS plugin to automatically rewrite XML (was: Conref URI parsing issue)
Posted: Thu Sep 26, 2024 7:06 am
by Radu
Hi,
If a document is already opened and selected and someone calls "Check out" from the main menu you will not receive an "editorSelected" event because the file is already opened and selected, but indeed you receive "editorActivated" events more often, whenever focus is given to the current selected document.
When a document tab gets selected you should receive both "editorSelected" and "editorActivated" events in this order.
The SDL integration is done with the same APIs as yours so sometimes it's also possible that maybe on "editorSelected" both your code and theirs attempts to do something and it depends on which listener gets called first.
Regards,
Radu