Ticket #8384: 8384v2.patch

File 8384v2.patch, 16.3 KB (added by Don-vip, 13 years ago)

new patch

  • core/src/org/openstreetmap/josm/actions/PasteTagsAction.java

     
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66import static org.openstreetmap.josm.tools.I18n.trn;
    77
     8import java.awt.GridBagLayout;
    89import java.awt.event.ActionEvent;
    910import java.awt.event.KeyEvent;
    1011import java.util.ArrayList;
     
    1213import java.util.HashMap;
    1314import java.util.List;
    1415import java.util.Map;
     16import javax.swing.JLabel;
     17import javax.swing.JOptionPane;
     18import javax.swing.JPanel;
    1519
    1620import org.openstreetmap.josm.Main;
    1721import org.openstreetmap.josm.command.ChangePropertyCommand;
     
    2024import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2125import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    2226import org.openstreetmap.josm.data.osm.PrimitiveData;
    23 import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
    24 import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy.PasteBufferChangedListener;
    2527import org.openstreetmap.josm.data.osm.Tag;
    2628import org.openstreetmap.josm.data.osm.TagCollection;
     29import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    2730import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
     31import org.openstreetmap.josm.gui.help.HelpUtil;
     32import org.openstreetmap.josm.tools.GBC;
    2833import org.openstreetmap.josm.tools.Shortcut;
     34import org.openstreetmap.josm.tools.TextTagParser;
     35import org.openstreetmap.josm.tools.UrlLabel;
     36import org.openstreetmap.josm.tools.Utils;
    2937
    3038/**
    3139 * Action, to paste all tags from one primitive to another.
     
    3543 *
    3644 * @author David Earl
    3745 */
    38 public final class PasteTagsAction extends JosmAction implements PasteBufferChangedListener {
     46public final class PasteTagsAction extends JosmAction {
    3947
    4048    public PasteTagsAction() {
    4149        super(tr("Paste Tags"), "pastetags",
    4250                tr("Apply tags of contents of paste buffer to all selected items."),
    4351                Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")),
    4452                KeyEvent.VK_V, Shortcut.CTRL_SHIFT), true);
    45         Main.pasteBuffer.addPasteBufferChangedListener(this);
    4653        putValue("help", ht("/Action/PasteTags"));
    4754    }
    4855
     56    private void showBadBufferMessage() {
     57        String msg = tr("<html><p> Sorry, it is impossible to paste tags from buffer. It does not contain any JOSM object"
     58            + " or suitable text. </p></html>");
     59        JPanel p = new JPanel(new GridBagLayout());
     60        p.add(new JLabel(msg),GBC.eop());
     61        p.add(new UrlLabel(
     62                HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic((String)getValue("help")))),
     63                GBC.eop());
     64
     65        ConditionalOptionPaneUtil.showMessageDialog(
     66            "paste_badbuffer", Main.parent,
     67            p, tr("Warning"), JOptionPane.WARNING_MESSAGE);
     68    }
     69
    4970    public static class TagPaster {
    5071
    5172        private final Collection<PrimitiveData> source;
     
    245266
    246267        if (selection.isEmpty())
    247268            return;
    248 
    249         TagPaster tagPaster = new TagPaster(Main.pasteBuffer.getDirectlyAdded(), selection);
     269       
     270        String buf = Utils.getClipboardContent();
    250271
    251272        List<Command> commands = new ArrayList<Command>();
    252         for (Tag tag: tagPaster.execute()) {
    253             commands.add(new ChangePropertyCommand(selection, tag.getKey(), "".equals(tag.getValue())?null:tag.getValue()));
     273        if (buf==null) {
     274            showBadBufferMessage();
     275            return;
     276        }
     277        if (buf.matches("(\\d+,)*\\d+")) { // Paste tags from JOSM buffer
     278            PasteTagsAction.TagPaster tagPaster = new PasteTagsAction.TagPaster(Main.pasteBuffer.getDirectlyAdded(), selection);
     279            for (Tag tag: tagPaster.execute()) {
     280                commands.add(new ChangePropertyCommand(selection, tag.getKey(), "".equals(tag.getValue())?null:tag.getValue()));
     281            }
     282        } else { // Paste tags from arbitrary text
     283            Map<String, String> tags = TextTagParser.readTagsFromText(buf);
     284            if (tags==null || tags.isEmpty()) {
     285                showBadBufferMessage();
     286                return;
     287            }
     288            if (!TextTagParser.validateTags(tags)) return;
     289            String v;
     290            for (String key: tags.keySet()) {
     291                v = tags.get(key);
     292                commands.add(new ChangePropertyCommand(selection, key, "".equals(v)?null:v));
     293            }
    254294        }
    255295        if (!commands.isEmpty()) {
    256296            String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
     
    261301                            commands
    262302                    ));
    263303        }
    264 
     304       
    265305    }
    266306
    267     @Override public void pasteBufferChanged(PrimitiveDeepCopy newPasteBuffer) {
    268         updateEnabledState();
    269     }
    270307
    271308    @Override
    272309    protected void updateEnabledState() {
    273         if (getCurrentDataSet() == null || Main.pasteBuffer == null) {
     310        if (getCurrentDataSet() == null) {
    274311            setEnabled(false);
    275312            return;
    276313        }
    277         setEnabled(
    278                 !getCurrentDataSet().getSelected().isEmpty()
    279                 && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
    280         );
     314        // buffer listening slows down the program and is not very good for arbitrary text in buffer
     315        setEnabled(!getCurrentDataSet().getSelected().isEmpty());
    281316    }
    282317
    283318    @Override
    284319    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
    285         setEnabled(
    286                 selection!= null && !selection.isEmpty()
    287                 && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
    288         );
     320        setEnabled(selection!= null && !selection.isEmpty());
    289321    }
    290322}
  • core/src/org/openstreetmap/josm/tools/TextTagParser.java

     
     1package org.openstreetmap.josm.tools;
     2
     3import java.util.HashMap;
     4import java.util.Map;
     5import java.util.regex.Matcher;
     6import java.util.regex.Pattern;
     7import javax.swing.JOptionPane;
     8import org.openstreetmap.josm.Main;
     9import org.openstreetmap.josm.gui.ExtendedDialog;
     10import org.openstreetmap.josm.io.XmlWriter;
     11
     12import static org.openstreetmap.josm.tools.I18n.tr;
     13import static org.openstreetmap.josm.tools.I18n.trn;
     14
     15/**
     16 * Class that helps to parse tags from arbitrary text
     17 */
     18public class TextTagParser {
     19   
     20    protected static final int MAX_KEY_LENGTH = 50;
     21    protected static final int MAX_VALUE_LENGTH = 255;
     22   
     23    public static class TextAnalyzer {
     24        int start = 0;
     25        boolean keyFound = false;
     26        boolean quotesStarted = false;
     27        boolean esc = false;
     28        StringBuilder s = new StringBuilder(200);
     29        int pos;
     30        String data;
     31        int n;
     32        boolean notFound;
     33
     34        public TextAnalyzer(String text) {
     35            pos = 0;
     36            data = text;
     37            n = data.length();
     38        }
     39       
     40        /**
     41         * Read tags from "Free format"
     42         */
     43        Map<String, String>  getFreeParsedTags() {
     44            String k, v;
     45            Map<String, String> tags = new HashMap<String,String>();
     46
     47            while (true) {
     48                skipEmpty();
     49                if (pos == n) { break; }
     50                k = parseString(true);
     51                if (pos == n) { tags.clear();  break; }
     52                skipSign();
     53                if (pos == n) { tags.clear();  break; }
     54                v = parseString(false);
     55                tags.put(k, v);
     56            }
     57            return tags;
     58        }
     59       
     60        private String parseString(boolean stopOnEquals) {
     61            char c;
     62            while (pos < n) {
     63                c = data.charAt(pos);
     64                if (esc) {
     65                    esc = false;
     66                    s.append(c); //  \" \\
     67                } else if (c == '\\') {
     68                    esc = true;
     69                } else if (c == '\"' && !quotesStarted) { // opening "
     70                    if (s.toString().trim().length()>0) { // we had   ||some text"||
     71                        s.append(c); // just add ", not open
     72                    } else {
     73                        s.delete(0, s.length()); // forget that empty characthers and start reading "....
     74                        quotesStarted = true;
     75                    }
     76                } else if (c == '\"' && quotesStarted) {  // closing "
     77                    quotesStarted = false;
     78                    pos++;
     79                    break;
     80                } else if (!quotesStarted && (c=='\n'|| c=='\t'|| c==' ' || c=='\r'
     81                      || (c=='=' && stopOnEquals))) {  // stop-symbols
     82                    pos++;
     83                    break;
     84                } else {
     85                    // skip non-printable characters
     86                    if(c>=32) s.append(c);
     87                }
     88                pos++;
     89            }
     90
     91            String res = s.toString();
     92            s.delete(0, s.length());
     93            return res.trim();
     94        }
     95       
     96        private void skipSign() {
     97            char c;
     98            boolean signFound = false;;
     99            while (pos < n) {
     100                c = data.charAt(pos);
     101                if (c == '\t' || c == '\n'  || c == ' ') {
     102                    pos++;
     103                } else if (c== '=') {
     104                    if (signFound) break; // a  =  =qwerty means "a"="=qwerty"
     105                    signFound = true;
     106                    pos++;
     107                } else {
     108                    break;
     109                }
     110            }
     111        }
     112
     113        private void skipEmpty() {
     114            char c;
     115            while (pos < n) {
     116                c = data.charAt(pos);
     117                if (c == '\t' || c == '\n' || c == '\r' || c == ' ' ) {
     118                    pos++;
     119                } else {
     120                    break;
     121                }
     122            }
     123        }
     124    }
     125
     126    private static String unescape(String k) {
     127        if(! (k.startsWith("\"") && k.endsWith("\"")) ) {
     128            if (k.contains("=")) {
     129                // '=' not in quotes will be treated as an error!
     130                return null;
     131            } else {
     132                return k;
     133            }
     134        }
     135        String text = k.substring(1,k.length()-1);
     136        return (new TextAnalyzer(text)).parseString(false);
     137    }
     138
     139    /**
     140     * Try to find tag-value pairs in given text 
     141     * @param text - text in which tags are looked for
     142     * @param splitRegex - text is splitted into parts with this delimiter
     143     * @param tagRegex - each part is matched against this regex
     144     * @param unescapeTextInQuotes - if true, matched tag and value will be analyzed more thoroughly
     145     */
     146    public static Map<String, String> readTagsByRegexp(String text, String splitRegex, String tagRegex, boolean unescapeTextInQuotes) {
     147         String lines[] = text.split(splitRegex);
     148         Pattern p = Pattern.compile(tagRegex);
     149         Map<String, String> tags = new HashMap<String,String>();
     150         String k=null, v=null;
     151         for (String  line: lines) {
     152            if (line.trim().isEmpty()) continue; // skip empty lines
     153            Matcher m = p.matcher(line);
     154            if (m.matches()) {
     155                 k=m.group(1).trim(); v=m.group(2).trim();
     156                 if (unescapeTextInQuotes) {
     157                     k = unescape(k);
     158                     v = unescape(v);
     159                     if (k==null || v==null) return null;
     160                 }
     161                 tags.put(k,v);
     162            } else {
     163                return null;
     164            }
     165         }
     166         if (!tags.isEmpty()) {
     167            return tags;
     168         }  else {
     169            return null;
     170         }   
     171    }
     172 
     173    public static Map<String,String> getValidatedTagsFromText(String buf) {
     174        Map<String,String> tags = readTagsFromText(buf);
     175        return validateTags(tags) ? tags : null;
     176    }
     177   
     178    /**
     179     * Apply different methods to extract tag-value pairs from arbitrary text
     180     * @param buf
     181     * @return null if no format is suitable
     182     */
     183   
     184    public static Map<String,String> readTagsFromText(String buf) {
     185        Map<String,String> tags;
     186       
     187        // Format
     188        // tag1\tval1\ntag2\tval2\n
     189        tags = readTagsByRegexp(buf, "[\r\n]+]", "(.*?)\t(.*?)", false);
     190                // try "tag\tvalue\n" format
     191        if (tags!=null) return tags;
     192
     193        // Format
     194        // a=b \n c=d \n "a b"=hello
     195        // SORRY: "a=b" = c is not supported fror now, only first = will be considered
     196        // a = "b=c" is OK
     197        // a = b=c  - this method of parsing fails intentionally
     198        tags = readTagsByRegexp(buf, "[\\n\\t\\r]+", "(.*?)=(.*?)", true);
     199                // try format  t1=v1\n t2=v2\n ...
     200        if (tags!=null) return tags;
     201       
     202        // JSON-format
     203        String bufJson = buf.trim();
     204        // trim { }, if there are any
     205        if (bufJson.startsWith("{") && bufJson.endsWith("}") ) bufJson = bufJson.substring(1,bufJson.length()-1);
     206        tags = readTagsByRegexp(bufJson, "[\\s]*,[\\s]*",
     207                "[\\s]*(\\\".*?[^\\\\]\\\")"+"[\\s]*:[\\s]*"+"(\\\".*?[^\\\\]\\\")[\\s]*", true);
     208        if (tags!=null) return tags;
     209
     210        // Free format
     211        // a 1 "b" 2 c=3 d 4 e "5"
     212        TextAnalyzer parser = new TextAnalyzer(buf);
     213        tags = parser.getFreeParsedTags();
     214        return tags;
     215    }
     216
     217    /**
     218     * Check tags for correctness and display warnings if needed
     219     * @param tags - map key->value to check
     220     * @return true if user decision was "OK"
     221     */
     222    public static boolean validateTags(Map<String, String> tags) {
     223        String value;
     224        int r;
     225        int s = tags.size();
     226        if (s > 30) {
     227            // Use trn() even if for english it makes no sense, as s > 30
     228            r=warning(trn("There was {0} tag found in the buffer, it is suspicious!",
     229            "There were {0} tags found in the buffer, it is suspicious!", s,
     230            s), "", "toomanytags");
     231            if (r==2) return false; if (r==3) return true;
     232        }
     233        for (String key: tags.keySet()) {
     234            value = tags.get(key);
     235            if (key.length() > MAX_KEY_LENGTH) {
     236                r = warning(tr("Key is too long (max {0} characters):", MAX_KEY_LENGTH), key+"="+value, "keytoolong");
     237                if (r==2) return false; if (r==3) return true;
     238            }
     239            if (!key.matches("[a-zA-Z:_]*")) {
     240                r = warning(tr("Suspicious characters in key:"), key, "keydoesnotmatch");
     241                if (r==2) return false; if (r==3) return true;
     242            }
     243            if (value.length() > MAX_VALUE_LENGTH) {
     244                r = warning(tr("Value is too long (max {0} characters):", MAX_VALUE_LENGTH), value, "valuetoolong");
     245                if (r==2) return false; if (r==3) return true;
     246            }
     247        }
     248        return true;
     249    }
     250   
     251    private static int warning(String text, String data, String code) {
     252        ExtendedDialog ed = new ExtendedDialog(
     253                    Main.parent,
     254                    tr("Do you want to paste these tags?"),
     255                    new String[]{tr("Ok"), tr("Cancel"), tr("Ignore warnings")});
     256        ed.setButtonIcons(new String[]{"ok.png", "cancel.png", "pastetags.png"});
     257        ed.setContent("<html><b>"+text + "</b><br/><br/><div width=\"300px\">"+XmlWriter.encode(data,true)+"</html>");
     258        ed.setDefaultButton(2);
     259        ed.setCancelButton(2);
     260        ed.setIcon(JOptionPane.WARNING_MESSAGE);
     261        ed.toggleEnable(code);
     262        ed.showDialog();
     263        Object o = ed.getValue();
     264        if (o instanceof Integer)
     265            return ((Integer)o).intValue();
     266        else
     267            return 2;
     268    }
     269}