Setup a shortcut to replace easily selected strings in VIM - php

I have a lot of php/html files with many strings that should be internationalized with gettext.
Therefore, I have to go through each file, spot the "message" strings and replace each one by
<?= _("<my string>") ?>
I use vim and would like to setup a shortcut (map) to do it easily in insert mode (With CtrlR for instance).
Do you know how to achieve that ?

I would use Tim Pope's wonderful surround plugin to accomplish this.
Add the following to your ~/.vim/after/ftplugin/php.vim file:
let b:surround_{char2nr('_')} = "<?= _(\"\r\") ?>"
Now you can select some via visual mode then surround. e.g vitS_
If you are in insert mode you can surround text via <c-s>_ and you cursor will be inserted in between the double quotes.
As a bonus if you want to do the delete the surrounding <?= _("<text here>") ?> and only leave <text here> you can add the following to your ~/.vim/after/ftplugin/php.vim as well:
nmap <buffer> <silent> ds_ ds<dt(%df?[(xds"
Tim Pope has many great plugins I highly suggest you take a look some of them.
For more help see:
:h surround
:h surround-customizing
:h after-directory
:h curly-braces-names
:h b:var

My guess is that you want the original message to actually be the input to the _() function, do you not?
The best thing I can think for you to do is to use macros. If I were doing this I would probably do something like record a macro #1 for one-word "messages" (that need to be replaced), #2 for two-word messages, #3 for 3 and so on. Then I could just skim or search through the documents and type #1 on the start of any one-word message like one
to replace it with <?= _("one") ?>. I would use #2 on a message like two words to transform it to <?= _("two words") /> and so forth.
To create/record the macro for one-word messages, #1, type these keys, preferably on the start of a one-word message:
q1i<?= _("<Esc>eli") ?><Esc>q
q 1 i < ? = Space _ ( " Esc e l i " ) Space ? > Esc q
The macros for more words can be created very similarly, just add additional es for more words. So for #2, type this:
q1i<?= _("<Esc>eeli") ?><Esc>q
q 1 i < ? = Space _ ( " Esc e e l i " ) Space ? > Esc q
In the case of really long messages, I would probably use an open and close macro. The open one would place <?= _(" wherever I had my cursor and the close one would put ") ?> wherever I had my cursor.

If you want to surround this strings manually and if your message does not contain ", then you can (after putting cursor somewhere inside the message) do the following once:
qaf"a)<Esc>2F"i_(<Esc>q
(press real escape for <Esc>) then, after putting the cursor on the next message, repeat this by
#a
(if you don’t like a, replace it with another latin lowercase letter here and above after q). If you still want to have a mapping:
:nnoremap <C-r> f"a)<Esc>2F"i_(<Esc>
. This time <Esc> is literally <, E, s, c, >.
First is using macros and they are quite handy as defining a mapping is more to type. Depending on 'viminfo' option they may be even saved across vim sessions, but you should not really rely on this, so if you want something persistent use the mapping putting it in the vimrc.
Update: If you don’t have <? "message" ?> which I assumed, but instead got <tag>message</tag>, you can do the following:
:nnoremap <C-r> f<i") ?><Esc>F>a<? _("<Esc>
. Note that this time message should not contain < or >.

Regex
Vim is very capable of handling tasks like this with ease. Without a before and after example it's difficult to give you a precise solution, but I'll make a hypothetical one to demonstrate some of vim's power. Say you wanted to change any text inside a <span> tag to be executed by a PHP function. I might have a span tag like this:
<span>I need this text and all other span tags run through PHP!</span>
Probably the easiest way to get the job done is using regex. For example:
:%s/<span>\([^<]*\)<\/span>/<?= _("\1") ?>/g
This finds all span tags in the document and replaces them appropriately. You can even run this on multiple files (see :help bufdo). However, regex can be difficult for some people at first and many haven't taken the time to learn it well. Another option might look like this:
CTRL-R
/<span><cr>f>lct<<?= _("<C-r>"") ?><esc>
Step by step
/<span><cr> - search for opening span tags
f>l - move cursor to the character after the opening span tag
ct< - change the text until the next < character
<?= _("<C-r>"") ?> - put in what we want. The <C-r>" (as you referred to) will put in the contents of our unnamed register ", which in this case is the text we executed ct< on a minute ago.
<esc> - return to normal mode
Macro this
This might be useful to use as a macro. If so, just do the exact same thing with a macro around it...
qq/<span><cr>f>lct<<?= _("<C-r>"") ?><esc>q
Now you can execute #q to do the same thing to the next <span> tag. After you've used #q once you can also use ## or even 100#q to do it 100 times.

Related

PHP: Setting missing " in HTML-code with preg_replace?

I've got a database with a lot of user made entries grown about 10 years. The users had the option to put HTML-code in their content. And some didn't that well. So I've a lot of content in where the quotes are missing. Need a valid HTML-code for an ex/import via XML.
Had tested to replace width but my regex doesn't work. Do you've an idea where's my fault?
$out=preg_replace("/<a href=h(.)*>/","<a href=\"h$1\">",$out);
PS: If you have an idea how to automatically make a correction on wrong html source this would alternatively be great.
I think you wanted to use "/<a href=h(.*)>/" (mind the star inside the parenthesis) since you want to capture all characters after the h and before the > inside the capture group.
You can also use <a href=([^"].*)> since the href may not start with h. This regex captures all href values that do not start with ".
Yet, all of these assume that the href is the last attribute in your a, i.e.., ending with >.
As a more general rule, I came up with (?<key>\w*)\s*=\s*(?<value>[^"][^\s>]*) that finds attribute-value pairs, separated by =. The values may not start with ", and they go until the next whitespace or >. Use this with caution, since it may fail in serveral circumstances: Multi-line html, inline JavaScript, etc.
Whether it is a good idea to use RegEx for such a task is a different discussion.

PhpStorm 2016.2 find and replace multiline text

In PhpStorm 2016.2 I have a new project that has been inherited and [badly] needs updating.
There are many pages each with opening line like so (example):
<?
include ("/inc/db.php");
I need to replace this line with several lines such as:
<?php
include "siteheader.php";
require "class.myclass.inc.php";
$dataBase = new DbObj();
I have previously simply copy and pasted multiline code into the PhpStorm search/replace function and that's (usually but not always) returned the correct changes, although they're all squished into single lines, making them harder to read (EOL characters are removed).
In this instance am looking specifically at the "replace in path" function as I need to apply this change to many pages.
I have Read the manual but can see no option for this. I think I could possibly use a Regular Expression but this would not be ideal (escapings etc.).
I have also looked but not found a suitable plugin from the PhpStorm Plugin Repository.
Is there a way of searching and/or replacing multiline text in path in PhpStorm 2016.2?
Cheers
There is no easy to use multi-line search or replace across multiple files (Find/Replace in Path functionality) unfortunately.
Right now you have to use Regex option for that -- that's the only option that works.
Watch these tickets (star/vote/comment) to get notified on any progress in this regard.
https://youtrack.jetbrains.com/issue/IDEA-69435
https://youtrack.jetbrains.com/issue/IDEA-61925
https://youtrack.jetbrains.com/issue/IDEA-145720
Manually making regex-compatible text can be quite problematic .. therefore you might use this few-steps trick:
Type your new text in one file to start with
Select such text and invoke Replace in Path... dialog -- with Regex option pre-selected it should automatically escape your selection to be regex-compatible
Copy that already-escaped text somewhere (just Clipboard should be enough)
Close dialog and go back to original file
Select text you want to replace and invoke Replace in Path... dialog -- it will have your initial text already filled in and regex compatible
Paste previously copied escaped text into Replace field
Execute find/replacement
On related note: https://stackoverflow.com/a/38672886/783119
You can do multiline Find&Replace with Regex option turned on
Find:
<\?\ninclude \("/inc/db\.php"\);
Replace:
<?php\ninclude "siteheader.php"; \nrequire "class.myclass.inc.php"; \n\$dataBase = new DbObj();
As you can see you need to do some additional work to escape some special characters and put \n instead of new lines, but it works. I've just checked.
P.S.
Indeed, it was possible to simply paste multiline text in previous versions, but it's not possible anymore. ;-(
Type Alt+Enter to add a new line in either the "search" or the "replace" field.
On a Mac:
open the 'Find' or 'Replace' tool, click into the text area and press the following keys once for every new line you want to create:
⌘ + 'Shift' + 'Enter'
Besides the suggestions on how to use regex for multiline, in case you want to match two pieces of code with arbitrary lines in the middle, you can use [\s\S]* instead of [\n.]* (which doesn't have the expected result). Example:
//you can match the $result-related code using `\$result([\s\S]*)while`
$result = DB::exec($query);
//blabla
//something else
while ($row = $result->fetch()) {
\s works as expected to match all whitespaces and newlines.
In my case I wanted to find switch ... case ... continue; syntax, so switch(\s|.)*continue worked as expected

Recursive Regex in PHP with variable names

I try to make bbcode-ish engine for me website. But the thing is, it is not clear which codes are available, because the codes are made by the users. And on top of that, the whole thing has to be recursive.
For example:
Hello my name is [name user-id="1"]
I [bold]really[/bold] like cheeseburgers
These are the easy ones and i achieved making it work.
Now the problem is, what happens, when two of those codes are behind each other:
I [bold]really[/bold] like [bold]cheeseburgers[/bold]
Or inside each other
I [bold]really like [italic]cheeseburgers[/italic][/bold]
These codes can also have attributes
I [bold strengh="600"]really like [text font-size="24px"]cheeseburgers[/text][bold]
The following one worked quite well, but lacks in the recursive part (?R)
(?P<code>\[(?P<code_open>\w+)\s?(?P<attributes>[a-zA-Z-0-1-_=" .]*?)](?:(?P<content>.*?)\[\/(?P<code_close>\w+)\])?)
I just dont know where to put the (?R) recursive tag.
Also the system has to know that in this string here
I [bold]really like [italic]cheeseburgers[/italic][/bold] and [bold]football[/bold]
are 2 "code-objects":
1. [bold]really like [italic]cheeseburgers[/italic][/bold]
and
2. [bold]football[/bold]
... and the content of the first one is
really like [italic]cheeseburgers[/italic]
which again has a code in it
[italic]cheeseburgers[/italic]
which content is
cheeseburgers
I searched the web for two days now and i cant figure it out.
I thought of something like this:
Look for something like [**** attr="foo"] where the attributes are optional and store it in a capturing group
Look up wether there is a closing tag somewhere (can be optional too)
If a closing tag exists, everything between the two tags should be stored as a "content"-capturing group - which then has to go through the same procedure again.
I hope there are some regex specialist which are willing to help me. :(
Thank you!
EDIT
As this might be difficult to understand, here is an input and an expected output:
Input:
[heading icon="rocket"]I'm a cool heading[/heading][textrow][text]<p>Hi!</p>[/text][/textrow]
I'd like to have an array like
array[0][name] = heading
array[0][attributes][icon] = rocket
array[0][content] = I'm a cool heading
array[1][name] = textrow
array[1][content] = [text]<p>Hi!</p>[/text]
array[1][0][name] = text
array[1][0][content] = <p>Hi!</p>
Having written multiple BBCode parsing systems, I can suggest NOT using regexes only. Instead, you should actually parse the text.
How you do this is up to you, but as a general idea you would want to use something like strpos to locate the first [ in your string, then check what comes after it to see if it looks like a BBCode tag and process it if so. Then, search for [ again starting from where you ended up.
This has certain advantages, such as being able to examine each code and skip it if it's invalid, as well as enforcing proper tag closing order ([bold][italic]Nesting![/bold][/italic] should be considered invalid) and being able to provide meaningful error messages to the user if something is wrong (invalid parameter, perhaps) because the parser knows exactly what is going on, whereas a regex would output something unexpected and potentially harmful.
It might be more work (or less, depending on your skill with regex), but it's worth it.

how to replace '\\\' to '\'?

my code is not working ? and i dont want to use str_replace , for there maybe more slashes than 3 to be replaced. how can i do the job using preg_replace?
my code here like this:
<?php
$str='<li>
<span class=\"highlight\">Color</span>
Can\\\'t find the exact color shown on the model pictures? Just leave a message (eg: color as shown in the first picture...) when you place order.
Please note that colors on your computer monitor may differ slightly from actual product colors depending on your monitor settings.
</li>';
$str=preg_replace("#\\+#","\\",$str);
echo $str;
There is merit in the other answers, but to me it looks like what you're actually trying to accomplish is something very different. In the php code \\\' is not three slashes followed by an apostrophe, it's one escaped slash followed by an escaped apostrophe, and in the rendered output, that's exactly what you see—a slash followed by an apostrophe (with no need to escape them in the rendered html). It's important to realize that the escape character is not actually part of the string; it's merely a way to help you represent a character that normally has very different meaning in within php—in this case, an apostrophe normally terminates a string literal. What looks like 4 characters in php is actually only 2 characters in the string.
If this is the extent of your code, there's no need for string manipulation or regular expressions. What you actually need is just this:
<?php
$str='<li>
<span class="highlight">Color</span>
Can\'t find the exact color shown on the model pictures? Just leave a message (eg: color as shown in the first picture...) when you place order.
Please note that colors on your computer monitor may differ slightly from actual product colors depending on your monitor settings.
</li>';
echo $str;
?>
Only one escape character is needed here for the apostrophe, and in the rendered HTML you will see no slashes at all.
Further Reading:
Escape sequences
The root of this problem is actually in how it was written into your database and likely to be caused by magic_quotes_gpc; this was used in older versions and a really bad idea.
The best fix
This requires a few steps:
Fix the script that puts the HTML inside your database by disabling magic_quotes_gpc.
Write a script that reads all existing database entries, applies stripslashes() and saves the changes.
Fix the presentation part (though, that may need no changes at all.
Alternative patch
Use stripslashes() before you present the HTML.
use this pattern
preg_replace('#\\+#', '\\', $text);
This replaces two or more \ symbols preceding an ' symbol with \'
$theConvertedString = preg_replace("/\\{2,}'/", "\'", $theSourceString);
Ideally, you shouldn't have code causing this issue in the first place so I would have a look at why you have \\' in your code to begin with. If you've manually put it in your variables, take it out. Often, this also happens with multiple calls to addslashes() or mysql_real_escape_string() or a cheap hosting providers' automatic transformation of all POST request variables to escape slashes, combined with your server side PHP code to do the same.

Matching all three kinds of PHP comments with a regular expression

I need to match all three types of comments that PHP might have:
# Single line comment
// Single line comment
/* Multi-line comments */
 
/**
* And all of its possible variations
*/
Something I should mention: I am doing this in order to be able to recognize if a PHP closing tag (?>) is inside a comment or not. If it is then ignore it, and if not then make it count as one. This is going to be used inside an XML document in order to improve Sublime Text's recognition of the closing tag (because it's driving me nuts!). I tried to achieve this a couple of hours, but I wasn't able. How can I translate for it to work with XML?
So if you could also include the if-then-else login I would really appreciate it. BTW, I really need it to be in pure regular expression expression, no language features or anything. :)
Like Eicon reminded me, I need all of them to be able to match at the start of the line, or at the end of a piece of code, so I also need the following with all of them:
<?php
echo 'something'; # this is a comment
?>
Parsing a programming language seems too much for regexes to do. You should probably look for a PHP parser.
But these would be the regexes you are looking for. I assume for all of them that you use the DOTALL or SINGLELINE option (although the first two would work without it as well):
~#[^\r\n]*~
~//[^\r\n]*~
~/\*.*?\*/~s
Note that any of these will cause problems, if the comment-delimiting characters appear in a string or somewhere else, where they do not actually open a comment.
You can also combine all of these into one regex:
~(?:#|//)[^\r\n]*|/\*.*?\*/~s
If you use some tool or language that does not require delimiters (like Java or C#), remove those ~. In this case you will also have to apply the DOTALL option differently. But without knowing where you are going to use this, I cannot tell you how.
If you cannot/do not want to set the DOTALL option, this would be equivalent (I also left out the delimiters to give an example):
(?:#|//)[^\r\n]*|/\*[\s\S]*?\*/
See here for a working demo.
Now if you also want to capture the contents of the comments in a group, then you could do this
(?|(?:#|//)([^\r\n]*)|/\*([\s\S]*?)\*/)
Regardless of the type of comment, the comments content (without the syntax delimiters) will be found in capture 1.
Another working demo.
Single-line comments
singleLineComment = /'[^']*'|"[^"]*"|((?:#|\/\/).*$)/gm
With this regex you have to replace (or remove) everything that was captured by ((?:#|\/\/).*$). This regex will ignore contents of strings that would look like comments (e.g. $x = "You are the #1"; or $y = "You can start comments with // or # in PHP, but I'm a code string";)
Multiline comments
multilineComment = /^\s*\/\*\*?[^!][.\s\t\S\n\r]*?\*\//gm

Categories