JS plugin to automatically rewrite XML (was: Conref URI parsing issue)
Post here questions and problems related to editing and publishing DITA content.
-
- Posts: 12
- Joined: Thu Oct 27, 2022 6:24 pm
JS plugin to automatically rewrite XML (was: Conref URI parsing issue)
Post 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:
However, XMetaL used a conref format like this:
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
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">
Code: Select all
<row conref="GUID-FA47B387-E74E-4284-99EB-6C1831AECB5D#OUTPUT_FORMAT" id="GUID-DB9E8AE7-42E7-4171-A620-659AE785C086">
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
Last edited by craigcharlie on Wed Sep 25, 2024 9:52 pm, edited 2 times in total.
Re: Conref URI parsing issue
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
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
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
<oXygen/> XML Editor
http://www.oxygenxml.com
-
- Posts: 12
- Joined: Thu Oct 27, 2022 6:24 pm
Re: Conref URI parsing issue
Post 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.
**************
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
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
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
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
<oXygen/> XML Editor
http://www.oxygenxml.com
-
- Posts: 12
- Joined: Thu Oct 27, 2022 6:24 pm
-
- Posts: 12
- Joined: Thu Oct 27, 2022 6:24 pm
Re: Conref URI parsing issue
Post 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.
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);
}
}
}
- Attachments
-
- image.png (19.79 KiB) Viewed 390 times
Re: Conref URI parsing issue
Hi,
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?
How about if you call:
?
Regards,
Radu
What does the SDL "checkout" operation do?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.
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?
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 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.
The "tabKey" parameter should be exactly the name of the result tab, in this case "Author".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.
How about if you call:
Code: Select all
resultsManager.setResults("Author", new ArrayList(), ResultType.GENERIC)
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
<oXygen/> XML Editor
http://www.oxygenxml.com
-
- Posts: 12
- Joined: Thu Oct 27, 2022 6:24 pm
[Removed]
Post by craigcharlie »
[Removed]
Last edited by craigcharlie on Wed Sep 25, 2024 9:54 pm, edited 1 time in total.
-
- Posts: 12
- Joined: Thu Oct 27, 2022 6:24 pm
Re: Conref URI parsing issue
Post 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:
This should be false if either returns false. Then after I perform the xml replacement, I try this: 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:
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
"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();
Code: Select all
if(!isEditable){editorAccess.setModified(false);}
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();
Thanks,
Charlie
Re: Conref URI parsing issue
Hi Charlie,
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
Regards,
Radu
Probably that third argument is somehow not properly passed. I think the javascript code should look like this: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). "
Code: Select all
resultsManager.setResults("Author", new Packages.java.util.ArrayList(), Packages.ro.sync.exml.workspace.api.results.ResultsManager.ResultType.GENERIC)
https://github.com/oxygenxml/sample-plu ... ace-access
Not sure why this does not work, maybe possibly because the "Author" tab with errors appears a bit after your code is executed?Using resultsManager.getAllResults("Author") doesn't throw any error but also doesn't return any results.
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
<oXygen/> XML Editor
http://www.oxygenxml.com
-
- Posts: 12
- Joined: Thu Oct 27, 2022 6:24 pm
Re: Conref URI parsing issue
Post by craigcharlie »
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.Not sure why this does not work, maybe possibly because the "Author" tab with errors appears a bit after your code is executed
Any comment on the isEditable part of my post? This is the main problem I need to solve...
Re: Conref URI parsing issue
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
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
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
<oXygen/> XML Editor
http://www.oxygenxml.com
-
- Posts: 12
- Joined: Thu Oct 27, 2022 6:24 pm
Re: Conref URI parsing issue
Post 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:
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)
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
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
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
<oXygen/> XML Editor
http://www.oxygenxml.com
Return to “DITA (Editing and Publishing DITA Content)”
Jump to
- Oxygen XML Editor/Author/Developer
- ↳ Feature Request
- ↳ Common Problems
- ↳ DITA (Editing and Publishing DITA Content)
- ↳ SDK-API, Frameworks - Document Types
- ↳ DocBook
- ↳ TEI
- ↳ XHTML
- ↳ Other Issues
- Oxygen XML Web Author
- ↳ Feature Request
- ↳ Common Problems
- Oxygen Content Fusion
- ↳ Feature Request
- ↳ Common Problems
- Oxygen JSON Editor
- ↳ Feature Request
- ↳ Common Problems
- Oxygen PDF Chemistry
- ↳ Feature Request
- ↳ Common Problems
- Oxygen Feedback
- ↳ Feature Request
- ↳ Common Problems
- Oxygen XML WebHelp
- ↳ Feature Request
- ↳ Common Problems
- XML
- ↳ General XML Questions
- ↳ XSLT and FOP
- ↳ XML Schemas
- ↳ XQuery
- NVDL
- ↳ General NVDL Issues
- ↳ oNVDL Related Issues
- XML Services Market
- ↳ Offer a Service