1

I have string like this:

1668955,,,    bunch of text</figcaption><br /><a href="https://the_url/article_detail.php?article_id=10003414" target="_blank">w..... </a><br /><h2> text ....<br /><br /><a href="https://the_url/article_detail.php?article_id=10002526" target="_blank">...... text .....

Is there a way to delete everything from ,,, to the end of the line except urls, so the result is:

1668955,,,  https://the_url/article_detail.php?article_id=10003414 https://the_url/article_detail.php?article_id=10002526
8
  • 1
    I have some almost solutions using mutually-recursive macros, but nothing that works yet. Still, yikes... It would be better to programatically with something that controls the output format or understands the input format.
    – D. Ben Knoble
    May 18, 2020 at 15:16
  • 1
    @D.BenKnoble thank you for your help. Yes, my plan B is to use PHP or some other language to do it.
    – whitesiroi
    May 18, 2020 at 15:36
  • 1
    Would you provide any known details about the possible formats? IOW, are there any assumptions that can be made? For example, if we were allowed to assume that, ummm, every URL was surrounded by double quotes and began with 'http' then it would be quite easy to distinguish them from other parts of the text. OTOH, if you tell us that zero such assumptions can be made then we'll need to proceed differently. Is it always legal HTML?
    – B Layer
    May 19, 2020 at 20:23
  • 1
    Then it may be possible in Vim. However, an unexpected newline or some quirky browser-accepted but structure altering text could throw it all off. HTML/XML cannot be reliably parsed with regexp alone (there's a proof somewhere). With Vim regex plus some Vimscript logic you've got a better shot. But unless you absolutely have to use Vim you really should consider using proper tools (e.g. tidy | xmllint -xpath or pup or even awk). You can even call 'em from Vim with bang filtering (:h :!). You'll find tons of helpful info by googling, for example, "command line html parsing".
    – B Layer
    May 20, 2020 at 9:59
  • 1
    @BLayer thank you very much for your help, I didn't know about "pup"
    – whitesiroi
    May 21, 2020 at 4:03

3 Answers 3

2

Here's a quick and dirty solution. You can certainly find/create input HTML that will break this, but it works fine for simple input such as your example:

  1. Add a " at the end of the commas:

    :norm! 0Ea"
    

    This is simply using a :normal command to execute the equivalent of typing 0Ea" in normal mode.

  2. Split the line on the URLs:

    :s/"\zehttps/\r/g
    

    Here we use a :substitute command, and replace the " that comes before each https with a newline.

  3. Delete the bits you don't want:

    :'',.norm! f"D
    

    Again we use :normal to perform f"D. This time, we do it on every line in the range encompassing the line with the '' mark and the current line.

  4. Join it up again by using the same range with the :join command.

    :'',.join
    

Packaging this up into an easily callable command or mapping is as simple as pasting the lines above into a function and then calling this:

function! DeleteNonURLs() abort
  norm! 0Ea"
  s/"\zehttps/\r/g
  '',.norm! f"D
  '',.join
endfunction

command DeleteNonURLs call DeleteNonURLs()
nnoremap <leader>d :call DeleteNonURLs()<CR>
4
  • Isnt (3) equivalent to '',.s/".*$//? And (1) might be s/\w\+/&a/—then its all plain ex
    – D. Ben Knoble
    May 20, 2020 at 12:23
  • @D.BenKnoble Sure! This is just what I came up with off the top of my head. Is there any particular benefit to doing it with :substitute instead of :normal, other than personal preference? (If so, I'll edit.)
    – Rich
    May 20, 2020 at 14:00
  • 1
    It's mostly a personal preference, but it also wouldn't surprise me if simulating normal commands has some slight overhead (though maybe the use of regex is even more expensive?). It really doesn't matter though; I just prefer to use ex when I can.
    – D. Ben Knoble
    May 20, 2020 at 14:44
  • @Rich thank you very much, it works perfectly
    – whitesiroi
    May 21, 2020 at 3:57
2

Here's a simple macro-based solution.

First place your cursor at the beginning of bunch: 0W.

Then type:

qqqqqd/\v($|"https)Enterlhxf"xl@qq

This records a recursive macro into the "q register that will remove everything from the current position to the end of the line, apart from the URLs.

Then undo the changes you made while recording the macro: 3u

Now type:

qa0W@qq

This records a macro into register "a that first moves to the correct location in the line, and then deletes everything apart from the URLs (by playing back the "q macro).

Now you have a macro that you can play on a single line by pressing @a, or on a range of lines with a command like :%norm! @a. (e.g. To apply the macro to every line in the visual selection, simply make the selection and then type :norm @a, which Vim will expand into :'<,'>norm! @a.)

How the "q macro works:

  • qqqqq Clear out the "q register by recording an empty macro, and then start recording into it
  • d Delete up to...
  • /\v($|"https) This is a "very magic" search that finds either the next "https or the end of the line.
  • lh Check if we're at the end of the line by attempting to move right. If we are already at the end of the line, then the macro will stop here. Otherwise, we move back left again.
  • xf"x If the macro is still running, that means we are on the start quote of a URL. Remove the quote, move to the next quote, and remove that too.
  • l Step rightwards over the space after the quote we just deleted.
  • @q When we are recording the macro, this does nothing, because the "q register is empty, but when we are playing back the macro, this plays the macro again.
  • q End the recording.

Assumptions:

  1. The URLs are always surrounded by double quotes, always use the https scheme, and never contain a double quote.

  2. The URLs are always followed by a space.

2
  • 1
    Again, thank you very much for your awesome help, I learned so much from your posts. Lemme know if you gonna be in Tokyo - gonna buy you a beer.
    – whitesiroi
    May 21, 2020 at 4:00
  • 1
    @whitesiroi So glad I could help!
    – Rich
    May 21, 2020 at 7:17
0

Use this substitution command:

%s/\(\d*,,,\).*href="\(https:.*\)" .*/\1 \2 /

explanation: % means "all lines in the buffer"

\d*,,, is any number of digits followed by 3 ","

.*href=" is anything following up to https:

" .* is all the text from closing quote to the end of the line including the quote (note the space between quote and dot.

enclosing parts of the pattern with "\(" and "\)" may be referred to in the replacement part via "\1", "\2", etc.

if there are other lines you don't want to be affected, highlight/select the desired lines via the mouse or using the select command "V", then enter the substitution command without the "%".

2
  • There are multiple URLs in the field and the OP wants to keep all of them... So this wouldn't really work.
    – filbranden
    May 19, 2020 at 17:11
  • 1
    @wmmso thank you for your help, as @ filbranden point it out, there are multiple URLs,
    – whitesiroi
    May 20, 2020 at 4:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.