UDNParser.cpp 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386
  1. // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
  2. #include "UDNParser.h"
  3. #include "DocumentationStyle.h"
  4. #include "Fonts/SlateFontInfo.h"
  5. #include "Misc/Paths.h"
  6. #include "HAL/FileManager.h"
  7. #include "Misc/FileHelper.h"
  8. #include "Framework/Application/SlateApplication.h"
  9. #include "Widgets/Layout/SSeparator.h"
  10. #include "Widgets/Images/SImage.h"
  11. #include "Widgets/Text/STextBlock.h"
  12. #include "Widgets/Text/SRichTextBlock.h"
  13. #include "Widgets/Layout/SBox.h"
  14. #include "Widgets/Input/SButton.h"
  15. #include "EditorStyleSet.h"
  16. #include "Editor/EditorPerProjectUserSettings.h"
  17. #include "Developer/MessageLog/Public/MessageLogModule.h"
  18. #include "Logging/MessageLog.h"
  19. //#include "Editor/Documentation/Private/DocumentationLink.h"
  20. #include "DocumentationLink.h"
  21. #include "ISourceCodeAccessor.h"
  22. #include "ISourceCodeAccessModule.h"
  23. #include "IContentBrowserSingleton.h"
  24. #include "ContentBrowserModule.h"
  25. #include "DesktopPlatformModule.h"
  26. #include "Framework/Notifications/NotificationManager.h"
  27. #include "Widgets/Notifications/SNotificationList.h"
  28. #include "Widgets/Input/SHyperlink.h"
  29. #include "Subsystems/AssetEditorSubsystem.h"
  30. #define LOCTEXT_NAMESPACE "IntroTutorials"
  31. FName UDNParseErrorLog("UDNParser");
  32. #include "ExtDocumentation.h"
  33. namespace LinkPrefixes
  34. {
  35. static const FString DocLinkSpecifier( TEXT( "DOCLINK:" ) );
  36. static const FString TutorialLinkSpecifier( TEXT( "TUTORIALLINK:" ) );
  37. static const FString HttpLinkSpecifier( TEXT( "http://" ) );
  38. static const FString HttpsLinkSpecifier( TEXT( "https://" ) );
  39. static const FString CodeLinkSpecifier(TEXT("CODELINK:"));
  40. static const FString AssetLinkSpecifier(TEXT("ASSETLINK:"));
  41. }
  42. TSharedRef< FExtUDNParser > FExtUDNParser::Create( const TSharedPtr< FParserConfiguration >& ParserConfig, const FDocumentationStyle& Style )
  43. {
  44. TSharedPtr< FParserConfiguration > FinalParserConfig = ParserConfig;
  45. if ( !FinalParserConfig.IsValid() )
  46. {
  47. struct Local
  48. {
  49. static void OpenLink( const FString& Link )
  50. {
  51. if ( !IDocumentation::Get()->Open( Link, FDocumentationSourceInfo(TEXT("udn_parser")) ) )
  52. {
  53. FNotificationInfo Info( NSLOCTEXT("FExtUDNParser", "FailedToOpenLink", "Failed to Open Link") );
  54. FSlateNotificationManager::Get().AddNotification(Info);
  55. }
  56. }
  57. };
  58. FinalParserConfig = FParserConfiguration::Create();
  59. FinalParserConfig->OnNavigate = FOnNavigate::CreateStatic( &Local::OpenLink );
  60. }
  61. TSharedRef< FExtUDNParser > Parser = MakeShareable( new FExtUDNParser( FinalParserConfig.ToSharedRef(), Style ) );
  62. Parser->Initialize();
  63. return Parser;
  64. }
  65. FExtUDNParser::FExtUDNParser( const TSharedRef< FParserConfiguration >& InConfiguration, const FDocumentationStyle& InStyle )
  66. : Configuration( InConfiguration )
  67. , Style(InStyle)
  68. , WrapAt(600.f)
  69. , ContentWidth(600.f)
  70. {
  71. }
  72. void FExtUDNParser::Initialize()
  73. {
  74. FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
  75. MessageLogModule.RegisterLogListing( UDNParseErrorLog, LOCTEXT("UDNParser", "UDN Parse Errors") );
  76. // Set up rules for interpreting strings as tokens
  77. TokenLibrary.Add(FTokenPair(TEXT("#"), EUDNToken::Pound));
  78. TokenLibrary.Add(FTokenPair(TEXT("["), EUDNToken::OpenBracket));
  79. TokenLibrary.Add(FTokenPair(TEXT("]"), EUDNToken::CloseBracket));
  80. TokenLibrary.Add(FTokenPair(TEXT("("), EUDNToken::OpenParenthesis));
  81. TokenLibrary.Add(FTokenPair(TEXT(")"), EUDNToken::CloseParenthesis));
  82. TokenLibrary.Add(FTokenPair(TEXT("1."), EUDNToken::Numbering));
  83. TokenLibrary.Add(FTokenPair(TEXT("!"), EUDNToken::Bang));
  84. TokenLibrary.Add(FTokenPair(TEXT("EXCERPT"), EUDNToken::Excerpt));
  85. TokenLibrary.Add(FTokenPair(TEXT("VAR"), EUDNToken::Variable));
  86. TokenLibrary.Add(FTokenPair(TEXT(":"), EUDNToken::Colon));
  87. TokenLibrary.Add(FTokenPair(TEXT("/"), EUDNToken::Slash));
  88. TokenLibrary.Add(FTokenPair(TEXT("-"), EUDNToken::Dash));
  89. TokenLibrary.Add(FTokenPair(TEXT("Availability:"), EUDNToken::MetadataAvailability));
  90. TokenLibrary.Add(FTokenPair(TEXT("Title:"), EUDNToken::MetadataTitle));
  91. TokenLibrary.Add(FTokenPair(TEXT("Crumbs:"), EUDNToken::MetadataCrumbs));
  92. TokenLibrary.Add(FTokenPair(TEXT("Description:"), EUDNToken::MetadataDescription));
  93. TokenLibrary.Add(FTokenPair(TEXT("%"), EUDNToken::Percentage));
  94. TokenLibrary.Add(FTokenPair(TEXT("*"), EUDNToken::Asterisk));
  95. // Set up rules for interpreting series of symbols into a line of Slate content
  96. TArray<EUDNToken::Type> TokenArray;
  97. TokenArray.Empty();
  98. TokenArray.Add(EUDNToken::Asterisk);
  99. TokenArray.Add(EUDNToken::Asterisk);
  100. TokenArray.Add(EUDNToken::Content);
  101. TokenArray.Add(EUDNToken::Asterisk);
  102. TokenArray.Add(EUDNToken::Asterisk);
  103. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::BoldContent));
  104. TokenArray.Empty();
  105. TokenArray.Add(EUDNToken::Percentage);
  106. TokenArray.Add(EUDNToken::Content);
  107. TokenArray.Add(EUDNToken::Percentage);
  108. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::VariableReference));
  109. TokenArray.Empty();
  110. TokenArray.Add(EUDNToken::Numbering);
  111. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::NumberedContent, true));
  112. TokenArray.Empty();
  113. for (int32 i = 0; i < 3; ++i)
  114. {
  115. TokenArray.Add(EUDNToken::Dash);
  116. }
  117. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::HorizontalRule));
  118. TokenArray.Empty();
  119. TokenArray.Add(EUDNToken::Pound);
  120. TokenArray.Add(EUDNToken::Pound);
  121. TokenArray.Add(EUDNToken::Pound);
  122. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::Header2, true));
  123. TokenArray.Empty();
  124. TokenArray.Add(EUDNToken::Pound);
  125. TokenArray.Add(EUDNToken::Pound);
  126. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::Header1, true));
  127. TokenArray.Empty();
  128. TokenArray.Add(EUDNToken::OpenBracket);
  129. TokenArray.Add(EUDNToken::Content);
  130. TokenArray.Add(EUDNToken::CloseBracket);
  131. TokenArray.Add(EUDNToken::OpenParenthesis);
  132. TokenArray.Add(EUDNToken::Content);
  133. TokenArray.Add(EUDNToken::CloseParenthesis);
  134. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::Link));
  135. TokenArray.Empty();
  136. TokenArray.Add(EUDNToken::OpenBracket);
  137. TokenArray.Add(EUDNToken::Bang);
  138. TokenArray.Add(EUDNToken::OpenBracket);
  139. TokenArray.Add(EUDNToken::Content);
  140. TokenArray.Add(EUDNToken::CloseBracket);
  141. TokenArray.Add(EUDNToken::OpenParenthesis);
  142. TokenArray.Add(EUDNToken::Content);
  143. TokenArray.Add(EUDNToken::CloseParenthesis);
  144. TokenArray.Add(EUDNToken::CloseBracket);
  145. TokenArray.Add(EUDNToken::OpenParenthesis);
  146. TokenArray.Add(EUDNToken::Content);
  147. TokenArray.Add(EUDNToken::CloseParenthesis);
  148. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::ImageLink));
  149. TokenArray.Empty();
  150. TokenArray.Add(EUDNToken::Bang);
  151. TokenArray.Add(EUDNToken::OpenBracket);
  152. TokenArray.Add(EUDNToken::Content);
  153. TokenArray.Add(EUDNToken::CloseBracket);
  154. TokenArray.Add(EUDNToken::OpenParenthesis);
  155. TokenArray.Add(EUDNToken::Content);
  156. TokenArray.Add(EUDNToken::CloseParenthesis);
  157. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::Image));
  158. TokenArray.Empty();
  159. TokenArray.Add(EUDNToken::OpenBracket);
  160. TokenArray.Add(EUDNToken::Excerpt);
  161. TokenArray.Add(EUDNToken::Colon);
  162. TokenArray.Add(EUDNToken::Content);
  163. TokenArray.Add(EUDNToken::CloseBracket);
  164. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::ExcerptOpen));
  165. TokenArray.Empty();
  166. TokenArray.Add(EUDNToken::OpenBracket);
  167. TokenArray.Add(EUDNToken::Slash);
  168. TokenArray.Add(EUDNToken::Excerpt);
  169. TokenArray.Add(EUDNToken::Colon);
  170. TokenArray.Add(EUDNToken::Content);
  171. TokenArray.Add(EUDNToken::CloseBracket);
  172. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::ExcerptClose));
  173. TokenArray.Empty();
  174. TokenArray.Add(EUDNToken::MetadataAvailability);
  175. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::MetadataAvailability, true));
  176. TokenArray.Empty();
  177. TokenArray.Add(EUDNToken::MetadataTitle);
  178. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::MetadataTitle, true));
  179. TokenArray.Empty();
  180. TokenArray.Add(EUDNToken::MetadataCrumbs);
  181. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::MetadataCrumbs, true));
  182. TokenArray.Empty();
  183. TokenArray.Add(EUDNToken::MetadataDescription);
  184. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::MetadataDescription, true));
  185. TokenArray.Empty();
  186. TokenArray.Add(EUDNToken::OpenBracket);
  187. TokenArray.Add(EUDNToken::Variable);
  188. TokenArray.Add(EUDNToken::Colon);
  189. TokenArray.Add(EUDNToken::Content);
  190. TokenArray.Add(EUDNToken::CloseBracket);
  191. TokenArray.Add(EUDNToken::Content);
  192. TokenArray.Add(EUDNToken::OpenBracket);
  193. TokenArray.Add(EUDNToken::Variable);
  194. TokenArray.Add(EUDNToken::CloseBracket);
  195. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::Variable));
  196. TokenArray.Empty();
  197. TokenArray.Add(EUDNToken::OpenBracket);
  198. TokenArray.Add(EUDNToken::Variable);
  199. TokenArray.Add(EUDNToken::Colon);
  200. TokenArray.Add(EUDNToken::Content);
  201. TokenArray.Add(EUDNToken::CloseBracket);
  202. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::VariableOpen));
  203. TokenArray.Empty();
  204. TokenArray.Add(EUDNToken::OpenBracket);
  205. TokenArray.Add(EUDNToken::Slash);
  206. TokenArray.Add(EUDNToken::Variable);
  207. TokenArray.Add(EUDNToken::CloseBracket);
  208. LineLibrary.Add(FTokenConfiguration(TokenArray, FUDNLine::VariableClose));
  209. }
  210. FExtUDNParser::~FExtUDNParser()
  211. {
  212. if ( FModuleManager::Get().IsModuleLoaded("MessageLog") )
  213. {
  214. FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
  215. MessageLogModule.UnregisterLogListing(UDNParseErrorLog);
  216. }
  217. }
  218. bool FExtUDNParser::LoadLink( const FString& Link, TArray<FString>& ContentLines )
  219. {
  220. FMessageLog UDNParserLog(UDNParseErrorLog);
  221. const FString SourcePath = FExtDocumentationLink::ToSourcePath( Link );
  222. if (!FPaths::FileExists(SourcePath))
  223. {
  224. return false;
  225. }
  226. TArray<uint8> Buffer;
  227. bool bLoadSuccess = FFileHelper::LoadFileToArray(Buffer, *SourcePath);
  228. if ( bLoadSuccess )
  229. {
  230. FString Result;
  231. FFileHelper::BufferToString( Result, Buffer.GetData(), Buffer.Num() );
  232. // Now read it
  233. // init traveling pointer
  234. TCHAR* Ptr = Result.GetCharArray().GetData();
  235. // iterate over the lines until complete
  236. bool bIsDone = false;
  237. while (!bIsDone)
  238. {
  239. // Store the location of the first character of this line
  240. TCHAR* Start = Ptr;
  241. // Advance the char pointer until we hit a newline character
  242. while (*Ptr && *Ptr!='\r' && *Ptr!='\n')
  243. {
  244. Ptr++;
  245. }
  246. // If this is the end of the file, we're done
  247. if (*Ptr == 0)
  248. {
  249. bIsDone = 1;
  250. }
  251. // Handle different line endings. If \r\n then NULL and advance 2, otherwise NULL and advance 1
  252. // This handles \r, \n, or \r\n
  253. else if ( *Ptr=='\r' && *(Ptr+1)=='\n' )
  254. {
  255. // This was \r\n. Terminate the current line, and advance the pointer forward 2 characters in the stream
  256. *Ptr++ = 0;
  257. *Ptr++ = 0;
  258. }
  259. else
  260. {
  261. // Terminate the current line, and advance the pointer to the next character in the stream
  262. *Ptr++ = 0;
  263. }
  264. ContentLines.Add(Start);
  265. }
  266. }
  267. else
  268. {
  269. UDNParserLog.Error(FText::Format(LOCTEXT("LoadingError", "Loading document '{0}' failed."), FText::FromString(SourcePath)));
  270. }
  271. if ( !bLoadSuccess && GetDefault<UEditorPerProjectUserSettings>()->bDisplayDocumentationLink )
  272. {
  273. UDNParserLog.Open();
  274. }
  275. return bLoadSuccess;
  276. }
  277. bool FExtUDNParser::Parse(const FString& Link, TArray<FExcerpt>& OutExcerpts, FUDNPageMetadata& OutMetadata)
  278. {
  279. FMessageLog UDNParserLog(UDNParseErrorLog);
  280. TArray<FString> ContentLines;
  281. if ( LoadLink( Link, ContentLines ) )
  282. {
  283. TArray<FExcerpt> TempExcerpts;
  284. const FString SourcePath = FExtDocumentationLink::ToSourcePath( Link );
  285. bool bParseSuccess = ParseSymbols( Link, ContentLines, FPaths::GetPath( SourcePath ), TempExcerpts, OutMetadata );
  286. if (bParseSuccess)
  287. {
  288. OutExcerpts = TempExcerpts;
  289. return true;
  290. }
  291. else
  292. {
  293. if ( GetDefault<UEditorPerProjectUserSettings>()->bDisplayDocumentationLink )
  294. {
  295. UDNParserLog.Open();
  296. }
  297. UDNParserLog.Error(FText::Format(LOCTEXT("GeneralParsingError", "Parsing document '{0}' failed."), FText::FromString(SourcePath)));
  298. }
  299. }
  300. return false;
  301. }
  302. bool FExtUDNParser::GetExcerptContent( const FString& Link, FExcerpt& Excerpt, bool bInSimpleText/* = false*/ )
  303. {
  304. FMessageLog UDNParserLog(UDNParseErrorLog);
  305. TArray<FString> ContentLines;
  306. if ( LoadLink( Link, ContentLines ) )
  307. {
  308. bool bSimpleTextBackup = bSimpleText;
  309. bSimpleText = bInSimpleText;
  310. Excerpt.Content = GenerateExcerptContent( Link, Excerpt, ContentLines, Excerpt.LineNumber);
  311. bSimpleText = bSimpleTextBackup;
  312. return true;
  313. }
  314. else
  315. {
  316. if ( GetDefault<UEditorPerProjectUserSettings>()->bDisplayDocumentationLink )
  317. {
  318. UDNParserLog.Open();
  319. }
  320. UDNParserLog.Error(FText::Format(LOCTEXT("GeneralExcerptError", "Generating a Widget for document '{0}' Excerpt '{1}' failed."), FText::FromString( FExtDocumentationLink::ToSourcePath( Link ) ), FText::FromString(Excerpt.Name) ));
  321. }
  322. return false;
  323. }
  324. void FExtUDNParser::SetWrapAt( TAttribute<float> InWrapAt )
  325. {
  326. WrapAt = InWrapAt;
  327. }
  328. int32 FExtUDNParser::FTokenConfiguration::CalculatedExpectedContentStrings()
  329. {
  330. int32 ExpectedContentStrings = 0;
  331. for (int32 i = 0; i < TokensAccepted.Num(); ++i)
  332. {
  333. if (TokensAccepted[i] == EUDNToken::Content) {++ExpectedContentStrings;}
  334. }
  335. return ExpectedContentStrings;
  336. }
  337. TSharedPtr<FSlateDynamicImageBrush> FExtUDNParser::GetDynamicBrushFromImagePath(FString Filename)
  338. {
  339. FName BrushName( *Filename );
  340. if (FPaths::GetExtension(Filename) == TEXT("png") || FPaths::GetExtension(Filename) == TEXT("jpg"))
  341. {
  342. FArchive* ImageArchive = IFileManager::Get().CreateFileReader(*Filename);
  343. if (ImageArchive && FSlateApplication::IsInitialized())
  344. {
  345. if (FSlateRenderer* Renderer = FSlateApplicationBase::Get().GetRenderer())
  346. {
  347. TSharedPtr<FSlateDynamicImageBrush> AlreadyExistingImageBrush;
  348. for (int32 i = 0; i < DynamicBrushesUsed.Num(); ++i)
  349. {
  350. if (DynamicBrushesUsed[i]->GetResourceName() == BrushName) { AlreadyExistingImageBrush = DynamicBrushesUsed[i]; break; }
  351. }
  352. if (AlreadyExistingImageBrush.IsValid())
  353. {
  354. return AlreadyExistingImageBrush;
  355. }
  356. else
  357. {
  358. FIntPoint Size = Renderer->GenerateDynamicImageResource(BrushName);
  359. return MakeShareable(new FSlateDynamicImageBrush(BrushName, FVector2D(Size.X, Size.Y)));
  360. }
  361. }
  362. }
  363. }
  364. return TSharedPtr<FSlateDynamicImageBrush>();
  365. }
  366. FString FExtUDNParser::ConvertSymbolIntoAString(const FUDNToken& Token)
  367. {
  368. if (Token.TokenType == EUDNToken::Content)
  369. {
  370. return Token.Content;
  371. }
  372. for (int32 i = 0; i < TokenLibrary.Num(); ++i)
  373. {
  374. auto& LibraryToken = TokenLibrary[i];
  375. if (LibraryToken.TokenType == Token.TokenType)
  376. {
  377. return LibraryToken.ParseText;
  378. }
  379. }
  380. return FString();
  381. }
  382. FString FExtUDNParser::ConvertSymbolsIntoAString(const TArray<FUDNToken>& TokenList, int32 StartingAfterIndex)
  383. {
  384. bool bIsInVariableSubstitution = false;
  385. FString Output;
  386. for (int32 i = StartingAfterIndex; i < TokenList.Num(); ++i)
  387. {
  388. auto& Token = TokenList[i];
  389. if(Token.TokenType == EUDNToken::Percentage)
  390. {
  391. bIsInVariableSubstitution = !bIsInVariableSubstitution;
  392. }
  393. if(!bIsInVariableSubstitution && Token.TokenType != EUDNToken::Percentage)
  394. {
  395. Output += ConvertSymbolIntoAString(Token);
  396. }
  397. }
  398. return Output;
  399. }
  400. bool FExtUDNParser::ParseLineIntoSymbols(int32 LineNumber, const FString& Line, TArray<FUDNToken>& SymbolList)
  401. {
  402. if (!Line.IsEmpty())
  403. {
  404. FString ChoppedLine;
  405. bool bFoundSymbol = false;
  406. for (int32 i = 0; i < TokenLibrary.Num(); ++i)
  407. {
  408. auto& Symbol = TokenLibrary[i];
  409. FString TrimmedLine = Line;
  410. TrimmedLine.TrimStartInline();
  411. if (TrimmedLine.StartsWith(Symbol.ParseText))
  412. {
  413. ChoppedLine = TrimmedLine.RightChop(Symbol.ParseText.Len());
  414. SymbolList.Add(FUDNToken(Symbol.TokenType));
  415. bFoundSymbol = true;
  416. break;
  417. }
  418. }
  419. if (!bFoundSymbol)
  420. {
  421. struct Local
  422. {
  423. static bool CharIsValid(const TCHAR& Char)
  424. {
  425. return
  426. Char != LITERAL(TCHAR, '[') &&
  427. Char != LITERAL(TCHAR, ']') &&
  428. Char != LITERAL(TCHAR, '(') &&
  429. Char != LITERAL(TCHAR, ')') &&
  430. Char != LITERAL(TCHAR, '%') &&
  431. Char != LITERAL(TCHAR, '*');
  432. }
  433. static bool FirstCharIsValid(const TCHAR& Char)
  434. {
  435. return
  436. Char != LITERAL(TCHAR, '[') &&
  437. Char != LITERAL(TCHAR, ']') &&
  438. Char != LITERAL(TCHAR, '(') &&
  439. Char != LITERAL(TCHAR, ')') &&
  440. Char != LITERAL(TCHAR, '!') &&
  441. Char != LITERAL(TCHAR, ':') &&
  442. Char != LITERAL(TCHAR, '/') &&
  443. Char != LITERAL(TCHAR, '%') &&
  444. Char != LITERAL(TCHAR, '*');
  445. }
  446. };
  447. int32 CharIdx = 0;
  448. for (; CharIdx < Line.Len(); ++CharIdx)
  449. {
  450. auto Char = Line[CharIdx];
  451. bool bIsContentChar = CharIdx == 0 ? Local::FirstCharIsValid(Char) : Local::CharIsValid(Char);
  452. if ( !bIsContentChar && CharIdx != 0 )
  453. {
  454. FString LeftString = Line.Left(CharIdx);
  455. ChoppedLine = Line.RightChop(CharIdx);
  456. SymbolList.Add(FUDNToken(EUDNToken::Content, LeftString));
  457. bFoundSymbol = true;
  458. break;
  459. }
  460. }
  461. // Indicates that we went to the end of the line, so the entire thing is a symbol
  462. if (CharIdx == Line.Len())
  463. {
  464. ChoppedLine = FString();
  465. SymbolList.Add(FUDNToken(EUDNToken::Content, Line));
  466. bFoundSymbol = true;
  467. }
  468. }
  469. if (!bFoundSymbol)
  470. {
  471. // Indicates that we found an unknown token, error
  472. FMessageLog UDNParserLog(UDNParseErrorLog);
  473. UDNParserLog.Error(FText::Format(LOCTEXT("TokenParseError", "Line {0}: Token '{1}' could not be parsed properly."), FText::AsNumber(LineNumber), FText::FromString(Line)));
  474. if ( GetDefault<UEditorPerProjectUserSettings>()->bDisplayDocumentationLink )
  475. {
  476. UDNParserLog.Open();
  477. }
  478. return false;
  479. }
  480. else
  481. {
  482. return ParseLineIntoSymbols(LineNumber, ChoppedLine, SymbolList);
  483. }
  484. }
  485. // Line is out of characters
  486. return true;
  487. }
  488. FUDNLine FExtUDNParser::ParseLineIntoUDNContent(int32 LineNumber, const FString& Line)
  489. {
  490. FMessageLog UDNParserLog(UDNParseErrorLog);
  491. FString TrimmedLine = Line;
  492. TrimmedLine.TrimStartInline();
  493. FUDNLine OutputLine;
  494. TArray<FUDNToken> SymbolList;
  495. bool bSuccessful = ParseLineIntoSymbols(LineNumber, TrimmedLine, SymbolList);
  496. if (bSuccessful)
  497. {
  498. if (SymbolList.Num() > 0)
  499. {
  500. bool bLineWasMatched = false;
  501. for (int32 i = 0; i < LineLibrary.Num() && !bLineWasMatched; ++i)
  502. {
  503. auto& LineConfig = LineLibrary[i];
  504. TArray<FString> Contents;
  505. FString CurrentContentString;
  506. bool bMatch = true;
  507. bool bInVariableSubstitution = false;
  508. int32 SymbolIdx = 0;
  509. for (int32 TokenIdx = 0; bMatch && TokenIdx < LineConfig.TokensAccepted.Num(); ++TokenIdx)
  510. {
  511. EUDNToken::Type& Token = LineConfig.TokensAccepted[TokenIdx];
  512. if (SymbolIdx < SymbolList.Num())
  513. {
  514. FUDNToken& Symbol = SymbolList[SymbolIdx];
  515. if(bInVariableSubstitution && Symbol.TokenType != EUDNToken::Percentage)
  516. {
  517. ++SymbolIdx;
  518. }
  519. else if (Symbol.TokenType == EUDNToken::Percentage)
  520. {
  521. bInVariableSubstitution = !bInVariableSubstitution;
  522. ++SymbolIdx;
  523. }
  524. else
  525. {
  526. if (Token == EUDNToken::Content)
  527. {
  528. check(TokenIdx + 1 < LineConfig.TokensAccepted.Num() && LineConfig.TokensAccepted[TokenIdx+1] != EUDNToken::Content);
  529. EUDNToken::Type& NextToken = LineConfig.TokensAccepted[TokenIdx+1];
  530. if (Symbol.TokenType == NextToken)
  531. {
  532. Contents.Add(CurrentContentString);
  533. CurrentContentString.Empty();
  534. }
  535. else
  536. {
  537. CurrentContentString += ConvertSymbolIntoAString(Symbol);
  538. ++SymbolIdx;
  539. --TokenIdx;
  540. }
  541. }
  542. else
  543. {
  544. if (Symbol.TokenType != Token)
  545. {
  546. bMatch = false;
  547. }
  548. ++SymbolIdx;
  549. }
  550. }
  551. }
  552. else
  553. {
  554. if(bInVariableSubstitution)
  555. {
  556. UDNParserLog.Error(FText::Format(LOCTEXT("VariableSubstitutionError", "Line {0}: Line '{1}' variable substitution was not terminated"), FText::AsNumber(LineNumber), FText::FromString(Line)));
  557. }
  558. if (Token != EUDNToken::Content)
  559. {
  560. bMatch = false;
  561. }
  562. }
  563. }
  564. if (bMatch && (SymbolIdx == SymbolList.Num() || LineConfig.bAcceptTrailingSymbolDumpAsContent))
  565. {
  566. if (LineConfig.CalculatedExpectedContentStrings() == Contents.Num())
  567. {
  568. OutputLine.ContentType = LineConfig.OutputLineType;
  569. for (const FString Content : Contents)
  570. {
  571. OutputLine.AdditionalContent.Add(Content);
  572. }
  573. if (LineConfig.bAcceptTrailingSymbolDumpAsContent)
  574. {
  575. OutputLine.AdditionalContent.Add(ConvertSymbolsIntoAString(SymbolList, SymbolIdx).TrimStart());
  576. }
  577. }
  578. else
  579. {
  580. if ( GetDefault<UEditorPerProjectUserSettings>()->bDisplayDocumentationLink )
  581. {
  582. UDNParserLog.Open();
  583. }
  584. UDNParserLog.Error(FText::Format(LOCTEXT("LineConvertError", "Line {0}: Line '{1}' could not converted into a Slate widget."), FText::AsNumber(LineNumber), FText::FromString(Line)));
  585. }
  586. check(!bLineWasMatched);
  587. bLineWasMatched = true;
  588. }
  589. }
  590. if (!bLineWasMatched)
  591. {
  592. OutputLine.ContentType = FUDNLine::Content;
  593. OutputLine.AdditionalContent.Add(ConvertSymbolsIntoAString(SymbolList));
  594. }
  595. }
  596. else
  597. {
  598. // empty line
  599. OutputLine.ContentType = FUDNLine::Whitespace;
  600. }
  601. }
  602. else
  603. {
  604. if ( GetDefault<UEditorPerProjectUserSettings>()->bDisplayDocumentationLink )
  605. {
  606. UDNParserLog.Open();
  607. }
  608. UDNParserLog.Error(FText::Format(LOCTEXT("LineParseError", "Line {0}: Line '{1}' could not be parsed into symbols properly."), FText::AsNumber(LineNumber), FText::FromString(Line)));
  609. }
  610. return OutputLine;
  611. }
  612. void FExtUDNParser::AppendExcerpt(TSharedPtr<SVerticalBox> Box, TSharedRef<SWidget> Content)
  613. {
  614. Box->AddSlot()
  615. .AutoHeight()
  616. .HAlign(HAlign_Center)
  617. [
  618. SNew(SBox)
  619. .HAlign(HAlign_Left)
  620. .WidthOverride(ContentWidth)
  621. .Padding(FMargin(0,0,0,8.f))
  622. [
  623. SNew(SHorizontalBox)
  624. +SHorizontalBox::Slot()
  625. .AutoWidth()
  626. [
  627. Content
  628. ]
  629. ]
  630. ];
  631. }
  632. static void AddLineSeperator(FExcerpt& Excerpt)
  633. {
  634. if(!Excerpt.RichText.IsEmpty())
  635. {
  636. Excerpt.RichText += LINE_TERMINATOR;
  637. Excerpt.RichText += LINE_TERMINATOR;
  638. }
  639. }
  640. void FExtUDNParser::AddContentToExcerpt(TSharedPtr<SVerticalBox> Box, const FString& ContentSource, FExcerpt& Excerpt)
  641. {
  642. if ( !ContentSource.IsEmpty() )
  643. {
  644. const ISlateStyle& DocumentationStyle = FExtDocumentationStyle::Get();
  645. AppendExcerpt(Box,
  646. SNew(STextBlock)
  647. .Text(FText::FromString(ContentSource))
  648. .TextStyle(DocumentationStyle, Style.ContentStyleName)
  649. .WrapTextAt(WrapAt)
  650. );
  651. AddLineSeperator(Excerpt);
  652. if (bSimpleText)
  653. {
  654. Excerpt.RichText += FString::Printf(TEXT("%s"), *ContentSource);
  655. }
  656. else
  657. {
  658. Excerpt.RichText += FString::Printf(TEXT("<TextStyle Style=\"%s\">%s</>"), *Style.ContentStyleName.ToString(), *ContentSource);
  659. }
  660. }
  661. }
  662. TSharedRef< SWidget > FExtUDNParser::GenerateExcerptContent( const FString& InLink, FExcerpt& Excerpt, const TArray<FString>& ContentLines, int32 StartingLineIndex )
  663. {
  664. FMessageLog UDNParserLog(UDNParseErrorLog);
  665. const FString SourcePath = FExtDocumentationLink::ToSourcePath( InLink );
  666. const FString FullPath = FPaths::GetPath( SourcePath );
  667. FSlateFontInfo Header1Font = FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 18 );
  668. FSlateFontInfo Header2Font = FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 14 );
  669. bool bCriticalError = false;
  670. FString VariableName;
  671. FString CurrentStringContent;
  672. int32 CurrentNumbering = 1;
  673. TSharedPtr<SVerticalBox> Box;
  674. TArray<FString> ExcerptStack;
  675. const ISlateStyle& DocumentationStyle = FExtDocumentationStyle::Get();
  676. for (int32 CurrentLineNumber = StartingLineIndex; CurrentLineNumber < ContentLines.Num(); ++CurrentLineNumber)
  677. {
  678. const FString& CurrentLine = ContentLines[ CurrentLineNumber ];
  679. const FUDNLine& Line = ParseLineIntoUDNContent(CurrentLineNumber, CurrentLine);
  680. if (Line.ContentType == FUDNLine::ExcerptOpen)
  681. {
  682. ExcerptStack.Push(Line.AdditionalContent[0]);
  683. Box = SNew( SVerticalBox );
  684. }
  685. else if (Line.ContentType == FUDNLine::ExcerptClose)
  686. {
  687. if (ExcerptStack.Num() == 0 || Line.AdditionalContent[0] != ExcerptStack.Top())
  688. {
  689. UDNParserLog.NewPage( FText::FromString( InLink + TEXT(" [") + Excerpt.Name + TEXT("]") ) );
  690. UDNParserLog.Error(FText::Format(LOCTEXT("ExcerptCloseError", "Line {0}: Excerpt {1} improperly closed."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  691. bCriticalError = true;
  692. break;
  693. }
  694. FString ExcerptName = ExcerptStack.Pop();
  695. if ( ExcerptStack.Num() == 0 )
  696. {
  697. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  698. break;
  699. }
  700. }
  701. else if ( Line.ContentType == FUDNLine::VariableOpen )
  702. {
  703. if ( !VariableName.IsEmpty() )
  704. {
  705. UDNParserLog.NewPage( FText::FromString( InLink + TEXT(" [") + Excerpt.Name + TEXT("]") ) );
  706. UDNParserLog.Error(FText::Format(LOCTEXT("VariableOpenError", "Line {0}: Excerpt {1} improperly attempting to define a variable within a variable."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  707. bCriticalError = true;
  708. break;
  709. }
  710. VariableName = Line.AdditionalContent[0];
  711. if ( VariableName.IsEmpty() )
  712. {
  713. UDNParserLog.NewPage( FText::FromString( InLink + TEXT(" [") + Excerpt.Name + TEXT("]") ) );
  714. UDNParserLog.Error(FText::Format(LOCTEXT("VariableWithOutName", "Line {0}: Excerpt {1} improperly attempted to define a variable with no name."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  715. bCriticalError = true;
  716. break;
  717. }
  718. }
  719. else if ( Line.ContentType == FUDNLine::VariableClose )
  720. {
  721. if ( VariableName.IsEmpty() )
  722. {
  723. UDNParserLog.NewPage( FText::FromString( InLink + TEXT(" [") + Excerpt.Name + TEXT("]") ) );
  724. UDNParserLog.Error(FText::Format(LOCTEXT("VariableCloseError", "Line {0}: Excerpt {1} improperly attempting to close a variable tag it never opened."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  725. bCriticalError = true;
  726. break;
  727. }
  728. VariableName.Empty();
  729. }
  730. else if ( Line.ContentType == FUDNLine::Variable )
  731. {
  732. if ( Line.AdditionalContent.Num() != 2 )
  733. {
  734. UDNParserLog.NewPage( FText::FromString( InLink + TEXT(" [") + Excerpt.Name + TEXT("]") ) );
  735. UDNParserLog.Error(FText::Format(LOCTEXT("Variable", "Line {0}: Excerpt {1} improperly attempted to define a variable with no name."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  736. bCriticalError = true;
  737. break;
  738. }
  739. VariableName = Line.AdditionalContent[0];
  740. if ( VariableName.IsEmpty() )
  741. {
  742. UDNParserLog.NewPage( FText::FromString( InLink + TEXT(" [") + Excerpt.Name + TEXT("]") ) );
  743. UDNParserLog.Error(FText::Format(LOCTEXT("VariableWithOutName", "Line {0}: Excerpt {1} improperly attempted to define a variable with no name."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  744. bCriticalError = true;
  745. break;
  746. }
  747. }
  748. FString ConcatenatedPath;
  749. TSharedPtr<FSlateDynamicImageBrush> DynamicBrush;
  750. if (Line.ContentType == FUDNLine::Content && !CurrentStringContent.IsEmpty())
  751. {
  752. CurrentStringContent += LINE_TERMINATOR;
  753. }
  754. // only emit widgets if we are not inside a variable declaration
  755. if ( VariableName.IsEmpty() )
  756. {
  757. switch (Line.ContentType)
  758. {
  759. case FUDNLine::Whitespace:
  760. // Will only apply whitespace for the first empty line
  761. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  762. CurrentStringContent.Empty();
  763. break;
  764. case FUDNLine::Content:
  765. CurrentStringContent += Line.AdditionalContent[0];
  766. break;
  767. case FUDNLine::BoldContent:
  768. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  769. CurrentStringContent.Empty();
  770. AppendExcerpt(Box,
  771. SNew(STextBlock)
  772. .Text(FText::FromString(Line.AdditionalContent[0]))
  773. .TextStyle(DocumentationStyle, Style.BoldContentStyleName)
  774. );
  775. AddLineSeperator(Excerpt);
  776. if (bSimpleText)
  777. Excerpt.RichText += FString::Printf(TEXT("%s"), *Line.AdditionalContent[0]);
  778. else
  779. Excerpt.RichText += FString::Printf(TEXT("<TextStyle Style=\"%s\">%s</>"), *Style.BoldContentStyleName.ToString(), *Line.AdditionalContent[0]);
  780. break;
  781. case FUDNLine::NumberedContent:
  782. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  783. CurrentStringContent = FString::Printf(TEXT("%i. %s"), CurrentNumbering, *Line.AdditionalContent[0]);
  784. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  785. CurrentStringContent.Empty();
  786. ++CurrentNumbering;
  787. break;
  788. case FUDNLine::HorizontalRule:
  789. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  790. CurrentStringContent.Empty();
  791. Box->AddSlot()
  792. .HAlign(HAlign_Center)
  793. [
  794. SNew(SBox)
  795. .WidthOverride(ContentWidth)
  796. .Padding(FMargin(0,0,0,10))
  797. [
  798. SNew(SSeparator)
  799. .SeparatorImage(FExtDocumentationStyle::Get().GetBrush(Style.SeparatorStyleName))
  800. ]
  801. ];
  802. AddLineSeperator(Excerpt);
  803. break;
  804. case FUDNLine::Header1:
  805. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  806. CurrentStringContent.Empty();
  807. AppendExcerpt(Box,
  808. SNew(STextBlock)
  809. .Text(FText::FromString(Line.AdditionalContent[0]))
  810. .TextStyle(DocumentationStyle, Style.Header1StyleName)
  811. );
  812. AddLineSeperator(Excerpt);
  813. if (bSimpleText)
  814. Excerpt.RichText += FString::Printf(TEXT("%s"), *Line.AdditionalContent[0]);
  815. else
  816. Excerpt.RichText += FString::Printf(TEXT("<TextStyle Style=\"%s\">%s</>"), *Style.Header1StyleName.ToString(), *Line.AdditionalContent[0]);
  817. break;
  818. case FUDNLine::Header2:
  819. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  820. CurrentStringContent.Empty();
  821. AppendExcerpt(Box,
  822. SNew(STextBlock)
  823. .Text(FText::FromString(Line.AdditionalContent[0]))
  824. .TextStyle(DocumentationStyle, Style.Header2StyleName)
  825. );
  826. AddLineSeperator(Excerpt);
  827. if (bSimpleText)
  828. Excerpt.RichText += FString::Printf(TEXT("%s"), *Line.AdditionalContent[0]);
  829. else
  830. Excerpt.RichText += FString::Printf(TEXT("<TextStyle Style=\"%s\">%s</>"), *Style.Header2StyleName.ToString(), *Line.AdditionalContent[0]);
  831. break;
  832. case FUDNLine::Link:
  833. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  834. CurrentStringContent.Empty();
  835. AppendExcerpt(Box,
  836. SNew(SHyperlink)
  837. .Text(FText::FromString(Line.AdditionalContent[0]))
  838. .TextStyle(DocumentationStyle, Style.HyperlinkTextStyleName)
  839. .UnderlineStyle(DocumentationStyle, Style.HyperlinkButtonStyleName)
  840. .OnNavigate( this, &FExtUDNParser::HandleHyperlinkNavigate, Line.AdditionalContent[1])
  841. );
  842. AddLineSeperator(Excerpt);
  843. if(Line.AdditionalContent[1].Contains(LinkPrefixes::DocLinkSpecifier))
  844. {
  845. const FString Link = Line.AdditionalContent[1].RightChop(LinkPrefixes::DocLinkSpecifier.Len());
  846. Excerpt.RichText += FString::Printf(TEXT("<a id=\"udn\" href=\"%s\" style=\"%s\">%s</>"), *Link, *Style.HyperlinkStyleName.ToString(), *Line.AdditionalContent[0]);
  847. }
  848. else if(Line.AdditionalContent[1].Contains(LinkPrefixes::AssetLinkSpecifier))
  849. {
  850. const FString Link = Line.AdditionalContent[1].RightChop(LinkPrefixes::AssetLinkSpecifier.Len());
  851. Excerpt.RichText += FString::Printf(TEXT("<a id=\"asset\" href=\"%s\" style=\"%s\">%s</>"), *Link, *Style.HyperlinkStyleName.ToString(), *Line.AdditionalContent[0]);
  852. }
  853. else if(Line.AdditionalContent[1].Contains(LinkPrefixes::CodeLinkSpecifier))
  854. {
  855. const FString Link = Line.AdditionalContent[1].RightChop(LinkPrefixes::CodeLinkSpecifier.Len());
  856. Excerpt.RichText += FString::Printf(TEXT("<a id=\"code\" href=\"%s\" style=\"%s\">%s</>"), *Link, *Style.HyperlinkStyleName.ToString(), *Line.AdditionalContent[0]);
  857. }
  858. else if(Line.AdditionalContent[1].Contains(LinkPrefixes::TutorialLinkSpecifier))
  859. {
  860. const FString Link = Line.AdditionalContent[1].RightChop(LinkPrefixes::TutorialLinkSpecifier.Len());
  861. Excerpt.RichText += FString::Printf(TEXT("<a id=\"tutorial\" href=\"%s\" style=\"%s\">%s</>"), *Link, *Style.HyperlinkStyleName.ToString(), *Line.AdditionalContent[0]);
  862. }
  863. else
  864. {
  865. Excerpt.RichText += FString::Printf(TEXT("<a id=\"browser\" href=\"%s\" style=\"%s\">%s</>"), *InLink, *Style.HyperlinkStyleName.ToString(), *Line.AdditionalContent[0]);
  866. }
  867. break;
  868. case FUDNLine::Image:
  869. ConcatenatedPath = FullPath / TEXT("Images") / Line.AdditionalContent[1];
  870. DynamicBrush = GetDynamicBrushFromImagePath(ConcatenatedPath);
  871. if (DynamicBrush.IsValid())
  872. {
  873. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  874. CurrentStringContent.Empty();
  875. AppendExcerpt(Box,
  876. SNew( SImage )
  877. .Image(DynamicBrush.Get())
  878. .ToolTipText(FText::FromString(Line.AdditionalContent[0]))
  879. );
  880. DynamicBrushesUsed.AddUnique(DynamicBrush);
  881. }
  882. AddLineSeperator(Excerpt);
  883. Excerpt.RichText += FString::Printf(TEXT("<img src=\"%s\"></>"), *ConcatenatedPath);
  884. break;
  885. case FUDNLine::ImageLink:
  886. ConcatenatedPath = FullPath / TEXT("Images") / Line.AdditionalContent[1];
  887. DynamicBrush = GetDynamicBrushFromImagePath(ConcatenatedPath);
  888. if (DynamicBrush.IsValid())
  889. {
  890. AddContentToExcerpt(Box, CurrentStringContent, Excerpt);
  891. CurrentStringContent.Empty();
  892. AppendExcerpt(Box,
  893. SNew(SButton)
  894. .ContentPadding(0)
  895. .ButtonStyle(FAppStyle::Get(), "HoverHintOnly" )
  896. .OnClicked( FOnClicked::CreateSP( this, &FExtUDNParser::OnImageLinkClicked, Line.AdditionalContent[2] ) )
  897. [
  898. SNew( SImage )
  899. .Image(DynamicBrush.Get())
  900. .ToolTipText(FText::FromString(Line.AdditionalContent[0]))
  901. ]
  902. );
  903. DynamicBrushesUsed.AddUnique(DynamicBrush);
  904. }
  905. AddLineSeperator(Excerpt);
  906. Excerpt.RichText += FString::Printf(TEXT("<img src=\"%s\" href=\"%s\"></>"), *ConcatenatedPath, *Line.AdditionalContent[2]);
  907. break;
  908. default: break;
  909. }
  910. }
  911. }
  912. if ( ExcerptStack.Num() > 0 )
  913. {
  914. if ( !bCriticalError )
  915. {
  916. UDNParserLog.NewPage( FText::FromString( InLink + TEXT(" [") + Excerpt.Name + TEXT("]") ) );
  917. }
  918. for (int32 i = 0; i < ExcerptStack.Num(); ++i)
  919. {
  920. UDNParserLog.Error(FText::Format(LOCTEXT("ExcerptMismatchError", "Excerpt {0} was never closed."), FText::FromString(ExcerptStack.Top())));
  921. }
  922. bCriticalError = true;
  923. }
  924. if ( bCriticalError && GetDefault<UEditorPerProjectUserSettings>()->bDisplayDocumentationLink )
  925. {
  926. UDNParserLog.Open();
  927. }
  928. if ( bCriticalError )
  929. {
  930. return SNew( STextBlock ).Text( LOCTEXT("ExcerptContentLoadingError", "Excerpt {0} could not be loaded. :(") );
  931. }
  932. return Box.ToSharedRef();
  933. }
  934. bool FExtUDNParser::ParseSymbols(const FString& Link, const TArray<FString>& ContentLines, const FString& FullPath, TArray<FExcerpt>& OutExcerpts, FUDNPageMetadata& OutMetadata)
  935. {
  936. FMessageLog UDNParserLog(UDNParseErrorLog);
  937. bool bCriticalError = false;
  938. FString CurrentStringContent;
  939. TArray<FString> ExcerptStack;
  940. int32 ExcerptStartingLineNumber = 0;
  941. FString VariableName;
  942. FString VariableValue;
  943. TMap< FString, FString > Variables;
  944. for (int32 CurrentLineNumber = 0; CurrentLineNumber < ContentLines.Num(); ++CurrentLineNumber)
  945. {
  946. const FString& CurrentLine = ContentLines[ CurrentLineNumber ];
  947. const FUDNLine& Line = ParseLineIntoUDNContent(CurrentLineNumber, CurrentLine);
  948. bool bIsReadingContent = ExcerptStack.Num() > 0;
  949. if (Line.ContentType == FUDNLine::ExcerptOpen)
  950. {
  951. if ( ExcerptStack.Num() == 0 )
  952. {
  953. ExcerptStartingLineNumber = CurrentLineNumber;
  954. }
  955. ExcerptStack.Push(Line.AdditionalContent[0]);
  956. }
  957. else if (Line.ContentType == FUDNLine::ExcerptClose)
  958. {
  959. if (ExcerptStack.Num() == 0 || Line.AdditionalContent[0] != ExcerptStack.Top())
  960. {
  961. UDNParserLog.NewPage( FText::FromString( Link ) );
  962. UDNParserLog.Error(FText::Format(LOCTEXT("ExcerptCloseError", "Line {0}: Excerpt {1} improperly closed."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  963. bCriticalError = true;
  964. break;
  965. }
  966. FString ExcerptName = ExcerptStack.Pop();
  967. if (ExcerptStack.Num() == 0)
  968. {
  969. OutExcerpts.Add(FExcerpt(ExcerptName, NULL, Variables, ExcerptStartingLineNumber));
  970. OutMetadata.ExcerptNames.Add( ExcerptName );
  971. Variables.Empty();
  972. ExcerptStartingLineNumber = 0;
  973. }
  974. }
  975. else if ( Line.ContentType == FUDNLine::VariableOpen )
  976. {
  977. if ( !VariableName.IsEmpty() )
  978. {
  979. UDNParserLog.NewPage( FText::FromString( Link ) );
  980. UDNParserLog.Error(FText::Format(LOCTEXT("VariableOpenError", "Line {0}: Excerpt {1} improperly attempting to define a variable within a variable."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  981. bCriticalError = true;
  982. break;
  983. }
  984. VariableName = Line.AdditionalContent[0];
  985. if ( VariableName.IsEmpty() )
  986. {
  987. UDNParserLog.NewPage( FText::FromString( Link ) );
  988. UDNParserLog.Error(FText::Format(LOCTEXT("VariableWithOutName", "Line {0}: Excerpt {1} improperly attempted to define a variable with no name."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  989. bCriticalError = true;
  990. break;
  991. }
  992. }
  993. else if ( Line.ContentType == FUDNLine::VariableClose )
  994. {
  995. if ( VariableName.IsEmpty() )
  996. {
  997. UDNParserLog.NewPage( FText::FromString( Link ) );
  998. UDNParserLog.Error(FText::Format(LOCTEXT("VariableCloseError", "Line {0}: Excerpt {1} improperly attempting to close a variable tag it never opened."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  999. bCriticalError = true;
  1000. break;
  1001. }
  1002. Variables.Add( VariableName, VariableValue );
  1003. VariableName.Empty();
  1004. VariableValue.Empty();
  1005. }
  1006. else if ( Line.ContentType == FUDNLine::Variable )
  1007. {
  1008. if ( Line.AdditionalContent.Num() != 2 )
  1009. {
  1010. UDNParserLog.NewPage( FText::FromString( Link ) );
  1011. UDNParserLog.Error(FText::Format(LOCTEXT("Variable", "Line {0}: Excerpt {1} improperly attempted to define a variable with no name."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  1012. bCriticalError = true;
  1013. break;
  1014. }
  1015. VariableName = Line.AdditionalContent[0];
  1016. VariableValue = Line.AdditionalContent[1];
  1017. if ( VariableName.IsEmpty() )
  1018. {
  1019. UDNParserLog.NewPage( FText::FromString( Link ) );
  1020. UDNParserLog.Error(FText::Format(LOCTEXT("VariableWithOutName", "Line {0}: Excerpt {1} improperly attempted to define a variable with no name."), FText::AsNumber(CurrentLineNumber), FText::FromString(Line.AdditionalContent[0])));
  1021. bCriticalError = true;
  1022. break;
  1023. }
  1024. Variables.Add( VariableName, VariableValue );
  1025. VariableName.Empty();
  1026. VariableValue.Empty();
  1027. }
  1028. if (!bIsReadingContent)
  1029. {
  1030. switch (Line.ContentType)
  1031. {
  1032. case FUDNLine::MetadataAvailability: OutMetadata.Availability = Line.AdditionalContent[0]; break;
  1033. case FUDNLine::MetadataTitle: OutMetadata.Title = FText::FromString(Line.AdditionalContent[0]); break;
  1034. case FUDNLine::MetadataCrumbs: OutMetadata.Crumbs = FText::FromString(Line.AdditionalContent[0]); break;
  1035. case FUDNLine::MetadataDescription: OutMetadata.Description = FText::FromString(Line.AdditionalContent[0]); break;
  1036. }
  1037. }
  1038. else
  1039. {
  1040. switch (Line.ContentType)
  1041. {
  1042. case FUDNLine::Content:
  1043. case FUDNLine::NumberedContent:
  1044. case FUDNLine::Header1:
  1045. case FUDNLine::Header2:
  1046. case FUDNLine::Image:
  1047. case FUDNLine::Link:
  1048. case FUDNLine::ImageLink:
  1049. {
  1050. if ( !VariableName.IsEmpty() )
  1051. {
  1052. VariableValue += Line.AdditionalContent[0];
  1053. }
  1054. }
  1055. break;
  1056. }
  1057. }
  1058. }
  1059. if ( ExcerptStack.Num() > 0 )
  1060. {
  1061. if ( !bCriticalError )
  1062. {
  1063. UDNParserLog.NewPage( FText::FromString( Link ) );
  1064. }
  1065. for (int32 i = 0; i < ExcerptStack.Num(); ++i)
  1066. {
  1067. UDNParserLog.Error(FText::Format(LOCTEXT("ExcerptMismatchError", "Excerpt {0} was never closed."), FText::FromString(ExcerptStack.Top())));
  1068. }
  1069. bCriticalError = true;
  1070. }
  1071. return !bCriticalError;
  1072. }
  1073. FReply FExtUDNParser::OnImageLinkClicked( FString AdditionalContent )
  1074. {
  1075. NavigateToLink( AdditionalContent );
  1076. return FReply::Handled();
  1077. }
  1078. void FExtUDNParser::HandleHyperlinkNavigate( FString AdditionalContent )
  1079. {
  1080. NavigateToLink( AdditionalContent );
  1081. }
  1082. void FExtUDNParser::NavigateToLink( FString AdditionalContent )
  1083. {
  1084. static const FString DocLinkSpecifier( TEXT( "DOCLINK:" ) );
  1085. static const FString TutorialLinkSpecifier( TEXT( "TUTORIALLINK:" ) );
  1086. static const FString HttpLinkSPecifier( TEXT( "http://" ) );
  1087. static const FString HttpsLinkSPecifier( TEXT( "https://" ) );
  1088. static const FString CodeLinkSpecifier(TEXT("CODELINK:"));
  1089. static const FString AssetLinkSpecifier(TEXT("ASSETLINK:"));
  1090. if (AdditionalContent.StartsWith(DocLinkSpecifier))
  1091. {
  1092. // external link to documentation
  1093. FString DocLink = AdditionalContent.RightChop(DocLinkSpecifier.Len());
  1094. IDocumentation::Get()->Open(DocLink, FDocumentationSourceInfo(TEXT("udn_parser")));
  1095. }
  1096. else if ( AdditionalContent.StartsWith( TutorialLinkSpecifier ) )
  1097. {
  1098. // internal link
  1099. FString InternalLink = AdditionalContent.RightChop( TutorialLinkSpecifier.Len() );
  1100. Configuration->OnNavigate.ExecuteIfBound( InternalLink );
  1101. }
  1102. else if ( AdditionalContent.StartsWith( HttpLinkSPecifier ) || AdditionalContent.StartsWith( HttpsLinkSPecifier ) )
  1103. {
  1104. // external link
  1105. FPlatformProcess::LaunchURL( *AdditionalContent, nullptr, nullptr);
  1106. }
  1107. else if (AdditionalContent.StartsWith(CodeLinkSpecifier))
  1108. {
  1109. FString InternalLink = AdditionalContent.RightChop(CodeLinkSpecifier.Len());
  1110. ParseCodeLink(InternalLink);
  1111. }
  1112. else if (AdditionalContent.StartsWith(AssetLinkSpecifier))
  1113. {
  1114. FString InternalLink = AdditionalContent.RightChop(AssetLinkSpecifier.Len());
  1115. ParseAssetLink(InternalLink);
  1116. }
  1117. else
  1118. {
  1119. // internal link
  1120. Configuration->OnNavigate.ExecuteIfBound( AdditionalContent );
  1121. }
  1122. }
  1123. bool FExtUDNParser::ParseCodeLink(FString &InternalLink)
  1124. {
  1125. // Tokens used by the code parsing. Details in the parse section
  1126. static const FString ProjectSpecifier(TEXT("[PROJECT]"));
  1127. static const FString ProjectRoot(TEXT("[PROJECT]/Source/[PROJECT]/"));
  1128. static const FString ProjectSuffix(TEXT(".uproject"));
  1129. bool bLinkParsedOK = false;
  1130. FString Path;
  1131. int32 Line = 0;
  1132. int32 Col = 0;
  1133. TArray<FString> Tokens;
  1134. InternalLink.ParseIntoArray(Tokens, TEXT(","), 0);
  1135. int32 TokenStringsCount = Tokens.Num();
  1136. if (TokenStringsCount > 0)
  1137. {
  1138. Path = Tokens[0];
  1139. }
  1140. if (TokenStringsCount > 1)
  1141. {
  1142. TTypeFromString<int32>::FromString(Line, *Tokens[1]);
  1143. }
  1144. if (TokenStringsCount > 2)
  1145. {
  1146. TTypeFromString<int32>::FromString(Col, *Tokens[2]);
  1147. }
  1148. ISourceCodeAccessModule& SourceCodeAccessModule = FModuleManager::LoadModuleChecked<ISourceCodeAccessModule>("SourceCodeAccess");
  1149. ISourceCodeAccessor& SourceCodeAccessor = SourceCodeAccessModule.GetAccessor();
  1150. // If we specified generic project specified as the project name try to replace the name with the name of this project
  1151. if (InternalLink.Contains(ProjectSpecifier) == true)
  1152. {
  1153. FString ProjectName = TEXT("Marble");
  1154. // Try to extract the name of the project
  1155. FString ProjectPath = FPaths::GetProjectFilePath();
  1156. if (ProjectPath.EndsWith(ProjectSuffix))
  1157. {
  1158. int32 ProjectPathEndIndex;
  1159. if (ProjectPath.FindLastChar(TEXT('/'), ProjectPathEndIndex) == true)
  1160. {
  1161. ProjectName = ProjectPath.RightChop(ProjectPathEndIndex + 1);
  1162. ProjectName.RemoveFromEnd(*ProjectSuffix);
  1163. }
  1164. }
  1165. // Replace the root path with the name of this project
  1166. FString RebuiltPath = ProjectRoot + Path;
  1167. RebuiltPath.ReplaceInline(*ProjectSpecifier, *ProjectName);
  1168. Path = RebuiltPath;
  1169. }
  1170. // Finally create the complete path - project name and all
  1171. return SourceCodeAccessor.OpenFileAtLine(FPaths::RootDir() + Path, Line, Col);
  1172. }
  1173. bool FExtUDNParser::ParseAssetLink(FString &InternalLink)
  1174. {
  1175. TArray<FString> Token;
  1176. InternalLink.ParseIntoArray(Token, TEXT(","), 0);
  1177. if (Token.Num() >= 2)
  1178. {
  1179. FString Action = Token[0];
  1180. FString AssetName = Token[1];
  1181. UObject* RequiredObject = FindFirstObject<UObject>(*AssetName, EFindFirstObjectOptions::NativeFirst | EFindFirstObjectOptions::EnsureIfAmbiguous);
  1182. if (RequiredObject != nullptr)
  1183. {
  1184. if (Action == TEXT("EDIT"))
  1185. {
  1186. GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(RequiredObject);
  1187. }
  1188. else
  1189. {
  1190. FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
  1191. TArray<UObject*> AssetToBrowse;
  1192. AssetToBrowse.Add(RequiredObject);
  1193. ContentBrowserModule.Get().SyncBrowserToAssets(AssetToBrowse);
  1194. }
  1195. }
  1196. }
  1197. return false;
  1198. }
  1199. #undef LOCTEXT_NAMESPACE
  1200. #ifdef EXT_DOC_NAMESPACE
  1201. #undef EXT_DOC_NAMESPACE
  1202. #endif