cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Paste Callback ACL Question

berard
1-Newbie

Paste Callback ACL Question

Our acl code has a callback for insert_tag that detects the insertion
of specific ID elements, and assigns them a unique @ID attribute
(using the OID) upon insertion. This has worked great for years, and
due to the nature of how we were using the tags, was sufficient for
our needs.

Our new tagsets, however, lend themselves to being copy/pasted in
order to use the previous structure as the basis for new items added.
Unfortunately, the insert_tag callback does not fire on a paste.

So... I was looking into using the paste callback, but it seems that
pasted content does not trigger insert_tag, even if the given tag is
in the buffer. Furthermore, I think it might be better to use the
copy callback, since I would want the ID to remain in the case of a
cut/paste.

Does anyone know offhand if there is any easy way to interrogate the
copy/pasted tags, aside from doing some sort of search across the
actual text in the buffer?

Ultimately, all I'm trying to do is determine if the user is trying to
copy something like <tag id="id_1234"> and to automatically update the
id to something new and unique before it has been committed.

Thanks in advance for any pointers.

keith
15 REPLIES 15

Hi Keith--

You might try doing something like this in your paste callback:

1) Create empty dummy document
2) Paste content into the dummy document
3) Do whatever you need to do to clean it up
4) Select the tweaked content of the dummy document and copy it
5) Dispose of the dummy document
6) Paste the tweaked content into the original document

--Clay
bibach
1-Newbie
(To:berard)

My colleagues and I had to do something like this a few years back. I
don't recall all the details, but the basic approach we used was to
have the paste callback paste the buffer into a temporary document,
walk through and fix up any IDs that already existed in the target
document, then yank it back into the buffer and proceed with the paste
into the target document.

This worked well since it only updated the IDs when absolutely
necessary to maintain uniqueness, so moving content around in the
document with cut and paste wouldn't break links.

We used a sequence number, maintained in an attribute on the document
element, which was incremented each time we needed a fresh ID, rather
than an OID-based value. (You don't get clashes with those? Seems
like it could happen, though probably not often.) However, I think
the approach could work with that system, as well.

Let me know if you want more details and I'll see if I can get my
hands on the code to refresh my memory.

-Brandon 🙂


bibach
1-Newbie
(To:berard)

On Tue, Nov 16, 2010 at 3:37 PM, Clay Helberg <chelberg@terraxml.com> wrote:
> You might try doing something like this in your paste callback:

Yeah... what Clay said. 😉 (The concise typers always seem to win the race.)

-Brandon 🙂
berard
1-Newbie
(To:berard)

Clay, Brandon... thanks,

I was afraid that it wouldn't be as straight-forward as I had hoped.

I might be best to do this using the Java API then. We have most of
our tag callbacks in ACL so I thought I'd keep it there, but pretty
much nobody is proficient in ACL here anymore. The rest of our code,
and all of our XUI is in Java, so that's probably a better place to
put "new" code anyway.

I just know that even Java sometimes has to revert to ACL at times
when there is no alternative, and was hoping on a longshot that there
was some crafty ACL snippet I could use.

No worries, thanks for keeping me from going down the wrong path for
too long. ACL programming is more of a research project than
productive for me these days, so I'd best leave it to the
professionals 🙂

keith

Hi Keith--

I'm sympathetic to your preference for Java, especially if that's what most of your customizations are written in. But ACL isn't really all that tough to pick up, and for certain things it is definitely easier. I happened to be working on a similar thing at the moment, so it's fresh in my mind, which is why I was able to out-type Brandon. 🙂

In any case, my function to do all of those steps (except for the "Do whatever you need to do to clean it up" step, which is split out to a separate function) is less than 20 lines of code--five of which are comments. I'm probably not quite as good at Java as you are, but I know I'd be hard-pressed to come near that efficiency writing in Java/AOM.

--Clay
jsulak
1-Newbie
(To:berard)

Sounds like you've gotten some good advice, but just to chime in...

We do something similar (well, we remove IDs on paste, not add them). In our case we add a Javascript listener to the AOMPaste event. The pasted nodes are available from the event object, and we can manipulate them directly using the DOM. In our experience that has worked pretty well.

-James


berard
1-Newbie
(To:berard)

Clay,

Man, I definitely regret not getting together with you back when you
were in the Boston area. Since my cross-country move, I've settled
in to a house in Middleboro (about 45mins south of beantown) so I'll
be here for the foreseeable future, but I digress...

As for preference for Java... it's really the lesser of two evils.
My company is based in Seattle, and thus a Microsoft shop. My
background has given me quite a bit of Java experience, but
unfortunately, it's probably as foreign to my peers as ACL. As for
Arbortext integration... Java is certainly slower than ACL (we even
have workarounds to allow our writers to "shut off" parts of the code
to avoid a signal violation, ugh), but loads faster than the .NET/COM
bridge.

So... I guess I'm still open to ACL, but here's a sample of the old
code I have to work with.... hopefully you feel my pain, and don't go
"yeah, so?"

function footnote_traverse(){
local t;
local h;
local cit_counter=0;
local i;
local f[];

oid_find_children( oid_null(), f, "footnote");
$cit_counter=count(f);
for($i=1;$i<=$cit_counter;$i++){
o=$f[$i];
t=oid_attr(o, "id");
#h=oid_content(o,10);
h=oid_xpath_string(o,"para/text()");
gsub("<para>",",$h);
gsub("<\/para>",",$h);
if(length($h)>50){
$h=substr($h,1,50);
}

$ft++;

$ftLetter = format_footnote_letter($ft);
$footnote_list_display[$ft]= "$ftLetter. $h";
$footnote_list_id[$ft]="$t";
}
}

function handle_footnotes(){
local $foot_id;
local $td;
local $first_o;


$td=";
$td=oid_attr(oid_first(), "hsim");
if($td=="){$td=oid_attr(oid_first(), "file_id");}
$o=oid_current_tag();
gsub('\(', '_', $o);
gsub('\'', '', $o);
gsub('\:', '', $o);
gsub('\)', '', $o);
gsub(',', '_', $o);
$foot_id="f" . "$td". "$o" . ";
insert_string -sgml "<footnote id="$foot_id"><para>Caret?></para></footnote>";

set gentext=on;
}

Not terrible in the grand scheme of things, but trying to apply that
to new code, and to avoid the myriad of "global" vars it's less than
fun. I've also discovered quite a bit of code that has absolutely
zero entry points, but I'm reluctant to just delete.

Ah well... I'll take a look at this with fresh eyes in the morning,
but since most of this is supposed to be "throw-away" code only used
for an initial data definition and load, I'm hesitant to refactor it
all or put too much effort into it.

For now, my Guinness and I will sit back and reflect.

keith

berard
1-Newbie
(To:berard)

Thanks James,

I think Java and Javascript are roughly equivalent in speed, since
Rhino Java calls the Java API, but I could be wrong.

We have quite a bit of compiled Java code, and a decent method of
distribution. That said, I'll probably go with a similar solution
to what you have used with the AOMPaste event.

keith

Hi Keith--

I don't think I knew you were in Boston, or I definitely would have looked you up while I was there! Next time I'm in town I'll try to let you know and maybe we can get together for a pint.

I feel your pain with the code. It's never fun to maintain someone else's code, especially when it's uncommented and written in an unfamiliar language. To be fair, I think a lot of the problem with the code samples you included is that it's undocumented. I can work out what it's doing if I stare at it long enough, but it really should have some comments to guide the maintainer about what it's doing and how.

My advice on refactoring is: if you ever think you'll want to do it, do it now, if you can spare the time. It will be easier when you've been looking at it and it's in the front of your mind than if you wait until 6 months from now and have to figure it all out again from (almost) scratch. If nothing else, maybe take a few minutes and add some comments to capture what you've figured out from staring at it. That way, someday when you or one of your colleagues has to come back and make some changes, you'll have something to start from.

--Clay

Well, no doubt you'll come up with the right answer. Gotta respect the judgment of a man who reflects over a Guinness.

Cheers!
Steve Thompson
+1(316)977-0515
BrianJ
3-Visitor
(To:berard)

Maybe I missed something from the discussion, but it seemed like nobody
mentioned the fact that we do have access to the documents in named
paste buffers using the acl function buffer_doc, which returns the doc
id of the named buffer whose name you pass in. So if you catch copy
events and reroute them to a named paste buffer, then catch the paste
event and change the id attr in the paste buffer document before the
paste occurs, you could avoid creating a separate document. Having
said that, sounds to me like James' suggestion might be the easiest route.





Brian Jensen
bjensen@bluelid.com


Just in case they're useful, I'll share some thoughts on managing ID attribute settings on XML elements through use of cut/copy/paste callbacks which we've been doing for years here at SAS. The points made below present the highlights. Of course, the details are much more involved. Most of our callback support is implemented within ACL code. A portion, that which sets new ID attribute values within content to be pasted, is implemented in java using the AOM.

Our cut callback is used to detect when we do not need to set new ID attribute values for a subsequent paste:

  • We check for the cut being part of a drag and drop operation, in which case we don't do any special processing. We key off buffer name "_APT_DRAGDROP_PASTEBUF" to detect a drag N drop operation.
  • We maintain a global state indicator to remember when a fresh cut is placed in the paste buffer. This is used on a paste operation to avoid setting new ID attribute values when the fresh cut is the subject of a paste.

We have an override implementation of the normal undo operation where we monitor for an undo operation so if, on a subsequent paste, we detect that an undo occurred while a fresh cut was in the paste buffer, we can prompt the user to determine what to do and thereby avoid the next paste possibly storing duplicate ID attribute values.

Our "notify" callback is used to monitor for an activate window operation occurring which would indicate the user may have copied something else into the paste buffer through another application. So if one occurs while a fresh cut is in the paste buffer, we can verify whether the paste buffer contents have changed and if so reset the fresh cut indicator and thereby avoid the next paste possibly storing duplicate ID attribute values.

Our copy callback is used to reset the global state indicators relating to a fresh cut being in the paste buffer.

Our paste callback is used to set new ID attribute values within the pasted content if it is needed:

  • If the paste is coming from a a drag N drop operation, buffer name "_APT_DRAGDROP_PASTEBUF", we do not set new ID attribute values within the pasted content.
  • We do a trial pre-paste operation to determine whether the paste will succeed or fail due to an out of context error. We do this so we can ensure we only reset our fresh cut is in the paste buffer indicator for a paste that is successful. To do this trial paste we insert a pair of "_display" tags into the paste target point and then do the trial paste inside them. That allows us to test whether anything was pasted between them and to accurately remove the content of the trial paste operation before proceeding.
  • If the paste is occurring while a fresh cut is in the paste buffer, we do not set new ID attribute values within the pasted content and we reset our fresh cut indicator. Part of the processing here is to check whether an activate window occurred while the fresh cut was in the buffer, and if it did, to verify as best we can whether the current contents of the paste buffer are the same as those we saved when the cut was initially performed. If they're not, we do not set new ID attribute values. Another part of the processing here is to check whether an activate window occurred while the fresh cut was in the buffer, and if it did, to prompt the user for whether to set new or retain the old ID attribute values. We resorted to asking the user because we couldn't confidently handle all the scenarios of undo/redo operations that might occur and be sure we're doing the right thing.
  • When we need to modify the content to be pasted, we do that using a separate paste buffer for the final paste operation because making changes in the original buffer passed into the callback resulted in Epic clearing out the altered original buffer after we exited from the callback. That resulted in users complaining they could only use the paste buffer for one paste operation. We are able to modify the content to be pasted right in this separate paste buffer by getting a doc handle for it (see buffer_doc command) and then traversing the element structure within it.



In Reply to Brian Jensen:

Maybe I missed something from the discussion, but it seemed like nobody
mentioned the fact that we do have access to the documents in named
paste buffers using the acl function buffer_doc, which returns the doc
id of the named buffer whose name you pass in. So if you catch copy
events and reroute them to a named paste buffer, then catch the paste
event and change the id attr in the paste buffer document before the
paste occurs, you could avoid creating a separate document. Having
said that, sounds to me like James' suggestion might be the easiest route.





Brian Jensen
bjensen@bluelid.com


jsulak
1-Newbie
(To:berard)

Thanks for sharing, that is pretty interesting. The idea of actually prompting the user is a good one we hadn't thought of.

In the past we tried managing behavior based on detecting copies vs. cuts, but couldn't ever get it working quite right. It ended up being easier all around to implement and explain to users a universal "if you move it, the IDs get changed" rule. Our users have to address a few more broken cross-references then they might otherwise, but that's simpler than dealing with than duplicate IDs.

-James

Thanks (3+ years later) Bob!

My initial pass at ID duplication protection did not accommodate for
copying content with IDs *between* two Editor instances, or, if for some
reason authors stored a table, let's say, as text or in Word or something
else crazy-town like that. Your notes helped me do so. Like you, I could
not detect enough conditions to write "idiot-proof" strip-or-preserve logic
that covered all use-cases. Fortunately, however, my authors understood the
scenario and let me strip IDs in all cases other than a those I could "do
the right thing."





Paul, you are welcome. Thanks for th post It's nice to learn someone benefited from the information I shared back then.

Announcements