Setting project item subproperties in IWizard? - visual-studio-2010
I have a custom item template that I am adding to a Sharepoint project. I need to ensure that my modules are only associated with my feature, even if the project already contains other features.
Replacing IDs in projectItemReference elements within .feature files is trivial to do by modifying the replacementsDictionary in the RunStarted method.
For example, I have the following SampleModule_WebParts.feature file:
<?xml version="1.0" encoding="utf-8"?>
<feature xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.0.0.0" Id="$SampleFeatureID$" activateOnDefault="false" description="Sample Web Part" featureId="$SampleFeatureID$" scope="Site" solutionId="00000000-0000-0000-0000-000000000000" title="Contoso Intranet Sample Module Web Parts" version="" deploymentPath="$SharePoint.Project.FileNameWithoutExtension$_$SharePoint.Feature.FileNameWithoutExtension$" xmlns="http://schemas.microsoft.com/VisualStudio/2008/SharePointTools/FeatureModel">
<projectItems>
<projectItemReference itemId="$SampleModuleID$" />
</projectItems>
</feature>
Replacing $SampleModuleID$ and $SampleFeatureID$ by modifying the replacementsDictionary in the IWizard.RunStarted method is trivial. But how can I modify the generated .csproj file snippet?
<None Include="Features\SampleModule_WebParts\SampleModule_WebParts.feature">
<FeatureId>{78185D58-6398-4ED2-B0D0-3DC20946FF8F}</FeatureId>
</None>
<Compile Include="SPItems\SampleModule\SampleWebPart\SampleWebPart.cs" />
<Compile Include="SPItems\SampleModule\SampleWebPart\SampleWebPartUserControl.ascx.cs">
<DependentUpon>SampleWebPartUserControl.ascx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="SPItems\SampleModule\SampleWebPart\SampleWebPartUserControl.ascx.designer.cs">
<DependentUpon>SampleWebPartUserControl.ascx.cs</DependentUpon>
</Compile>
<None Include="SPItems\SampleModule\SampleWebPart\SampleWebPart.webpart" />
<None Include="SPItems\SampleModule\SampleWebPart\SharePointProjectItem.spdata">
<SharePointProjectItemId>{D982D304-E7FB-4E8C-899B-7D4096A55892}</SharePointProjectItemId>
</None>
In this case, I'd need to set the FeatureId and SharePointProjectItemId properties for the .feature and .spdata items. If I don't do anything, Visual Studio will autogenerate those GUIDs, but they won't match what I have in my replacementsDictionary. And that in turn leads to a broken reference in my .feature file and my module may get associated with the wrong feature.
How can I set those custom properties so that they are persisted into the .csproj file when the user saves it? IWizard.ProjectItemFinishedGenerating seems like the correct method to implement and I can inspect the ProjectItem parameter to figure out when the .spdata and .feature files have been generated, but what should I do there to set the FeatureId and SharePointProjectItemId properties?
The solution was to convert the Project reference into an ISharePointProject and calling
ISharepointProject.Synchronize(). After that I could traverse the SharePoint project's object model.
var sp = project.DTE as Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
var projectService = new ServiceProvider(sp).GetService(typeof(ISharePointProjectService)) as ISharePointProjectService;
var sharepointProject = projectService.Convert<Project, ISharePointProject>(project);
sharepointProject.Synchronize();
After finding the my module and my feature from the collections returned by the ProjectItems and Features properties, I could simply associate the module with the feature:
sampleFeature.ProjectItems.Add(sampleModule);
Because I can fix the references programmatically, I can leave the old module GUID in the .feature file and clean up the invalid association by modifying the sampleFeature.Model.ProjectItems collection.
var invalidSampleModuleAssociation = (from projectItem in sampleFeature.Model.ProjectItems
where projectItem.ItemId == originalSampleModuleID
select projectItem).First();
sampleFeature.Model.ProjectItems.Remove(invalidSampleModuleAssociation);
Finally, I need to remove my module from any other features that the project might have, because Visual Studio automatically associates a new module with the first feature with an appropriate scope.
var unneededAssociations = (from otherFeature in sharepointProject.Features
where otherFeature.Name != sampleFeature.Name
from projectItem in otherFeature.ProjectItems
where projectItem.Name == sampleModule.Name
select new
{
Feature = otherFeature,
ModuleToRemove = projectItem
}).ToArray();
foreach (var moduleAssociation in unneededAssociations)
{
moduleAssociation.Feature.ProjectItems.Remove(moduleAssociation.ModuleToRemove);
}
AFAIK, you can make replacements also in any file, including the .csproj (in both filename and content)
If you have the Guids you wanna replace in the replacementDictionary, simply check the .vstemplate in order to be sure that file replacement is true:
<Project
File="MyProject.proj"
TargetFileName="$safeprojectname$.csproj"
ReplaceParameters="true">
</Project>
And edit the .csproj properly, replacing the Guids:
<None Include="Features\SampleModule_WebParts\SampleModule_WebParts.feature">
<FeatureId>{$SampleFeatureID$}</FeatureId>
</None>
<Compile Include="SPItems\SampleModule\SampleWebPart\SampleWebPart.cs" />
<Compile Include="SPItems\SampleModule\SampleWebPart\SampleWebPartUserControl.ascx.cs">
<DependentUpon>SampleWebPartUserControl.ascx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="SPItems\SampleModule\SampleWebPart\SampleWebPartUserControl.ascx.designer.cs">
<DependentUpon>SampleWebPartUserControl.ascx.cs</DependentUpon>
</Compile>
<None Include="SPItems\SampleModule\SampleWebPart\SampleWebPart.webpart" />
<None Include="SPItems\SampleModule\SampleWebPart\SharePointProjectItem.spdata">
<SharePointProjectItemId>{$SampleModuleID$}</SharePointProjectItemId>
</None>
Important! replacementDictionary, as IDictionaryl is case sensitive!!
Related
Is there a way I can remove the need to have an App.Xaml and add resources to App.Xaml.cs?
I would like to move this to my App.Xaml.cs. Can someone give me some suggestion how I can do this? <Application x:Class="Test.App" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:converters="clr-namespace:Test"> <Application.Resources> <converters:InverseBool x:Key="InverseBool" /> <converters:ToUpper x:Key="ToUpper" /> <converters:BoolToStringStateConverter x:Key="BoolToStringStateConverter" /> <converters:AddHashToNumberConverter x:Key="AddHashToNumberConverter" /> <converters:BoolToIntRowSpanConverter x:Key="BoolToIntRowSpanConverter" /> <converters:StringToTextAligmentConverter x:Key="StringToTextAligmentConverter" /> <converters:BoolToStringTextConverter x:Key="BoolToStringTextConverter" /> <converters:BoolToStringTextForDeckSourcesConverter x:Key="BoolToStringTextForDeckSourcesConverter" /> <converters:EpochSecondsToDateStringConverter x:Key="EpochSecondsToDateStringConverter" /> <converters:IntervalToDaysConverter x:Key="IntervalToDaysConverter" /> <converters:BoolToBackgroundColorConverter x:Key="BoolToBackgroundColorConverter" /> </Application.Resources> </Application> Is there a way that I can add this to App.xaml.cs instead of having this in a XAML file?
Here's the steps to delete App.xaml, replace it with App.cs and add your converter resources to Application.Resources. The resource dictionary, Application.Resources, is just a Dictionary<string, object> into which we can pass any key-value pair. So we'll pass in each IValueConverter using the name of the class and an instance of the class, e.g. Application.Current.Resources.Add(nameof(InverseBool), new InverseBool()); 1. Delete App.xaml In the Solution Explorer, right click on App.xaml In the right-click drop-down menu, click Delete 2. Add App.cs In the Solution Explorer, right-click on the .NET Standard Project containing your Xamarin.Forms UI In the right-click drop-down menu, select Add > New File In the New File window, on the left-hand pane, select General In the New File window, in the center pane, select Empty Class In the New File window, in the bottom Name text box, enter App In the New File window, click New 3. Add Resources to App.cs In the Solution Explorer, open App.cs In App.cs, add your converter resources by entering the following code: public class App : Application { public App() { Resources.Add(nameof(InverseBool), new InverseBool()); Resources.Add(nameof(ToUpper), new ToUpper()); Resources.Add(nameof(BoolToStringStateConverter), new BoolToStringStateConverter()); Resources.Add(nameof(AddHashToNumberConverter), new AddHashToNumberConverter()); Resources.Add(nameof(BoolToIntRowSpanConverter), new BoolToIntRowSpanConverter()); Resources.Add(nameof(StringToTextAligmentConverter), new StringToTextAligmentConverter()); Resources.Add(nameof(BoolToStringTextConverter), new BoolToStringTextConverter()); Resources.Add(nameof(BoolToStringTextForDeckSourcesConverter), new BoolToStringTextForDeckSourcesConverter()); Resources.Add(nameof(EpochSecondsToDateStringConverter), new EpochSecondsToDateStringConverter()); Resources.Add(nameof(IntervalToDaysConverter), new IntervalToDaysConverter()); Resources.Add(nameof(BoolToBackgroundColorConverter), new BoolToBackgroundColorConverter()); MainPage = ... } }
If the goal is to keep App.xaml clean, you can use the merged dictionary (and this is a best practice) and split all your resources into its own dictionary file, then use the merged dictionary to combine them in App.xaml. If the goal is to define resources in .cs instead of .xaml, then you can access that dictionary right from the App.cs via this.Resources dictionary.
Visual Studio folding solution items recursively
I am trying to fold all XAML dependents files. <None Include=".\**\*.xaml.js"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <DependentUpon>.\%(RecursiveDir)%(FileName)</DependentUpon> </None> <None Include=".\**\*.xaml.d.ts"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <DependentUpon>.\%(RecursiveDir)%(FileName)</DependentUpon> </None> I was able to fold JSs but TS definitions fail I tried to create a "temp" items collection and tried to iterate and parse paths ... no success VS didn't load the project anymore <ToFold Include=".\**\*.xaml" /> <None Include="#(ToFold->%(RecursiveDir)%(FileName).js')" DependentUpon="#(ToFold)"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> I tried the same way in "another way" ... same way :( <ToFold Include=".\**\*.xaml" /> <None Include="#(ToFold->%(ToFold).js')" DependentUpon="#(ToFold)"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> Question Is it possible to parse path in another way than x->%(RecursiveDir)%(FileName ? Is it possible to fold items like *.dash.ext ? How ? Or someone could help me achieving this ?! NOTE xaml.ts are auto generated and somehow auto folded
I think the issue is that: For KYCHome.xaml.d.ts file, the value of %(FileName) is KYCHome.xaml.d rather than KYCHome.xaml. So when you use it, it will not find the KYCHome.xaml file. So the method I can think of is to intercept the FileName, remove the .d, and then the changed value is what we need. Solution This is what I used: <ItemGroup> <None Include=".\**\*.xaml.js"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <DependentUpon>%(RecursiveDir)%(FileName)</DependentUpon> </None> <None Include=".\**\*.xaml.d.ts"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <File>$([System.String]::Copy('%(Filename)').Replace('.d', ''))</File> <DependentUpon>%(RecursiveDir)%(File)</DependentUpon> </None> </ItemGroup> Note: you should remove the .\ under DependentUpon node. Otherwise, sometimes, the DependentUpon will not work when you restart your project or unload the project.
I'm first French and I think it was a barrier for me to achieve what I wanted to do. I was saying folding items but if instead I had said nesting items I would quickly get my answer from google. Visual Studio has a nesting items feature I got the answer from here Path segment pattern and here is the node I added in the .filenesting.json solution file "dependentFileProviders": { "add": { "addedExtension": {}, "pathSegment": {}, "fileSuffixToExtension": { "add": { ".xaml.d.ts": [".xaml"] } } } }
TFS Work Item Type Definition System.AssignedTo Transition Error
I'm getting the following error when trying to change a custom work item's state through Visual Studio: The field 'Assigned To' contains the value 'Seth Denburg <Project\SDenburg>' that is not in the list of supported values. During the state transition the value from another field is copied to the System.AssignedTo field. This error stopping me from resolving a related work item during my check in through Visual Studio. I've noticed the following alternatives allow me to successfully change the state which could help point to what the issue is: Changing the work item's state in the web interface. No errors are displayed here. Reentering the user's name in the field being copied from before the transition in Visual Studio. Reentering the user's name in the System.AssignedTo field after the transition in Visual Studio. Here is a subset of states, transitions, and fields from the custom work item type definition that I think are related to this issue: <FIELD name="Assigned To" refname="System.AssignedTo" type="String" syncnamechanges="true" reportable="dimension"> <ALLOWEXISTINGVALUE /> <VALIDUSER group="Project\Users" /> </FIELD> <FIELD name="Lead" refname="Project.Tfs.Lead" type="String" reportable="dimension"> <ALLOWEXISTINGVALUE /> <DEFAULT from="value" value="Seth Denburg" /> <REQUIRED /> <VALIDUSER group="Project\TechnicalLeads" /> </FIELD> <STATE value="Pending"> <FIELDS> <FIELD refname="System.AssignedTo"> <VALIDUSER /> </FIELD> </FIELDS> </STATE> <TRANSITION from="Active" to="Pending"> <REASONS> <DEFAULTREASON value="Completed" /> </REASONS> <FIELDS> <FIELD refname="System.AssignedTo"> <COPY from="field" field="Project.Tfs.Lead" /> </FIELD> <ACTIONS> <ACTION value="Microsoft.VSTS.Actions.Checkin" /> </ACTIONS> </TRANSITION>
The issue ended up being that the field Project.Tfs.Lead didn't have syncnamechanges="true". Here is what the field looked like after the change was made: <FIELD name="Lead" refname="Project.Tfs.Lead" type="String" syncnamechanges="true" reportable="dimension"> <ALLOWEXISTINGVALUE /> <DEFAULT from="value" value="Seth Denburg" /> <REQUIRED /> <VALIDUSER group="Project\TechnicalLeads" /> </FIELD> When making the change ensure that you use witadmin changefield like the following command because the field needs to be updated across work item type definitions. Importing an xml change won't work and will give you warning TF248017. witadmin changefield /collection:https://project.com/tfs/projectCollection/ /n:Project.Tfs.Lead /syncnamechanges:true Here's why this change was necessary from MSDN: You must manually enable synchronization of any custom work item fields that you have created in previous releases of Visual Studio Team Foundation Server and that are used to assign person names that reference Active Directory. You must enable synchronization for each field for each team project collection that contains the custom fields. https://msdn.microsoft.com/en-us/library/dd286562(v=vs.100).aspx
Have created a test in myside, works well. The code of the custom work item type definition above seems missing a </FIELDS> of TRANSITION part. Make sure the user Seth Denburg is in both group Project\TechnicalLeads and Project\Users. You could also create a new team project in TFS2015 and use this custom work item type definition to see if the issue still exists. If not, the issue should related to the upgrade from TFS 2012 to 2015. Make sure you have Configure features after an upgrade.
QuickLink in UML Profile not displaying in Sparx enterprise Architect
I am trying to add a new item to the 'quick link' menu in Sparx Enterprise Architect. I have followed the instructions on the EA website: http://www.sparxsystems.com/enterprise_architect_user_guide/10/extending_uml_models/add_quick_linker_definition_to.html and in despiration I have also tried a vanilla copy of the example from http://www.sparxsystems.com/enterprise_architect_user_guide/10/extending_uml_models/quick_linker_example.html I have a new profile with a new stereotype of 'quick' which extends the metaclass 'Class'. I have added a 'QuickLink' artefact and copied the below entries into it (from the example above): Class,quick,,,,Component,,Dependency,,to,,Dependency to,TRUE,TRUE,TRUE,TRUE,Component,0,,,,, Class,quick,,,,Component,,Dependency,,from,,Dependency from,TRUE,TRUE,TRUE,TRUE,Component,0,,,TRUE,, Class,quick,Component,,,,,Dependency,,to,Dependency to,,TRUE,,TRUE,TRUE,,0,,,,, Class,quick,Component,,,,,Dependency,,from,Dependency from,,TRUE,,TRUE,TRUE,,0,,,TRUE,, Class,quick,Port,,,,,Dependency,,to,Dependency to,,TRUE,,TRUE,TRUE,,0,,,,, Class,quick,Port,,,,,Dependency,,from,Dependency from,,TRUE,,TRUE,TRUE,,0,,,TRUE,, Class,quick,Component,,,Port,,Dependency,,to,,Dependency to,TRUE,TRUE,TRUE,TRUE,Port,0,,TRUE,,, Class,quick,Component,,,Port,,Dependency,,from,,Dependency from,TRUE,TRUE,TRUE,TRUE,Port,0,,TRUE,TRUE,, I have then saved the UML Profile and generated an MDG from it. I have looked in the profile.xml and MDG xml files and in both cases the CSV information appears as I would expect in the QuickLink element: e.g. When I import the MDG, I can create a new diagram, the correct toolbox appears with my 'quick' stereotype on it. When I drag it onto the diagram however and try to create new links, the quicklinks menu is not showing any of my customisations. Is there anythink I am missing here to make this work?
The problem doesn't appear to be the quicklinker definition. I have created a working technology using that definition (below). It is likely that the problem is with the way the technology has been put together, rather than the quicklinks themselves. Hopefully you can see what is different by comparing this to your technology. If not then post your technology and I will take a look. <?xml version="1.0" encoding="windows-1252"?> <UMLProfile profiletype="uml2"> <Documentation id="C5186393-D" name="MyProfile" version="1.0" notes="MyProfile"/> <Content> <Stereotypes> <Stereotype name="quick" notes=""> <AppliesTo> <Apply type="Class"> <Property name="isActive" value=""/> </Apply> </AppliesTo> </Stereotype> </Stereotypes> <TaggedValueTypes/> <QuickLink data="Class,quick,,,,Component,,Dependency,,to,,Dependency to,TRUE,TRUE,TRUE,TRUE,Component,0,,,,, Class,quick,,,,Component,,Dependency,,from,,Dependency from,TRUE,TRUE,TRUE,TRUE,Component,0,,,TRUE,, Class,quick,Component,,,,,Dependency,,to,Dependency to,,TRUE,,TRUE,TRUE,,0,,,,, Class,quick,Component,,,,,Dependency,,from,Dependency from,,TRUE,,TRUE,TRUE,,0,,,TRUE,, Class,quick,Port,,,,,Dependency,,to,Dependency to,,TRUE,,TRUE,TRUE,,0,,,,, Class,quick,Port,,,,,Dependency,,from,Dependency from,,TRUE,,TRUE,TRUE,,0,,,TRUE,, Class,quick,Component,,,Port,,Dependency,,to,,Dependency to,TRUE,TRUE,TRUE,TRUE,Port,0,,TRUE,,, Class,quick,Component,,,Port,,Dependency,,from,,Dependency from,TRUE,TRUE,TRUE,TRUE,Port,0,,TRUE,TRUE,, "/> </Content> </UMLProfile>
How can I find (and rehabilitate, if necessary) the Code Snippet I created?
I created a code snippet (the first of many, hopefully) using mainly this article as guidance. It seemed to work. Here are the steps I took: First, I created this file with the code snippet (HtmlTableRowWithTwoCells.snippet): <?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/CodeSnippet"> <CodeSnippet Format="1.0.0"> <!-- The Title of the snippet, this will be shown in the snippets manager. --> <Title>Create a 2-Cell HTML Table Row</Title> <!-- The description of the snippet. --> <Description>Creates a 2-Cell Row to be added to an HtmlTable</Description> <!-- The author of the snippet. --> <Author>Warble P. McGorkle for Platypi R Us (Duckbills Unlimited)</Author> <!-- The set of characters that must be keyed in to insert the snippet. --> <Shortcut>row2</Shortcut> <!-- The set of snippet types we're dealing with - either Expansion or --> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <!-- Now we have the snippet itself. --> <Snippet> <Declarations> <Literal> <ID>RowName</ID> <ToolTip>Enter the Row instance name</ToolTip> <Default>RowName</Default> </Literal> <Literal> <ID>Cell1Name</ID> <ToolTip>Enter the name for Cell 1</ToolTip> <Default>Cell1Name</Default> </Literal> <Literal> <ID>Cell2Name</ID> <ToolTip>Enter the name for Cell 2</ToolTip> <Default>Cell2Name</Default> </Literal> </Declarations> <!-- Sepecify the code language and the actual snippet content. --> <Code Language="CSharp" Kind="any"> <![CDATA[ var $RowName$ = new HtmlTableRow(); var $Cell1Name$ = new HtmlTableCell(); var $Cell2Name$ = new HtmlTableCell(); $RowName$.Cells.Add($Cell1Name$); $RowName$.Cells.Add($Cell2Name$); ]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets> Then, I created this "manifest" file (.vscontent): <?xml version="1.0" encoding="utf-8" ?> <VSContent xmlns="http://schemas.microsoft.com/developer/vscontent/2005" > <Content> <FileName>HtmlTableRowWithTwoCells.snippet</FileName> <DisplayName>Inserts a 2-Cell HTML Table Row</DisplayName> <Description>Inserts a 2-Cell Row to be added to an HtmlTable</Description> <FileContentType>Code Snippet</FileContentType> <ContentVersion>2.0</ContentVersion> <Attributes> <Attribute name="lang" value="csharp"/> </Attributes> </Content> </VSContent> I zipped those two files together, renamed the extension from .zip to .vsi, 2-clicked it, and installed it into the "My Code Snippets" folder here with these steps: And it indicates the snippet was installed in a reasonable location: Yet, when I attempt to add a Code Snippet in VS, the only categories I see are these (no "My Code Snippets"): When I select Tools > Code Snippets Manager..., I can navigate to Visual C# > My Code Snippets, but it is empty. When I use the Code Snippets Manager's "Import" button and navigate to the location of the snippet and attempt to add the snippet file, I get, "The snippet files chosen were not valid." Why does it tell me it installed successfully, when it apparently didn't (or where is it hiding)? What flaming hoop did I neglect to catapult myself through? Is the "weird" name of the "manifest" file possibly the problem? ".vscontent" seems odd to me, but that's what the article referenced above says to name it. Perhaps that was just on oversight, and it should really be [snippetName].vscontent? UPDATE Apparently, naming the "manifest" file *.vscontent" is not the problem. Maybe it's a problem, but it's not the problem, because I named it the same as the .snippets file (except for the extension), went through the installation process again, and got the same exact results: seeming success, actual demoralization. BTW, by default, when choosing a category into which to place the snippet, the Code Snipeets Manager puts a checkbox in "Microsoft Visual Studio Tools for Applications 2005 > My Code Snippets". I had previously unticked that and ticked the topmost "My Code Snippets"; this time I retained the default selection, PLUS my preferred location/category PLUS "Visual C#" But alas and anon, the only category of those that seems to dispaly via Ctrl+K, X in VS is C#, and the expected shortcut ("row2") does not appear in the snippet dropdown.
Here's an easier way (and, as a bonus, it works): Download the Snippets Designer: Write the code directly in Visual Studio: var RowName = new HtmlTableRow(); var Cell1Name = new HtmlTableCell(); var Cell2Name = new HtmlTableCell(); RowName.Cells.Add(Cell1Name); RowName.Cells.Add(Cell2Name); Right-click and select "Export as Snippet" This puts the code in the Snippet Designer. Here's what it looks like after setting a few properties, and electing which parts of the code would be replaceable: This is quite easy - sure beats the "olde-fashioned" way I was trying (which would have been okay, had it worked). All I had to do was right-click the elements of code I wanted to replace on adding a new snippet, set the snippet's shortcut and description properties in Solution Explorer, save it, and voila! Now mashing Ctrl+K, X in VS shows the snippet in "My Code Snippets" along with its description and shortcut: Selecting it adds the snippet with the replacement strings highlighted: