tags:

views:

99

answers:

4

I want to add a set of (double) quotes to a python string if they are missing but the string can also contain quotes.

The purpose of this is to quote all command that are not already quoted because Windows API requires you to quote the entire command line when you execute a process using _popen().

Here are some strings that should be quoted:

<empty string>
type
"type" /?
"type" "/?"
type "a a" b
type "" b

Here are some that should not be quoted:

"type"
""type" /?"

Please take the time to test all examples; it is not too easy to detect if the string needs the quotes or not.

A: 

You have three cases:

  1. String is less than two characters long: add quotes
  2. String has quotes at s[0] and at s[1]: don't add quotes
  3. Add quotes

And by "add quotes" I mean simply construct '"'+string+'"' and return it.

Translate to if-statements, and you're done.

Ian
Also fails test #5
delnan
+8  A: 

Your problem is inconsistent.

Consider the two cases

""a" b"

"a" "b"

The former is interpreted as a pre-quoted string with 'nested quotes', but the latter is interpreted as separately-quoted strings. Here are some examples that highlight the issue.

" "a" "b" "

" "a" b"

"a ""b"

How should they be treated?

katrielalex
All the time, I felt like something was wrong with this question... now I know. +1 for bringing it up.
delnan
I was just typing the same thing. Instead I'll upvote your answer. Another input example: `" a " foo " b "`. Minor clarification: not sure that the question is *inconsistent* so much as *under-specified*.
FM
@bogdan: removing the spaces, respectively considering only the quotes, the two are equivalent.
delnan
@FM: Well, it's inconsistent under the assumption that all non-quote characters are treated equally. Adding more specification changes the problem.
katrielalex
@bogdan: unfortunately that doesn't help: the commands `"a "foo" b"` and `"a" foo "b"` seem indistinguishable but require different rules. Maybe try one and catch the resulting error?
katrielalex
I made a mistake by trying to simplify the problem too much. Now I updated the question with new examples. It looks that you cannot ignore the spaces if you want to properly detect if the string is already quoted or not.
bogdan
+2  A: 

I think this is a difficult question to specify in a precise way, but perhaps this strategy will approximate your goal.

The basic idea is to create a copy of the original string, removing the internally quoted items. An internally quoted item is defined here so that it must contains at least one non-whitespace character.

After the internally quoted items have been removed, you then check whether the entire string needs surrounding quotes or not.

import re

tests = [
    # Test data in original question.
    ( '',                '""'                ),
    ( 'a',               '"a"'               ),
    ( '"a"',             '"a"'               ), # No change.
    ( '""a" b"',         '""a" b"'           ), # No change.
    ( '"a" b',           '""a" b"'           ),
    ( '"a" "b"',         '""a" "b""'         ),
    ( 'a "b" c',         '"a "b" c"'         ),

    # Test data in latest edits.
    ( 'type',            '"type"'         ),    # Quote these.
    ( '"type" /?',       '""type" /?"'    ),
    ( '"type" "/?"',     '""type" "/?""'  ),
    ( 'type "a a" b',    '"type "a a" b"' ),
    ( 'type "" b',       '"type "" b"'    ),
    ( '"type"',          '"type"'         ),    # Don't quote.
    ( '""type" /?"',     '""type" /?"'    ),

    # Some more tests.
    ( '"a b" "c d"',     '""a b" "c d""'     ),
    ( '" a " foo " b "', '"" a " foo " b ""' ),
]

Q = '"'
re_quoted_items = re.compile(r'" \s* [^"\s] [^"]* \"', re.VERBOSE)

for orig, expected in tests:
    # The orig string w/o the internally quoted items.
    woqi = re_quoted_items.sub('', orig)

    if len(orig) == 0:
        orig_quoted = Q + orig + Q
    elif len(woqi) > 0 and not (woqi[0] == Q and woqi[-1] == Q):
        orig_quoted = Q + orig + Q    
    else:
        orig_quoted = orig

    print orig_quoted == expected
FM
+1  A: 

I wrote a simple state machine to track if we are in a word or not. If the quote depth is ever zero in the string, then we need quotes:

def quotify(s):
    if s == "":
        return '""'

    depth = 0
    in_word = False
    needs_quotes = False
    for c in s:
        if c == '"':
            if in_word:
                depth -= 1
            else:
                depth += 1
        else:
            if depth == 0:
                needs_quotes = True
                break
            in_word = not c.isspace()

    if needs_quotes:
        return '"' + s + '"'
    else:
        return s

assert quotify('') == '""'
assert quotify('''type''') == '''"type"'''
assert quotify('''"type" /?''') == '''""type" /?"'''
assert quotify('''"type" "/?"''') == '''""type" "/?""'''
assert quotify('''type "a a" b''') == '''"type "a a" b"'''
assert quotify('''type "" b''') == '''"type "" b"'''
assert quotify('''"type"''') == '''"type"'''
assert quotify('''""type" /?"''') == '''""type" /?"'''
Ned Batchelder
I like your approach because it does not require `re`, but it does fail on the last test made by FM - his `re` solution does pass all tests.
bogdan