PathRelativePathTo के तर्कों पर प्रतिबंध "लंबे पथ के प्रति जागरूक" वातावरण में


12

विंडोज 10 पर एक लंबी पथ जागरूक प्रक्रिया के लिए, मैं यह समझने की कोशिश कर रहा हूं कि विंडोज़ शेल विधि PathRelativePathico का उपयोग करते समय तर्क प्रतिबंध क्या हैं ।

नीचे मेरे उदाहरण में, मैं विधि को कॉल करने के लिए पिनवोक के माध्यम से सी # का उपयोग कर रहा हूं।
मैंने नीचे और उनके आउटपुट में कई उदाहरण दिए हैं। ध्यान दें:

  • सभी उदाहरण "से" के लिए निर्देशिका पथ देते हैं और "के लिए" के लिए पथ पथ (इनमें से कोई भी पथ वास्तव में डिस्क पर मौजूद नहीं है)
  • मेरे अवलोकन हैं कि
    • "लघु" MAX_PATH लंबाई (260) के तहत पथ अपेक्षित परिणाम के साथ सफलता लौटाता है।
    • "शॉर्ट" MAX_PATH पर कुछ पथ सही परिणाम के साथ सफलता लौटाते हैं।
    • "शॉर्ट" MAX_PATH पर कुछ रास्तों में गलत उत्तर (yikes!) के साथ सफलता मिलती है ।
    • कुछ बहुत लंबे पथ एक त्रुटि लौटाते हैं। हालाँकि, यह कुछ निश्चित अधिकतम लंबाई पर नहीं है।

स्रोत:

    class Program
    {
        static class Native
        {
            // https://www.pinvoke.net/default.aspx/shlwapi.pathrelativepathto
            // https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-pathrelativepathtoa
            [DllImport("shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool PathRelativePathTo([Out] StringBuilder pszPath, [In] string pszFrom, [In] int dwAttrFrom, [In] string pszTo, [In] int dwAttrTo);
        }

        static void Main(string[] args)
        {
            string pszFrom, pszTo;
            int i = 0;

            // #1 At "short" max path (259)
            // Succeeds with right answer
            pszFrom = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD123456789";
            pszTo = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD123456789\abcdefghijklmnop.txt";
            TestPathRelativePathTo(++i, pszFrom, pszTo);

            // #2 One over "short" max path
            // Succeeds with right answer
            pszFrom = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD1234567890";
            pszTo = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD1234567890\abcdefghijklmnop.txt";
            TestPathRelativePathTo(++i, pszFrom, pszTo);

            // #3 Shortest path (by experiment) that returned the wrong answer
            pszFrom = @"c:\ABCDEFGHIJKLMNOPQRS\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD1234567890";
            pszTo = @"c:\ABCDEFGHIJKLMNOPQRS\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCD1234567890\b.txt";
            TestPathRelativePathTo(++i, pszFrom, pszTo);

            // #4: Long path that errors out
            // Errors out
            pszFrom = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
            pszTo = @"c:\ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b.txt";
            TestPathRelativePathTo(++i, pszFrom, pszTo);

            // #5: Same as previous except one character removed from beginning of first folder
            // Succeeds, but wrong return result
            pszFrom = @"c:\BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
            pszTo = @"c:\BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b.txt";
            TestPathRelativePathTo(++i, pszFrom, pszTo);

            // #6: Same as previous except 3 characters added to filename. 
            // Succeeds, but wrong return result
            pszFrom = @"c:\BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
            pszTo = @"c:\BCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGH\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b123.txt";
            TestPathRelativePathTo(++i, pszFrom, pszTo);
        }

        static void TestPathRelativePathTo(int i, string pszFromDir, string pszToFile)
        {
            int maxResult = 10000;
            StringBuilder result = new StringBuilder(maxResult);
            Console.WriteLine($"#{i}: Calling PathRelativePathTo(...): pszFrom.Length: {pszFromDir.Length}; pszTo.Length {pszToFile.Length} ");
            bool bRet = Native.PathRelativePathTo(result, pszFromDir, (int)FileAttributes.Directory, pszToFile, (int)FileAttributes.Normal);
            if (!bRet)
            {
                // *Edit*: As pointed out in the comments, PathRelativePathTo does not set last error, so this part of the code is incorrect, it should really just print out that the method returned false.
                // https://blogs.msdn.microsoft.com/shawnfa/2004/09/10/formatmessage-shortcut-for-win32-error-codes/
                int currentError = Marshal.GetLastWin32Error();
                var errorMessage = new Win32Exception(currentError).Message;
                Console.WriteLine($"  Error: {errorMessage}");
            }
            else
            {
                Console.WriteLine($"  Result: {result}");
            }
        }
    }

आउटपुट:

#1: Calling PathRelativePathTo(...): pszFrom.Length: 238; pszTo.Length 259
  Result: .\abcdefghijklmnop.txt
#2: Calling PathRelativePathTo(...): pszFrom.Length: 239; pszTo.Length 260
  Result: .\abcdefghijklmnop.txt
#3: Calling PathRelativePathTo(...): pszFrom.Length: 259; pszTo.Length 265
  Result: ..\ABCD1234567890\b.txt
#4: Calling PathRelativePathTo(...): pszFrom.Length: 481; pszTo.Length 487
  Error: The system cannot find the file specified
#5: Calling PathRelativePathTo(...): pszFrom.Length: 480; pszTo.Length 486
  Result: .\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b.txt
#6: Calling PathRelativePathTo(...): pszFrom.Length: 480; pszTo.Length 489
  Result: .\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\b123.txt

प्रशन:

  • PathRelativePathToउपरोक्त के संबंध में अपेक्षित व्यवहार क्या है ?
  • क्या यह केवल "संक्षिप्त" MAX_PATH सीमा (और शेष व्यवहार अपरिभाषित है) के तहत रास्तों के साथ ठीक से काम करने की उम्मीद है?
  • क्या .net फ्रेमवर्क में कुछ और भी है जिसका मैं उपयोग कर सकता हूं (नोट: मैं देखता हूं कि .NET कोर के पास Path.GetRelativePath है , लेकिन मैं अभी तक इसका उपयोग नहीं कर सकता)?

टिप्पणियाँ विस्तारित चर्चा के लिए नहीं हैं; इस वार्तालाप को बातचीत में स्थानांतरित कर दिया गया है ।
शमूएल ल्यू

भूल जाओ PathRelativePathTo, यह लंबे रास्तों के लिए नहीं है। यह वास्तव में इसका उपयोग करने के लिए सुरक्षित नहीं है, क्योंकि आप गंतव्य बफर के आकार को नहीं बता सकते हैं, प्रलेखन केवल यह कहता है कि "आकार में कम से कम MAX_PATH वर्ण होना चाहिए।"
तेज

आधिकारिक doc MAX_PATH सीमा पर बहुत स्पष्ट है। प्रतिस्थापन के रूप में, इसे प्राप्त करना आसान है, आप .NET कोर स्रोत का फिर से उपयोग कर सकते हैं या इसे एक प्रारंभिक बिंदु के रूप में उपयोग कर सकते हैं: github.com/dotnet/corefx/blob/…
साइमन मूरियर

आप अब तक क्या उपयोग कर रहे हैं? क्लासिक .NET या .NET कोर, कौन सा संस्करण?
पावेल एनीकौस्की

।शुद्ध रूपरेखा। एक बार जब मैं .net कोर 3.0 को स्थानांतरित करने में सक्षम हो जाता हूं, तो मैं सभी सेट हो जाऊंगा क्योंकि उनके पास मेरे द्वारा उल्लिखित विधि में अंतर्निहित है।
मैट स्मिथ

जवाबों:


4

इसके लुक से ऐसा लगता है जैसे PathRelativePathTo API केवल MAX_LENGTH तक के रास्तों के लिए सुरक्षित है। वाइन दस्तावेज़ीकरण से कम से कम, हम देखते हैं कि Win32 कार्यान्वयन में एपीआई समस्याग्रस्त रहा है।

इस फ़ंक्शन के Win32 संस्करण में एक बग है जिसमें lpszTo स्ट्रिंग को स्ट्रिंग के अंत से परे 1 बाइट संदर्भित किया जा सकता है। परिणामस्वरूप यादृच्छिक कचरा आउटपुट पथ पर लिखा जा सकता है, जो स्ट्रिंग के अंतिम बाइट से परे झूठ पर निर्भर करता है। PathCommonPrefix () और उस फ़ंक्शन के लिए नोट देखें) के व्यवहार के कारण यह बग उत्पन्न होती है, और Win32 के साथ कोई वर्कअराउंड संभव नहीं लगता है। यह बग यहां तय किया गया है, इसलिए उदाहरण के लिए "\" से "\" तक का सापेक्ष पथ सही रूप में निर्धारित किया गया है। " इस कार्यान्वयन में।

और PathCommonPrefix प्रलेखन से,

2 का एक सामान्य उपसर्ग हमेशा 3 के रूप में लौटाया जाता है। इस प्रकार यह संभव है कि लंबाई अमान्य हो (अर्थात एक पैरामीटर से दिए गए दोनों तारों की तुलना में लंबा या दोनों)। यह Win32 व्यवहार यहाँ लागू किया गया है, और अन्य SHLWAPI कॉल को तोड़े बिना (निश्चित) बदला नहीं जा सकता है। इस फ़ंक्शन का उपयोग करते समय इसके चारों ओर काम करने के लिए, हमेशा जांचें कि बाइट [common_prefix_len-1] पर NUL नहीं है। यदि यह है, तो उपसर्ग से 1 घटाएं।

यह जानकारी और मानने की श्‍लवापी कार्यान्वयन MAX_SIZE लंबाई के बफ़र्स के साथ काम करता है और यह वाइन या रिएक्टोस ( https://doxygen.reactos.org/de/dff/dll_2win32_shlwapi_2path_8c_source.html ) के समान है जो अपरिभाषित की तरह लगता है व्यवहार जिसे आप परीक्षण में देख रहे हैं।

एक .NET समाधान के रूप में, सबसे आसान तरीका (सबसे अच्छा नहीं हो सकता है) मैं उपयोग करने के लिए सोच सकता हूं System.Uri

Uri path1 = new Uri(@"c:\lvl1\lvl2\");
Uri path2 = new Uri(@"c:\lvl1\lvl3\file1.txt");
Uri diff = path1.MakeRelativeUri(path2);
// Uri will switch to forward slashes, so to fix that...
string relPath = 
Uri.UnescapeDataString(diff.OriginalString).Replace("/",@"\");

या टोकेस्टर आप .NET कोर स्रोत के आधार पर कुछ लागू कर सकते हैं Path.GetRelativePath


3

.NET 4.6.2 समाधान

यहाँ\\?\C:\Verrrrrrrrrrrry long path वर्णित के रूप में वाक्यविन्यास का उपयोग करें

इस बारे में एक बेहतरीन ब्लॉग पोस्ट भी है

सामान्य तौर पर मेरे पास सबसे बड़ा मुद्दा वेब पर साझा किए गए फ़ोल्डर्स के साथ है। बाकी ठीक है।

पुराने .NET संस्करण

यदि आप .NET के पुराने संस्करण का उपयोग कर रहे हैं , तो आप इस Win32 API फ़ंक्शन को देख सकते हैं, आपको इसके लिए आवश्यकता होगी P/Invoke

विंडोज एपीआई में कई कार्य हैं जिनमें 32,767 वर्णों की अधिकतम पथ लंबाई के लिए विस्तारित-लंबाई पथ की अनुमति देने के लिए यूनिकोड संस्करण भी हैं

इसके अलावा आप इस एसओ प्रश्न की जांच कर सकते हैं, जो आपके लिए बहुत समान है।
259 वर्णों से अधिक लंबे नाम वाली फ़ाइलों से कैसे निपटें?


4
लेकिन यह सब असंबंधित हैPathRelativePathTo
RbMm

2
यह सवाल का जवाब कैसे दे रहा है?
जेस्टर

यह सभी पथ कार्यक्षमता के पीछे बिल्कुल समान विचार है।
गिलाद

1
कोई भी पथ कार्यक्षमता का कोई भी ठोस PathRelativePathToकिसी उपसर्ग से प्रभावित नहीं है। यह शुद्ध शाब्दिक पार्सिंग एपी है, 260 वर्णों की सीमा तक हार्डकोडेड है। यह भी \\ बनाम / अलग - इसे तोड़
RbMm

यह बताते हुए भी कि यह काम नहीं करता है एक टिप्पणी है: chat.stackoverflow.com/transcript/message/47826723#47826723
16:17 पर user1781290

2

पर । .NET में कोई पूर्ण या सामान्यीकृत फ़ाइल पथ कैसे प्राप्त कर सकता है? समझा

public static string NormalizePath(string path)
{
    return Path.GetFullPath(new Uri(path).LocalPath)
           .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
           .ToUpperInvariant();
}

इसलिए मैं इसके साथ शुरू करूँगा कि दो रास्तों को सामान्य करें (यह भी देखें कि https://blogs.msdn.microsoft.com/jeremykuhne/2016/04/21/path-normalization/ उस मामले में जो अधिक मामलों को कवर करता है)

तब मैं उन्हें उप-प्रक्षेत्रों के सरणियों / सूचियों में विभाजित करूँगा (किसी एक विधि से कहूं कि कोई एक पथ से प्रत्येक फ़ोल्डर को कैसे निकालता है? )

वहां से मुझे अधिकतम N पहले भाग मिले जो सामान्य हैं।

फिर मैं भागों को C, उर्फ ​​CN के पहले पथ की गिनती से N को घटाऊंगा। कितने रास्ते में वापस पाने के लिए .. मुझे पहले रास्ते में जोड़ना होगा।

आखिरकार, मैं इसमें से पहले N आइटम को हटाने के बाद टाप के बाकी हिस्सों को जोड़ दूंगा और परिणामस्वरूप पथ वापस कर दूंगा

लगता है कि एक बार सामान्य पथ मिलने के बाद आप स्ट्रिंग पार्सिंग (सूचियों में विभाजन के बिना) के साथ (अतिरिक्त भंडारण से बचने के लिए) भी कर सकते हैं। यह विचार यह होगा कि आपको सामान्य स्ट्रिंग उपसर्ग मिल जाएगा और फिर इसका अंतिम भाग ट्रिम कर दिया जाएगा यदि सामान्य भाग पथ विभाजक के साथ समाप्त नहीं हुआ (क्योंकि यह एक संयोगवश अतिरिक्त सामान्य भाग होगा, जैसे c: \ a \ test1 और c: \ a \ test2 में सामान्य पथ है c: \ a a और not c: \ a \ test जैसा कि आप एक सामान्य सामान्य उपसर्ग स्ट्रिंग निष्कर्षण के साथ प्राप्त करेंगे)।

वैकल्पिक रूप से आप एक एल्गोरिथ्म का उपयोग कर सकते हैं, जो एक ही समय में दो सामान्यीकृत पथों को एक लूप (प्रत्येक पर एक कदम) में काम करने के लिए वर्ण सूचकांक देता है ताकि आपको कुछ अतिरिक्त संग्रहित करने की आवश्यकता न हो। तर्क ऊपर वर्णित के समान होगा।


1

मैंने विधि के एक बंदरगाह का उपयोग करने का फैसला किया ।dotnet/corefx Path.GetRelativePath

निम्नलिखित स्रोतों से निम्नलिखित कोड को अनुकूलित किया गया था। उस कोड में टिप्पणियों को पढ़ें जहां मैंने अपने द्वारा उपयोग किए गए किसी समायोजन या वर्कअराउंड को सूचीबद्ध किया है:

कोड को अपनाने में मेरा लक्ष्य था

  • संभव के रूप में कुछ संशोधन करें (कोड टिप्पणियों में नोट किए गए किसी भी संशोधन)
  • कक्षा की संरचना को मूल स्रोत के समान रखें
  • केवल उन विधियों / गुणों को शामिल करें जो विधि को लागू करने के लिए आवश्यक थे GetRelativePath

कोड

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using static System.IO.Path;

static class PathExtension
{
    // Port of .net 3.0 Path.GetRelativePath (Windows version)
    // https://docs.microsoft.com/en-us/dotnet/api/system.io.path.getrelativepath?view=netcore-3.0
    // 
    // Adapted from:
    // https://github.com/dotnet/corefx/blob/b123ba4b9107c73cbc02010dc1ee78eb8ffccb93/src/Common/src/CoreLib/System/IO/Path.cs
    // https://github.com/dotnet/corefx/blob/4a7075f188b5777ccb519f2af9b8a284f4383357/src/Common/src/CoreLib/System/IO/Path.Windows.cs
    //
    // Notes:
    // * I didn't have access to ReadOnlySpan<T> nor .AsSpan(), so I removed them.  I just used regular string instead.
    // * I hard coded some resource strings (from exceptions)
    // * Replaced ValueStringBuild with StringBuilder

    /// <summary>
    /// Create a relative path from one path to another. Paths will be resolved before calculating the difference.
    /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix).
    /// </summary>
    /// <param name="relativeTo">The source path the output should be relative to. This path is always considered to be a directory.</param>
    /// <param name="path">The destination path.</param>
    /// <returns>The relative path or <paramref name="path"/> if the paths don't share the same root.</returns>
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
    public static string GetRelativePath(string relativeTo, string path)
    {
        return GetRelativePath(relativeTo, path, StringComparison);
    }

    private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
    {
        if (relativeTo == null)
            throw new ArgumentNullException(nameof(relativeTo));

        if (PathInternal.IsEffectivelyEmpty(relativeTo.AsSpan()))
            throw new ArgumentException(SR.Arg_PathEmpty, nameof(relativeTo));

        if (path == null)
            throw new ArgumentNullException(nameof(path));

        if (PathInternal.IsEffectivelyEmpty(path.AsSpan()))
            throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));

        Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);

        relativeTo = GetFullPath(relativeTo);
        path = GetFullPath(path);

        // Need to check if the roots are different- if they are we need to return the "to" path.
        if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType))
            return path;

        int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);

        // If there is nothing in common they can't share the same root, return the "to" path as is.
        if (commonLength == 0)
            return path;

        // Trailing separators aren't significant for comparison
        int relativeToLength = relativeTo.Length;
        if (EndsInDirectorySeparator(relativeTo.AsSpan()))
            relativeToLength--;

        bool pathEndsInSeparator = EndsInDirectorySeparator(path.AsSpan());
        int pathLength = path.Length;
        if (pathEndsInSeparator)
            pathLength--;

        // If we have effectively the same path, return "."
        if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";

        // We have the same root, we need to calculate the difference now using the
        // common Length and Segment count past the length.
        //
        // Some examples:
        //
        //  C:\Foo C:\Bar L3, S1 -> ..\Bar
        //  C:\Foo C:\Foo\Bar L6, S0 -> Bar
        //  C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
        //  C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar

        // Original: var sb = new ValueStringBuilder(stackalloc char[260]);
        var sb = new StringBuilder(260);
        sb.EnsureCapacity(Math.Max(relativeTo.Length, path.Length));

        // Add parent segments for segments past the common on the "from" path
        if (commonLength < relativeToLength)
        {
            sb.Append("..");

            for (int i = commonLength + 1; i < relativeToLength; i++)
            {
                if (PathInternal.IsDirectorySeparator(relativeTo[i]))
                {
                    sb.Append(DirectorySeparatorChar);
                    sb.Append("..");
                }
            }
        }
        else if (PathInternal.IsDirectorySeparator(path[commonLength]))
        {
            // No parent segments and we need to eat the initial separator
            //  (C:\Foo C:\Foo\Bar case)
            commonLength++;
        }

        // Now add the rest of the "to" path, adding back the trailing separator
        int differenceLength = pathLength - commonLength;
        if (pathEndsInSeparator)
            differenceLength++;

        if (differenceLength > 0)
        {
            if (sb.Length > 0)
            {
                sb.Append(DirectorySeparatorChar);
            }

            sb.Append(path.AsSpan(commonLength, differenceLength));
        }

        return sb.ToString();
    }

    /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
    internal static StringComparison StringComparison =>
        IsCaseSensitive ?
            StringComparison.Ordinal :
            StringComparison.OrdinalIgnoreCase;

    /// <summary>
    /// Returns true if the path ends in a directory separator.
    /// </summary>
    public static bool EndsInDirectorySeparator(string path) // Originally was public static bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
        => path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);

    #region Resources
    // From https://github.com/dotnet/corefx/blob/c390ce7df50252e11f5d322276e9d19e046d1332/src/Microsoft.IO.Redist/src/Resources/Strings.resx

    static class SR
    {
        public static string Arg_PathEmpty => "The path is empty.";
    }
    #endregion Resources

    #region Path.Windows 
    // Code from 
    // https://github.com/dotnet/corefx/blob/4a7075f188b5777ccb519f2af9b8a284f4383357/src/Common/src/CoreLib/System/IO/Path.Windows.cs

    // https://github.com/dotnet/corefx/blob/4a7075f188b5777ccb519f2af9b8a284f4383357/src/Common/src/CoreLib/System/IO/Path.Windows.cs#L235
    /// <summary>Gets whether the system is case-sensitive.</summary>
    internal static bool IsCaseSensitive => false;

    #endregion Path.Windows

    #region Workarounds

    // Note, this is here just to cause all .AsSpan() calls to return a string since I don't have access to ReadOnlySpan<char>
    // https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.primitives.stringsegment.asspan?view=dotnet-plat-ext-3.0
    static string AsSpan(this string s)
    {
        return s;
    }

    // Note, this is here just to cause all .AsSpan() calls to return a string since I don't have access to ReadOnlySpan<char>
    // https://docs.microsoft.com/en-us/dotnet/api/system.memoryextensions.asspan?view=netcore-3.0#System_MemoryExtensions_AsSpan_System_String_System_Int32_System_Int32_
    static string AsSpan(this string s, int startIndex, int length)
    {
        return s.Substring(startIndex, length);
    }


    #endregion Workarounds

    // Code from 
    // https://github.com/dotnet/corefx/blob/b123ba4b9107c73cbc02010dc1ee78eb8ffccb93/src/Common/src/CoreLib/System/IO/PathInternal.cs
    // https://github.com/dotnet/corefx/blob/b123ba4b9107c73cbc02010dc1ee78eb8ffccb93/src/Common/src/CoreLib/System/IO/PathInternal.Windows.cs
    static class PathInternal
    {
        /// <summary>
        /// Returns true if the two paths have the same root
        /// </summary>
        internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType)
        {
            int firstRootLength = GetRootLength(first.AsSpan());
            int secondRootLength = GetRootLength(second.AsSpan());

            return firstRootLength == secondRootLength
                && string.Compare(
                    strA: first,
                    indexA: 0,
                    strB: second,
                    indexB: 0,
                    length: firstRootLength,
                    comparisonType: comparisonType) == 0;
        }

        #region PathInternal.Windows
        // Code from https://github.com/dotnet/corefx/blob/b123ba4b9107c73cbc02010dc1ee78eb8ffccb93/src/Common/src/CoreLib/System/IO/PathInternal.Windows.cs

        // \\?\, \\.\, \??\
        internal const int DevicePrefixLength = 4;

        // \\
        internal const int UncPrefixLength = 2;

        // \\?\UNC\, \\.\UNC\
        internal const int UncExtendedPrefixLength = 8;

        /// <summary>
        /// Returns true if the given character is a valid drive letter
        /// </summary>
        internal static bool IsValidDriveChar(char value)
        {
            return (value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z');
        }

        /// <summary>
        /// True if the given character is a directory separator.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static bool IsDirectorySeparator(char c)
        {
            return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
        }

        /// <summary>
        /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
        /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
        /// and path length checks.
        /// </summary>
        internal static bool IsExtended(string path) // Original was internal static bool IsExtended(ReadOnlySpan<char> path)
        {
            // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
            // Skipping of normalization will *only* occur if back slashes ('\') are used.
            return path.Length >= DevicePrefixLength
                && path[0] == '\\'
                && (path[1] == '\\' || path[1] == '?')
                && path[2] == '?'
                && path[3] == '\\';
        }

        /// <summary>
        /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
        /// </summary>
        internal static bool IsDevice(string path) // Original was: internal static bool IsDevice(ReadOnlySpan<char> path)
        {
            // If the path begins with any two separators is will be recognized and normalized and prepped with
            // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
            return IsExtended(path)
                ||
                (
                    path.Length >= DevicePrefixLength
                    && IsDirectorySeparator(path[0])
                    && IsDirectorySeparator(path[1])
                    && (path[2] == '.' || path[2] == '?')
                    && IsDirectorySeparator(path[3])
                );
        }

        /// <summary>
        /// Returns true if the path is a device UNC (\\?\UNC\, \\.\UNC\)
        /// </summary>
        internal static bool IsDeviceUNC(string path) // Original was: internal static bool IsDeviceUNC(ReadOnlySpan<char> path) 
        {
            return path.Length >= UncExtendedPrefixLength
                && IsDevice(path)
                && IsDirectorySeparator(path[7])
                && path[4] == 'U'
                && path[5] == 'N'
                && path[6] == 'C';
        }

        /// <summary>
        /// Gets the length of the root of the path (drive, share, etc.).
        /// </summary>
        internal static int GetRootLength(string path) // Note: original was internal static int GetRootLength(ReadOnlySpan<char> path)

        {
            int pathLength = path.Length;
            int i = 0;

            bool deviceSyntax = IsDevice(path);
            bool deviceUnc = deviceSyntax && IsDeviceUNC(path);

            if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator(path[0]))
            {
                // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
                if (deviceUnc || (pathLength > 1 && IsDirectorySeparator(path[1])))
                {
                    // UNC (\\?\UNC\ or \\), scan past server\share

                    // Start past the prefix ("\\" or "\\?\UNC\")
                    i = deviceUnc ? UncExtendedPrefixLength : UncPrefixLength;

                    // Skip two separators at most
                    int n = 2;
                    while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0))
                        i++;
                }
                else
                {
                    // Current drive rooted (e.g. "\foo")
                    i = 1;
                }
            }
            else if (deviceSyntax)
            {
                // Device path (e.g. "\\?\.", "\\.\")
                // Skip any characters following the prefix that aren't a separator
                i = DevicePrefixLength;
                while (i < pathLength && !IsDirectorySeparator(path[i]))
                    i++;

                // If there is another separator take it, as long as we have had at least one
                // non-separator after the prefix (e.g. don't take "\\?\\", but take "\\?\a\")
                if (i < pathLength && i > DevicePrefixLength && IsDirectorySeparator(path[i]))
                    i++;
            }
            else if (pathLength >= 2
                && path[1] == VolumeSeparatorChar
                && IsValidDriveChar(path[0]))
            {
                // Valid drive specified path ("C:", "D:", etc.)
                i = 2;

                // If the colon is followed by a directory separator, move past it (e.g "C:\")
                if (pathLength > 2 && IsDirectorySeparator(path[2]))
                    i++;
            }

            return i;
        }

        /// <summary>
        /// Gets the count of common characters from the left optionally ignoring case
        /// </summary>
        internal static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase)
        {
            if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0;

            int commonChars = 0;

            fixed (char* f = first)
            fixed (char* s = second)
            {
                char* l = f;
                char* r = s;
                char* leftEnd = l + first.Length;
                char* rightEnd = r + second.Length;

                while (l != leftEnd && r != rightEnd
                    && (*l == *r || (ignoreCase && char.ToUpperInvariant(*l) == char.ToUpperInvariant(*r))))
                {
                    commonChars++;
                    l++;
                    r++;
                }
            }

            return commonChars;
        }

        /// <summary>
        /// Get the common path length from the start of the string.
        /// </summary>
        internal static int GetCommonPathLength(string first, string second, bool ignoreCase)
        {
            int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase);

            // If nothing matches
            if (commonChars == 0)
                return commonChars;

            // Or we're a full string and equal length or match to a separator
            if (commonChars == first.Length
                && (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
                return commonChars;

            if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
                return commonChars;

            // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
            while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
                commonChars--;

            return commonChars;
        }

        /// <summary>
        /// Returns true if the path is effectively empty for the current OS.
        /// For unix, this is empty or null. For Windows, this is empty, null, or
        /// just spaces ((char)32).
        /// </summary>
        /// 
        internal static bool IsEffectivelyEmpty(string path)
        {
            // Note, see the original version below
            return string.IsNullOrWhiteSpace(path);
        }

        // Note: here's the original version.  I've replaced it with the version above that just uses string
        // 
        //internal static bool IsEffectivelyEmpty(ReadOnlySpan<char> path)
        //{
        //    if (path.IsEmpty)
        //        return true;

        //    foreach (char c in path)
        //    {
        //        if (c != ' ')
        //            return false;
        //    }
        //    return true;
        //}

        #endregion PathInternal.Windows
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.