I store translation memory contents in a MySQL database. Some segments contain tags, they can be TMX format tags or HTML tags. This is what a segment with TMX tags looks like in the database:
I use htmlentities
to display the tags as they are in the browser-based editor. I then use jQuery to colour them red:
$(".editable").html(function(_, html) {
return html.replace(/(<.+?>)/gm, '<span class="text-danger">$1</span>');
});
Previously, I used to accomplish the same using PHP but without htmlentities
:
$sourceText = preg_replace('/<(.*?)/>/', "<span style='color:red'><$1></span>", $sourceText);
$targetText = preg_replace('/<(.*?)/>/', "<span style='color:red'><$1></span>", $targetText);
It doesn’t really matter which method I use to colour the tags. The segments look like this when displayed in my browser editor:
If I click the table cell that contains the source or target text, it opens for editing and looks like this:
When I click “Save”, the Javascript makes an Ajax call to a simle PHP script to save the changes into the database. This is the step where something causes the ampersand in the angle brackets html entities delimiting the tags to be converted to &
. This is what the segment looks like when saved in the database (look at the right half):
To be expected, when the edited segment is displayed in the browser in view mode again, the jQuery function that colours the tags in red no longer works – because the regex can no longer capture them, becuse the &
is now &
. And it looks like this:
And it looks like this when opened for editing:
It gets better! When I save this again, the &
in &
is itself converted to &
and it now looks like this in the database:
This can go on ad nauseam.
My question is: How can I prevent the conversion of &
into &
when saved into the database?
Nothing in the Javascript that converts the table cell to textarea
seems to be responsible for this conversion. Below is the entire JS code:
let table = document.getElementById('editable');
let editingTd;
table.onclick = function(event) {
/* if 'table.ondblclick' is used, this requires double-clicking the OK and Cancel buttons, too */
// 3 possible targets
let target = event.target.closest('.edit-cancel.btn.btn-danger,.edit-ok.btn.btn-success,.editable');
if (!table.contains(target)) return;
if (target.className == 'edit-cancel btn btn-danger') {
finishTdEdit(editingTd.elem, false);
} else if (target.className == 'edit-ok btn btn-success') {
finishTdEdit(editingTd.elem, true);
} else if (target.nodeName == 'TD') {
if (editingTd) return; // already editing
makeTdEditable(target);
}
};
function makeTdEditable(td) {
editingTd = {
elem: td,
data: td.innerHTML
};
td.classList.add('edit-td'); // td is in edit state, CSS also styles the area inside
let textArea = document.createElement('textarea');
textArea.style.width = td.clientWidth + 'px';
textArea.style.height = td.clientHeight + 'px';
textArea.className = 'edit-area';
textArea.value = td.innerHTML;
td.innerHTML = '';
td.appendChild(textArea);
textArea.focus();
td.insertAdjacentHTML("beforeEnd",
'<div class="edit-controls"><button class="edit-ok btn btn-success">SAVE</button><button class="edit-cancel btn btn-danger">CANCEL</button></div>'
);
}
function finishTdEdit(td, isOk) {
if (isOk) {
td.innerHTML = td.firstChild.value;
id = td.getAttribute("data-id");
segmentID = td.getAttribute("data-segmentID");
content = td.innerHTML;
fileName = td.getAttribute("data-fileName");
console.log('saved '+id+' successfully: '+td.innerHTML);
// AJAX to save edit to DB
$.ajax({
type: "POST",
url: "tmx_update.php",
data: {
fileName: fileName,
id: id,
content: content
},
success: function(data) {
$("#status-report-"+segmentID).html(data);
}
});
} else {
td.innerHTML = editingTd.data;
}
td.classList.remove('edit-td');
editingTd = null;
}